/* hlw8032.c - HLW8032 电量计量芯片驱动(UART5 只收,4800 8E1) * * 帧格式(24 字节,每 50ms 一帧,主动上报): * [0] 状态寄存器 State REG 0x55=正常 / 0xAA=修调失效 / 0xFx=有寄存器溢出 * [1] 检测寄存器 Check REG 固定 0x5A * [2:5] 电压参数寄存器(本模块不用) * [5:8] 电压寄存器(不用) * [8:11] 电流参数寄存器(不用) * [11:14]电流寄存器(不用) * [14:17]功率参数寄存器 PowerPar 芯片出厂修调常数,算电量要用 * [17:20]功率寄存器(不用) * [20] 数据更新寄存器 bit7 = PF 溢出翻转标志 * [21:23]PF 寄存器 16bit 脉冲计数,溢出清零同时 bit7 翻转 * [23] 校验和 [2]~[22] 共 21 字节相加取低 8 位 * * 手册电量公式: * 1 度电脉冲数 = (1/PowerPar) × (1/(电压系数×电流系数)) × 10^9 × 3600 * 电量(kWh) = 总脉冲数 × PowerPar × 电压系数 × 电流系数 / (10^9 × 3600) * 电压/电流系数在 main.h(HLW_U_COEF / HLW_I_COEF),由采样电阻和互感器变比决定。 * * 溢出处理:PF 寄存器只有 16 位,溢出时 bit7 翻转。本驱动记住 bit7 上次值, * 每帧比对,翻转一次说明溢出一次(+65536)。50ms 一帧 vs 溢出最快也要数小时, * 不可能在一帧间隔内翻两次,不会漏。 */ #include "hlw8032.h" #include "uart.h" #include "24c02.h" #include "log.h" #include #define HLW_FRAME_LEN 24 #define HLW_OVERFLOW_PULSES 65536u #define HLW_EEP_ADDR 164 /* EEPROM 164~171: uint64 累计脉冲数(132~157 蓝牙缓存, 160 日志等级) */ static uint8_t s_frame[HLW_FRAME_LEN]; static uint8_t s_idx = 0; /* 帧同步状态:0=等首字节 1=等 0x5A >=2=收集中 */ static uint64_t s_total_pulses = 0; /* 累计脉冲(含 EEPROM 恢复的历史值) */ static uint64_t s_saved_pulses = 0; /* 上次已写入 EEPROM 的值 */ static uint32_t s_power_par = 0; /* 功率参数寄存器(芯片常数,收到帧后才有) */ static uint32_t s_u_par = 0, s_u_reg = 0; /* 电压参数/电压寄存器(显示电压用) */ static uint32_t s_i_par = 0, s_i_reg = 0; /* 电流参数/电流寄存器 */ static uint32_t s_p_reg = 0; /* 功率寄存器 */ static uint32_t s_k = 0; /* 本次上电以来 bit7 翻转(PF 溢出)次数 */ static uint8_t s_last_bit7 = 0; static uint32_t s_last_chip = 0; /* 上一帧芯片侧总脉冲 k*65536+n */ static uint8_t s_synced = 0; /* 已确认同步(连续有效帧) */ static uint8_t s_streak = 0; /* 连续有效帧计数(防噪声假同步) */ static uint32_t s_last_valid_tick = 0; /* 上一有效帧时刻(帧间隔校验用) */ static uint32_t s_last_frame_tick = 0; /*==== 帧校验与电量累计(中断上下文执行,保持轻量)====*/ static void hlw8032_parse(const uint8_t *f) { uint8_t sum = 0; for (uint8_t i = 2; i <= 22; i++) sum += f[i]; if (sum != f[23]) return; /* 校验失败丢帧 */ uint32_t power_par = ((uint32_t)f[14] << 16) | ((uint32_t)f[15] << 8) | f[16]; if (power_par == 0) return; /* 参数寄存器异常,帧不可用 */ s_power_par = power_par; /* 电压/电流/功率原始寄存器(调试用,每分钟日志显示) */ s_u_par = ((uint32_t)f[2] << 16) | ((uint32_t)f[3] << 8) | f[4]; s_u_reg = ((uint32_t)f[5] << 16) | ((uint32_t)f[6] << 8) | f[7]; s_i_par = ((uint32_t)f[8] << 16) | ((uint32_t)f[9] << 8) | f[10]; s_i_reg = ((uint32_t)f[11] << 16) | ((uint32_t)f[12] << 8) | f[13]; s_p_reg = ((uint32_t)f[17] << 16) | ((uint32_t)f[18] << 8) | f[19]; uint8_t bit7 = f[20] & 0x80; uint16_t pf = ((uint16_t)f[21] << 8) | f[22]; if (!s_synced) { /* 首帧只建立基准,不累计增量: * 单片机重启而芯片一直在跑时,寄存器里攒的脉冲(几秒,约零点几 Wh)舍弃 */ s_last_bit7 = bit7; s_k = 0; } else if (bit7 != s_last_bit7) { s_k++; /* PF 溢出一次 */ s_last_bit7 = bit7; } /* 防噪声:真芯片 50ms 一帧稳定到达,要求连续 3 帧且间隔 20~150ms 才算同步。 * PD2 悬空时的电磁干扰偶发也能凑出校验和正确的假帧(约 1/256), * 但不可能连续 3 帧都卡在 50ms 节奏上 */ { uint32_t now = HAL_GetTick(); uint32_t gap = now - s_last_valid_tick; s_last_valid_tick = now; if (gap >= 20 && gap <= 150) { if (s_streak < 3) s_streak++; } else { s_streak = 1; } if (s_streak < 3) return; /* 未确认同步:帧内容一律不采用 */ } uint32_t chip_total = s_k * HLW_OVERFLOW_PULSES + pf; if (s_synced) { /* 正常单调递增;变小说明芯片断电重启过(寄存器清零),从当前值重新累计 */ uint32_t delta = (chip_total >= s_last_chip) ? (chip_total - s_last_chip) : chip_total; s_total_pulses += delta; } s_last_chip = chip_total; if (!s_synced) { s_synced = 1; log_info("> 电能: HLW8032 在线, 功率参数=%lu", (unsigned long)s_power_par); } s_last_frame_tick = HAL_GetTick(); } /*==== UART5 中断逐字节喂入:首字节同步 + 0x5A 二次确认 + 收满 24 字节解析 ====*/ void hlw8032_rx_byte(uint8_t b) { if (s_idx == 0) { /* 首字节候选:0x55 / 0xAA / 0xFx */ if (b == 0x55 || b == 0xAA || (b & 0xF0) == 0xF0) { s_frame[0] = b; s_idx = 1; } return; } if (s_idx == 1) { if (b == 0x5A) { s_frame[1] = b; s_idx = 2; } else { /* 第二字节不是 0x5A:伪同步,但它本身可能是真首字节 */ s_idx = 0; if (b == 0x55 || b == 0xAA || (b & 0xF0) == 0xF0) { s_frame[0] = b; s_idx = 1; } } return; } s_frame[s_idx++] = b; if (s_idx >= HLW_FRAME_LEN) { s_idx = 0; hlw8032_parse(s_frame); } } /*==== 初始化:EEPROM 恢复累计 + UART5 起收 ====*/ void hlw8032_init(void) { uint64_t v = 0; eepromReadData(HLW_EEP_ADDR, &v, sizeof(v)); if (v == 0xFFFFFFFFFFFFFFFFULL) v = 0; /* 空白 EEPROM(出厂 0xFF) */ s_total_pulses = v; s_saved_pulses = v; MX_UART5_UART_Init(); UART5_StartRx(); if (s_total_pulses) { log_info("> 电能: HLW8032 就绪 (UART5 PD2), 历史脉冲 %lu%09lu", (unsigned long)(s_total_pulses / 1000000000ULL), (unsigned long)(s_total_pulses % 1000000000ULL)); } else { log_info("> 电能: HLW8032 就绪 (UART5 PD2)"); } } /*==== 累计电量 kWh ====*/ float hlw8032_get_energy_kwh(void) { if (!s_power_par) return 0.0f; return (float)((double)s_total_pulses * (double)s_power_par * (double)HLW_U_COEF * (double)HLW_I_COEF / 3.6e12); } /*==== 电压/电流/功率(手册:有效值 = 参数寄存器/寄存器 × 系数)====*/ float hlw8032_get_voltage_v(void) { if (!s_u_reg) return 0.0f; return (float)((double)s_u_par / (double)s_u_reg * HLW_U_COEF); } float hlw8032_get_current_a(void) { if (!s_i_reg) return 0.0f; return (float)((double)s_i_par / (double)s_i_reg * HLW_I_COEF); } float hlw8032_get_power_w(void) { if (!s_p_reg) return 0.0f; return (float)((double)s_power_par / (double)s_p_reg * HLW_U_COEF * HLW_I_COEF); } /*==== 在线判断:1s 内收到过有效帧 ====*/ uint8_t hlw8032_online(void) { return s_synced && (HAL_GetTick() - s_last_frame_tick) < 1000; } /*==== 立即写 EEPROM(断电预警/计划复位前调用)====*/ void hlw8032_save(void) { if (s_total_pulses == s_saved_pulses) return; uint64_t v = s_total_pulses; if (eepromWriteData(HLW_EEP_ADDR, &v, sizeof(v)) == 0) { s_saved_pulses = s_total_pulses; } } /*==== 1s 周期:每攒够约 0.1 kWh 写一次 EEPROM,限制擦写频率 ====*/ void hlw8032_tick(void) { if (!s_power_par) return; double per_01kwh = 3.6e11 / ((double)s_power_par * HLW_U_COEF * HLW_I_COEF); if (per_01kwh < 1.0) per_01kwh = 1.0; if (s_total_pulses - s_saved_pulses >= (uint64_t)per_01kwh) { hlw8032_save(); } }