
简介一个基于STM32与HLW8032电能计量芯片的完整嵌入式数据采集工程包面向单片机开发及能源管理方向的入门与进阶学习者解决STM32通过串口与HLW8032通信、采集电能参数并上报调试助手的实际需求。压缩包共129个文件总体积仅1.52MB主要包含C源码与H头文件、Keil工程配置文件.uvprojx/.uvoptx、编译生成文件.hex/.axf以及说明文档.docx等目录结构清晰便于整体参考或局部移植。已有3113人学习下载。资源完整展示了USART1与HLW8032的串行通信、USART3调试输出、中断与DMA传输的配置思路并附有可直接烧录的工程文件。学习者可对照源码理解串口初始化、引脚映射、数据解析和异常处理流程同时利用map、lst等辅助文件排查编译链接问题非常适合在电能采集项目中快速上手。 STM32采集HLW8032数据这事儿我前后折腾了两三个晚上踩了一堆坑最后总算把电压、电流、功率、电量这些数据稳定地读出来了。这芯片在智能插座、功率监测这类项目里用得太多了网上资料虽然不少但很多都是只贴个代码不讲为什么这么写也不说坑在哪。我这边把整个从零开始的过程捋一遍硬件怎么接、协议怎么解析、代码怎么写、问题怎么排查一次说清楚。1. 项目整体思路与方案选型1.1 HLW8032这颗芯片到底能干什么HLW8032是上海海凌科出品的一款单相电能计量芯片它的核心卖点就是一颗芯片搞定电压、电流、有功功率、电量这四个关键参数的采集。内部集成了ADC采样、信号放大、数据处理和UART通讯外部只需要很小的电路就能工作。和传统的方案对比一下你就知道它有多省事。以前做交流电参数采集常见路线是电流互感器或者采样电阻加运放把信号抬到ADC能采的范围再用STM32内部ADC去采采完还得做RMS计算、相位校正、功率因数分析一套搞下来代码量大不说精度也很难保证。HLW8032把这些全干了MCU只负责通过串口读数据能省下大量时间和精力。另外还有个优势——电气隔离性好处理。芯片自己供电对外只出一根UART信号线用光耦隔离一下就能用在强电环境这对于做插座、断路器这类产品特别关键安全性和稳定性都有了保障。1.2 为什么最终选了它而不是其他计量芯片市面上同类芯片还有HLW8012、BL0942、CS5463这些我简单对比一下它们的特点。HLW8012其实是HLW8032的前代产品它输出的是一路脉冲信号PF/CF管脚输出频率频率高低对应功率大小然后通过SEL引脚切换选择是输出有功功率还是电压电流相关的频率。用起来不是不行但MCU这边要配置定时器去测频率精度和实时性都打折而且切换通道需要延时等稳定。BL0942也是UART输出协议和HLW8032完全不同数据格式偏工业风格。CS5463是SPI接口功能强大但外围电路复杂一般用在三相电表上。HLW8032好就好在一是UART直接输出标准的24字节数据帧串口中断收就好了逻辑简单二是电压电流功率电量四个量全都有不缺东西三是外围电路极简一个电阻采样电流分压电阻采样电压加几个电容就完事了四是成本低几块钱一颗做产品也有性价比。总之花最少的功夫得到最完整的数据这就是它最大的价值。2. 硬件连接与电路设计要点2.1 引脚功能梳理HLW8032常见封装是SOP8一共有8个引脚别看它小功能分配得很清晰引脚编号引脚名称功能说明1VDD电源正极接3.3V电源2GND电源地3RXD串口接收接MCU的TX4TXD串口发送接MCU的RX5V1P电流采样正输入6V1N电流采样负输入7V2P电压采样正输入8CF校准脉冲输出本设计未使用需要注意的是RXD和TXD这两个引脚的名字是从HLW8032自身角度定义的连接STM32时交叉芯片的TXD接STM32的RXPA3或其它芯片的RXD接STM32的TXPA2或其它。2.2 与STM32的接线方案我用的是STM32F103C8T6这颗经典片子串口用的USART2PA2TXPA3RX当然用USART1也行代码里改一下就行。硬件连接非常简单HLW8032的VDD接3.3VGND接GNDHLW8032的TXD接STM32的PA3USART2_RXHLW8032的RXD接STM32的PA2USART2_TX电流采样电阻接V1P和V1N之间电压采样电阻分压后接V2P这里有一个特别重要的坑HLW8032的TXD引脚必须上拉到3.3V。它正常工作时的TX输出其实是开漏结构没有上拉的话信号无法正确输出高电平串口收到的就全是乱码或者0。我一开始没注意这个细节调试的时候串口助手收到的数据全都不对折腾了好久才发现是上拉电阻的问题。一般接一个10kΩ到3.3V就够了太小了会增大功耗太大了信号边沿可能变缓。2.3 电源滤波和采样电路注意点电源方面VDD必须做好滤波我习惯在VDD和GND之间接一个10uF电解电容加一个0.1uF陶瓷电容并联电解电容稳住低频纹波陶瓷电容滤掉高频噪声双管齐下芯片的ADC采样精度才有保证。电流采样电阻的选择有个重要公式。HLW8032内部电流采样通道的满偏大约是±100mV也就是说电流采样电阻上的压降不能超过100mV。如果电路最大电流是10A那采样电阻的阻值应该是100mV / 10A 10mΩ功率为10A × 10A × 0.01Ω 1W需要选2W以上的电阻留出裕量。如果最大电流只有5A可以用20mΩ需要注意电阻精度尽量选1%或者更高的因为它直接决定电流测量的精度。电压采样通道的输入范围更微妙。V2P引脚的输入电压不是直接接220V而是通过一个大电阻降压后采样。数据手册推荐的方案是V2P通过一个串联电阻接到火线上阻值一般在470kΩ到1MΩ之间芯片内部还有分压结构V2P引脚对GND的电压大约是2V左右。这里有个容易犯的错误——有人直接用万用表量V2P电压发现只有1-2V就觉得是接线有问题其实这是正常现象芯片内部已经把电压拉低了。采样电阻的选型还牵扯到一个安全设计电流采样电阻一定要串联在火线上零线直接接负载这样芯片采到的电流才是真正流过负载的电流。2.4 强电隔离的个人建议如果你只是在自己桌面上调试芯片和220V直接共地问题不大。但如果你要做成产品或者长期运行的设备强烈建议在UART的TXD和RXD上加两颗光耦隔离开来。HLW8032工作在强电侧STM32工作在弱电侧中间用光耦隔开既能保护MCU也能防止强电侧的噪声干扰数字电路。我实测过加光耦但没做好供电隔离的话还是有地环路噪声建议隔离侧单独用隔离电源模块供电这样才是完整的隔离方案。3. 通信协议深度解析3.1 串口参数和帧格式HLW8032的串口参数是固定的4800bps、8位数据、无校验位、1位停止位。注意波特率低是这颗芯片的特点不能用常用的9600或者115200初始化的时候必须按这个来。芯片上电后会周期性、不间断地向外发送24字节的数据。一帧数据的结构如下字节索引内容说明00x55帧头10x55帧头20x55帧头30x71电压电流数据帧标识0x714-5电压寄存器高字节在前共16位6-7电流寄存器高字节在前共16位8校验和字节4到字节7的累加校验90x55帧头100x55帧头110x55帧头120x51功率电量数据帧标识0x5113-16功率寄存器高字节在前共32位17-20电量寄存器高字节在前共32位21校验和字节13到字节20的累加校验22-230x00保留字节固定为0有些资料里说一帧是22字节实际上芯片发的是24字节最后两字节保留位也会发出来。接收的时候按24字节处理更稳妥。3.2 校验和的原理和计算校验和是把对应区间的每一个字节的值累加起来只取低8位。比如电压电流帧的校验和check_sum (vol_H vol_L cur_H cur_L) 0xFF收到的校验和字节如果和这个计算结果不一样就说明这一帧数据在传输过程中出了错直接扔掉等下一帧。我调试的时候发现有些情况下芯片偶发会发出一帧校验错误的数据概率大概千分之一这是正常现象不需要担心芯片坏了程序里做好校验丢弃就行。功率和电量区域的校验相同只是累加的字节更多。3.3 真实物理量的换算公式这是全项目最核心的部分。HLW8032的寄存器值并不是直接的电压电流值需要结合分压电阻和采样电阻的参数做换算。电压计算公式V_rms reg_vol / 131072 * V_REF * 电压分压比其中131072是2的17次方这是芯片内部ADC的满量程对应的数值。V_REF约等于2.56V数据手册参考值电压分压比由外部电阻决定。如果用厂商推荐的电压采样电路即V2P串联阻抗约470kΩ加上内部的分压实际分压比大约是1:60.5左右那么公式可以进一步简化为V_rms reg_vol / 131072 * 154.9这里的154.9是实测校准值不同板子因为电阻误差会有微小的区别想要精确的话要用电表实测校准一次。电流计算公式I_rms reg_cur / 131072 * 100mV / R_sense100mV是芯片电流通道的满偏电压R_sense是锰铜采样电阻或康铜采样电阻的阻值。比如R_sense5mΩ满偏电流就是100mV/5mΩ20A那么I_rms reg_cur / 131072 * 20功率计算P reg_power / 131072 * 满偏电压 * 满偏电流满偏电压 154.9V按上述分压比满偏电流 100mV / R_sense。所以P reg_power / 131072 * 154.9 * (0.1 / R_sense)电量计算E reg_energy / 131072 * 满偏电压 * 满偏电流 * 3600单位焦耳换算成度kWh的话还要再除以3600000。每次都除这么多东西浮点运算在F103上也不算吃力但效率起见可以先算常数因子后面直接用常数乘寄存器值。4. 核心代码实现与实操记录4.1 串口初始化配置STM32这边我用的标准外设库第一步是串口和GPIO的时钟使能及配置void HLW8032_UART_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // USART2 TX-PA2, RX-PA3 GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 4800; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, USART_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_Cmd(USART2, ENABLE); }这里有个小细节值得提USART1在APB2总线上时钟是72MHzUSART2在APB1总线上时钟是36MHz两者的时钟源不同计算波特率时的分频结果不一样。但只要我们正确调用了对应的RCC时钟使能函数标准库会处理好这些不需要手动干预。4.2 接收缓冲与帧同步策略HLW8032持续发数据但MCU不可能保证每次上电都正好从帧头开始接收。我的做法是维护一个环形缓冲区或者状态机来跟踪帧头。简单可靠的方案是每收到一个新字节就放入24字节长的数组同时检测数组前四个字节是否都等于0x55第5个字节是0x71或者0x51。我用的是移位寄存器思想每进入一个字节把整个数组往前移动一位再填充最后一个位置然后判断uint8_t rx_buf[24]; uint8_t rx_index 0; void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { uint8_t data USART_ReceiveData(USART2); // 左移 for (int i 0; i 23; i) { rx_buf[i] rx_buf[i 1]; } rx_buf[23] data; // 帧头判断 if (rx_buf[0] 0x55 rx_buf[1] 0x55 rx_buf[2] 0x55 rx_buf[3] 0x71) { process_hlw8032_vi_frame(rx_buf); } if (rx_buf[0] 0x55 rx_buf[1] 0x55 rx_buf[2] 0x55 rx_buf[3] 0x51) { process_hlw8032_power_frame(rx_buf); } } }这种方式不需要复杂的DMA也不需要非常精确的状态机思路很简单但实测是稳定可靠的。4.3 数据解析主逻辑电压电流帧的解析函数void process_hlw8032_vi_frame(uint8_t *buf) { if (buf[8] ! 0x60) // 这个检查要慎用下面细说 { return; } uint16_t vol (buf[4] 8) | buf[5]; uint16_t cur (buf[6] 8) | buf[7]; // 校验累加 uint8_t sum (uint8_t)(buf[4] buf[5] buf[6] buf[7]); if (sum ! buf[8]) { return; // 校验失败丢弃 } float voltage vol / 131072.0f * 154.9f; float current cur / 131072.0f * (0.1f / 0.005f); // 采样电阻5mΩ printf(电压: %.2f V, 电流: %.3f A\n, voltage, current); }功率电量帧的解析函数void process_hlw8032_power_frame(uint8_t *buf) { uint32_t power (buf[13] 24) | (buf[14] 16) | (buf[15] 8) | buf[16]; uint32_t energy (buf[17] 24) | (buf[18] 16) | (buf[19] 8) | buf[20]; uint8_t sum buf[13] buf[14] buf[15] buf[16] buf[17] buf[18] buf[19] buf[20]; if (sum ! buf[21]) { return; // 校验失败 } float power_val power / 131072.0f * 154.9f * (0.1f / 0.005f); float energy_val energy / 131072.0f * 154.9f * (0.1f / 0.005f) * 3600.0f; printf(功率: %.2f W, 电量: %.4f kWh\n, power_val, energy_val / 3600000.0f); }代码里我特意留了个注释点是这个项目里最容易误导人的地方不要对buf[8]做固定值检查。一开始我看到网上有人写if (buf[8] ! 0x60) return就跟着写了。结果烧录上去之后发现数据完全不出来。后来仔细一查0x60只是某一种特定电压电流值下的校验和它不是一个固定值它是随寄存器值变化的。这种判断是特定场景下的产物不是通用逻辑。只有完整重算校验和才是正确做法。4.4 用串口打印和OLED显示验证解析得到的数据要能看得见才有意义。我调试的时候用串口1转发打印到电脑波特率设成115200方便看int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }重定向fputc之后printf就能直接用了非常方便。如果不想重定向也可以自己封装一个UART发送字符串的函数。后来项目做完整了我接了一块0.96寸OLEDI2C接口把电压、电流、功率、电量轮流显示。OLED的驱动例程网上很多不展开讲了。实际使用中发现一个体验问题——HLW8032每秒大约发50帧数据OLED刷新太快会闪我让它每500ms才刷新一次数值看起来舒服多了主循环里加个定时判断就行。5. 实测数据与校准过程5.1 空载和带负载的数据对比我用一个白炽灯和一个电水壶做负载测试。白炽灯是纯阻性负载功率因数为1结果比较直观。空载时电流大约显示0.005A到0.010A之间这是正常的零点漂移procent很小不需要处理。接上60W白炽灯后读到的数据大概在55-58W之间。电压读出来231V左右电流0.25A左右算下乘积等于58W左右和功率数据基本对得上说明芯片内部逻辑没问题只是系统误差来源主要在于电压分压电阻和电流采样电阻的精度有限。校准的方法很简单用一个精度高一点的万用表或者功率计记录下标准值和HLW8032的读数然后把电压系数154.9改成实际比值电流的100mV参数也按实际微调。比如标准表读225VHLW8032读231V那新的系数就是154.9 * 225 / 231 ≈ 150.9改到代码里就行。5.2 数据稳定性观察我连续跑了两个小时观察数据稳定性。功率波动大概在±2W以内电流在±0.01A以内这个精度对于大多数非计量场景完全够用。电量数据是会累积的但它的寄存器在掉电后会清零需要自行在Flash里保存累计值才能实现断电记忆。想做断电记忆的话有个小技巧设定一个阈值比如电量寄存器变化超过0.1kWh就写入一次Flash不要每帧都写。Flash是有擦写寿命的常见MCU的Flash写次数大约一万到十万次频繁写很快就废了。6. 常见问题与避坑指南6.1 串口收到数据全是0x00或者乱码这个问题的原因九成是TXD引脚没有上拉电阻。HLW8032的TXD是开漏输出没有上拉高电平就上不去接收端看到的全是低电平0。解决办法是在TXD和3.3V之间接一个10kΩ电阻。这是我在这个项目里踩的最大一个坑遇到类似现象先查这个。另外检查一下接线是否交叉正确芯片TXD接MCU的RX这个低级错误也常有人犯。还有一点如果用了USB转TTL模块直接连HLW8032却不用STM32也能看到数据因为4800波特率在串口助手上很容易设置。这种调试方法可以先用来看芯片本身是不是正常工作排除MCU侧的问题。6.2 数据偶尔有一帧错乱HLW8032每秒发约50帧偶发一两帧校验错误是很正常的事只要程序里丢弃坏帧不会对显示造成任何影响。但如果错帧率特别高达到10%以上就要检查供电是否稳定、上拉电阻是否合适、信号线是否过长。我有一条40cm左右的杜邦线连接时错帧率会升高缩短到10cm以内就正常了信号完整性在低速串口下也需要注意。6.3 上电瞬间读数异常HLW8032刚上电的几百毫秒内ADC采样还没稳定读出来的电压电流值可能会跳变很大。解决办法是程序里加一个稳定时间判断上电后过了500ms再开始处理数据或者连续丢弃前几十帧数据。我生产代码里用的是后者逻辑前2秒内收到的帧全丢干净利落。6.4 功率显示很小或者为0功率寄存器是32位的但它只有在电流和电压都接入负载的情况下才会正确累积。如果你空载测试功率寄存器可能读出来就是0或者很小的值这是正常的。另一个原因是HLW8032的功率寄存器需要一段时间累积测量小功率负载时比如5W以下的电器数值会跳得厉害这是分辨率限制不是故障。6.5 与开发环境的坑我用的STM32F103C8T6是蓝色Pill板默认是Keil MDK 标准外设库开发。如果你用的是STM32CubeIDE或者HAL库只是串口初始化部分写法不同协议解析代码完全通用。调试时如果发现进不了串口中断先检查NVIC是否使能了USART2中断标准库的初始化和HAL库的初始化差异很大这也是一个常见的坑。另外如果你和我一样用5V供电的LCD或者其它外设注意千万别把5V直接引到HLW8032的VDD上它只能吃3.3V高了会烧芯片。我做的这个设计里HLW8032和STM32共用一个3.3V电源轨。7. 后续扩展方向HLW8032的数据采集只是第一步拿到数据之后可以做很多事。比如加上ESP8266或者ESP32把电压、电流、功率通过Wi-Fi传到手机App或者云平台做远程用电监测。我之前试过用ESP8266的AT指令转发数据结构是STM32解析完数据通过串口发给ESP8266ESP8266再通过MQTT上报到服务器。这样手机随时能看到家里的用电情况还能做用电异常的报警比如功率突然升高说明大功率电器启动了或者人不在家时还有大功率负载工作说明有隐患。另外一个方向是数据本地存储用STM32的Flash或者外接EEPROM记录每天的电量使用情况形成日用电曲线这对分析用电习惯很有帮助。需要注意的是电量寄存器会溢出到32位甚至更高长期运行的设备要用64位计数来保存累计值避免溢出丢数据。这个项目总体来说难度不大但很锻炼串口协议处理、浮点换算、硬件调试这几项基本功。如果能把这一整套流程跑通以后遇到其他UART接口的传感器芯片基本都能快速上手——毕竟底层逻辑都是收帧、找帧头、校验、解算一通百通。本文还有配套的精品资源点击获取