ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HLW8112电能计量芯片驱动开发实战:从接口选型到精度校准

HLW8112电能计量芯片驱动开发实战:从接口选型到精度校准 简介这份驱动源码围绕 hlw8112 能源计量芯片展开覆盖电力、燃气等能源计量场景适合嵌入式开发、智能仪表及电力计量方向的工程师参考。压缩包共 6 个文件由 3 个头文件与 3 个 C 源文件组成RAR 整包仅 7KB代码量精简头文件用于寄存器定义与接口声明C 文件则实现 SPI 读写、初始化及数据处理逻辑便于快速移植到资源受限的 MCU 项目。源码完整实现 hlw8112 的初始化、SPI 接口通信、寄存器访问、计量数据获取与校准流程其中校准包含零点校准和线性校准可帮助开发者理解如何保证能量计量的准确性与一致性整体结构清晰可直接嵌入现有工程。理解这份代码时通常还要结合硬件电路设计中的电源稳定、传感器接入、抗干扰和 EMC 等要点可同步建立软件与硬件的整体认知。目前已有 2251 人浏览学习对正在选型或调试 hlw8112 的开发者具有直接参考价值。1. 看到 hlw8112 这个编号就知道要写的是计量程序看到 hlw8112 这个编号做嵌入式的人一般会先想到上海贝岭那颗单相电能计量芯片。它在智能插座、充电桩、导轨表、电力监控里出现得非常多负责量测电压、电流、有功功率和电能再通过 SPI 或 UART 送给主控。hlw8112_程序这种命名在芯片厂 SDK 和个人工程里都很常见核心就是驱动程序和芯片型号一一对应。很多人拿到芯片第一件事是找现成例程但实际项目里卡住的从来不是代码行数而是初始化时序、数据位宽和校准系数。难怪同一个芯片有人一天调通有人折腾一周。这篇按我自己做电力采集项目的习惯来写从选接口、配寄存器、读数据到校准确认一条线走完。适合正在做计量产品、或者想把这颗芯片评测自己做掉的工程师。2. HLW8112 的接口选型与寄存器架构先看手册里的这几项2.1 UART 和 SPIHLW8112 的两种通信接口怎么选HLW8112 同时开出 UART 和 SPI 两组通信脚引脚上是复用的。具体激活哪一路通常由芯片外部引脚电平或上电时序决定不同封装和批次会略有差异拿到样片后先按手册确认。这样设计的好处是同一颗芯片既能进低成本仪表也能进高速采集板。从项目选型角度我一般这样判断主控资源紧张、计量数据量不大用 UART。波特率常见在 4800 到 38400 之间读取间隔几十到两百毫秒完全够用而且只需要两根线方便做隔离。比如后面接 Modbus 的采集器一根 TX、一根 RX 就进去了。数据更新率要求高比如要做快速保护判断或实时电能质量分析用 SPI。SPI 时钟可以跑到几兆一次读三个数据寄存器也就几十微秒。代价是四根线对时序要求更严隔离成本也高。还有一点要提醒很多主控的 SPI 引脚已经被 LCD 或者 flash 占用这时候宁可走 UART也不要硬做软件模拟 SPI。软件模拟 SPI 在中断多的裸机工程里很容易断帧排错成本远高于换接口的成本。HLW8112 一般是单电源 3.3V 供电采样端是差分输入。无论选哪种接口电平都必须和主控对齐5V 主控要加电平转换。接口方案没定清楚就写驱动后面一定回改。2.2 寄存器映射与 HLW8112 的数据格式HLW8112 的手册里寄存器分两块配置寄存器和数据寄存器。配置寄存器管 ADC 的量程、通道开关、功率更新时间、滤波方式、中断屏蔽数据寄存器保存电压有效值、电流有效值、有功功率、电能计数。编写程序时关心的是以下几类我把它整理成一张表具体地址以你手里的手册为准写代码前务必逐项回填核对寄存器分组典型名称主要作用注意点系统配置SYSCFG / STATUS软复位、晶振状态、通信模式上电后先读状态确认 UART/SPI 模式已切对ADC 配置ADCCFG通道增益、更新速率功率更新时间决定数据刷新率影响主循环节拍电压数据VRMS电压有效值原始码无符号数具体位数看手册电流数据IRMS电流有效值原始码小电流时低几位会跳不能直接拿来显示功率数据P_ACT有功功率原始码有符号数注意符号扩展电能数据ENERGY电能脉冲计数读后是否自动清零必须确认数据寄存器一般是 24 位也有部分寄存器是 16 位必须按手册给的位宽取数。这里有个常见坑直接读回来的值不能当有效值用要先按位宽做符号扩展再乘换算系数。芯片寄存器里存的是原始码物理量等于原始码乘以换算系数。换算系数手册会给一个典型值但实际由于采样电阻、分压电阻的精度差异必须做标定这个放到第五章细说。2.3 功率更新时间与读取节拍HLW8112 的 ADC 会按固定周期刷新数据刷新率由配置寄存器里的字段决定常见选项在 100ms 到 1s 之间。读取程序最好和数据更新周期错开先读状态寄存器里的数据更新标志位置位之后再读取数据寄存器。如果找不到这个标志位就用时间片比如配置成 200ms 更新一次主循环每 50ms 读一次取最近一次完整数据。很多新手上来就连续读会读到新旧混拼的数据电压是一个采样窗口的、电流是另一个窗口的。瞬时功率还好电能累加值可能出现跳变。处理很简单把读取函数做成一段完整操作读到的数据先存 buffer 再统一解析避免读一半被中断打断。如果走 UARTHLW8112 的通信协议一般是命令帧加响应帧结构主控发一条读取命令芯片回固定长度数据帧帧里通常带校验字节。这和我们写 Modbus 单片机帧接收数据程序的思路一致先收完一整帧再解析不要收到一个字节就处理一个字节。UART 模式下芯片是从机不会主动上报主循环里必须周期查询不能指望芯片中断通知你。3. HLW8112 驱动初始化与数据读取的代码实现3.1 初始化 HLW8112 的最小 C 代码写驱动前先定硬件连接。以 STM32 加 SPI 为例PB12 做片选 CSPB13 接 SCLKPB14 接 MISOPB15 接 MOSI。HLW8112 的 SPI 操作是主控发命令、芯片回数据一条命令同时完成写和读。先写初始化函数#include hlw8112.h /* 寄存器地址占位以你的芯片手册为准 */ #define HLW8112_REG_SYSCFG 0x01 #define HLW8112_REG_ADCCFG 0x02 #define HLW8112_REG_I_RMS 0x10 #define HLW8112_REG_V_RMS 0x11 #define HLW8112_REG_P_ACT 0x12 #define HLW8112_CS_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET) #define HLW8112_CS_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET) static uint8_t hlw8112_readwrite(uint8_t byte) { uint8_t rx 0; HAL_SPI_TransmitReceive(hspi1, byte, rx, 1, 10); return rx; } void HLW8112_Init(void) { /* 拉低片选进入命令模式 */ HLW8112_CS_LOW(); hlw8112_readwrite(HLW8112_REG_SYSCFG); hlw8112_readwrite(0x00); /* 软复位 */ HLW8112_CS_HIGH(); HAL_Delay(10); /* 等内部上电稳定 */ /* 配置 ADC更新速率、通道使能、增益 */ HLW8112_CS_LOW(); hlw8112_readwrite(HLW8112_REG_ADCCFG); hlw8112_readwrite(0x18); /* 200ms 功率更新不启用高通滤波 */ HLW8112_CS_HIGH(); }逻辑说明HLW8112 的 SPI 时序就是先发寄存器地址再发数据或读数据。代码把写寄存器和读寄存器统一成hlw8112_readwriteCS 低有效每次操作拉低片选完成后拉高。HLW8112_REG_ADCCFG后面的0x18是配置值每一位的含义看手册这里给的是可运行的结构。不同批次芯片可能不一样拿到样片后先用逻辑分析仪抓一遍 SPI 波形确认时钟极性和相位与主控匹配。参数说明HAL_SPI_TransmitReceive第四个参数是数据长度这里每次传 1 字节最后一个 10 是超时毫秒数。如果返回超时先检查 SPI 时钟极性、分频系数和 CS 引脚配置不要急着改寄存器值。SPI 时钟建议先从 1MHz 开始调通后再往上提。3.2 读取 HLW8112 电压电流功率的代码读取同样走 SPI。先发寄存器地址再连续读多个字节拼成整型。24 位数据的读取写成这样static uint32_t HLW8112_ReadReg24(uint8_t reg) { uint8_t buf[4] {0}; uint32_t val 0; HLW8112_CS_LOW(); hlw8112_readwrite(reg | 0x80); /* 读命令标志位按手册确认 */ buf[0] hlw8112_readwrite(0x00); buf[1] hlw8112_readwrite(0x00); buf[2] hlw8112_readwrite(0x00); HLW8112_CS_HIGH(); val ((uint32_t)buf[0] 16) | ((uint32_t)buf[1] 8) | buf[2]; return val; }逻辑说明reg | 0x80是很多计量芯片通用的读命令习惯地址最高位为 1 表示读。如果手册没写这一位直接发地址即可。三次readwrite(0x00)是在同一个 CS 低电平窗口里取回三个字节顺序通常是高位在前。如果读出来数据明显不对把 buf[0] 和 buf[2] 对调再试。24 位数据里电压和电流无符号有功功率有符号要先把第 23 位当符号位扩展成 int32_t。初次调试时不要直接接 220V。用 USB 供电的 5V 直流接在电压采样端电流端短接。读回来的 VRMS 寄存器应该稳定在一个固定值IRMS 接近 0这样 SPI 通没通一下就看出来了。很多人一上来就接强电读回全 0xFFFFFF以为是芯片烧了其实是时序不对或者地线没共好。读取字节数要和寄存器定义长度严格一致。有些工程师图省事把所有寄存器都按 24 位读遇到 16 位寄存器会把相邻字段也带进来。宁可多写 ReadReg16、ReadReg24 两个函数也不要一个函数通吃后续维护时看到函数名就知道位宽不容易错。3.3 原始码值换算成真实电压电流原始码换算成真实值公式非常直白V_real VRMS_code × V_gain I_real IRMS_code × I_gain P_real P_code × P_gain三个 gain 不是手册里的固定常数而是跟采样电路强相关。电压通道用了 1MΩ 加 1kΩ 分压220V 对应 ADC 输入大约几百毫伏这时一个码值对应多少伏特由分压网络决定。程序里我会把三个增益定义成宏方便校准后统一修改#define HLW8112_V_GAIN 0.001234f /* 单码对应电压校准后回填 */ #define HLW8112_I_GAIN 0.000098f /* 单码对应电流 */ #define HLW8112_P_GAIN 0.000987f /* 单码对应功率 */这里特意说明不要在应用层用 VRMS × IRMS 重新算功率。HLW8112 内部算的是瞬时有功功率的积分功率寄存器和电压电流码之间不存在简单的乘法关系。在功率因数低的负载上硬算误差能到 5% 以上。直接信任芯片的功率寄存器结果。提示调试 HLW8112 时电压采样端务必先用调压器或隔离变压器供电不要直接碰强电。先用直流小信号验证程序逻辑再接 220V 交流。4. 把 HLW8112 程序接入 STM32 工程Modbus 上报与 LCD1602 显示4.1 硬件连接与引脚分配实际项目里HLW8112 通常和主控在同一个板上之间走短走线。以最小系统为例引脚分配如下HLW8112 引脚STM32 引脚说明CSPB12片选必须由 GPIO 控制SCLKPB13SPI 时钟复用为 SPI1_SCKMISOPB14芯片输出接主控输入MOSIPB15主控输出接芯片输入VDD3.3V供电就近放 0.1uF 去耦电容GNDGND和主控共地如果 HLW8112 和主控距离超过 10cm或者中间穿过开关电源区域SPI 时钟速率不要超过 2Mbps信号线上串一个 33Ω 电阻底下铺完整地铜皮。UART 模式对走线没那么敏感这也是很多人把计量芯片放在强电一侧、用 UART 跨接的原因。跨接时通信线要加数字隔离器不要用光耦随便搭计量数据的连续性和稳定性会被光耦的温漂影响。4.2 定时读取与 Modbus 上报采集项目里最常见的上报方式是 Modbus RTU。CT 采到的电流经过 HLW8112 算好之后主控把电压、电流、功率、电能更新到一组寄存器上位机轮询即可。核心读取代码是这样void HLW8112_Update(uint8_t interval_ms) { static uint32_t last_tick 0; if (HAL_GetTick() - last_tick interval_ms) { return; } last_tick HAL_GetTick(); /* 先读状态确认数据更新完成 */ uint8_t status HLW8112_ReadReg8(HLW8112_REG_STATUS); if (!(status 0x01)) { return; /* 数据尚未更新保留上一帧 */ } g_vrms_code HLW8112_ReadReg24(HLW8112_REG_V_RMS); g_irms_code HLW8112_ReadReg24(HLW8112_REG_I_RMS); g_p_code HLW8112_ReadReg24(HLW8112_REG_P_ACT); g_vrms (float)g_vrms_code * HLW8112_V_GAIN; g_irms (float)g_irms_code * HLW8112_I_GAIN; g_pwr (float)(int32_t)g_p_code * HLW8112_P_GAIN; }逻辑说明函数用HAL_GetTick()做时间片interval_ms 一般传 200和 ADC 功率更新周期错开。status 寄存器最低位置 1 表示新数据就绪此时读到的三个寄存器同属一个采样窗口。g_p_code 是 24 位有符号数强转 int32_t 再乘增益不能省。否则负功率会被解释成很大的正值在并网逆变器或者回馈负载场景里会发现用电量越用越少。Modbus 寄存器映射我做成了连续地址段方便上位机一次读多个Modbus 寄存器地址内容数据类型缩放0x0001电压uint16放大 100 倍0x0002电流uint16放大 1000 倍0x0003有功功率int16放大 10 倍0x0004电能累计uint32跨两个寄存器单位 WhModbus 帧接收程序的要点是第一个字节不一定要对齐帧里的长度字段和 CRC 校验才是关键。HLW8112 的计量值放进 Modbus 寄存器后从机侧只要保证 CRC 正确主站怎么轮询都不会错位。上报周期和采样周期解耦HLW8112 自己 200ms 更新一次数据Modbus 寄存器 500ms 刷一次就够没必要每次上位机请求都去翻转 SPI 总线。这能显著降低主循环阻塞尤其在同时跑 LCD、按键和通信任务的裸机工程里。4.3 用 LCD1602 做本地显示与验证项目要本地看数据LCD1602 是和 HLW8112 搭配很多的一块屏幕。两行显示放电压、电流第三、第四个参数用翻页处理。注意分压比例和电流互感器变比必须折算不能直接打印 g_vrms。我一般会在 LCD1602 上专门留一行显示状态寄存器值。排查问题时比串口打印直观看 status 是否按规律切换如果是说明 ADC 还在出数据如果一直是 0x00说明配置没写进去或者 CS 时序不对导致芯片复位失败。这个思路同样适合接小程序做远程监控的系统。本地显示和远端上报用同一份数据源只是传输通道不同。前端和后端都不要重新计算功率直接用 HLW8112 算好的结果省得两边数值对不上。多通信任务并存时把 HLW8112 读取放到独立任务或定时器中断里数据写入全局结构体Modbus 和蓝牙只读结构体SPI 总线不会被抢占。5. HLW8112 的最后一道工序精度校准与 3 个常见坑写程序只是第一步计量产品必须有校准环节。做法是给 HLW8112 接入已知精度的标准源或者精度 0.2% 以上的功率计跑一段稳定负载读取原始码反推增益系数。校准系数V_gain 标准源电压读数 / VRMS_code I_gain 标准源电流读数 / IRMS_code P_gain 标准源功率读数 / P_code分别在 10%、50%、100% 负载三个点采样三次增益相差 0.5% 以内取中间值。偏差更大时检查采样电阻温漂和 PCB 布局不要直接平均了事。常见坑一空载功率不归零。小电流时 IRMS 低几位跳动是正常的但空载功率码应该接近 0。如果空载 P 码稳定在较大值把采样电阻两端短接读回来的码值就是零漂程序里做数字零点扣除。注意刷新率变化时零漂也会变最好在校准模式里动态测一次。常见坑二读取被打断。HLW8112 寄存器分时更新SPI 读三个字节中间插入其他中断三个字节可能来自不同更新窗口。解决办法是读完一整帧后做合理性检查电压码值与上一次相差不超过 5%超过就丢弃重读。不要用关中断解决否则计量任务一多主控响应会劣化。常见坑三电能寄存器清零策略。电能是计费数据掉电要保存。有些芯片读后自动清零有些手动清零必须确认手上这颗的行为。读后清零的主机要在掉电前把累计值写入 EEPROM。我一般把 EEPROM 写入周期设为 1 分钟一次同时掉电检测中断里强制再存一次最多丢 1 分钟累计量。最后验收有个土办法用 1kW 电暖器做负载连续跑两个小时对比电表和 HLW8112 累计电能。误差稳定在固定百分比说明是增益问题回填系数即可。误差随时间漂移优先查芯片供电电压和晶体而不是采样电阻。这个方向基本能覆盖绝大多数精度异常。本文还有配套的精品资源点击获取
返回列表