shuichan_old/V1.5/STM32F103_App1/HardWare/HLW8032/hlw8032.c

242 lines
8.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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.hHLW_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 <string.h>
#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 float s_u_f = 0, s_i_f = 0, s_p_f = 0; /* 电压/电流/功率 EMA 滤波值 */
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; /* 未确认同步:帧内容一律不采用 */
}
/* EMA 滑动平均(系数 1/8约 0.4s 响应):压空载噪声跳变,
* 真实负载变化延迟 <0.5s,不影响使用 */
{
float u = s_u_reg ? (float)((double)s_u_par / (double)s_u_reg * HLW_U_COEF) : 0.0f;
float a = s_i_reg ? (float)((double)s_i_par / (double)s_i_reg * HLW_I_COEF) : 0.0f;
float w = s_p_reg ? (float)((double)s_power_par / (double)s_p_reg * HLW_U_COEF * HLW_I_COEF) : 0.0f;
s_u_f += (u - s_u_f) * 0.125f;
s_i_f += (a - s_i_f) * 0.125f;
s_p_f += (w - s_p_f) * 0.125f;
}
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_synced) return 0.0f;
return s_u_f;
}
float hlw8032_get_current_a(void)
{
if (!s_synced) return 0.0f;
float a = s_i_f - HLW_I_ZERO_A; /* 减零点偏移 */
return a > 0.0f ? a : 0.0f; /* 钳位到 0 */
}
float hlw8032_get_power_w(void)
{
if (!s_synced) return 0.0f;
if (s_p_f < HLW_P_DEAD_W) return 0.0f; /* 死区:空载噪声底显示 0 */
return s_p_f - HLW_P_ZERO_W;
}
/*==== 在线判断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;
}
}
/*==== 累计电量清零(蓝牙 ENCLR 命令):清 RAM 与 EEPROM ====*/
void hlw8032_clear(void)
{
s_total_pulses = 0;
s_saved_pulses = 0;
uint64_t v = 0;
eepromWriteData(HLW_EEP_ADDR, &v, sizeof(v));
}
/*==== 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();
}
}