基于 HLW8012 的电能计量演示板程序
基于 UART 通信方式 的 HLW8012 电能计量演示板程序。此方案适用于 STM32、ESP32 等主流微控制器,代码简洁高效,可直接集成到您的工程中。
一、 硬件连接说明
HLW8012 芯片通过 UART 接口与主控制器通信,无需复杂的外部中断脉冲捕获,仅需连接 TX、RX 引脚即可。
| HLW8012 引脚 | 主控制器引脚 | 说明 |
|---|---|---|
| VDD | 3.3V / 5V | 电源正极 |
| RX | MCU TX | 接收来自 MCU 的指令 |
| TX | MCU RX | 向 MCU 发送计量数据 |
| SEL | GND | 选择电流通道(通常接地) |
| CF / CF1 | 悬空 | UART 模式下无需连接 |
二、 HLW8012 通信协议格式
HLW8012 默认波特率为 9600 bps,8 位数据位,无校验位,1 位停止位。
芯片会主动上报数据,或根据指令返回数据。典型的数据包格式如下:
| 字节位置 | 名称 | 内容 (十六进制) | 说明 |
|---|---|---|---|
| Byte 0 | 帧头 | 55 |
固定帧头 |
| Byte 1 | 长度 | 09 |
数据长度 |
| Byte 2 | 命令字 | 01 / 02 / … |
01: 读取电能数据,02: 读取电压电流等 |
| Byte 3 ~ N | 数据域 | 可变 | 具体的计量参数 |
| Byte N+1 | 校验和 | 可变 | 从长度字节到数据域最后一个字节的累加和 |
(注:不同厂家的 HLW8012 模块固件指令可能微调,请以实际模块手册为准,以下代码以读取标准电能参数为例)
三、 演示板程序代码 (基于 C 语言)
1. HLW8012 驱动头文件 (hlw8012.h)
#ifndef __HLW8012_H__
#define __HLW8012_H__
#include "stm32f1xx_hal.h" // 根据实际MCU修改头文件
// 定义串口句柄 (以STM32 HAL库为例)
extern UART_HandleTypeDef huart2;
#define HLW8012_UART huart2
// HLW8012 数据接收缓冲区大小
#define HLW8012_RX_BUFFER_SIZE 64
// HLW8012 电能数据结构体
typedef struct {
float voltage; // 电压 (V)
float current; // 电流 (A)
float active_power; // 有功功率 (W)
float energy; // 累计电量 (kWh)
uint8_t update_flag; // 数据更新标志
} HLW8012_Data_t;
// 函数声明
void HLW8012_Init(void);
void HLW8012_UART_IRQHandler(void);
void HLW8012_ReadData(HLW8012_Data_t *data);
uint8_t HLW8012_CalculateChecksum(uint8_t *data, uint8_t len);
#endif
2. HLW8012 驱动源文件 (hlw8012.c)
#include "hlw8012.h"
#include <string.h>
HLW8012_Data_t hlw_data;
static uint8_t rx_buffer[HLW8012_RX_BUFFER_SIZE];
static uint8_t rx_index = 0;
/**
* @brief HLW8012 初始化
*/
void HLW8012_Init(void) {
memset(rx_buffer, 0, HLW8012_RX_BUFFER_SIZE);
rx_index = 0;
// 启动UART中断接收
HAL_UART_Receive_IT(&HLW8012_UART, &rx_buffer[0], 1);
}
/**
* @brief 计算校验和 (从Byte1累加到Byte N)
* @param data 数据指针
* @param len 长度
* @return 校验和
*/
uint8_t HLW8012_CalculateChecksum(uint8_t *data, uint8_t len) {
uint32_t sum = 0;
for(uint8_t i = 0; i < len; i++) {
sum += data[i];
}
return (uint8_t)(sum & 0xFF);
}
/**
* @brief 解析HLW8012返回的数据包
* @param data 指向接收缓冲区的指针
*/
static void HLW8012_ParseData(uint8_t *data) {
// 校验帧头
if(data[0] != 0x55) return;
uint8_t len = data[1];
uint8_t *payload = &data[2];
// 校验和检查
uint8_t checksum = HLW8012_CalculateChecksum(&data[1], len);
if(checksum != data[1 + len]) {
return; // 校验失败,丢弃数据
}
// 根据命令字解析数据 (示例:假设返回的是电压、电流、功率、电量)
// 实际解析需根据模块的具体协议调整偏移量
if(payload[0] == 0x01) {
// 假设数据格式为: [Cmd] [V_H] [V_L] [I_H] [I_L] [P_H] [P_L] [E_H] [E_L]
uint16_t voltage_raw = (payload[1] << 8) | payload[2];
uint16_t current_raw = (payload[3] << 8) | payload[4];
uint16_t power_raw = (payload[5] << 8) | payload[6];
uint16_t energy_raw = (payload[7] << 8) | payload[8];
// 转换为实际物理单位 (系数需根据硬件分压/分流电阻校准)
hlw_data.voltage = voltage_raw * 0.1f; // 例如: 0.1V/LSB
hlw_data.current = current_raw * 0.01f; // 例如: 0.01A/LSB
hlw_data.active_power = power_raw * 0.1f; // 例如: 0.1W/LSB
hlw_data.energy = energy_raw * 0.001f; // 例如: 0.001kWh/LSB
hlw_data.update_flag = 1;
}
}
/**
* @brief UART接收中断回调函数
*/
void HLW8012_UART_IRQHandler(void) {
static uint8_t rx_byte;
// 读取接收到的字节
if(HAL_UART_GetState(&HLW8012_UART) == HAL_UART_STATE_READY) {
rx_byte = rx_buffer[rx_index - 1];
// 简单的帧同步: 如果收到0x55且当前不在收包中,则认为是新的一帧开始
if(rx_byte == 0x55 && rx_index == 0) {
rx_buffer[0] = rx_byte;
rx_index = 1;
}
else if(rx_index > 0) {
rx_buffer[rx_index++] = rx_byte;
// 防止缓冲区溢出
if(rx_index >= HLW8012_RX_BUFFER_SIZE) {
rx_index = 0;
}
// 如果接收到足够的数据 (帧头 + 长度 + 数据 + 校验和)
// 这里简单判断,实际应用应根据长度字节动态判断
if(rx_index >= 10) {
HLW8012_ParseData(rx_buffer);
rx_index = 0; // 重置接收索引
}
}
// 重新启动接收中断
HAL_UART_Receive_IT(&HLW8012_UART, &rx_buffer[rx_index], 1);
}
}
/**
* @brief 读取最新的电能数据
*/
void HLW8012_ReadData(HLW8012_Data_t *data) {
if(hlw_data.update_flag) {
memcpy(data, &hlw_data, sizeof(HLW8012_Data_t));
hlw_data.update_flag = 0;
}
}
3. 主程序演示 (main.c)
#include "main.h"
#include "hlw8012.h"
#include <stdio.h>
// 重定向printf到串口
int _write(int fd, char *ptr, int len) {
HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
int main(void) {
HLW8012_Data_t current_data;
// HAL库初始化
HAL_Init();
SystemClock_Config();
// 初始化GPIO和UART
MX_GPIO_Init();
MX_USART1_UART_Init(); // 用于printf打印
MX_USART2_UART_Init(); // 用于HLW8012通信
// 初始化HLW8012
HLW8012_Init();
printf("HLW8012 Electric Energy Meter Demo Start!\r\n");
while (1) {
// 读取电能数据
HLW8012_ReadData(¤t_data);
if(current_data.update_flag) {
printf("====== HLW8012 Data ======\r\n");
printf("Voltage: %.2f V\r\n", current_data.voltage);
printf("Current: %.3f A\r\n", current_data.current);
printf("Power: %.2f W\r\n", current_data.active_power);
printf("Energy: %.3f kWh\r\n", current_data.energy);
printf("=========================\r\n\r\n");
}
// 延时1秒
HAL_Delay(1000);
}
}
// UART接收中断回调函数注册
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART2) {
HLW8012_UART_IRQHandler();
}
}
参考代码 基于HLW8012的电能计量演示板程序 www.youwenfan.com/contentcsu/60377.html
四、 关键注意事项与校准
- 系数校准:代码中的
0.1f、0.01f等转换系数是示例值。实际应用中,必须根据您的演示板原理图中的分压电阻(电压采样)和锰铜电阻(电流采样)的阻值进行校准。通常厂家会提供校准公式或上位机工具。 - 校验和算法:部分 HLW8012 模块的校验和可能是“累加和后取反加1”(补码),具体请查阅您手中模块的数据手册。
- 中断优先级:如果使用 STM32,请确保 UART 接收中断的优先级高于主循环中的任务,以免丢失数据。