简介:面向电子信息、自动化及电气工程专业的毕业设计与课程实践需求,这份基于单片机控制的智能电表抄表系统设计与实现文档,梳理了从总体方案到软硬件落地的完整思路。内容围绕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 级 | 成本敏感、只抄总量 |
| HLW8032 | UART 4800 固定 | 周期读帧、解析 | 0.5 级 | 单相分表 |
| RN8209D | UART/SPI | 寄存器读取 | 0.5 级 | 单相计费 |
| CS5463 | SPI | 寄存器读取+校表 | 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 偏移。
| 字段 | 长度 | 说明 |
|---|---|---|
| 起始符 | 1+1 | 0x68,地址域后重复一次 0x68 |
| 地址域 | 6 | BCD,低字节在前 |
| 控制码 | 1 | 0x11 读数据,0x91 应答读数据 |
| 数据长度 | 1 | 数据域字节数 |
| 数据域 | N | 数据标识 DI + 数据 |
| 校验 CS | 1 | 起始符到数据域末字节的算术和对 256 取模 |
| 结束符 | 1 | 0x16 |
以读正向有功总电能为 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(跳过前导 FE),len 取到数据域末尾,把校验字节也算进去时返回值应为 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 ms(2400bps 下一帧约 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, parity='E', bytesize=8, timeout=0.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 分钟,失败表优先补抄能把整个抄表周期的完成时间压掉一半以上,而且掉电重启后依然从上次的断点继续,不用人工干预。
本文还有配套的精品资源,点击获取