STM32读取HLW8032电能计量芯片:从硬件连接到数据解析全攻略
2026/9/10 23:18:07 网站建设 项目流程

简介:一个基于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的TX)
4TXD串口发送(接MCU的RX)
5V1P电流采样正输入
6V1N电流采样负输入
7V2P电压采样正输入
8CF校准脉冲输出(本设计未使用)

需要注意的是,RXD和TXD这两个引脚的名字是从HLW8032自身角度定义的,连接STM32时交叉:芯片的TXD接STM32的RX(PA3或其它),芯片的RXD接STM32的TX(PA2或其它)。

2.2 与STM32的接线方案

我用的是STM32F103C8T6这颗经典片子,串口用的USART2(PA2=TX,PA3=RX),当然用USART1也行,代码里改一下就行。硬件连接非常简单:

  • HLW8032的VDD接3.3V,GND接GND
  • HLW8032的TXD接STM32的PA3(USART2_RX)
  • HLW8032的RXD接STM32的PA2(USART2_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电压电流数据帧标识(0x71)
4-5电压寄存器高字节在前,共16位
6-7电流寄存器高字节在前,共16位
8校验和字节4到字节7的累加校验
90x55帧头
100x55帧头
110x55帧头
120x51功率电量数据帧标识(0x51)
13-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_sense

100mV是芯片电流通道的满偏电压,R_sense是锰铜采样电阻或康铜采样电阻的阻值。比如R_sense=5mΩ,满偏电流就是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总线上,时钟是72MHz,USART2在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寸OLED(I2C接口),把电压、电流、功率、电量轮流显示。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参数也按实际微调。比如标准表读225V,HLW8032读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解析完数据,通过串口发给ESP8266,ESP8266再通过MQTT上报到服务器。这样手机随时能看到家里的用电情况,还能做用电异常的报警,比如功率突然升高说明大功率电器启动了,或者人不在家时还有大功率负载工作,说明有隐患。

另外一个方向是数据本地存储,用STM32的Flash或者外接EEPROM记录每天的电量使用情况,形成日用电曲线,这对分析用电习惯很有帮助。需要注意的是,电量寄存器会溢出到32位甚至更高,长期运行的设备要用64位计数来保存累计值,避免溢出丢数据。

这个项目总体来说难度不大,但很锻炼串口协议处理、浮点换算、硬件调试这几项基本功。如果能把这一整套流程跑通,以后遇到其他UART接口的传感器芯片,基本都能快速上手——毕竟底层逻辑都是收帧、找帧头、校验、解算,一通百通。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询