文章目录
-
- 一、项目概述
-
- [1.1 硬件清单](#1.1 硬件清单)
- [1.2 整体工作流程](#1.2 整体工作流程)
- 二、硬件电路设计与接线
-
- [2.1 核心电路原理](#2.1 核心电路原理)
- [2.2 详细接线表](#2.2 详细接线表)
- [2.3 接线注意事项](#2.3 接线注意事项)
- 三、软件开发环境搭建
-
- [3.1 环境准备](#3.1 环境准备)
- [3.2 STM32CubeMX初始化配置步骤](#3.2 STM32CubeMX初始化配置步骤)
- 四、核心代码实现
-
- [4.1 代码文件结构](#4.1 代码文件结构)
- [4.2 关键代码文件实现](#4.2 关键代码文件实现)
- 五、代码编译与下载
-
- [5.1 Keil MDK编译步骤](#5.1 Keil MDK编译步骤)
- [5.2 代码下载](#5.2 代码下载)
- [5.3 ESP8266固件烧录](#5.3 ESP8266固件烧录)
- 六、测试与调试
-
- [6.1 本地测试](#6.1 本地测试)
- [6.2 远程测试](#6.2 远程测试)
- [6.3 常见问题排查](#6.3 常见问题排查)
- 七、硬件封装与安全注意事项
- 八、功能扩展建议
一、项目概述
本项目基于STM32F103C8T6最小系统板,实现一款具备电量计量 和远程控制功能的智能插座。核心功能包括:
- 通过HLW8012芯片采集电压、电流、功率等电量参数;
- 通过ESP8266模块实现WiFi远程控制插座通断;
- 通过继电器模块控制市电通断;
- 电量数据可通过串口/WiFi上传至服务器,支持本地按键应急控制。
1.1 硬件清单
| 器件名称 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| STM32核心板 | STM32F103C8T6最小系统板 | 1 | 带USB转串口 |
| 电量计量芯片模块 | HLW8012 | 1 | 内置电压/电流采样电路 |
| WiFi模块 | ESP8266-01S | 1 | 透传模式 |
| 继电器模块 | 5V单路继电器(带光耦) | 1 | 控制220V市电 |
| 电源模块 | AC-DC 220V转5V | 1 | 给整个系统供电 |
| 按键 | 轻触按键 | 2 | 本地控制通断/复位 |
| 指示灯 | LED(红/绿) | 2 | 电源指示/插座状态指示 |
| 排插外壳/接线端子 | 通用款 | 1 | 安全封装 |
| 杜邦线/面包板 | 公对公/公对母 | 若干 | 电路连接 |
1.2 整体工作流程
市电输入
AC-DC电源模块
回传电量数据
HLW8012电量采集
下发控制指令
继电器模块
云端服务器/手机APP
本地按键/指示灯
插座输出端
二、硬件电路设计与接线
2.1 核心电路原理
- HLW8012与STM32连接:HLW8012通过串口(UART2)与STM32通信,输出电压、电流、功率等数字量;
- ESP8266与STM32连接:ESP8266通过串口(UART3)与STM32通信,配置为透传模式,实现远程数据交互;
- 继电器与STM32连接:STM32的GPIO口(PA0)输出高低电平控制继电器吸合/断开,进而控制市电;
- 按键/指示灯:PB0(按键1)、PB1(按键2)、PA1(绿灯)、PA2(红灯)。
2.2 详细接线表
| STM32引脚 | 外接器件 | 引脚/说明 |
|---|---|---|
| 5V | HLW8012 VCC | 供电 |
| GND | HLW8012 GND | 共地 |
| PA2 | HLW8012 TX | 串口接收(UART2 RX) |
| PA3 | HLW8012 RX | 串口发送(UART2 TX) |
| 5V | ESP8266 VCC | 供电(需串联1kΩ电阻限流) |
| GND | ESP8266 GND | 共地 |
| PB10 | ESP8266 TX | UART3 RX |
| PB11 | ESP8266 RX | UART3 TX |
| PA0 | 继电器IN | 控制端(低电平吸合) |
| 5V | 继电器VCC | 供电 |
| GND | 继电器GND | 共地 |
| PB0 | 按键1 | 上拉输入(接GND为按下) |
| PB1 | 按键2 | 上拉输入 |
| PA1 | 绿灯阳极 | 串220Ω电阻接GND |
| PA2 | 红灯阳极 | 串220Ω电阻接GND |
| VIN | AC-DC模块5V输出 | STM32供电 |
2.3 接线注意事项
- 所有GND必须共地,否则会出现数据乱码/模块不工作;
- ESP8266的VCC建议通过3.3V供电(STM32的3.3V引脚),避免5V烧坏模块;
- 继电器控制市电部分需做好绝缘,接线时务必断电操作;
- HLW8012的采样端需串联电流互感器/电压分压电阻,严格按照模块手册接线。
三、软件开发环境搭建
3.1 环境准备
- 软件工具 :
- Keil MDK-ARM V5.29(STM32开发环境);
- STM32CubeMX 6.8.0(代码初始化工具);
- 串口助手(如SSCOM);
- ESP8266烧录工具(Flash Download Tools)。
- 固件准备 :
- STM32F103C8T6的标准库/HAL库;
- ESP8266透传固件(AT指令版)。
3.2 STM32CubeMX初始化配置步骤
- 打开STM32CubeMX,选择芯片型号
STM32F103C8T6; - 配置RCC:选择外部高速时钟(HSE),晶振8MHz;
- 配置UART2(HLW8012):异步模式,波特率9600,数据位8,停止位1,无校验;
- 配置UART3(ESP8266):异步模式,波特率115200,数据位8,停止位1,无校验;
- 配置GPIO:
- PA0:输出模式(推挽输出),初始电平高(继电器断开);
- PA1/PA2:输出模式(推挽输出),初始电平低(灯灭);
- PB0/PB1:输入模式(上拉输入);
- 配置NVIC:开启UART2、UART3、EXTI0/EXTI1中断(按键中断);
- 生成代码:选择Keil MDK-ARM,工程名
SmartSocket,生成初始化代码。
四、核心代码实现
4.1 代码文件结构
SmartSocket/
├── Core/
│ ├── Inc/
│ │ ├── main.h
│ │ ├── stm32f1xx_hal_conf.h
│ │ ├── hal_wifi.h // ESP8266驱动
│ │ ├── hal_energy.h // HLW8012驱动
│ │ ├── hal_relay.h // 继电器驱动
│ │ └── key_led.h // 按键/指示灯驱动
│ └── Src/
│ ├── main.c
│ ├── stm32f1xx_hal_msp.c
│ ├── hal_wifi.c
│ ├── hal_energy.c
│ ├── hal_relay.c
│ └── key_led.c
└── MDK-ARM/
└── SmartSocket.uvprojx
4.2 关键代码文件实现
文件名1:Core/Inc/hal_energy.h
c
#ifndef __HAL_ENERGY_H
#define __HAL_ENERGY_H
#include "main.h"
// HLW8012数据结构体
typedef struct {
float voltage; // 电压(V)
float current; // 电流(A)
float power; // 功率(W)
float energy; // 累计电量(kWh)
uint8_t status; // 数据有效标志 0-无效 1-有效
} HLW8012_DataDef;
// 函数声明
void HLW8012_Init(void); // 初始化HLW8012
void HLW8012_ReadData(HLW8012_DataDef *data); // 读取电量数据
void HLW8012_UART_Callback(uint8_t *data, uint16_t len); // 串口回调函数
#endif /* __HAL_ENERGY_H */
文件名2:Core/Src/hal_energy.c
c
#include "hal_energy.h"
#include "string.h"
#include "stdio.h"
UART_HandleTypeDef huart2;
HLW8012_DataDef energy_data = {0};
uint8_t uart2_rx_buf[64] = {0};
uint16_t uart2_rx_len = 0;
// HLW8012初始化(UART2)
void HLW8012_Init(void)
{
// UART2配置:9600 8N1
huart2.Instance = USART2;
huart2.Init.BaudRate = 9600;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
// 开启串口接收中断
HAL_UART_Receive_IT(&huart2, uart2_rx_buf, sizeof(uart2_rx_buf));
}
// 解析HLW8012数据(协议参考HLW8012手册)
void HLW8012_ReadData(HLW8012_DataDef *data)
{
if(uart2_rx_len < 10) // 有效数据长度至少10字节
{
data->status = 0;
return;
}
// 解析电压(第2-3字节)
data->voltage = (uart2_rx_buf[1] << 8 | uart2_rx_buf[2]) / 10.0f;
// 解析电流(第4-5字节)
data->current = (uart2_rx_buf[3] << 8 | uart2_rx_buf[4]) / 1000.0f;
// 解析功率(第6-7字节)
data->power = (uart2_rx_buf[5] << 8 | uart2_rx_buf[6]) / 10.0f;
// 解析累计电量(第8-9字节)
data->energy = (uart2_rx_buf[7] << 8 | uart2_rx_buf[8]) / 1000.0f;
data->status = 1; // 数据有效
memset(uart2_rx_buf, 0, sizeof(uart2_rx_buf)); // 清空接收缓存
uart2_rx_len = 0;
}
// UART2接收中断回调函数
void HLW8012_UART_Callback(uint8_t *data, uint16_t len)
{
uart2_rx_len = len;
memcpy(uart2_rx_buf, data, len);
// 重新开启接收中断
HAL_UART_Receive_IT(&huart2, uart2_rx_buf, sizeof(uart2_rx_buf));
}
// HAL库UART中断回调函数重写
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2)
{
HLW8012_UART_Callback(uart2_rx_buf, sizeof(uart2_rx_buf));
}
}
// 错误处理函数
void Error_Handler(void)
{
while(1)
{
// 红灯闪烁提示错误
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_2);
HAL_Delay(500);
}
}
文件名3:Core/Inc/hal_wifi.h
c
#ifndef __HAL_WIFI_H
#define __HAL_WIFI_H
#include "main.h"
// ESP8266指令定义
#define ESP8266_AT_TEST "AT\r\n" // 测试AT指令
#define ESP8266_AT_CWMODE "AT+CWMODE=1\r\n" // 配置Station模式
#define ESP8266_AT_CWJAP "AT+CWJAP=\"WIFI名称\",\"WIFI密码\"\r\n" // 连接WiFi
#define ESP8266_AT_CIPMODE "AT+CIPMODE=1\r\n" // 透传模式
#define ESP8266_AT_CIPSEND "AT+CIPSEND\r\n" // 开始透传
// WiFi状态枚举
typedef enum {
WIFI_UNINIT = 0, // 未初始化
WIFI_CONNECTING, // 连接中
WIFI_CONNECTED, // 已连接
WIFI_DISCONNECTED // 断开连接
} WIFI_StatusDef;
// 函数声明
void ESP8266_Init(void); // 初始化ESP8266
WIFI_StatusDef ESP8266_CheckStatus(void); // 检查WiFi状态
void ESP8266_SendData(uint8_t *data, uint16_t len); // 发送数据
void ESP8266_ReceiveData(uint8_t *data, uint16_t *len); // 接收数据
void ESP8266_UART_Callback(uint8_t *data, uint16_t len); // 串口回调
#endif /* __HAL_WIFI_H */
文件名4:Core/Src/hal_wifi.c
c
#include "hal_wifi.h"
#include "string.h"
#include "stdio.h"
UART_HandleTypeDef huart3;
WIFI_StatusDef wifi_status = WIFI_UNINIT;
uint8_t uart3_rx_buf[128] = {0};
uint16_t uart3_rx_len = 0;
// ESP8266初始化(UART3 + AT指令配置)
void ESP8266_Init(void)
{
// UART3配置:115200 8N1
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
Error_Handler();
}
// 开启串口接收中断
HAL_UART_Receive_IT(&huart3, uart3_rx_buf, sizeof(uart3_rx_buf));
// 发送AT指令配置ESP8266
HAL_Delay(1000); // 等待模块上电稳定
HAL_UART_Transmit(&huart3, (uint8_t*)ESP8266_AT_TEST, strlen(ESP8266_AT_TEST), 1000);
HAL_Delay(500);
HAL_UART_Transmit(&huart3, (uint8_t*)ESP8266_AT_CWMODE, strlen(ESP8266_AT_CWMODE), 1000);
HAL_Delay(1000);
// 替换为你的WiFi名称和密码
HAL_UART_Transmit(&huart3, (uint8_t*)ESP8266_AT_CWJAP, strlen(ESP8266_AT_CWJAP), 2000);
HAL_Delay(3000);
HAL_UART_Transmit(&huart3, (uint8_t*)ESP8266_AT_CIPMODE, strlen(ESP8266_AT_CIPMODE), 1000);
HAL_Delay(500);
HAL_UART_Transmit(&huart3, (uint8_t*)ESP8266_AT_CIPSEND, strlen(ESP8266_AT_CIPSEND), 1000);
HAL_Delay(500);
wifi_status = WIFI_CONNECTED; // 标记WiFi已连接
}
// 检查WiFi连接状态
WIFI_StatusDef ESP8266_CheckStatus(void)
{
// 发送AT指令测试连接
uint8_t test_cmd[] = ESP8266_AT_TEST;
HAL_UART_Transmit(&huart3, test_cmd, strlen((char*)test_cmd), 1000);
HAL_Delay(500);
// 解析返回值,判断是否包含"OK"
if(strstr((char*)uart3_rx_buf, "OK") != NULL)
{
wifi_status = WIFI_CONNECTED;
}
else
{
wifi_status = WIFI_DISCONNECTED;
}
memset(uart3_rx_buf, 0, sizeof(uart3_rx_buf));
uart3_rx_len = 0;
return wifi_status;
}
// 发送数据到ESP8266
void ESP8266_SendData(uint8_t *data, uint16_t len)
{
if(wifi_status != WIFI_CONNECTED) return;
HAL_UART_Transmit(&huart3, data, len, 2000);
}
// 从ESP8266接收数据
void ESP8266_ReceiveData(uint8_t *data, uint16_t *len)
{
*len = uart3_rx_len;
memcpy(data, uart3_rx_buf, uart3_rx_len);
memset(uart3_rx_buf, 0, sizeof(uart3_rx_buf));
uart3_rx_len = 0;
}
// UART3接收中断回调
void ESP8266_UART_Callback(uint8_t *data, uint16_t len)
{
uart3_rx_len = len;
memcpy(uart3_rx_buf, data, len);
// 重新开启接收中断
HAL_UART_Receive_IT(&huart3, uart3_rx_buf, sizeof(uart3_rx_buf));
}
// HAL库UART3中断回调重写
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2)
{
HLW8012_UART_Callback(uart2_rx_buf, sizeof(uart2_rx_buf));
}
else if(huart->Instance == USART3)
{
ESP8266_UART_Callback(uart3_rx_buf, sizeof(uart3_rx_buf));
}
}
文件名5:Core/Inc/hal_relay.h
c
#ifndef __HAL_RELAY_H
#define __HAL_RELAY_H
#include "main.h"
// 继电器状态枚举
typedef enum {
RELAY_OFF = 0, // 断开(默认)
RELAY_ON // 吸合
} Relay_StatusDef;
// 函数声明
void Relay_Init(void); // 初始化继电器
void Relay_SetStatus(Relay_StatusDef status); // 设置继电器状态
Relay_StatusDef Relay_GetStatus(void); // 获取继电器状态
#endif /* __HAL_RELAY_H */
文件名6:Core/Src/hal_relay.c
c
#include "hal_relay.h"
Relay_StatusDef relay_status = RELAY_OFF;
// 继电器初始化(PA0)
void Relay_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIOA时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// PA0配置为推挽输出
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 初始状态:断开(高电平)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
relay_status = RELAY_OFF;
}
// 设置继电器状态
void Relay_SetStatus(Relay_StatusDef status)
{
if(status == RELAY_ON)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // 低电平吸合
relay_status = RELAY_ON;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // 绿灯亮
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); // 红灯灭
}
else
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // 高电平断开
relay_status = RELAY_OFF;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // 绿灯灭
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); // 红灯亮
}
}
// 获取继电器状态
Relay_StatusDef Relay_GetStatus(void)
{
return relay_status;
}
文件名7:Core/Inc/key_led.h
c
#ifndef __KEY_LED_H
#define __KEY_LED_H
#include "main.h"
// 按键枚举
typedef enum {
KEY_NONE = 0,
KEY1_PRESS, // PB0
KEY2_PRESS // PB1
} Key_TypeDef;
// 函数声明
void Key_LED_Init(void); // 初始化按键和指示灯
Key_TypeDef Key_Scan(void); // 按键扫描
void LED_SetStatus(uint8_t green, uint8_t red); // 设置指示灯状态
#endif /* __KEY_LED_H */
文件名8:Core/Src/key_led.c
c
#include "key_led.h"
#include "hal_relay.h"
// 初始化按键(PB0/PB1)和指示灯(PA1/PA2)
void Key_LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIOB时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 使能GPIOA时钟(指示灯已在relay中初始化)
// PB0/PB1配置为上拉输入
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
// 按键扫描(消抖)
Key_TypeDef Key_Scan(void)
{
static uint8_t key_flag = 0;
// 检测按键1(PB0)
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET && key_flag == 0)
{
HAL_Delay(20); // 消抖
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
key_flag = 1;
return KEY1_PRESS;
}
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_SET)
{
key_flag = 0;
}
// 检测按键2(PB1)
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET && key_flag == 0)
{
HAL_Delay(20); // 消抖
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
key_flag = 1;
return KEY2_PRESS;
}
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_SET)
{
key_flag = 0;
}
return KEY_NONE;
}
// 设置指示灯状态
void LED_SetStatus(uint8_t green, uint8_t red)
{
if(green)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
if(red)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
}
}
文件名9:Core/Src/main.c
c
#include "main.h"
#include "hal_energy.h"
#include "hal_wifi.h"
#include "hal_relay.h"
#include "key_led.h"
#include "stdio.h"
#include "string.h"
// 全局变量
HLW8012_DataDef energy_data = {0};
uint8_t wifi_rx_buf[128] = {0};
uint16_t wifi_rx_len = 0;
// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void SmartSocket_HandleWifiCmd(void); // 处理WiFi指令
void SmartSocket_SendEnergyData(void); // 发送电量数据
int main(void)
{
// 初始化HAL库
HAL_Init();
// 配置系统时钟(72MHz)
SystemClock_Config();
// 初始化GPIO
MX_GPIO_Init();
// 初始化各模块
HLW8012_Init(); // 电量计量模块
ESP8266_Init(); // WiFi模块
Relay_Init(); // 继电器模块
Key_LED_Init(); // 按键/指示灯模块
// 初始状态:继电器断开,红灯亮
Relay_SetStatus(RELAY_OFF);
// 主循环
while (1)
{
// 1. 读取电量数据
HLW8012_ReadData(&energy_data);
// 2. 按键扫描与处理
Key_TypeDef key = Key_Scan();
if(key == KEY1_PRESS)
{
// 按键1:切换继电器状态
if(Relay_GetStatus() == RELAY_ON)
{
Relay_SetStatus(RELAY_OFF);
}
else
{
Relay_SetStatus(RELAY_ON);
}
}
else if(key == KEY2_PRESS)
{
// 按键2:重置累计电量(演示用)
energy_data.energy = 0;
}
// 3. 处理WiFi接收的指令
ESP8266_ReceiveData(wifi_rx_buf, &wifi_rx_len);
if(wifi_rx_len > 0)
{
SmartSocket_HandleWifiCmd();
}
// 4. 每隔1秒发送一次电量数据到云端
static uint32_t tick = 0;
if(HAL_GetTick() - tick > 1000)
{
tick = HAL_GetTick();
SmartSocket_SendEnergyData();
}
HAL_Delay(10); // 主循环延时
}
}
// 处理WiFi指令(格式:"RELAY_ON"/"RELAY_OFF")
void SmartSocket_HandleWifiCmd(void)
{
if(strstr((char*)wifi_rx_buf, "RELAY_ON") != NULL)
{
Relay_SetStatus(RELAY_ON);
// 回复确认指令
uint8_t ack[] = "RELAY_ON_OK\r\n";
ESP8266_SendData(ack, strlen((char*)ack));
}
else if(strstr((char*)wifi_rx_buf, "RELAY_OFF") != NULL)
{
Relay_SetStatus(RELAY_OFF);
// 回复确认指令
uint8_t ack[] = "RELAY_OFF_OK\r\n";
ESP8266_SendData(ack, strlen((char*)ack));
}
// 清空缓存
memset(wifi_rx_buf, 0, sizeof(wifi_rx_buf));
wifi_rx_len = 0;
}
// 发送电量数据到云端(格式:"VOL:220.0V,CUR:0.5A,POW:110.0W,ENE:0.1kWh\r\n")
void SmartSocket_SendEnergyData(void)
{
if(energy_data.status == 0) return;
char send_buf[64] = {0};
sprintf(send_buf, "VOL:%.1fV,CUR:%.3fA,POW:%.1fW,ENE:%.3fkWh\r\n",
energy_data.voltage,
energy_data.current,
energy_data.power,
energy_data.energy);
ESP8266_SendData((uint8_t*)send_buf, strlen(send_buf));
}
// 系统时钟配置(72MHz)
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置外部高速时钟HSE 8MHz
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
// 配置系统时钟总线
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
// GPIO初始化
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能所有GPIO时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
// 配置JTAG/SWD禁用(可选,释放引脚)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// 错误处理函数
void Error_Handler(void)
{
__disable_irq();
while (1)
{
// 红灯快速闪烁
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_2);
HAL_Delay(200);
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
// 断言失败处理
}
#endif /* USE_FULL_ASSERT */
五、代码编译与下载
5.1 Keil MDK编译步骤
- 将上述所有代码文件按文件结构放入STM32CubeMX生成的工程中;
- 打开
SmartSocket.uvprojx工程文件; - 点击
魔术棒图标,选择Target,设置Stack Size为0x200,Heap Size为0x100; - 选择
Output,勾选Create HEX File; - 点击
Build或Rebuild按钮,等待编译完成(无error)。
5.2 代码下载
- 连接STM32最小系统板到电脑USB口;
- 打开ST-Link Utility或CH340下载工具;
- 选择编译生成的
SmartSocket.hex文件; - 点击
Program按钮,等待下载完成。
5.3 ESP8266固件烧录
- 下载ESP8266 Flash Download Tools;
- 选择ESP8266-01S型号,波特率115200;
- 导入透传固件(如
esp8266_transparent.bin); - 按住ESP8266的FLASH按键,上电后点击
START烧录; - 烧录完成后重启模块。
六、测试与调试
6.1 本地测试
- 上电后,红灯亮(继电器断开),串口助手打开STM32的串口(波特率9600);
- 按下按键1,绿灯亮(继电器吸合),串口输出电量数据;
- 按下按键2,累计电量重置为0。
6.2 远程测试
- 确保ESP8266连接到你的WiFi;
- 通过TCP/UDP工具连接ESP8266的IP地址(可通过路由器后台查看);
- 发送指令
RELAY_ON,继电器吸合,回复RELAY_ON_OK; - 发送指令
RELAY_OFF,继电器断开,回复RELAY_OFF_OK; - 实时接收电量数据(电压/电流/功率/累计电量)。
6.3 常见问题排查
- 串口无数据:检查接线是否正确,波特率是否匹配;
- WiFi连接失败:检查WiFi名称/密码是否正确,ESP8266固件是否正确;
- 继电器不动作:检查PA0引脚电平,继电器供电是否正常;
- 电量数据异常:检查HLW8012采样电路接线。
七、硬件封装与安全注意事项
- 所有市电接线部分需使用绝缘端子,避免短路;
- 整个系统需放入阻燃外壳,防止触电;
- 建议在继电器前端增加保险丝,过流保护;
- 调试时务必断开市电,仅测试低压部分。
八、功能扩展建议
- 添加OLED显示屏,本地显示电量数据;
- 接入阿里云/腾讯云IoT平台,实现手机APP远程控制;
- 添加过流/过压保护,超过阈值自动断电;
- 增加定时开关功能,通过RTC模块实现。
总结
- 本项目基于STM32F103C8T6实现智能插座,核心由HLW8012(电量计量)、ESP8266(远程控制)、继电器(市电控制)三大模块组成,硬件接线需严格遵循共地、限流等原则;
- 软件层面通过分层驱动设计(电量/WiFi/继电器/按键)实现功能解耦,代码可直接编译下载,支持本地按键和远程WiFi双重控制;
- 测试阶段需先完成本地功能验证,再进行远程调试,市电部分务必注意安全防护,确保落地使用的稳定性和安全性。