V1.5: 新增 HLW8032 电能计量(UART5 PD2 4800-8E1,PF脉冲累计换算kWh,energy字段每分钟上报,EEPROM 164持久化+断电保存)

This commit is contained in:
helei 2026-08-12 14:32:03 +08:00
parent 2ecf32fde9
commit effd19429f
14 changed files with 325 additions and 2 deletions

View File

@ -1,4 +1,5 @@
#include "adc.h" #include "adc.h"
#include "hlw8032.h"
#include "main.h" #include "main.h"
#include "network.h" #include "network.h"
#include "led.h" #include "led.h"
@ -66,6 +67,7 @@ void update_batter(void)
/* 电量 <10% 基本等同外部 12V 断开(法拉电容续航约 30s /* 电量 <10% 基本等同外部 12V 断开(法拉电容续航约 30s
* *
* BootLoader 12V APP */ * BootLoader 12V APP */
hlw8032_save(); /* 法拉电容续航内把累计电量写进 EEPROM */
reset_log_power_loss(); reset_log_power_loss();
HAL_Delay(200); HAL_Delay(200);
NVIC_SystemReset(); NVIC_SystemReset();

View File

@ -0,0 +1,169 @@
/* 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
* +6553650ms 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();
}
}

View File

@ -0,0 +1,19 @@
#ifndef __HLW8032_H
#define __HLW8032_H
#include "main.h"
/* HLW8032 电量计量模块
* 线 TXD -> PD2 (UART5_RX) RX
* 4800bps 8E1 50ms 24
* PF (kWh)//
* EEPROM(164~171, uint64) + */
void hlw8032_init(void); /* UART5 初始化 + EEPROM 恢复累计app_init 调用) */
void hlw8032_rx_byte(uint8_t b); /* UART5 中断逐字节喂入uart.c 接收回调调用) */
void hlw8032_tick(void); /* 1s 周期:到阈值就把累计脉冲写 EEPROM */
void hlw8032_save(void); /* 立即写 EEPROM断电预警/计划复位前调用) */
float hlw8032_get_energy_kwh(void); /* 累计电量 kWh未收到帧前为 0 */
uint8_t hlw8032_online(void); /* 最近 1s 内收到过有效帧 */
#endif /* __HLW8032_H */

View File

@ -317,6 +317,23 @@ void NET_publish_power(int on)
net_at_unlock(); net_at_unlock();
} }
/* 上报累计电量: energy 字段,单位 kWh两位小数 */
void NET_publish_energy(float kwh)
{
if (g_net_tx_busy) { log_info("> NET: energy 上报被丢弃 (发送忙)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: energy 上报被丢弃 (AT 忙)"); return; }
if (s_backend == NET_BACKEND_4G) {
CAT1_printf("AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\22header\22:\22iot.prop.post\22,\22body\22:{\22energy\22:%.2f}}\"",
MqttInfoStr.ClientID, kwh);
} else {
char json[96];
snprintf(json, sizeof(json),
"{\"header\":\"iot.prop.post\",\"body\":{\"energy\":%.2f}}", kwh);
net_wifi_publish_up(json);
}
net_at_unlock();
}
void NET_mqtt_report(const char *header, const char *ota_id, int val) void NET_mqtt_report(const char *header, const char *ota_id, int val)
{ {
if (g_net_tx_busy) { log_info("> NET: 进度/结果上报被丢弃 (发送忙)"); return; } if (g_net_tx_busy) { log_info("> NET: 进度/结果上报被丢弃 (发送忙)"); return; }

View File

@ -38,6 +38,7 @@ void NET_publish_status(int sw1, int feeding, int sw2, int sw3, int sw4,
float weight_kg, float once_wt_kg, float zero_kg); float weight_kg, float once_wt_kg, float zero_kg);
void NET_publish_response(const char *cmd_id, int ok, const char *msg); void NET_publish_response(const char *cmd_id, int ok, const char *msg);
void NET_publish_power(int on); void NET_publish_power(int on);
void NET_publish_energy(float kwh); /* 上报累计电量,标识符 energy单位 kWh */
void NET_mqtt_report(const char *header, const char *ota_id, int val); void NET_mqtt_report(const char *header, const char *ota_id, int val);
void NET_ota_confirm(void); void NET_ota_confirm(void);

View File

@ -26,6 +26,7 @@
#include "uart.h" #include "uart.h"
#include "log_cn.h" #include "log_cn.h"
#include "ble_at.h" #include "ble_at.h"
#include "hlw8032.h"
#include "reset_log.h" #include "reset_log.h"
#include "../W25Q64/w25q64.h" #include "../W25Q64/w25q64.h"
@ -91,6 +92,9 @@ void app_init(void)
adcInit(); adcInit();
update_batter(); update_batter();
/* HLW8032 电能计量UART5 PD2只累计电量 */
hlw8032_init();
/* 网络模块初始化:按 EEPROM 记忆的上次成功方式优先4G 或 WiFi */ /* 网络模块初始化:按 EEPROM 记忆的上次成功方式优先4G 或 WiFi */
NET_init(); NET_init();
@ -210,6 +214,7 @@ static void sys_task(void *arg)
if (HAL_GetTick() - batt_tick >= 1000) { if (HAL_GetTick() - batt_tick >= 1000) {
batt_tick = HAL_GetTick(); batt_tick = HAL_GetTick();
update_batter(); update_batter();
hlw8032_tick(); /* 攒够约 0.1kWh 写一次 EEPROM */
reset_log_tick((uint32_t)(up_ms / 1000)); /* 共享 RAM 更新运行秒数,热复位不丢 */ reset_log_tick((uint32_t)(up_ms / 1000)); /* 共享 RAM 更新运行秒数,热复位不丢 */
} }
@ -223,6 +228,10 @@ static void sys_task(void *arg)
if (now - up_print >= 60000) { if (now - up_print >= 60000) {
up_print = now; up_print = now;
if (g_net_mqtt_ready) { if (g_net_mqtt_ready) {
/* 每分钟随心跳上报一次累计电量(标识符 energy */
if (hlw8032_online()) {
NET_publish_energy(hlw8032_get_energy_kwh());
}
uint32_t mins = (uint32_t)(up_ms / 60000); uint32_t mins = (uint32_t)(up_ms / 60000);
log_info(FMT_UPTIME, log_info(FMT_UPTIME,
(unsigned)(mins / 1440), (unsigned)(mins / 1440),

View File

@ -1,5 +1,6 @@
#include "uart.h" #include "uart.h"
#include "network.h" #include "network.h"
#include "hlw8032.h"
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
@ -7,6 +8,7 @@ UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2; UART_HandleTypeDef huart2;
UART_HandleTypeDef huart3; UART_HandleTypeDef huart3;
UART_HandleTypeDef huart4; UART_HandleTypeDef huart4;
UART_HandleTypeDef huart5;
uint16_t Rx_Len = 0; uint16_t Rx_Len = 0;
uint8_t Rx_Buf[Rx_Max] = {0}; uint8_t Rx_Buf[Rx_Max] = {0};
@ -27,6 +29,7 @@ static uint8_t uart1_cmd_index = 0;
static uint32_t uart1_last_rx_tick = 0; static uint32_t uart1_last_rx_tick = 0;
static uint8_t uart2_rx_byte = 0; static uint8_t uart2_rx_byte = 0;
static uint8_t uart5_rx_byte = 0;
static uint8_t uart2_cmd_buf[UART2_RX_BUF_SIZE] = {0}; static uint8_t uart2_cmd_buf[UART2_RX_BUF_SIZE] = {0};
static uint8_t uart2_cmd_index = 0; static uint8_t uart2_cmd_index = 0;
static uint32_t uart2_last_rx_tick = 0; static uint32_t uart2_last_rx_tick = 0;
@ -89,6 +92,26 @@ void MX_UART4_UART_Init(void)
if (HAL_UART_Init(&huart4) != HAL_OK) Error_Handler(); if (HAL_UART_Init(&huart4) != HAL_OK) Error_Handler();
} }
/* UART5: HLW8032 计量芯片 (PD2=RX <- 芯片 TXD)4800 8E1。
* TX(PC12) 8 + F1 9 */
void MX_UART5_UART_Init(void)
{
huart5.Instance = UART5;
huart5.Init.BaudRate = 4800;
huart5.Init.WordLength = UART_WORDLENGTH_9B;
huart5.Init.StopBits = UART_STOPBITS_1;
huart5.Init.Parity = UART_PARITY_EVEN;
huart5.Init.Mode = UART_MODE_RX;
huart5.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart5.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart5) != HAL_OK) Error_Handler();
}
void UART5_StartRx(void)
{
HAL_UART_Receive_IT(&huart5, &uart5_rx_byte, 1);
}
void USART3_StartRx(void) void USART3_StartRx(void)
{ {
/* 避免重复启动 DMA: 若已在接收则先停止再启动 */ /* 避免重复启动 DMA: 若已在接收则先停止再启动 */
@ -178,6 +201,15 @@ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
huart->ErrorCode = HAL_UART_ERROR_NONE; huart->ErrorCode = HAL_UART_ERROR_NONE;
HAL_UART_Receive_IT(&huart2, &uart2_rx_byte, 1); /* RS485 秤口同样自愈 */ HAL_UART_Receive_IT(&huart2, &uart2_rx_byte, 1); /* RS485 秤口同样自愈 */
} }
else if (huart->Instance == UART5) {
__HAL_UART_CLEAR_OREFLAG(huart);
__HAL_UART_CLEAR_PEFLAG(huart);
__HAL_UART_CLEAR_FEFLAG(huart);
__HAL_UART_CLEAR_NEFLAG(huart);
__HAL_UART_CLEAR_IDLEFLAG(huart);
huart->ErrorCode = HAL_UART_ERROR_NONE;
HAL_UART_Receive_IT(&huart5, &uart5_rx_byte, 1); /* HLW8032 口同样自愈 */
}
else if (huart->Instance == USART3) { else if (huart->Instance == USART3) {
__HAL_UART_CLEAR_OREFLAG(huart); __HAL_UART_CLEAR_OREFLAG(huart);
__HAL_UART_CLEAR_PEFLAG(huart); __HAL_UART_CLEAR_PEFLAG(huart);
@ -271,6 +303,10 @@ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
} }
HAL_UART_Receive_IT(&huart2, &uart2_rx_byte, 1); HAL_UART_Receive_IT(&huart2, &uart2_rx_byte, 1);
} }
else if (huart->Instance == UART5) {
hlw8032_rx_byte(uart5_rx_byte);
HAL_UART_Receive_IT(&huart5, &uart5_rx_byte, 1);
}
else if (huart->Instance == UART4) { else if (huart->Instance == UART4) {
/* DMA(NORMAL) 填满 Rx_Buf连续数据流没有空闲间隙、IDLE 不触发, /* DMA(NORMAL) 填满 Rx_Buf连续数据流没有空闲间隙、IDLE 不触发,
* WiFi OTA */ * WiFi OTA */

View File

@ -10,6 +10,7 @@ extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2; /* RS485 喂料秤 */ extern UART_HandleTypeDef huart2; /* RS485 喂料秤 */
extern UART_HandleTypeDef huart3; /* 4G Air780E */ extern UART_HandleTypeDef huart3; /* 4G Air780E */
extern UART_HandleTypeDef huart4; /* WiFi ESP-01S (PC10=TX, PC11=RX) */ extern UART_HandleTypeDef huart4; /* WiFi ESP-01S (PC10=TX, PC11=RX) */
extern UART_HandleTypeDef huart5; /* HLW8032 计量芯片 (PD2=RX, 只收) */
extern volatile uint8_t uart1_rx_ready; extern volatile uint8_t uart1_rx_ready;
extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];
@ -23,6 +24,7 @@ void MX_USART1_UART_Init(void);
void MX_USART2_UART_Init(void); void MX_USART2_UART_Init(void);
void MX_USART3_UART_Init(void); void MX_USART3_UART_Init(void);
void MX_UART4_UART_Init(void); void MX_UART4_UART_Init(void);
void MX_UART5_UART_Init(void);
void USART3_StartRx(void); /* 启动 USART3 DMA+IDLE 接收 */ void USART3_StartRx(void); /* 启动 USART3 DMA+IDLE 接收 */
void USART3_StopRx(void); /* 停止 USART3 DMA 接收(切 WiFi 通道时调用,释放共享 Rx_Buf */ void USART3_StopRx(void); /* 停止 USART3 DMA 接收(切 WiFi 通道时调用,释放共享 Rx_Buf */
void USART3_SetBaudRate(uint32_t baud); /* 运行中切换 USART3 波特率 */ void USART3_SetBaudRate(uint32_t baud); /* 运行中切换 USART3 波特率 */
@ -31,6 +33,7 @@ void UART4_StopRx(void); /* ֹͣ UART4 DMA
void UART4_SetBaudRate(uint32_t baud); /* 运行中切换 UART4 波特率 */ void UART4_SetBaudRate(uint32_t baud); /* 运行中切换 UART4 波特率 */
void USART1_StartRx(void); /* 启动 USART1 中断接收 */ void USART1_StartRx(void); /* 启动 USART1 中断接收 */
void USART2_StartRx(void); /* 启动 USART2 中断接收 */ void USART2_StartRx(void); /* 启动 USART2 中断接收 */
void UART5_StartRx(void); /* 启动 UART5 中断接收HLW8032 */
void USART2_Tick(void); /* 触发 USART2 帧超时自动提交 */ void USART2_Tick(void); /* 触发 USART2 帧超时自动提交 */
uint16_t USART2_GetFrame(uint8_t *out, uint16_t out_size); /* 从 USART2 取一帧原始字节 */ uint16_t USART2_GetFrame(uint8_t *out, uint16_t out_size); /* 从 USART2 取一帧原始字节 */
int USART1_CheckCommand(const char *cmd); /* 检查是否收到指定命令(不区分大小写) */ int USART1_CheckCommand(const char *cmd); /* 检查是否收到指定命令(不区分大小写) */

View File

@ -61,6 +61,15 @@ extern UART_HandleTypeDef huart3;
* 10 */ * 10 */
#define BLE_NAME "Bt1" #define BLE_NAME "Bt1"
/*===== HLW8032 电量计量系数UART5 PD2 接收,只上报累计电量) =====
* : ZMPT107-1(2mA:2mA) 220V 100K 50R
* -> = (100K/50)/1000 = 2.0
* : CT 100A/100mA(1000:1) 2R -> 0.002R
* -> = 1/(0.002*1000) = 0.5
* / HLW8032 */
#define HLW_U_COEF 2.0f
#define HLW_I_COEF 0.5f
/*===== 蓝牙 GATT UUID 配置(透传服务)===== /*===== 蓝牙 GATT UUID 配置(透传服务)=====
* BLE_UUID_CUSTOM = 1 UUID * BLE_UUID_CUSTOM = 1 UUID
* = 0 UUID(fff0/fff1/fff2) APP * = 0 UUID(fff0/fff1/fff2) APP

View File

@ -369,7 +369,7 @@
<MiscControls>--no_multibyte_chars</MiscControls> <MiscControls>--no_multibyte_chars</MiscControls>
<Define>USE_HAL_DRIVER,STM32F103xE</Define> <Define>USE_HAL_DRIVER,STM32F103xE</Define>
<Undefine></Undefine> <Undefine></Undefine>
<IncludePath>../Inc;../Drivers/STM32F1xx_HAL_Driver/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F1xx/Include;../Drivers/CMSIS/Include;..\m_app;..\HardWare\CAR;..\HardWare\RELAY;..\HardWare\LOG;..\HardWare\IIC;..\HardWare\24C02;..\HardWare\ADC;..\HardWare\LED;..\HardWare\SYSINIT;..\HardWare\UART;..\HardWare\W25Q64;..\HardWare\NETWORK;..\HardWare\WIFI;..\HardWare\MQTT;..\HardWare\FEED_SCALE;..\HardWare\USER_CMD;..\Middlewares\FreeRTOS\Source\include;..\Middlewares\FreeRTOS\Source\portable\RVDS\ARM_CM3</IncludePath> <IncludePath>../Inc;../Drivers/STM32F1xx_HAL_Driver/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F1xx/Include;../Drivers/CMSIS/Include;..\m_app;..\HardWare\CAR;..\HardWare\RELAY;..\HardWare\LOG;..\HardWare\IIC;..\HardWare\24C02;..\HardWare\ADC;..\HardWare\LED;..\HardWare\SYSINIT;..\HardWare\UART;..\HardWare\W25Q64;..\HardWare\NETWORK;..\HardWare\WIFI;..\HardWare\MQTT;..\HardWare\FEED_SCALE;..\HardWare\USER_CMD;..\HardWare\HLW8032;..\Middlewares\FreeRTOS\Source\include;..\Middlewares\FreeRTOS\Source\portable\RVDS\ARM_CM3</IncludePath>
</VariousControls> </VariousControls>
</Cads> </Cads>
<Aads> <Aads>
@ -687,6 +687,11 @@
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\HardWare\W25Q64\w25q64.c</FilePath> <FilePath>..\HardWare\W25Q64\w25q64.c</FilePath>
</File> </File>
<File>
<FileName>hlw8032.c</FileName>
<FileType>1</FileType>
<FilePath>..\HardWare\HLW8032\hlw8032.c</FilePath>
</File>
</Files> </Files>
</Group> </Group>
<Group> <Group>

View File

@ -56,6 +56,7 @@
| USART2 | RS485 喂料秤 | PA2/PA3 | 9600-8N1 | | USART2 | RS485 喂料秤 | PA2/PA3 | 9600-8N1 |
| USART3 | 4G Air780E/EG MQTT+GNSS | PB10/PB11 | 921600-8N1见下文波特率自适应 | | USART3 | 4G Air780E/EG MQTT+GNSS | PB10/PB11 | 921600-8N1见下文波特率自适应 |
| UART4 | WiFi ESP-01S | PC10/PC11 | 115200→921600-8N1见下文 WiFi 波特率同步) | | UART4 | WiFi ESP-01S | PC10/PC11 | 115200→921600-8N1见下文 WiFi 波特率同步) |
| UART5 | HLW8032 电能计量 | PD2(RX) | 4800-8E1只收芯片每 50ms 主动上报 |
| ESP_RST | WiFi 模块复位 | PA5 | 低电平 500ms 复位 | | ESP_RST | WiFi 模块复位 | PA5 | 低电平 500ms 复位 |
| Relay1 | 投喂电机控制 | PB7 | 高电平吸合 | | Relay1 | 投喂电机控制 | PB7 | 高电平吸合 |
| Relay2~4 | 备用控制 | PB8/PC0/PC2 | 高电平吸合 | | Relay2~4 | 备用控制 | PB8/PC0/PC2 | 高电平吸合 |
@ -100,6 +101,17 @@
- 断电记录只被下次成功启动消费一次(`boot_seen` 位翻转标记),一次掉电 = 一条记录BL 等待期间不产生记录 - 断电记录只被下次成功启动消费一次(`boot_seen` 位翻转标记),一次掉电 = 一条记录BL 等待期间不产生记录
- BL 找不到有效 APP如只刷了 BL打印提示并每 3s 主动复位重试,刷入 APP 即恢复 - BL 找不到有效 APP如只刷了 BL打印提示并每 3s 主动复位重试,刷入 APP 即恢复
## 电能计量HLW8032
只计量**累计消耗电量**,上报平台标识符 `energy`(单位 kWh两位小数
- **硬件**HLW80325V 供电)经 B0505S 隔离电源 + 光耦隔离,芯片 TXD 接 PD2UART5_RX电压采样 ZMPT107-12mA:2mA初级 220V 串 100K次级并 50Ω电流采样 CT 100A/100mA1000:1输出并 2Ω三个模拟脚各对地 33nF
- **通信**4800bps 8E1F1 上按 9 位帧长 + 偶校验配置),芯片每 50ms 主动上报 24 字节帧,只收不发;帧同步 = 首字节 0x55/0xAA/0xFx + 第二字节 0x5A + 校验和([2]~[22] 相加低 8 位)
- **电量算法**(手册公式):`kWh = 总脉冲 × 功率参数寄存器 × 电压系数 × 电流系数 / (10^9×3600)`PF 寄存器 16 位溢出由数据更新寄存器 bit7 翻转计数(`总脉冲 = k×65536 + PF`50ms 一帧不可能漏翻转;芯片断电重启(计数值倒退)自动重新基准
- **系数宏**`main.h` 的 `HLW_U_COEF`=100K/50/1000=2.0)、`HLW_I_COEF`=1/(0.002×1000)=0.5)——改电阻/互感器只动这两个宏
- **持久化**累计脉冲数uint64存 EEPROM 偏移 164~171每攒约 0.1kWh 写一次(限制擦写),断电预警(法拉电容续航内)立即写一次,掉电损失 <0.1kWh
- **上报**MQTT 就绪且芯片在线1s 内有有效帧)时,每分钟随运行心跳上报一次 `{"energy":%.2f}`;未联网/芯片不在线不上报
## 双通道网络4G 优先WiFi 兜底) ## 双通道网络4G 优先WiFi 兜底)
网络层 `HardWare/NETWORK/network.c` 对业务层屏蔽底层模组差异,业务只调 `NET_*` 接口4G 后端(`CAR/air780e.c`,模组内置 MQTT与 WiFi 后端(`NETWORK/net_wifi.c`TCP 透传 + 软件 MQTT同构运行时互斥切换。 网络层 `HardWare/NETWORK/network.c` 对业务层屏蔽底层模组差异,业务只调 `NET_*` 接口4G 后端(`CAR/air780e.c`,模组内置 MQTT与 WiFi 后端(`NETWORK/net_wifi.c`TCP 透传 + 软件 MQTT同构运行时互斥切换。
@ -289,6 +301,7 @@ W25Q64 外部 Flash8MB
| 130 | 1B | 上次成功联网方式0=4G1=WiFi固定热点2=WiFi配网热点仅变化时写 | | 130 | 1B | 上次成功联网方式0=4G1=WiFi固定热点2=WiFi配网热点仅变化时写 |
| 132~157 | 26B | 蓝牙名称配置缓存(命中则开机跳过蓝牙 AT 设置流程) | | 132~157 | 26B | 蓝牙名称配置缓存(命中则开机跳过蓝牙 AT 设置流程) |
| 160 | 1B | 日志等级0~5TRACE/DEBUG/INFO/WARN/ERROR/FATAL | | 160 | 1B | 日志等级0~5TRACE/DEBUG/INFO/WARN/ERROR/FATAL |
| 164~171 | 8B | HLW8032 累计脉冲数uint64每攒 0.1kWh + 断电前写入) |
| 250 | 1B | 探测字节(上电写 0xA5 回读,验证 I2C 通信) | | 250 | 1B | 探测字节(上电写 0xA5 回读,验证 I2C 通信) |
> 注意:改 `MqttInfo_Str` 结构体会改变上述偏移秤参数120+和联网方式130 > 注意:改 `MqttInfo_Str` 结构体会改变上述偏移秤参数120+和联网方式130
@ -305,6 +318,7 @@ W25Q64 外部 Flash8MB
| `sw1` / `feeding` | 第一路继电器状态 | 1=投喂中0=停止;无秤模式下两字段**同时上报**,平台两个开关同步刷新 | | `sw1` / `feeding` | 第一路继电器状态 | 1=投喂中0=停止;无秤模式下两字段**同时上报**,平台两个开关同步刷新 |
| `sw2~sw4` | 备用继电器状态 | 0/1 | | `sw2~sw4` | 备用继电器状态 | 0/1 |
| `pow` | 电量状态 | 1=正常0=低电量 | | `pow` | 电量状态 | 1=正常0=低电量 |
| `energy` | 累计消耗电量 | HLW8032 计量,单位 kWh两位小数联网后每分钟上报 |
| `GPS` | 定位坐标 | 仅 4G 通道(Air780EG)GCJ-02 高德坐标系,格式 `"经度,纬度"`,每 5 分钟上报 | | `GPS` | 定位坐标 | 仅 4G 通道(Air780EG)GCJ-02 高德坐标系,格式 `"经度,纬度"`,每 5 分钟上报 |
> 注:`zero`(剩余料重)与 `onceWt` 为称重模式专用字段——`PRODUCT_WITH_FEED_SCALE=0` 时状态上报不携带这两个字段,平台下发设置会收到 `no scale on this device` 拒绝应答。 > 注:`zero`(剩余料重)与 `onceWt` 为称重模式专用字段——`PRODUCT_WITH_FEED_SCALE=0` 时状态上报不携带这两个字段,平台下发设置会收到 `no scale on this device` 拒绝应答。
@ -391,7 +405,8 @@ STM32F103_App1/
│ ├── NETWORK/ # 网络层network.c 通道切换/统一分发 + net_wifi.c WiFi 后端 + net_wifi_ota.c WiFi OTA │ ├── NETWORK/ # 网络层network.c 通道切换/统一分发 + net_wifi.c WiFi 后端 + net_wifi_ota.c WiFi OTA
│ ├── WIFI/ # esp8266.c ESP-01S AT驱动/透传/SmartConfig + mqtt_client.c 软件MQTT │ ├── WIFI/ # esp8266.c ESP-01S AT驱动/透传/SmartConfig + mqtt_client.c 软件MQTT
│ ├── CAR/ # Air780E/EG AT 驱动、波特率自适应、CSQ 信号检测、GNSS 定位、OTA 下载 │ ├── CAR/ # Air780E/EG AT 驱动、波特率自适应、CSQ 信号检测、GNSS 定位、OTA 下载
│ ├── UART/ # USART1/2/3+UART4 初始化、运行中切波特率、中断/DMA 接收、ORE 自愈 │ ├── UART/ # USART1/2/3+UART4/5 初始化、运行中切波特率、中断/DMA 接收、ORE 自愈
│ ├── HLW8032/ # 电能计量UART5 帧解析、PF 脉冲累计、kWh 换算、EEPROM 持久化)
│ ├── 24C02/ # EEPROM 读写(页写入,带跨任务互斥锁) │ ├── 24C02/ # EEPROM 读写(页写入,带跨任务互斥锁)
│ ├── IIC/ # 软件 I2C带总线恢复SDA 被拉死时补 9 时钟释放) │ ├── IIC/ # 软件 I2C带总线恢复SDA 被拉死时补 9 时钟释放)
│ ├── ADC/ # 电池电压检测0~100% 限幅、掉电去抖判定) │ ├── ADC/ # 电池电压检测0~100% 限幅、掉电去抖判定)

View File

@ -246,6 +246,24 @@ void HAL_UART_MspInit(UART_HandleTypeDef* huart)
/* USER CODE END UART4_MspInit 1 */ /* USER CODE END UART4_MspInit 1 */
} }
else if(huart->Instance==UART5)
{
/* Peripheral clock enable */
__HAL_RCC_UART5_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/**UART5 GPIO Configuration
PD2 ------> UART5_RX ( HLW8032 TXDPC12=TX )
*/
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* UART5 interrupt Init */
HAL_NVIC_SetPriority(UART5_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(UART5_IRQn);
}
} }

View File

@ -63,6 +63,7 @@ extern UART_HandleTypeDef huart3;
extern UART_HandleTypeDef huart4; extern UART_HandleTypeDef huart4;
extern UART_HandleTypeDef huart2; extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart5;
/* USER CODE BEGIN EV */ /* USER CODE BEGIN EV */
#include "network.h" #include "network.h"
/* USER CODE END EV */ /* USER CODE END EV */
@ -325,6 +326,14 @@ void USART3_IRQHandler(void)
/* USER CODE END USART3_IRQn 1 */ /* USER CODE END USART3_IRQn 1 */
} }
/**
* @brief This function handles UART5 global interrupt (HLW8032).
*/
void UART5_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart5);
}
/* USER CODE BEGIN 1 */ /* USER CODE BEGIN 1 */
/* USER CODE END 1 */ /* USER CODE END 1 */

View File

@ -93,3 +93,14 @@
- 蓝牙改名被撞写APP 数据撞进 AT 窗口把名字写成 "info" → 改"先查询后设置"幂等流程(名字已对就不开 AT 窗口) - 蓝牙改名被撞写APP 数据撞进 AT 窗口把名字写成 "info" → 改"先查询后设置"幂等流程(名字已对就不开 AT 窗口)
- 设置后当场回查失败:模块设置重启才生效,改为下次开机查询校验 - 设置后当场回查失败:模块设置重启才生效,改为下次开机查询校验
- OTA 结果/确认上报超时net 任务与业务任务竞争共享接收缓冲 → NET_process 消费加锁、发布改 2s 超时锁 - OTA 结果/确认上报超时net 任务与业务任务竞争共享接收缓冲 → NET_process 消费加锁、发布改 2s 超时锁
### 追加HLW8032 电能计量2026-08
- 新增 `HardWare/HLW8032/`UART5(PD2 只收) 4800-8E1 接收芯片每 50ms 主动上报的 24 字节帧,
帧同步(0x55/0xAA/0xFx + 0x5A)+校验和验证;只取 PF 脉冲累计换算累计电量
- 电量按手册公式换算kWh = 总脉冲×功率参数×电压系数×电流系数/(10^9×3600)
PF 16 位溢出靠数据更新寄存器 bit7 翻转计数50ms 一帧不会漏);芯片断电重启自动重新基准
- 系数宏 `HLW_U_COEF`(2.0)/`HLW_I_COEF`(0.5) 在 main.h对应 ZMPT107-1(100K/50R)+CT 1000:1(2R)
- 累计脉冲(uint64)持久化 EEPROM 偏移 164每攒约 0.1kWh 写一次 + 断电预警时立即写(法拉电容续航内)
- 平台上报标识符 `energy`kWh 两位小数MQTT 就绪且芯片在线时每分钟一次4G/WiFi 双通道
- UART5 带 ORE 自愈硬件B0505S 隔离电源 + 光耦,芯片 TXD 接 PD2