HLW8112+STM32电能计量开发:SPI与UART通信实战指南 简介本资源是一套面向嵌入式硬件开发者与STM32初学者的HLW8112电能计量芯片实战开发例程聚焦SPI与UART双通信接口在电量采集系统中的工程落地。资源完整覆盖HLW8112寄存器配置、数据读取、校准逻辑及通信异常处理等关键环节适用于智能插座、能耗监测终端等低功耗计量类项目开发。压缩包共191个文件含47个C源码如stm32f4xx_tim.c、stm32f4xx_rtc.c、45个头文件h、25个编译中间文件o/d及Keil工程核心文件uvproj、uvopt、axf、hex等结构清晰便于理解驱动分层与工程组织逻辑整体大小为6.51MB。已有2316人学习下载配套工程可直接编译运行包含串口调试输出、SPI时序验证及多级寄存器读写测试用例显著降低HLW8112与STM32联调门槛。 做计量插座、智能断路器、充电桩监控模块这类东西的差不多都会撞上同一个问题能不能用MCU自带ADC去采电压电流再把功率算出来说实话对成本极度敏感的玩具方案能凑合但一旦需要稳定精度、长期不漂、还要把数据做成协议上报这条路就会越走越难。我这次的方案是直接上一颗单相计量芯片HLW8112搭配STM32做上位处理器一套例程里把SPI和串口通信都覆盖了下面把完整的开发思路和踩坑记录整理出来。如果你正在做电能计量、功率监测或是准备从分立计量方案往专用计量芯片迁移这篇文章里的思路应该能直接帮你省掉至少一周的调试时间。先说结论HLW8112不是那种需要你实时做FFT或积分运算的芯片。它把电压、电流采样后的计算都在芯片内部处理完你通过SPI或者UART去读结果就行。对MCU来说这就变成了一项外设访问工作重点是搞懂它的寄存器和帧格式而不是去搞一堆浮点算法。1. HLW8112这块单相计量芯片解决的是哪类项目需求1.1 为什么放着MCU ADC不用非上计量芯片很多工程师的第一反应是我已经有STM32了它自带12位ADC分压电阻加采样电阻再写个均方根算法不就能测电压电流了吗理论上确实可以但实际做产品就是另一回事了。首先是误差管理。MCU内部ADC的增益误差、偏移误差加上外部电阻的精度偏差整套下来要做到1%以内都很吃力。批量生产的时候每一块板的电阻阻值不会完全一致你不可能逐台去手工校正。其次是隔离问题。如果做的是市电计量采样回路和MCU之间如果没有隔离或者保护设计一个浪涌可能把MCU一起带走。还有温漂ADC基准电压本身就会随温度变化冬天测的和夏天测的数值可能差好几个点。专用计量芯片则把这些事全部接管了内部有精密ADC、可编程增益放大器、数字滤波连电压电流有效值、有功功率、无功功率、电量累加这些计算都在芯片内完成MCU只负责读结果省心得多。还有一个很容易忽略的点做计量认证或产品验收时用专用计量芯片的方案测试报告里的相位误差、线性度、温漂数据都更漂亮过检的时候少折腾。用MCU ADC硬凑的方案光是校准流程就会让产线效率大打折扣。1.2 HLW8112能拿到哪些计量数据HLW8112是一颗单相电能计量芯片它输出的不是原始采样值而是经过芯片内部DSP计算后的统计量。典型的数据包括电压有效值也就是市电的RMS值正常在220V附近。电流有效值负载电流的RMS值范围取决于你选的采样电阻或互感器。有功功率电压和电流同相分量相乘后的平均值。功率因数有功功率和视在功率的比值反映负载性质。电量累计芯片内部有电能累加寄存器可以读出从上次清零以来的用电量。瞬时电压电流波形相关的原始数据部分场景下也会用到。这些数据在HLW8112内部都换算成寄存器值。MCU拿到的其实是原始整数需要结合校准系数还原成实际的电压伏特数、电流安培数和功率瓦数。这个过程我后面会专门讲千万别直接拿寄存器值当物理量用那是新手最容易犯的错。对比一下HLW8112和传统MCU ADC方案的差异可以用一个表格直观说明对比项MCU ADC方案HLW8112方案电压/电流有效值计算需要自己写均方根算法芯片内部算好读出即用有功功率需要电压电流同步采样芯片内部完成乘法累加电量累计需要MCU定时累加芯片有专用累加寄存器校准难度每台都要校准整机校准一次即可稳定性受MCU基准源温漂影响芯片内部基准独立稳定性更高1.3 适合用这颗芯片的典型产品形态从我实际接触到的情况看HLW8112主要出现在这些产品里智能插座和计量插排这是最典型的应用一个HLW8112加一个STM32G0或者STM32F103成本控制得相当好。充电桩的交流计量模块交流充电桩需要统计充电电量HLW8112的精度和稳定性足够满足标准要求。智能断路器不但要计量还要配合脱扣控制HLW8112把计量部分包掉之后MCU可以专心处理逻辑。服务器PDU、机房列头柜监控需要远程读取每一路的电压电流功率HLW8112作为前端采集芯片很合适。能耗管理终端比如工厂级的能耗监测设备做多回路扩展时每回路面一颗HLW8112MCU统一读取。如果你的项目正好落在这几个方向那HLW8112这颗芯片基本是够用的。2. 硬件接线与模式选择SPI和UART的接法差异很大2.1 供电、采样回路和晶振这些共用部分不管用SPI还是UART硬件上的公共部分是一样的。HLW8112一般用5V供电芯片的VCC和GND之间必须就近放一个0.1uF陶瓷去耦电容有条件的话再并一个10uF电解电容。很多计量不准的问题最后查下来都是电源纹波大导致内部ADC基准抖动造成的去耦电容不是摆设。电压采样通道一般是电阻分压网络把市电电压降低到芯片ADC输入允许的范围。分压电阻建议用0.1%甚至0.05%精度的低温漂电阻阻值按照芯片数据手册里推荐的量程去选不要自己随便改。电流采样通道有两种常见接法低端用小阻值锰铜采样电阻高端用电流互感器。用互感器时要注意相位补偿因为互感器本身有相位误差会影响功率因数的测量。PCB布局上采样信号要尽量远离数字信号线尤其不要让SPI的SCLK和采样线并行走很长距离否则串扰会让读数跳动。另外如果芯片外围需要晶振一定要按数据手册指定的负载电容来选不要随便拿两个22pF焊上去。晶振起振异常会导致芯片完全不通信而且这种问题用万用表还量不出来。2.2 SPI模式接法和STM32引脚映射HLW8112的SPI接口一般会引出SCLK、SDIMOSI、SDOMISO、CS这四根信号线有的封装还会带一个中断/状态输出脚。和STM32连接时SDI接STM32的MOSISDO接STM32的MISOCS接任意一个空闲GPIOSCLK接SPI时钟脚。我习惯把CS接在普通GPIO上而不使用STM32硬件NSS原因很简单软件片选可控性更强不会因为SPI外设状态异常导致CS误拉低。关于硬件片选和软件片选的取舍我再多说一句。STM32的硬件NSS在某些情况下会由外设自动控制如果你的程序里同时有多个SPI器件或者SPI中断优先级处理不当CS信号可能出现意外拉低导致从机误响应。用软件片选CS的拉低和拉高完全由代码控制虽然多花一条GPIO但调试起来清晰得多。SPI模式下还需要注意接线顺序。SCLK和CS是从STM32输出给HLW8112SDI是STM32输出给芯片SDO是芯片输出给STM32。三个方向不要搞混特别是SDI和SDO这两个名字很容易看反接反之后数据读写全乱。如果芯片支持单线半双工模式也就是SDIO一根线双向通信那STM32侧的SPI要配置成半双工模式发送和接收分时进行这个我在下面代码部分会提到。2.3 UART模式接法和电平适配UART模式更简单芯片的TXD接STM32的UART RX芯片的RXD接STM32的UART TXGND共地就这三根线。不过有一个关键点必须注意HLW8112的供电如果是5V它的TXD输出高电平很可能也是5V。STM32的引脚并不全是5V容忍的尤其在F4、H7这些较新的系列上很多普通IO只支持3.3V电平。把5V电平直接接到3.3V容忍引脚上短时间能用长期跑会降低引脚寿命严重的会直接烧坏IO。我的处理方式一般是加一个简单的电阻分压或者用一颗电平转换芯片。分压的话串一个1k再对地接一个2k把5V降到3.3V左右成本最低。需要注意串口RX方向也就是STM32发给芯片的方向3.3V高电平能不能被芯片识别为高电平这个要看芯片手册里的高低电平阈值。大多数情况下3.3V是够的但最好确认一下VIH参数。接入顺序也要注意插拔串口线时优先接GND再接TX和RX。有些板子共地没接好时信号线会通过芯片内部保护二极管形成回路导致芯片发热甚至通信异常。2.4 模式切换到底听谁的HLW8112支持SPI和UART两种接口但同一时刻只能工作在一种模式下。模式选择一般由芯片的某个管脚在复位时的电平状态决定比如一个MODE脚拉高选SPI、拉低选UART或者反过来。这个必须严格按照数据手册来。我见过太多人直接在软件里换通信方式发现怎么都不通最后查下来是硬件模式脚根本没设对。软件里配置了SPI芯片硬件却还停在UART状态那怎么可能读得到数据。所以拿到板子先确认模式脚的接法再用万用表量一下实际电平确认无误后再开始写代码。在例程里我会用宏定义把接口模式固定下来#define HLW_IF_MODE_UART 0 #define HLW_IF_MODE_SPI 1 #define HLW_IF_MODE HLW_IF_MODE_SPI后面代码中所有跟接口相关的分支都用这个宏控制切换模式时只改这一行。实际项目中不要动态切换我做过测试热切换的成功率并不高除非芯片手册明确支持否则不建议在运行中来回切。3. SPI例程拆解初始化参数、读寄存器函数和时序验证3.1 先读懂芯片时序图再写配置拿到任何一颗SPI从机芯片我从来不急着写STM32的初始化代码而是先翻数据手册里的时序图。要看清楚三个参数SCLK空闲电平是高还是低数据在上升沿还是下降沿被采样MSB还是LSB先传输。这三个参数决定了STM32 SPI的CPOL、CPHA和FirstBit配置。HLW8112的SPI时序大多数情况下支持模式0也就是CPOL0、CPHA0SCLK空闲为低数据在第一个跳变沿即上升沿被采样。但我建议你拿到具体批次芯片后用逻辑分析仪对比芯片厂商的示例波形确认一次。为什么因为有些芯片厂商在数据手册里给的时序图画得模棱两可或者实际上还兼容模式1、模式2、模式3但对某些命令只在特定模式下才稳定。我之前的经验是先用模式0跑不行就试模式1再不行试其他两个模式用一次读器件ID的操作做验证哪种模式读出来的数据符合预期就用哪种。SCLK频率也要注意。HLW8112这类计量芯片不是高速SPI设备手册上一般会写最大SPI时钟频率可能只有几MHz。STM32 SPI初始化时先配一个较大的分频系数比如APB2总线时钟72MHz除以64得到1.125MHz不要上来就除以2。等通信稳定了再逐步提高时钟频率压测一下读数是否正确。3.2 STM32 SPI主机初始化与寄存器读取实现我用STM32CubeMX做工程初始化时SPI配置如下模式Master数据宽度8 bit时钟极性Low时钟相位1 EdgeNSSSoftware波特率分频64首位MSB First对应的HAL初始化代码void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); }CS引脚我单独做两个宏#define HLW_CS_Pin GPIO_PIN_12 #define HLW_CS_GPIO_Port GPIOB #define HLW_CS_LOW() HAL_GPIO_WritePin(HLW_CS_GPIO_Port, HLW_CS_Pin, GPIO_PIN_RESET) #define HLW_CS_HIGH() HAL_GPIO_WritePin(HLW_CS_GPIO_Port, HLW_CS_Pin, GPIO_PIN_SET)读取一个寄存器的最底层函数是这样写的uint16_t HLW_ReadRegister(uint8_t regAddr) { uint8_t txData[2] {0}; uint8_t rxData[2] {0}; uint16_t regValue 0; txData[0] (regAddr 0x3F) | 0x80; // 示例最高位置1表示读命令 txData[1] 0x00; // 占位字节用于驱动时钟 HLW_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, txData, rxData, 2, 100); HLW_CS_HIGH(); regValue (uint16_t)((rxData[0] 8) | rxData[1]); return regValue; }这里要专门说明一下芯片的具体寄存器地址编码、命令字节格式、返回数据长度以你拿到的手册为准。我这只是一个通用框架把CS操作和SPI收发串起来。你拿到HLW8112手册后把命令字和地址位按实际定义替换就行。重点在于理解这个流程拉低CS - 发命令和地址 - 同时接收数据 - 拉高CS - 解析数据。如果芯片是多字节寄存器比如32位电量寄存器需要连续读4个字节我一般会封装一个多字节读取函数void HLW_ReadMultiBytes(uint8_t regAddr, uint8_t *buf, uint8_t len) { uint8_t txData[len 1]; uint8_t rxData[len 1]; memset(txData, 0, sizeof(txData)); memset(rxData, 0, sizeof(rxData)); txData[0] (regAddr 0x3F) | 0x80; HLW_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, txData, rxData, len 1, 100); HLW_CS_HIGH(); memcpy(buf, rxData[1], len); }HAL库的HAL_SPI_TransmitReceive是阻塞式收发速度慢一点但逻辑清晰。如果后期需要高速连续读取可以升级成SPI DMA方式把CS拉低后启动DMA传输传输完成中断里再拉高CS。但DMA方式要特别注意CS拉高的时机必须在最后一个SCLK时钟结束之后才能拉高CS否则最后一个字节可能没被正确采到。3.3 SPI通信验证三板斧SPI代码写完先不要急着解析数据按下面三步验证第一用逻辑分析仪抓CS、SCLK、MOSI、MISO四根线的波形。确认CS有拉低动作SCLK有正常脉冲MOSI上能看到你发出的命令字节MISO上有芯片返回的数据。如果MISO一直是高电平说明芯片根本没有响应大概率是SPI模式不对或者芯片没进入SPI模式。第二读芯片固定的标志寄存器或器件ID寄存器。计量芯片一般会有软件版本号、器件ID之类的只读寄存器读出来的值是和手册对得上的。这个能对上说明通信链路完全通了。第三给电压通道接一个稳定的实际电压比如220V市电单独读电压寄存器观察数值是否稳定。电流回路开路时电流寄存器应该接近0功率寄存器也应该接近0。如果这些都有了SPI部分就算完全打通了。4. 串口例程拆解用空闲中断做不定长帧接收4.1 芯片串口的波特率、帧结构和握手率HLW8112的UART模式帧格式一般是波特率4800或96008位数据位无校验或偶校验1位停止位具体以手册为准。计量芯片的串口不太可能特别快因为它本身的数据更新率不高一个周期可能几百毫秒才更新一次你就算用115200波特率读到的数据更新频率还是那样。HLW8112在UART模式下通常是主动上报模式也就是芯片每隔一段时间自动把一帧计量数据发出来MCU只需要被动接收。有的计量芯片也支持命令应答模式MCU发一个查询命令芯片再回一帧。这个要区分清楚。如果是主动上报模式MCU不要发任何东西只开接收中断。帧结构一般是帧头寄存器数据校验字节帧尾。比如帧头可能是0xAA 0x55帧尾可能是0x0D 0x0A也就是回车换行。这里要注意0x0D 0x0A只是帧尾分隔符的一种常见形式把它当作数据结束标志来切帧非常适合UART不定长接收。如果你在调试时发现数据里莫名其妙多出0x0D 0x0A不要慌先看是不是帧尾。4.2 STM32串口空闲中断DMA接收的实现接收HLW8112的UART数据我推荐用STM32的空闲中断加DMA方式而不是最简单的单字节中断。单字节中断虽然写起来简单但每来一个字节进一次中断CPU占用率高而且在处理其他任务时容易丢字节。空闲中断加DMADMA自动把数据搬进缓冲区当总线空闲时触发一次空闲中断这时候我们把一整帧数据拿去解析效率高得多。使用CubeMX配置UART1的DMA接收然后在代码中初始化接收uint8_t hlwRxBuf[128]; volatile uint16_t hlwRxLen 0; // 启动空闲中断接收 HAL_UARTEx_ReceiveToIdle_DMA(huart1, hlwRxBuf, sizeof(hlwRxBuf)); __HAL_DMA_DISABLE_IT(hdma_usart1_rx, DMA_IT_HT); // 去掉半传输中断只关心空闲对应的回调函数void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if (huart huart1) { hlwRxLen Size; HLW_ParseFrame(hlwRxBuf, hlwRxLen); // 重新启动接收 HAL_UARTEx_ReceiveToIdle_DMA(huart1, hlwRxBuf, sizeof(hlwRxBuf)); } }这套代码的核心思想是DMA一直在后台接收字节缓冲区满或者串口空闲时触发中断在中断回调里处理这一帧数据。注意回调执行完要马上重新开启接收否则下一帧数据就收不到了。使用DMA时还要注意缓冲区的生命周期。回调里处理完数据之后就不需要再持有hlwRxBuf了如果解析函数里需要长时间保存数据应该拷贝到另一块缓冲区否则下一帧DMA写进来时会把之前的数据覆盖掉。4.3 解析帧数据并换算成实际电量帧解析的代码我一般先做基础合法性检查再按偏移量提取字段void HLW_ParseFrame(uint8_t *buf, uint16_t len) { uint8_t checkSum; // 长度检查 if (len 8) return; // 帧头检查 if (buf[0] ! 0xAA || buf[1] ! 0x55) return; // 校验和假设是除帧头外所有字节累加取低8位 checkSum 0; for (uint16_t i 1; i len - 2; i) checkSum buf[i]; if (checkSum ! buf[len - 2]) return; // 提取字段示例电压原始值占3字节 uint32_t rawVolt (uint32_t)buf[2] 16; rawVolt | (uint32_t)buf[3] 8; rawVolt | (uint32_t)buf[4]; float voltage rawVolt * g_voltCoeff; // 电流、功率、电量同理 }实际协议里每个字段的长度和顺序完全取决于芯片数据手册。有的是高字节在前有的是低字节在前我这里给的是通用结构。最保险的做法是先用逻辑分析仪抓一帧完整数据对照手册把每个字节含义标出来再手写解析代码。不要凭猜测去移位一个字节错位后面全部乱掉。解析完数据后为了验证协议理解对不对可以把原始帧和解析出来的物理量同时通过调试串口打印出来人工比对一轮。这一步能帮你快速发现位序和字节序的错误。5. 校准必须自己做精度不是芯片给出来的5.1 HLW8112原始值和真实物理量的关系很多人以为HLW8112读出来的寄存器值直接就是多少伏、多少安这个理解是错的。芯片输出的是原始计数值它和真实物理量之间是线性关系但这个比例系数取决于外围采样电阻的阻值、分压网络的系数、电流采样元件的阻值甚至PCB布局都会带来微小的差异。举个例子电压分压网络你用1%精度的电阻两个电阻的比值就会和理论值有偏差。芯片即使内部做得再准它看到的也只是分压后的电压而不是真实的220V。所以每一台整机都要做校准把原始寄存器值和真实物理量之间的系数标定出来。校准的思路很简单用已知精度的标准源输入读出芯片的原始值然后计算系数电压系数 标准电压值 / 电压寄存器原始值 电流系数 标准电流值 / 电流寄存器原始值 功率系数 标准功率值 / 功率寄存器原始值如果芯片有功率增益寄存器或者校准寄存器也可以把系数换算后写回芯片由芯片内部直接完成校正。如果没有就在MCU固件里保存这三个系数读出来之后做浮点乘法。5.2 校准系数计算与实际操作步骤我用的校准设备是一台高精度交流功率源再加一块0.1级以上的标准电表做比对。没有功率源的话用市电加高精度万用表也能做粗略校准但精度会受限而且市电本身电压会有波动建议用稳定的可调交流源。校准步骤按下面来先不接负载记录空载时电压寄存器的值。如果电压通道在无输入时不为0需要在固件里做零点修正。把标准源输出设定到额定电压比如220V。稳定后读100次电压寄存器值取平均记为rawV。用标准源显示值220.0除以rawV得到电压系数。接上纯阻性负载让电流达到额定值比如10A。读电流寄存器平均值为rawI记录标准源的电流显示值Iref电流系数等于Iref/rawI。功率校准用纯阻性负载时功率因数近似为1功率系数可以用参考功率除以原始功率得到。如果有可调功率因数的负载可以进一步校准。校准完成后把系数存储到STM32的Flash或外部EEPROM里。由于量产时每块板的电阻误差不同产线流程通常是每一块板都跑一遍校准程序把系数写入特定地址。这个步骤千万别省不然你第一批100块板就会因为电阻精度不同导致精度参差不齐。5.3 线性度校正和温漂处理单点校准只能保证在校准点附近精度高当电流从1A到30A时芯片的增益线性度未必完全是一条直线。要求高的项目我会在空载、小电流、半载、满载这四个点分别测试算出每一点的校准系数误差。如果误差呈线性趋势可以在固件里做分段线性插值如果误差比较随机那就要考虑是不是采样电路本身的问题。温漂是另一种常见的精度杀手。计量芯片内部的基准源会有温漂系数外围采样电阻也会随温度变化。我的做法是在硬件设计阶段就选低温漂电阻电压分压电阻用25ppm甚至10ppm的贴片电阻电流采样电阻用专门的低温度系数合金电阻。算法层面可以在固件里加温度补偿但前提是你得有板级温度传感器而且要有足够的测试数据拟合出补偿曲线。大部分项目其实把硬件用料这块做好就够了算法补偿是锦上添花。6. 联调阶段最容易踩的坑我按报错现象逐个排过6.1 SPI读回数据全是FF/00如果你用SPI读HLW8112读回来的数据全是0xFF或者0x00最可能的原因按优先级排是这样芯片根本没有进入SPI模式。硬件模式脚没拉对芯片还在UART模式或者复位状态。先量模式脚电平确认无误再去折腾软件。SPI时钟极性和相位不匹配。模式0不行就试模式1这个问题通常调整CPOL和CPHA就能解决。SPI时钟频率太高。芯片跟不上数据采样错误。把分频系数调大降到1MHz以下再试。引脚接反。MOSI和MISO接反是最常见的低级错误量一下波形就能发现。接线虚焊或排针接触不良。这个排查起来最费时间而且你越不怀疑它越容易是它。建议按照从硬件到软件的顺序排查不要一上来就改代码。6.2 串口波形正常但解析出来是乱码串口波形正常指的是用示波器或逻辑分析仪能看到TXD脚上有连续的方波波形但MCU解析出来的数据对不上。首先确认波特率是否一致。计量芯片的波特率偏差如果超过2%串口就会频繁出错。用示波器量一个字节的位宽换算成波特率和代码配置的对比一下误差一目了然。其次是帧格式问题。数据位、停止位、校验位和芯片手册不一致接收端会把对齐方式搞错。比如芯片是偶校验你按无校验接收数据本身没错但解析帧头时会被校验位干扰。还有个容易忽略的就是芯片TXD如果不在空闲状态时为高电平接收端可能会出现持续接收错误。这个多半是硬件上拉电阻缺失或者电平转换电路配置不对。如果波形和配置都对那就检查你的帧解析逻辑。帧头判断、长度判断、校验和算法都要和芯片手册逐字对照尤其是高位字节和低位字节的顺序。我用过一个芯片手册上是小端存储但帧发送顺序又是大端一开始没反应过来解析出来的数据完全离谱最后把一帧数据逐字节打印出来才找到规律。6.3 电压电流值正常功率和电量却离谱这个现象很有意思电压寄存器正常、电流寄存器正常但计算出来的功率和电量明显不对。我遇到过的原因主要有两个。一是功率计算需要电压通道和电流通道的采样时间同步。如果芯片内部把电压和电流采样错开了或者外围采样电路不对称有功功率就会偏大或偏小。有些计量芯片提供相位校正寄存器可以通过配置补偿电流互感器带来的相位差。你如果用了互感器务必检查这一点。二是校准系数没有正确区分。有些项目电压电流用了校准系数但功率没用或者功率系数和电压电流系数的乘积关系搞错了。理论上在纯阻性负载下功率应该等于电压乘以电流如果你标定后的电压电流都对但功率差很多重点检查功率系数的计算过程。还有可能是负载是容性或感性负载功率因数不等于1这时候只看功率读数会觉得很奇怪但其实是正常的。接一个纯阻性负载再测一次就可以排除这个因素。结尾如果把项目再往后扩展我一般会再加固件里的参数存储模块把校准系数、清零标志、累计电量存到Flash或EEPROM里防止掉电丢失接着处理上位机协议把HLW8112读出的数据通过Modbus RTU或者MQTT上报通信方式上STM32的串口还可以外接WiFi模块或者以太网模块做成联网计量设备也就是把HLW8112的串口数据流接进整个IoT系统里后面接一个HTTP库就能实现云端上报。HLW8112加STM32这套组合真正的难点其实不在芯片本身而在于把通信时序、帧解析、校准系数、异常处理这几层问题串起来。你把SPI和串口两条路都跑通之后这套代码的复用价值会很高。以后换别的计量芯片通信底层的逻辑是通用的改改寄存器地址和帧格式就能用。这也是我当初坚持把SPI和UART两套接口都调通的原因硬件兼容性从设计上就留好了余地。本文还有配套的精品资源点击获取