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

170 lines
5.7 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_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 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;
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;
}
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;
s_synced = 1;
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);
}
/*==== 在线判断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();
}
}