简介这是一份面向嵌入式开发者与单片机学习者的STM32电能数据采集工程资源围绕HLW8032电能计量芯片展开通过USART1接口采集电流、电压、功率等参数并利用串口3将数据上送至串口调试助手可直接运行验证也可作为能源管理或物联网仪表项目的参考模板。资源包共129个文件压缩包仅1.52MB内容包含基于标准库的C/H源码、Keil编译生成的hex/axf与中间文件.o/.crf、工程配置文件uvprojx/uvoptx还附有说明文档docx和清理脚本文件归类清晰无需额外依赖即可打开工程查看或二次编译。项目在代码中展示了USART1与USART3的初始化与GPIO引脚复用配置支持中断和DMA两种收发方式并涉及CRC校验、通信协议定义、超时异常处理以及软件定时器采集等工程细节对掌握STM32串口通信、DMA搬运和外部芯片交互有实际参考价值。目前已有3113人学习下载适合需要快速掌握STM32HLW8032采集方案的中初级开发者使用。1. 项目缘起与方案选型1.1 为什么选择 HLW8032 做电能采集做电力计量相关的项目市面上的方案其实不少。早些年大家爱用分立器件电压互感器加电流互感器再配合运放把信号搬到 ADC 采样范围最后在 MCU 里做有效值运算。这套方案不是不行就是太折腾。且不说互感器的选型和隔离电路设计光是软件里算有效值、功率、功率因数这些量就要写一大堆数学逻辑而且精度还容易受电源噪声、运放温漂的影响调起来非常痛苦。后来换了一个思路用专用的计量芯片。我最初看的是某国际大厂的方案精度确实好但芯片价格和采购周期对做小批量产品来说有点劝退。几经对比最后选了 HLW8032。这颗芯片在国内做智能插座、智能电表、充电桩监测的项目里用得非常多核心优势就是单芯片搞定交流电压、电流、有效功率、功率因数的采集内部自带 ADC 和 DSP 运算MCU 只需要通过 UART 把数据读出来就行。这意味着软件工作量大幅度下降你不需要关心什么傅里叶变换、均方根算法把串口数据收好、解析对就行了。1.2 HLW8032 的工作原理简析HLW8032 内部其实做了很多事。它的电压采样通道和电流采样通道分别对交流电压信号和交流电流信号进行 ADC 采样然后在内部完成有效值计算和功率计算。采样完成之后芯片会按照固定的周期把计算结果通过 UART 口自动发出不需要主机去寄存器里读也不需要主动请求命令。这种“主动上报”的方式特别适合做小系统的数据采集——主控这边只管收数据。另外HLW8032 是单火线方案吗不是它需要零线和火线都接入电流采样通常是在零线侧串一个低阻值的采样电阻电压采样则通过电阻分压之后接入芯片的 VP 引脚。芯片的供电由 VCC 引脚提供一般用 3.3V 或者 5V 都可以但要注意芯片与 STM32 之间的电平匹配问题。芯片的 UART 输出电平取决于 VCC所以如果你的 VCC 用 3.3V那和 STM32 之间可以直接连接如果 VCC 用了 5V信号线上最好加电阻分压防止把 STM32 的 RX 引脚打坏。2. 硬件连接与关键细节2.1 典型接线方案以我这次用的 STM32F103C8T6 为例接线非常简单。HLW8032 一共没几个引脚核心用到的就是 VCC、GND、TX。VCC 接 3.3VGND 接系统地TX 引脚接到 STM32 的串口 RX。我用的串口是 USART1对应 PA10 作为 RX 输入。注意要把 HLW8032 的 TX 接到 STM32 的 RX这个是最容易搞反的地方之一。电流采样部分我在零线回路中串联了一个毫欧级采样电阻电阻两端分别接 HLW8032 的 IP 和 IN 引脚。电压采样部分从火线拉一个分压网络接到 VP 引脚分压电阻的取值要把 220V 交流有效值衰减到芯片允许的输入范围内。提示这个芯片是市电直接相关的东西调试时一定要用隔离变压器或者隔离电源给系统供电示波器探头也要注意共地问题安全第一。2.2 容易踩的硬件坑第一个坑是采样电阻的选型。电流采样电阻阻值太小信号幅度低精度就差阻值太大功耗又扛不住。比如你最大要测 10A 电流选 1 毫欧的采样电阻满载时上面功耗是 0.1W勉强可以接受但如果你选 2 毫欧功耗就变成 0.2W电阻发热后阻值漂移电流测量必然不准。我还见过有人为了省成本直接拿普通贴片电阻来采样结果发热严重数据跳来跳去。这里我建议用合金电阻或专门的采样电阻温度系数要低的。第二个坑是 PCB 布局。电流采样信号是比较微弱的如果走线过长又和高频信号挨着很容易被干扰。采样电阻到 HLW8032 之间的走线要尽量短最好采用差分走线形式。另外电压采样电阻串联在火线上这部分走线要满足安规距离否则过不了认证。第三个坑是电源纹波。HLW8032 的 ADC 精度直接受 VCC 影响如果你用开关电源给整个系统供电VCC 上的纹波又没处理好测出来的电压和电流数据会周期性抖动。我给 HLW8032 的 VCC 口单独加了一个 10uF 电解电容和一个 0.1uF 瓷片电容组合滤波实测抖动改善还是比较明显的。3. HLW8032 通信协议与关键参数换算3.1 数据帧格式HLW8032 的输出周期通常在几百毫秒左右具体以芯片手册为准。芯片每次发出来的一串数据就是一帧帧里面包含帧头、状态信息、电压寄存器、电流寄存器、功率寄存器还有一个校验字段。我之前看到网上有人贴的数据帧是这种开头55 5A 5A A5有的版本是持续多个字节的帧头。这里要提醒一下HLW8032 有不同批次和不同版本数据帧字段的位置可能存在差异拿到芯片之后第一件事应该是用逻辑分析仪抓一次原始波形然后对照你手头芯片的数据手册把每个字段的偏移和长度确认下来不要直接照搬别人的解析代码。我当时抓到的典型帧大概是这样的字段长度说明帧头4字节用于同步识别数据起始状态字2字节芯片状态一般可以跳过电压寄存器2字节电压有效值相关原始值电流寄存器2字节电流有效值相关原始值功率寄存器2字节有功功率相关原始值校验字段1~2字节用于校验帧数据完整性你要是手头没有逻辑分析仪也可以用串口直接看把波特率配置正确收几帧数据慢慢琢磨。HLW8032 默认波特率我记得是 4800数据格式是 8 数据位、无校验、1 停止位也就是最常用的 8N1。这个波特率很低但好处是抗干扰能力强而且传输距离可以稍微长一点。3.2 从寄存器值换算成真实物理量这是整个项目里最容易迷糊的地方。HLW8032 输出的寄存器值并不是直接的电压电流数值而是一个基于内部参考电压和采样通道满量程的原始码值。你换算的时候需要用数据手册给出的公式结合外围电路的电阻分压比和采样电阻值来计算。我在工程里一般这样做先找一个精度较高的可调交流源或者直接以市电为基准用万用表读一次真实电压值再记一次对应的 HLW8032 电压寄存器值两者相除得到一个比例系数。后面程序里就用这个系数去换算。电流通道也类似只不过电流的物理量换算还要再乘上采样电阻的折算关系。举个实际例子。假设电压通道的换算公式是V_real V_reg × K_v其中 K_v 是在额定输入下根据实测标定出来的。如果你用万用表读到 220.5V芯片寄存器值是 1102那么 K_v 就约等于 0.2。反过来如果寄存器值跳到了 1108那当前电压就是 1108 × 0.2 221.6V。注意这种线性标定方法是建立在芯片内部 ADC 线性度足够好的前提下。HLW8032 在小范围内线性度确实可以但如果你的测量范围很大比如电压从 80V 到 300V 都要覆盖就需要多点标定分段线性补偿。3.3 校验逻辑通信数据校验很重要尤其是交流电环境下强电干扰可能会让串口数据出现随机错误。HLW8032 的校验算法一般是前面所有字节的累加和取低 8 位或者 16 位具体算法要看手册。我在代码里实现了两种校验一种是累加和校验另一种是简单的帧长和超时校验。前者用来保证数据内容正确后者用来保证一帧数据完整地接收到了。4. STM32 端软件实现4.1 串口初始化我用的是 STM32 标准库来开发工程是在 Keil MDK5 下建的。USART1 的初始化主要包括使能 GPIOA 和 USART1 时钟配置 PA9 为复用推挽输出PA10 为浮空输入或者带上下拉的输入然后配置 USART1 的波特率、字长、停止位和中断。void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; 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_10; 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(USART1, USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); USART_Cmd(USART1, ENABLE); }这段代码没什么特别高深的地方就是最基础的串口接收配置。需要注意的一个点是波特率 4800 比较低如果系统时钟配置不对实际波特率偏差可能超过 2%从而导致收帧不稳定。建议先跑一个最简单的心跳程序让 STM32 自己收自己发确认串口功能正常再往下写解析逻辑。4.2 数据接收与状态机解析HLW8032 是主动上报数据的所以 STM32 这边不需要发任何命令只需要被动接收。但接收不能只看单个字节否则你根本不知道数据从哪里开始。常规做法是用一个环形缓冲区接收所有串口数据然后在主循环里或者定时中断里做帧同步和解析。我用的方式比较简单粗暴在串口接收中断里把每个字节放进一个 buf 数组同时维护一个索引在主循环里检查有没有新的数据有就搜索帧头。找到帧头之后再判断长度是否足够足够就取完整一帧做校验和处理。这种方式在数据传输量不大的情况下完全够用HLW8032 每几百毫秒才发一帧MCU 的压力很小。帧解析状态机可以这样设计typedef enum { STATE_WAIT_HEAD, STATE_COLLECT_DATA, STATE_CHECK, STATE_DONE } ParseState;在等待帧头状态里面每次来一个字节就判断它是不是帧头标志的对应字节如果不是就继续等待。一旦帧头匹配就进入收集数据状态把后面的字节依次存下来。当累计长度达到预期帧长之后进入校验状态计算前面所有字节的累加和和收到的校验字段对比。这种状态机的好处是结构清晰出问题的时候好排查。如果后续系统里同时有多个串口设备上报数据你也可以在帧里面区分设备地址不过 HLW8032 没有这个功能所以多片 HLW8032 同时使用的时候就得用多路串口或者用外部切换电路来分时读取。4.3 寄存值提取与最终换算帧校验通过之后就到了提取寄存器值的步骤。我这里用一个结构体保存解析结果。typedef struct { uint8_t frameReceived; uint16_t voltReg; uint16_t currReg; uint16_t powerReg; } Hlw8032Data; Hlw8032Data hlw8032 {0};从 buf 中按偏移取出电压寄存器、电流寄存器和功率寄存器的值。需要注意大小端的问题HLW8032 发送数据一般先发高位还是先发低位这个一定要看手册别想当然。如果大小端搞反换算出来的数据可能会跳得非常离谱你查半天都查不出原因。取到寄存器值之后按照之前标定好的比例系数做乘法得到物理量。再把物理量用 printf 重定向到串口打印出来或者通过 OLED 屏显示。我在项目里是把数据直接通过串口发到上位机方便实时观察。void Hlw8032_Parse(uint8_t *buf, uint8_t len) { uint8_t sum 0; uint8_t i; if (buf[0] ! 0x55 || buf[1] ! 0x5A || buf[2] ! 0x5A || buf[3] ! 0xA5) { return; } for (i 0; i len - 1; i) { sum buf[i]; } if (sum ! buf[len - 1]) { return; } hlw8032.voltReg (uint16_t)((buf[offset_v] 8) | buf[offset_v 1]); hlw8032.currReg (uint16_t)((buf[offset_c] 8) | buf[offset_c 1]); hlw8032.powerReg (uint16_t)((buf[offset_p] 8) | buf[offset_p 1]); hlw8032.frameReceived 1; }注意这个代码里面用的offset_v、offset_c、offset_p要根据实际手册去定义我这边为了写例子就省略了宏定义。还有校验算法也不一定是简单累加和有的版本可能是带异或的这个务必看手册。4.4 后续扩展思路跑通基本流程之后你可以把这套采集逻辑接到别的模块里。比如加上一个 OLED 显示电压、电流、功率或者通过 ESP8266 把数据上传到物联网平台。因为 HLW8032 输出的是实时有效值不需要后端再补滤波算法拿来即用。我自己在这个项目后面加了一个简单的掉电存储功能每隔一分钟把电量相关数据写到 STM32 内部 Flash 里防止断电丢数据这在实际产品里是一个很常见的需求。5. 调试中的典型问题与排查技巧5.1 数据帧经常对不齐、解析不到帧头这个是我调试过程中遇到最多的问题尤其在强电环境下面。表现是上位机偶尔能看到一帧正确的数据但大部分时候数据乱飞。排查思路其实很简单先用示波器或者逻辑分析仪看 HLW8032 的 TX 引脚波形确认波形是否干净有没有毛刺再确认波特率是否准确。我那次的问题是电源纹波太大导致芯片输出信号边沿抖动接收端采样点偏了。后来在 HLW8032 供电这里用 LDO 单独供电问题就消失了。还有个比较隐蔽的原因串口接收中断里处理的任务太重导致后续字节没及时读走USART 的 RXNE 标志被覆盖产生丢字节。解决方法是中断服务函数里只做“存字节”这一个动作解析过程放主循环。5.2 电压、电流读数整体偏大或者偏小这种问题大概率出在标定系数上面。前面提到的换算系数是实测标出来的不同板子因为电阻精度差异系数会有一定区别。批量生产的时候建议在出厂校准或者烧录程序时写入每块板子自己的系数而不是所有板子用同一个系数。电流读数不准还有一个常见因素采样电阻的精度。很多采样电阻标称精度只有 1%实际阻值可能偏了 0.5%如果最大量程设得大误差会很明显。有条件就选 0.1% 精度的合金电阻贵不了多少但省心很多。5.3 芯片发热导致数据漂移这问题我最初没想到后来一摸采样电阻滚烫才知道不对劲。电流采样电阻一旦发热阻值就会变化而且这个变化是动态的电流越大发热越厉害读数偏差越大。如果你做的是大电流项目比如充电桩这种 10A 级别以上的采样电阻的额定功率必须留足余量PCB 上也最好有大面积铺铜散热。另外芯片本身也会发热VCC 附近的温度漂移会影响内部基准表现出来就是电压通道的读数随着开机时间缓慢漂移。要缓解这个问题可以从降低 VCC 电压入手也可以用低温漂系数的分压电阻。5.4 串口打印正常但上位机收到乱码大概率是串口助手的波特率设置和程序不一致。HLW8032 是 4800 波特率但很多现成的串口调试助手默认是 9600 或者 115200改过来就好了。还有一种情况是你重定向 printf 时底层 fputc 函数配置错误导致进不了串口这一类问题用仿真器打断点很容易定位。6. 一点实操体会最后分享一个小的经验把 HLW8032 接入系统之后不要急着写大量代码先用一个现成的串口转 USB 模块接在 HLW8032 的 TX 上在电脑串口助手里面观察原始数据是否正常。如果这一步通过了说明硬件链路没问题再写 STM32 的解析逻辑。如果这一步都收不到数据那就检查供电是不是对啊、TX 是不是虚焊啊这些基础问题比软件逻辑更容易浪费你一整天时间。我这次的板子上第一版有一个低级错误就是把电压采样分压电阻的封装画小了。220V 直接怼上去电阻发热到可以煎鸡蛋数据当然也是一团糟。后来换了 1206 封装的贴片电阻情况立刻好转。所以搞这种强电弱电混合的项目画板的时候就要把爬电距离、电阻功率、散热这些因素提前考虑进去不要等调试的时候才来补救。说到调试我强烈建议你固件里加一个 Debug 模式用一个 GPIO 引脚来指示帧是否解析成功这样不用接上位机也能快速判断当前通信状态。这个调试方式帮我省了很多事情遇到问题可以先看灯再决定要不要接逻辑分析仪深挖。本文还有配套的精品资源点击获取