STM32实战案例:基于STM32F103的智能插座(电量计量+远程控制)

文章目录

一、项目概述

本项目基于STM32F103C8T6最小系统板,实现一款具备电量计量远程控制功能的智能插座。核心功能包括:

  1. 通过HLW8012芯片采集电压、电流、功率等电量参数;
  2. 通过ESP8266模块实现WiFi远程控制插座通断;
  3. 通过继电器模块控制市电通断;
  4. 电量数据可通过串口/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 核心电路原理

  1. HLW8012与STM32连接:HLW8012通过串口(UART2)与STM32通信,输出电压、电流、功率等数字量;
  2. ESP8266与STM32连接:ESP8266通过串口(UART3)与STM32通信,配置为透传模式,实现远程数据交互;
  3. 继电器与STM32连接:STM32的GPIO口(PA0)输出高低电平控制继电器吸合/断开,进而控制市电;
  4. 按键/指示灯: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 接线注意事项

  1. 所有GND必须共地,否则会出现数据乱码/模块不工作;
  2. ESP8266的VCC建议通过3.3V供电(STM32的3.3V引脚),避免5V烧坏模块;
  3. 继电器控制市电部分需做好绝缘,接线时务必断电操作;
  4. HLW8012的采样端需串联电流互感器/电压分压电阻,严格按照模块手册接线。

三、软件开发环境搭建

3.1 环境准备

  1. 软件工具
    • Keil MDK-ARM V5.29(STM32开发环境);
    • STM32CubeMX 6.8.0(代码初始化工具);
    • 串口助手(如SSCOM);
    • ESP8266烧录工具(Flash Download Tools)。
  2. 固件准备
    • STM32F103C8T6的标准库/HAL库;
    • ESP8266透传固件(AT指令版)。

3.2 STM32CubeMX初始化配置步骤

  1. 打开STM32CubeMX,选择芯片型号STM32F103C8T6
  2. 配置RCC:选择外部高速时钟(HSE),晶振8MHz;
  3. 配置UART2(HLW8012):异步模式,波特率9600,数据位8,停止位1,无校验;
  4. 配置UART3(ESP8266):异步模式,波特率115200,数据位8,停止位1,无校验;
  5. 配置GPIO:
    • PA0:输出模式(推挽输出),初始电平高(继电器断开);
    • PA1/PA2:输出模式(推挽输出),初始电平低(灯灭);
    • PB0/PB1:输入模式(上拉输入);
  6. 配置NVIC:开启UART2、UART3、EXTI0/EXTI1中断(按键中断);
  7. 生成代码:选择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编译步骤

  1. 将上述所有代码文件按文件结构放入STM32CubeMX生成的工程中;
  2. 打开SmartSocket.uvprojx工程文件;
  3. 点击魔术棒图标,选择Target,设置Stack Size为0x200,Heap Size为0x100;
  4. 选择Output,勾选Create HEX File
  5. 点击BuildRebuild按钮,等待编译完成(无error)。

5.2 代码下载

  1. 连接STM32最小系统板到电脑USB口;
  2. 打开ST-Link Utility或CH340下载工具;
  3. 选择编译生成的SmartSocket.hex文件;
  4. 点击Program按钮,等待下载完成。

5.3 ESP8266固件烧录

  1. 下载ESP8266 Flash Download Tools;
  2. 选择ESP8266-01S型号,波特率115200;
  3. 导入透传固件(如esp8266_transparent.bin);
  4. 按住ESP8266的FLASH按键,上电后点击START烧录;
  5. 烧录完成后重启模块。

六、测试与调试

6.1 本地测试

  1. 上电后,红灯亮(继电器断开),串口助手打开STM32的串口(波特率9600);
  2. 按下按键1,绿灯亮(继电器吸合),串口输出电量数据;
  3. 按下按键2,累计电量重置为0。

6.2 远程测试

  1. 确保ESP8266连接到你的WiFi;
  2. 通过TCP/UDP工具连接ESP8266的IP地址(可通过路由器后台查看);
  3. 发送指令RELAY_ON,继电器吸合,回复RELAY_ON_OK
  4. 发送指令RELAY_OFF,继电器断开,回复RELAY_OFF_OK
  5. 实时接收电量数据(电压/电流/功率/累计电量)。

6.3 常见问题排查

  1. 串口无数据:检查接线是否正确,波特率是否匹配;
  2. WiFi连接失败:检查WiFi名称/密码是否正确,ESP8266固件是否正确;
  3. 继电器不动作:检查PA0引脚电平,继电器供电是否正常;
  4. 电量数据异常:检查HLW8012采样电路接线。

七、硬件封装与安全注意事项

  1. 所有市电接线部分需使用绝缘端子,避免短路;
  2. 整个系统需放入阻燃外壳,防止触电;
  3. 建议在继电器前端增加保险丝,过流保护;
  4. 调试时务必断开市电,仅测试低压部分。

八、功能扩展建议

  1. 添加OLED显示屏,本地显示电量数据;
  2. 接入阿里云/腾讯云IoT平台,实现手机APP远程控制;
  3. 添加过流/过压保护,超过阈值自动断电;
  4. 增加定时开关功能,通过RTC模块实现。

总结

  1. 本项目基于STM32F103C8T6实现智能插座,核心由HLW8012(电量计量)、ESP8266(远程控制)、继电器(市电控制)三大模块组成,硬件接线需严格遵循共地、限流等原则;
  2. 软件层面通过分层驱动设计(电量/WiFi/继电器/按键)实现功能解耦,代码可直接编译下载,支持本地按键和远程WiFi双重控制;
  3. 测试阶段需先完成本地功能验证,再进行远程调试,市电部分务必注意安全防护,确保落地使用的稳定性和安全性。
相关推荐
至为芯2 小时前
PY32F005至为芯支持32位ARM内核的高主频MCU微控制器
单片机·集成电路·芯片
somi72 小时前
ARM-06-时钟系统配置
arm开发·单片机·嵌入式硬件·时钟配置
爱喝纯牛奶的柠檬2 小时前
基于STM32和HAL库的大夏龙雀BT311-10C02S蓝牙模块驱动
stm32·单片机·嵌入式硬件
小谦32512 小时前
NTC热敏电阻分压测量电路的数学特性与应用选择研究
stm32·嵌入式硬件
Xueqian E3 小时前
驱动策略和效率的整理
stm32·单片机·嵌入式硬件
电子工程师成长日记-C515 小时前
51单片机气压检测仪
单片机·嵌入式硬件·51单片机
嵌入式老菜鸟qq1252427735 小时前
nRF54H20 + Zephyr 开发环境(二):烧录与踩坑实录
stm32·单片机·嵌入式硬件
-Try hard-5 小时前
ARM | 点亮LED灯!
arm开发·单片机·嵌入式硬件
llilian_165 小时前
卫星时钟 时钟同步解决方案——基于高精度卫星时钟同步授时装置 卫星同步时钟 授时同步装置
功能测试·单片机·测试工具