单片机抄表系统实战:DL/T 645 帧解析、RS-485 组网与掉电存储
简介面向电子信息、自动化及电气工程专业的毕业设计与课程实践需求这份基于单片机控制的智能电表抄表系统设计与实现文档梳理了从总体方案到软硬件落地的完整思路。内容围绕STM32F103C8T6单片机与ST7540电力载波通信芯片展开涵盖电力载波电路、电平转换电路MAX232等外围电路设计并借助FSK调制解调技术实现电能数据稳定传输依次讲解绪论、总体方案论证、硬件电路原理图绘制、软件代码编写与联调排错最终形成抄表电路板与配套管理系统的实现路径并总结可靠性高、计量准确、可扩展性强与成本较低等特点可应用于居民住宅电量自动检测、收费与管理等场景。资源包为1个docx文档约1.57MB图文与目录结构清晰便于按章节检索参考。目前已有84人学习适合需要参照现成方案完成类似课题、补齐电路与通信细节的读者。1. 一块抄不准的表把问题引到单片机上园区里 120 块分表人工抄表要跑两天抄完还得拿计算器核一遍月底对不上账是常事。真正让人头疼的不是抄表本身而是三件事凑在一起表在配电箱里人够不着、抄下来的数字没法追溯、每月只有一天能看到全貌。把这三件事拆开看正好对应单片机抄表系统的三个模块——本地计量与累积、本地存储与掉电保护、总线组网与规约上报。这个标题讲的不是造一块家用电表而是以单片机为控制核心把计量、存储、通信串成一条可抄读的链路计量芯片给出电量脉冲或寄存器值单片机做累积、校时、掉电保存再通过 RS-485 按规约把数据交给集中器或上位机。适合做配电分表、宿舍用电管理、充电桩计费这类场景的人也适合拿它当 51 单片机综合练习——定时器、串口、EEPROM、中断、状态机一次全用上。2. 智能电表抄表的硬件底座计量芯片与单片机怎么配硬件这一层决定了后面软件能拿到什么数据。同样是抄个电量走脉冲计数的方案和走计量芯片寄存器的方案单片机侧的工作量差了一倍以上误差来源也完全不同。2.1 两种计量路线脉冲计数法与专用计量芯片法最常见的一条路是脉冲计数。电表上有一个光电耦合输出口每消耗 1 kWh 输出固定数量的脉冲标称 1600 imp/kWh 或 3200 imp/kWh。单片机用一个外部中断引脚接这个脉冲进一次中断就把累积量加一个固定当量。这条路代码量极小成本也低但它只懂有能量流过不知道方向、不知道电压电流、更不知道功率因数遇到脉冲抖动或中断被长任务占用丢一个脉冲就是永久性误差。另一条路是专用计量芯片。ADE7755、BL0937 这类老芯片仍然是脉冲输出本质上还是第一条路HLW8032、RN8209D、CS5463 这类则是通过 UART 或 SPI 输出寄存器单片机可以直接读到电压有效值、电流有效值、有功功率、有功电能。多花的十几块钱换来的是可以现场校表、可以做反向电量判断、可以做电压异常记录。做分表计费我一般会选第二条路因为后面一旦出现纠纷能拿出电压电流曲线比只拿一个电量数字有说服力得多。2.2 单片机选型51、STC、STM32 在抄表端各自的位置选型不用一步到位看三件事串口数量、EEPROM、抗干扰余量。经典 51如 AT89C52没有片内 EEPROM也没有双串口做 485 抄表和调试口冲突需要外挂 24C02 加模拟 I2C属于能跑但费劲。STC 系列STC89C52RC、STC12C5A60S2、STC8 系列在这一点上优势明显片内 EEPROM 可以做参数和电量掉电保存双串口让调试和 485 各占一个宽压供电也省掉一路 LDO。以 STC12C5A60S2 为例它有 2 个 UART、1 KB 片内 EEPROM、硬件看门狗做单表抄读完全够用用 Keil C51 编译后 11 KB 左右就能放下规约栈。STM32F103 系列则是在需要一机多表时才值得上。它能挂 4 到 6 路 485 或者 8 路计量芯片带 DMA 的串口收帧几乎不占 CPU做 128 户的分表集中器就很从容。热词里常出现的c51 单片机串口升级也是这个场景的延伸——现场装好的表不方便拆下来烧录用 STC 的 ISP 机制或自写的 Bootloader 通过 485 总线把新固件推下去比派人爬配电箱划算得多。2.3 计量通道与单片机接口的关键参数下面这张表是选型时真正会去核对的几项参数以实际芯片手册为准接线前务必确认电压采样是电阻分压还是互感器。方案接口单片机侧工作典型精度适用场景脉冲输出表外部中断/计数器计数当量换算1.0 级成本敏感、只抄总量HLW8032UART 4800 固定周期读帧、解析0.5 级单相分表RN8209DUART/SPI寄存器读取0.5 级单相计费CS5463SPI寄存器读取校表0.2 级需要精度较高用 STC 做脉冲计数时计数口一定要走 T0/T1 的计数器模式而不是纯外部中断原因很直接中断在 Flash 擦写期间会被延迟而硬件计数器不会。计数器模式下一个机器周期采样一次电平1600 imp/kWh 的表在全功率下脉冲间隔也有几十毫秒绰绰有余。2.4 RS-485 与红外接口的电路要点总线侧最容易被忽略。收发器用 SP485 或 MAX485 都可以DE/RE 并在一起由单片机一个 IO 控制发送前拉高、发完最后一个字节等 TI 移位寄存器空再拉低这个等通常用发送完成中断或延时 1 个字节时间来做。总线上加 120 Ω 终端电阻只在两端各加一个中间节点加了反而加重负载。A/B 线并上 TVS 和共模电感配电箱里接触器动作频繁不加这两样通信误码率会明显上去。红外口用于现场手持抄表机通常是一路 38 kHz 调制的 UART和 485 复用同一套规约只是物理层换成红外收发管。做的时候注意把红外接收头的输出接单片机 RX发射用定时器产生 38 kHz 载波两者不要接反。3. DL/T 645-2007 规约帧解析与单片机侧收发实现硬件打通之后真正的活是在字节层面。抄表能不能抄到八成问题出在帧格式和校验上而不是电路。3.1 帧结构逐字段拆解一帧报文由起始符、地址域、控制码、长度、数据域、校验和结束符组成。地址域 6 字节是 BCD 码低位在前数据域里凡是数值都用 BCD 加 0x33 偏移。字段长度说明起始符110x68地址域后重复一次 0x68地址域6BCD低字节在前控制码10x11 读数据0x91 应答读数据数据长度1数据域字节数数据域N数据标识 DI 数据校验 CS1起始符到数据域末字节的算术和对 256 取模结束符10x16以读正向有功总电能为 0x00010000 为例按低字节在前写数据标识就是 00 00 01 00每个字节加 0x33 变成 33 33 34 33。FE FE FE FE 68 12 34 56 78 90 12 68 11 04 33 33 34 33 68 16前面四个 FE 是唤醒字节单片机在接收时先丢弃地址 12 34 56 78 90 12 表示表号 129078563412控制码 0x11 表示读长度 0x04 表示数据域 4 字节末尾 68 是校验和可以自己加一遍验证——把 0x68 到最后一个 0x33 逐字节求和再对 256 取模结果正是 0x68。应答帧的控制码是 0x91数据域在数据标识后面接 4 字节 BCD 电量值和 1 字节单位0x02 表示 kWh 两位小数。3.2 用 C51 写一个最小收发回路下面这段是接收侧的状态机骨架放在串口中断里跑主循环只负责判断帧完成标志。// STC12C5A60S2, 11.0592MHz, UART1 2400bps 偶校验 #define FRAME_MAX 64 idata unsigned char rxbuf[FRAME_MAX]; idata unsigned char rxlen 0; bit frame_ok 0; void uart_isr(void) interrupt 4 { unsigned char ch; if (RI) { RI 0; // 清接收标志 ch SBUF; if (rxlen FRAME_MAX) { rxbuf[rxlen] ch; } // 结束符一出现就置位由主循环做校验 if (ch 0x16 rxlen 12) { frame_ok 1; } } } // 校验从第一个 0x68 到数据域最后一个字节求和 unsigned char dt645_cs(unsigned char *p, unsigned char len) { unsigned char i, sum 0; for (i 0; i len; i) sum p[i]; return sum; // 结果为 0 表示校验通过 }状态机这样写的好处是中断里不做任何耗时判断超时重发交给主循环的定时器计数。dt645_cs传入的指针要指向第二个 0x68跳过前导 FElen 取到数据域末尾把校验字节也算进去时返回值应为 0——这是最省事的判据。如果返回值不为 0先别怀疑芯片先确认波特率和校验位DL/T 645 的 485 口默认 2400bps、偶校验、8 数据位很多模块出厂是 9600 无校验直接对不上。3.3 半双工收发的时序与常见错帧排查发送侧最容易踩的坑是方向切换太快。发完最后一个字节就立刻把 DE 拉低发出的数据会缺尾字节。正确做法是等发送完成标志 TI 置位后再延时约 1 个字节时间2400bps 下约 4 ms再切回接收。3.3.1 校验和与数据域偏移两个高频错误排查顺序建议固定下来一看有没有回帧示波器看 A/B 差分或用串口助手先手动发一帧二看校验是否过三看数据标识是否对得上四看 BCD 解码是否正确。数据域减 0x33 的顺序不能错必须是先减偏移再按低字节在前还原曾经见过把地址域也减 0x33 的解码代码结果表号全乱抄表软件里显示成一串不存在的地址。另外注意地址 0xAA 是通配地址用于广播校时控制码 0x08广播帧不应答所以发完广播不要等回帧等不到是正常的。4. 抄表任务的调度、存储与断点续传单表读通只是第一步现场是一串表挂在同一条总线上要让 128 块表在一个抄表周期内全部读回并落盘靠的是调度而不是循环延时。4.1 定时器加状态机做多表轮询轮询的核心参数有三项单帧超时、重发次数、帧间间隔。经验值是单帧超时 300 ms2400bps 下一帧约 60 ms留足余量重发 3 次帧间间隔不少于 50 ms。把握好这三个数总线负载和抄表成功率就能兼顾。// 主循环里的轮询状态机tick 由 10ms 定时器累加 typedef enum { IDLE, SEND, WAIT, RETRY, NEXT } poll_state; unsigned char cur_idx 0; unsigned int tick 0; void poll_task(void) { switch (state) { case SEND: rs485_send(addr_list[cur_idx]); // 组帧并发送 tick 0; state WAIT; break; case WAIT: if (frame_ok) { save_record(); state NEXT; } else if (tick 30) state RETRY; // 300ms 无回帧 break; case RETRY: if (retry 3) state SEND; else { mark_fail(cur_idx); state NEXT; } // 记录失败表号下轮补抄 break; case NEXT: retry 0; frame_ok 0; rxlen 0; cur_idx; if (cur_idx addr_count) { cur_idx 0; state IDLE; } else if (tick 5) state SEND; // 50ms 帧间间隔 break; } }这段代码里mark_fail是关键它把抄失败的表号记下来下一轮优先补抄而不是从头再轮一遍。tick 5的判断看起来多余实际是防止状态机空转占用总线——如果去掉一帧接一帧连续发某些响应慢的表会整片丢帧。4.2 数据落 EEPROM 与 TF 卡的格式选择存储分两级。掉电必须保住的东西放片内 EEPROM表号、倍率、累计电量、最后校时时间按结构体整块写方便一次读回。typedef struct { unsigned char addr[6]; // BCD 表号 unsigned long energy; // 累计电量单位 0.01kWh unsigned int volt; // 电压单位 0.1V unsigned char flag; // bit0 有效bit1 已上报 unsigned int crc; // 整块校验 } MeterRec;写 EEPROM 时要注意两点一是擦写次数有限别每次抄回都写攒够一个周期或掉电中断里再写二是整块带校验写上电读回校验不过就丢弃并置默认值避免半写状态被当成有效数据。需要留历史曲线时热词里提到的单片机存储到 TF 卡中以表格形式存储就是走这条路。做法是用 SPI 挂一张 TF 卡文件系统可以上 FatFs文件内容直接按 CSV 写一行为一条记录字段用逗号分隔首行写表头和单位。这样拷出来能用表格软件直接打开不用再写解析工具。风险点是 TF 卡热插拔和掉电时文件系统容易损坏建议按天分文件、每条记录写完就同步一次或者干脆用定长二进制记录加固定扇区回绕牺牲可读性换可靠性。4.3 掉电保护与校时的一致性掉电检测通常在电源前级分压后接一个 ADC 通道或比较器检测到输入电压跌到阈值以下就立刻进中断把当前累积量和时间写进 EEPROM然后停止抄表任务。这里有个顺序问题必须先关 485 发送、再写 EEPROM否则写期间的电流跌落会让串口发出半帧把总线搅乱。校时用广播帧控制码 0x08一次把所有表的时间对齐不需要逐表写。RTC 用 DS1302 或 PCF8563注意晶振负载电容按手册配配错了走时一天差几分钟抄表记录的时间戳就没有参考价值。每抄一轮表把集中器时间跟表内时间比一次偏差超过 5 秒就补发一次广播校时。5. 联调与现场排错把抄表成功率从 80% 拉到接近 100%系统装完第一周是问题最集中的时候用一套固定动作去验证比凭感觉改代码有效。我一般先在实验室用 PC 侧脚本压测再上现场。PC 侧脚本的作用是模拟集中器不停发帧暴露单片机侧的接收溢出和缓冲区问题。# 实验室压测循环发读电量帧统计回帧率 import serial, time def cs(frame): # 校验和返回 0 表示正确 return sum(frame) % 256 head bytes([0x68, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x68, 0x11, 0x04, 0x33, 0x33, 0x34, 0x33]) body bytes([cs(head)]) b\x16 ser serial.Serial(COM3, 2400, parityE, bytesize8, timeout0.3) ok 0 for i in range(500): ser.write(head body) resp ser.read(32) if resp and resp[-1] 0x16 and sum(resp[:-1]) % 256 0: ok 1 time.sleep(0.05) # 与固件帧间间隔保持一致 print(回帧率 %.1f%% % (ok / 500 * 100))这段脚本的价值在于把能抄到变成可量化的数字。回帧率低于 95% 时按下面的顺序查基本能定位到原因。现象优先排查处理完全无回帧波特率、校验位、A/B 是否接反手动发单帧确认回帧率 60%~90%帧间间隔、终端电阻、线径间隔拉到 50ms 以上校验错但内容可见偏移 0x33 未处理、干扰检查解码顺序加共模电感只有某几块表抄不到表号冲突、地址写错逐表读地址域核对白天正常晚上失败大功率设备启停干扰屏蔽线单端接地、远离动力线最后补一个提高上限的技巧把表地址表和最近三次的失败记录存在 EEPROM 里抄表轮询从失败表开始而不是从 1 号表开始。128 块表按 3 秒一轮算全轮一遍 6 分钟失败表优先补抄能把整个抄表周期的完成时间压掉一半以上而且掉电重启后依然从上次的断点继续不用人工干预。本文还有配套的精品资源点击获取