文章目录
-
- 一、方案整体设计与原理说明
-
- 1.1 核心原理
- 1.2 整体架构流程图
- 二、硬件选型与接线
-
- 2.1 核心硬件清单
- 2.2 详细接线说明
-
- (1)STM32与电参数采集模块接线
- (2)STM32与DS18B20接线
- (3)STM32与继电器模块接线
- (4)STM32与ESP8266-01S接线
- (5)STM32与OLED12864接线(I2C)
- (6)电源接线
- 三、软件开发环境搭建
-
- 3.1 环境准备
- 3.2 STM32CubeMX初始化配置步骤
-
- 步骤1:新建工程
- 步骤2:配置RCC时钟
- 步骤3:配置ADC外设(电参数采集)
- 步骤4:配置UART外设(ESP8266通信)
- 步骤5:配置I2C外设(OLED通信)
- 步骤6:配置GPIO外设
- 步骤7:配置TIM定时器(用电量计时)
- 步骤8:生成工程代码
- 四、核心代码编写
-
- 4.1 头文件与全局变量定义(main.h)
- 4.2 外设初始化代码(main.c)
- 4.3 电参数采集与计算函数(main.c续)
- 4.4 温度采集函数(main.c续)
- 4.5 OLED显示函数(main.c续)
- 4.6 继电器与安全保护函数(main.c续)
- 4.7 ESP8266物联网通信函数(main.c续)
- 4.8 主函数(main.c续)
- 五、代码烧录与调试
-
- 5.1 代码编译与烧录
- 5.2 硬件调试步骤
-
- 步骤1:基础功能验证
- 步骤2:物联网通信调试
- 步骤3:远程控制调试
- 步骤4:安全保护调试
- 5.3 常见问题与解决
- 六、功能扩展建议
- 总结
一、方案整体设计与原理说明
你需要实现的是基于STM32物联网的家用智能插座多参数监控系统,核心功能包含电压/电流/功率/用电量精准采集、环境/插座温度检测、OLED本地实时显示、ESP8266无线上传至OneNET物联网平台、远程继电器通断控制、过压/过流/过热安全保护六大模块。本方案以STM32F103C8T6为核心控制器,通过ACS712电流传感器+电阻分压电路采集市电参数,经ADC转换和算法校准后计算功率/用电量;利用DS18B20采集温度,OLED实时显示所有参数;通过ESP8266-01S接入WiFi,将数据上传至OneNET平台并接收远程控制指令;内置保护逻辑,异常时自动切断插座供电。整个方案模块化拆解,代码注释详尽,零基础小白可完全复刻落地。
1.1 核心原理
- 电参数采集原理:市电220V经降压、整流、分压后输入STM32 ADC通道(电压采集),ACS712电流传感器串联在插座回路中,输出模拟电压对应电流值(10A量程对应185mV/A),同样经ADC转换;通过公式功率P=电压U×电流I计算实时功率,用电量=功率×时间累计计算(单位:度,1度=1kWh)。
- 温度检测原理:DS18B20通过单总线与STM32通信,无需外部元件即可采集-55℃~+125℃温度,精准度±0.5℃,用于检测插座本体温度和环境温度,避免过热风险。
- 物联网通信原理:ESP8266-01S通过UART与STM32通信,采用AT指令配置WiFi连接,基于MQTT协议将采集数据上传至OneNET平台;同时监听平台下发的控制指令(如“打开插座”“关闭插座”),解析后控制继电器通断。
- 安全保护原理:预设电压(200V~240V)、电流(≤10A)、温度(≤60℃)阈值,实时对比采集值,超出阈值时立即切断继电器,并上传报警信息至平台。
- 本地显示原理:0.96寸I2C OLED12864模块,通过I2C与STM32通信,分页显示电压、电流、功率、用电量、温度、插座状态,刷新频率1Hz,兼顾功耗与实时性。
1.2 整体架构流程图
以下是智能插座系统完整工作流程的Mermaid流程图:
#mermaid-svg-6T86DuSNW2LcFrgk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6T86DuSNW2LcFrgk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6T86DuSNW2LcFrgk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6T86DuSNW2LcFrgk .error-icon{fill:#552222;}#mermaid-svg-6T86DuSNW2LcFrgk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6T86DuSNW2LcFrgk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6T86DuSNW2LcFrgk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6T86DuSNW2LcFrgk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6T86DuSNW2LcFrgk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6T86DuSNW2LcFrgk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6T86DuSNW2LcFrgk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6T86DuSNW2LcFrgk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6T86DuSNW2LcFrgk .marker.cross{stroke:#333333;}#mermaid-svg-6T86DuSNW2LcFrgk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6T86DuSNW2LcFrgk p{margin:0;}#mermaid-svg-6T86DuSNW2LcFrgk .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-6T86DuSNW2LcFrgk .cluster-label text{fill:#333;}#mermaid-svg-6T86DuSNW2LcFrgk .cluster-label span{color:#333;}#mermaid-svg-6T86DuSNW2LcFrgk .cluster-label span p{background-color:transparent;}#mermaid-svg-6T86DuSNW2LcFrgk .label text,#mermaid-svg-6T86DuSNW2LcFrgk span{fill:#333;color:#333;}#mermaid-svg-6T86DuSNW2LcFrgk .node rect,#mermaid-svg-6T86DuSNW2LcFrgk .node circle,#mermaid-svg-6T86DuSNW2LcFrgk .node ellipse,#mermaid-svg-6T86DuSNW2LcFrgk .node polygon,#mermaid-svg-6T86DuSNW2LcFrgk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6T86DuSNW2LcFrgk .rough-node .label text,#mermaid-svg-6T86DuSNW2LcFrgk .node .label text,#mermaid-svg-6T86DuSNW2LcFrgk .image-shape .label,#mermaid-svg-6T86DuSNW2LcFrgk .icon-shape .label{text-anchor:middle;}#mermaid-svg-6T86DuSNW2LcFrgk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6T86DuSNW2LcFrgk .rough-node .label,#mermaid-svg-6T86DuSNW2LcFrgk .node .label,#mermaid-svg-6T86DuSNW2LcFrgk .image-shape .label,#mermaid-svg-6T86DuSNW2LcFrgk .icon-shape .label{text-align:center;}#mermaid-svg-6T86DuSNW2LcFrgk .node.clickable{cursor:pointer;}#mermaid-svg-6T86DuSNW2LcFrgk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6T86DuSNW2LcFrgk .arrowheadPath{fill:#333333;}#mermaid-svg-6T86DuSNW2LcFrgk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6T86DuSNW2LcFrgk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6T86DuSNW2LcFrgk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6T86DuSNW2LcFrgk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6T86DuSNW2LcFrgk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6T86DuSNW2LcFrgk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6T86DuSNW2LcFrgk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6T86DuSNW2LcFrgk .cluster text{fill:#333;}#mermaid-svg-6T86DuSNW2LcFrgk .cluster span{color:#333;}#mermaid-svg-6T86DuSNW2LcFrgk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6T86DuSNW2LcFrgk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6T86DuSNW2LcFrgk rect.text{fill:none;stroke-width:0;}#mermaid-svg-6T86DuSNW2LcFrgk .icon-shape,#mermaid-svg-6T86DuSNW2LcFrgk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6T86DuSNW2LcFrgk .icon-shape p,#mermaid-svg-6T86DuSNW2LcFrgk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6T86DuSNW2LcFrgk .icon-shape rect,#mermaid-svg-6T86DuSNW2LcFrgk .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6T86DuSNW2LcFrgk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6T86DuSNW2LcFrgk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6T86DuSNW2LcFrgk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-6T86DuSNW2LcFrgk .darkStyle>*{fill:#2c3e50!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .darkStyle span{fill:#2c3e50!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .darkStyle tspan{fill:#ffffff!important;}#mermaid-svg-6T86DuSNW2LcFrgk .startEnd>*{fill:#e74c3e!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .startEnd span{fill:#e74c3e!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .startEnd tspan{fill:#ffffff!important;}#mermaid-svg-6T86DuSNW2LcFrgk .decision>*{fill:#3498db!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .decision span{fill:#3498db!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .decision tspan{fill:#ffffff!important;}#mermaid-svg-6T86DuSNW2LcFrgk .process>*{fill:#27ae60!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .process span{fill:#27ae60!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .process tspan{fill:#ffffff!important;}#mermaid-svg-6T86DuSNW2LcFrgk .display>*{fill:#9b59b6!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .display span{fill:#9b59b6!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .display tspan{fill:#ffffff!important;}#mermaid-svg-6T86DuSNW2LcFrgk .iot>*{fill:#1abc9c!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .iot span{fill:#1abc9c!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .iot tspan{fill:#ffffff!important;}#mermaid-svg-6T86DuSNW2LcFrgk .protect>*{fill:#f39c12!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .protect span{fill:#f39c12!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-6T86DuSNW2LcFrgk .protect tspan{fill:#ffffff!important;}
否
是
是
否
有指令
无指令
是
否
系统上电初始化
RCC/ADC/UART/I2C/GPIO配置
初始化继电器为断开状态
初始化OLED/DS18B20/ESP8266
ESP8266连接WiFi+OneNET平台
连接成功?
重试连接(最多3次)
采集电压/电流数据(ADC转换)
计算实时功率/累计用电量
采集温度数据(DS18B20)
OLED本地显示所有参数
参数超出安全阈值?
切断继电器+上传报警信息
ESP8266上传正常数据至OneNET
监听OneNET远程控制指令
解析指令:开/关插座
指令为开且参数正常?
闭合继电器
断开继电器
二、硬件选型与接线
2.1 核心硬件清单
| STM32控制器 | STM32F103C8T6最小系统板 | 1 | 核心控制与算法运算 |
| 电流传感器 | ACS712-10A(量程0-10A) | 1 | 采集插座回路电流 |
| 电压采集模块 | AC-DC降压模块(220V转5V)+分压电阻 | 1 | 采集市电电压 |
| 温度传感器 | DS18B20(防水型) | 2 | 检测环境/插座温度 |
| 继电器模块 | 5V 1路继电器(带光耦隔离) | 1 | 控制插座通断 |
| WiFi模块 | ESP8266-01S(带AT固件) | 1 | 物联网通信(WiFi上传/接收) |
| 显示模块 | 0.96寸I2C OLED12864(蓝色) | 1 | 本地参数显示 |
| 电源模块 | DC-DC 5V 2A(隔离型) | 1 | 给STM32/传感器/模块供电 |
| 交流插座模块 | 五孔AC220V插座(带接线端子) | 1 | 负载供电 |
| 整流桥/滤波电容 | 1N4007×4 + 100μF电容 | 1套 | 电压采集整流滤波 |
| 分压电阻 | 100kΩ/10kΩ(1%精度) | 若干 | 电压信号分压 |
| 杜邦线 | 公对公/公对母/母对母 | 若干 | 硬件接线 |
| ST-Link下载器 | V2版 | 1 | 代码烧录与调试 |
| 面包板/万能板 | 大号 | 1 | 硬件搭建平台 |
| 保险丝 | 10A 250V | 1 | 过流物理保护 |
2.2 详细接线说明
(1)STM32与电参数采集模块接线
| PA0 | ACS712输出端 | 电流采集ADC通道(模拟输入) |
| PA1 | 电压分压模块输出端 | 电压采集ADC通道(模拟输入) |
| GND | ACS712/GND、分压模块GND | 共地(必须共地,否则数据不准) |
| 5V | ACS712/VCC | 电流传感器供电 |
电压采集电路搭建:220V市电经AC-DC降压模块转为5V直流 → 经100kΩ+10kΩ分压(总电阻110kΩ) → 输出至PA1(分压后电压≈0.45V~0.5V,在STM32 ADC量程0-3.3V内) → 并联100nF电容滤波。
(2)STM32与DS18B20接线
| PA2 | DQ | 单总线数据引脚(上拉4.7kΩ) |
| 3.3V | VCC | 传感器供电 |
| GND | GND | 共地 |
第二个DS18B20(插座温度)DQ引脚接PA3,供电/接地同上述,无需额外配置。
(3)STM32与继电器模块接线
| PB0 | IN | 继电器控制引脚(低电平触发) |
| 5V | VCC | 继电器供电 |
| GND | GND | 共地 |
继电器输出端串联保险丝后接AC220V和插座模块,实现通断控制。
(4)STM32与ESP8266-01S接线
| PA9 | TX | STM32 USART1_RX(接收ESP数据) |
| PA10 | RX | STM32 USART1_TX(发送数据至ESP) |
| 3.3V | VCC | ESP供电(禁止接5V,必烧) |
| GND | GND | 共地 |
| CH_PD | 3.3V | 拉高使能ESP模块 |
| RST | 悬空 | 复位引脚,无需接线 |
(5)STM32与OLED12864接线(I2C)
| PB6 | SCL | I2C1时钟线(上拉4.7kΩ) |
| PB7 | SDA | I2C1数据线(上拉4.7kΩ) |
| 3.3V | VCC | OLED供电 |
| GND | GND | 共地 |
(6)电源接线
- 主供电:AC220V → AC-DC隔离降压模块 → 5V输出 → 分两路:
- 一路给STM32最小系统板5V引脚(给STM32/ESP8266/OLED供电);
- 一路给继电器模块5V引脚(继电器独立供电,避免干扰);
- 3.3V供电:STM32板载3.3V引脚 → DS18B20/ACS712供电。
三、软件开发环境搭建
3.1 环境准备
3.2 STM32CubeMX初始化配置步骤
步骤1:新建工程
- 打开STM32CubeMX,点击“New Project”,在搜索框输入“STM32F103C8T6”,选择对应型号(STM32F103C8Tx)后点击“Start Project”。
- 弹出“Project Manager”提示框,点击“OK”跳过(默认配置即可)。
步骤2:配置RCC时钟
- 左侧菜单栏选择“RCC”:
- High Speed Clock (HSE):选择“Crystal/Ceramic Resonator”(外部8MHz晶振),启用HSE;
- Low Speed Clock (LSI):保持默认(内部低速晶振);
- 点击顶部“Clock Configuration”,配置系统时钟为72MHz:
- HSE = 8MHz → PLLMUL = 9 → SYSCLK = 72MHz;
- AHB Prescaler = 1(HCLK=72MHz);
- APB1 Prescaler = 2(PCLK1=36MHz);
- APB2 Prescaler = 1(PCLK2=72MHz);
- ADC时钟源:PCLK2/6 = 12MHz(ADC最大时钟14MHz)。
步骤3:配置ADC外设(电参数采集)
- 左侧菜单栏选择“ADC1”:
- 模式选择“Independent Mode”(独立模式);
- 配置参数:Resolution = 12-bit(12位精度),Data Alignment = Right(右对齐);
- 启用“IN0”(PA0)、“IN1”(PA1)通道,转换模式为“Continuous Conversion Mode”(连续转换);
- 采样时间:设置为239.5 Cycles(提高采样精度)。
步骤4:配置UART外设(ESP8266通信)
- 左侧菜单栏选择“USART1”:
- 模式选择“Asynchronous”(异步模式);
- 配置参数:Baud Rate = 115200 bps,Word Length = 8 Bits,Parity = None,Stop Bits = 1;
- 引脚映射:PA9(TX)、PA10(RX);
- 启用“NVIC Settings”中的“USART1 global interrupt”(串口中断,用于接收ESP8266数据)。
步骤5:配置I2C外设(OLED通信)
- 左侧菜单栏选择“I2C1”:
- 模式选择“I2C”(标准模式);
- 配置参数:Clock Speed = 100kHz,Addressing Mode = 7-bit;
- 引脚映射:PB6(SCL)、PB7(SDA)。
步骤6:配置GPIO外设
- 左侧菜单栏选择“GPIO”:
- PA2/PA3:设置为“Input Pull-Up”(DS18B20单总线,上拉输入),命名为“DS18B20_1”/“DS18B20_2”;
- PB0:设置为“Output Push Pull”(继电器控制),命名为“RELAY_PIN”,初始电平“High”(继电器断开);
- 所有GPIO速度设为“High”。
步骤7:配置TIM定时器(用电量计时)
- 左侧菜单栏选择“TIM3”:
- 模式选择“Up Counter”(向上计数);
- 配置参数:Prescaler = 7199(72MHz/7200=10kHz),Auto-Reload Value = 9999(10kHz/10000=1Hz,1秒中断一次);
- 启用“NVIC Settings”中的“TIM3 global interrupt”(定时中断,用于累计时间)。
步骤8:生成工程代码
- 点击顶部“Project Manager”:
- Project Name:设置为“SmartSocket_IoT”;
- Project Path:选择非中文路径(如D:\\STM32_Projects\\SmartSocket_IoT);
- Toolchain/IDE:选择“MDK-ARM”,版本“V5”;
- 点击“Code Generator”:
- 勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”;
- 勾选“Copy all used libraries into the project folder”;
- 点击“Generate Code”,生成完成后点击“Open Project”直接打开Keil工程。
四、核心代码编写
4.1 头文件与全局变量定义(main.h)
#ifndef __MAIN_H
#define __MAIN_H
#include "stm32f1xx_hal.h"
#include "stdio.h"
#include "string.h"
/* 引脚宏定义 */
// 继电器控制
#define RELAY_PIN GPIO_PIN_0
#define RELAY_GPIO_PORT GPIOB
#define RELAY_ON GPIO_PIN_RESET // 低电平闭合
#define RELAY_OFF GPIO_PIN_SET // 高电平断开
// DS18B20温度传感器
#define DS18B20_1_PIN GPIO_PIN_2
#define DS18B20_1_PORT GPIOA
#define DS18B20_2_PIN GPIO_PIN_3
#define DS18B20_2_PORT GPIOA
// OLED I2C地址
#define OLED_I2C_ADDR 0x78
/* 功能宏定义 */
// ADC校准参数(需用万用表实际校准)
#define VOLTAGE_RATIO 220.0f / 0.45f // 分压后电压转实际市电电压
#define CURRENT_RATIO 1.0f / 0.185f // ACS712输出电压转电流(185mV/A)
#define ADC_REF_VOLTAGE 3.3f // ADC参考电压3.3V
#define ADC_MAX_VALUE 4095.0f // 12位ADC最大值
// 安全阈值
#define VOLTAGE_MIN 200.0f // 最低电压(V)
#define VOLTAGE_MAX 240.0f // 最高电压(V)
#define CURRENT_MAX 10.0f // 最大电流(A)
#define TEMP_MAX 60.0f // 最高温度(℃)
// 物联网参数
#define WIFI_SSID "你的WiFi名称"
#define WIFI_PWD "你的WiFi密码"
#define ONENET_DEV_ID "你的OneNET设备ID"
#define ONENET_API_KEY "你的OneNET API密钥"
// 数据更新频率
#define DATA_UPDATE_FREQ 1000 // 1秒更新一次
/* 数据类型定义 */
// 电参数结构体
typedef struct
{
float voltage; // 市电电压(V)
float current; // 负载电流(A)
float power; // 实时功率(W)
float electricity; // 累计用电量(度,1度=1kWh)
uint32_t time_count; // 通电时间计数(秒)
} Elec_ParamTypeDef;
// 温度数据结构体
typedef struct
{
float temp1; // 环境温度(℃)
float temp2; // 插座温度(℃)
} Temp_ParamTypeDef;
// 系统状态结构体
typedef struct
{
uint8_t relay_state; // 继电器状态:0=关,1=开
uint8_t error_flag; // 错误标志:0=正常,1=过压,2=过流,3=过热
} System_StateTypeDef;
/* 全局变量 */
extern ADC_HandleTypeDef hadc1;
extern UART_HandleTypeDef huart1;
extern I2C_HandleTypeDef hi2c1;
extern TIM_HandleTypeDef htim3;
extern Elec_ParamTypeDef g_elec_param;
extern Temp_ParamTypeDef g_temp_param;
extern System_StateTypeDef g_sys_state;
extern uint8_t g_uart_rx_buf[128]; // 串口接收缓冲区
extern uint16_t g_uart_rx_len; // 串口接收长度
/* 函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_I2C1_Init(void);
static void MX_TIM3_Init(void);
// ADC采集函数
void ADC_Get_Value(float *voltage_adc, float *current_adc);
void Elec_Param_Calc(void);
// 温度采集函数
uint8_t DS18B20_Reset(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
void DS18B20_Write_Byte(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint8_t dat);
uint8_t DS18B20_Read_Byte(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
float DS18B20_Read_Temp(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
void Temp_Param_Get(void);
// OLED显示函数
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str);
void OLED_ShowNum(uint8_t x, uint8_t y, float num, uint8_t len, uint8_t dot);
void OLED_Display_All(void);
// 继电器控制函数
void Relay_Control(uint8_t state);
// 安全保护函数
uint8_t Safety_Check(void);
// ESP8266物联网函数
void ESP8266_Send_AT_CMD(uint8_t *cmd, uint8_t *ack, uint16_t timeout);
void ESP8266_Init(void);
void ESP8266_Upload_Data(void);
void ESP8266_Receive_Cmd(void);
// 串口中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
#endif /* __MAIN_H */
4.2 外设初始化代码(main.c)
#include "main.h"
/* 全局变量定义 */
ADC_HandleTypeDef hadc1;
UART_HandleTypeDef huart1;
I2C_HandleTypeDef hi2c1;
TIM_HandleTypeDef htim3;
Elec_ParamTypeDef g_elec_param = {0};
Temp_ParamTypeDef g_temp_param = {0};
System_StateTypeDef g_sys_state = {0};
uint8_t g_uart_rx_buf[128] = {0};
uint16_t g_uart_rx_len = 0;
/* 系统时钟配置函数 */
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/* 配置HSE外部晶振 */
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();
}
/* 配置ADC时钟源 */
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* ADC1初始化(电压/电流采集) */
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/* 配置IN0通道(PA0-电流) */
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* 配置IN1通道(PA1-电压) */
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
// 启动ADC连续转换
HAL_ADC_Start(&hadc1);
}
/* USART1初始化(ESP8266通信) */
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
// 启用串口接收中断
HAL_UART_Receive_IT(&huart1, g_uart_rx_buf, 1);
}
/* I2C1初始化(OLED通信) */
static void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
/* TIM3初始化(1秒中断) */
static void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 7199;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 9999;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
// 启动定时器中断
HAL_TIM_Base_Start_IT(&htim3);
}
/* GPIO初始化 */
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIO时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 配置继电器引脚(PB0) */
GPIO_InitStruct.Pin = RELAY_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(RELAY_GPIO_PORT, &GPIO_InitStruct);
/* 配置DS18B20引脚(PA2/PA3) */
GPIO_InitStruct.Pin = DS18B20_1_PIN|DS18B20_2_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 初始状态:继电器断开 */
HAL_GPIO_WritePin(RELAY_GPIO_PORT, RELAY_PIN, RELAY_OFF);
}
/* 错误处理函数 */
void Error_Handler(void)
{
__disable_irq();
while (1)
{
// 错误时继电器断开,OLED显示错误
Relay_Control(0);
HAL_Delay(500);
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif /* USE_FULL_ASSERT */
4.3 电参数采集与计算函数(main.c续)
/* ADC读取电压/电流原始值 */
void ADC_Get_Value(float *voltage_adc, float *current_adc)
{
uint16_t adc_val[2] = {0};
// 读取IN1(电压)通道值
HAL_ADC_PollForConversion(&hadc1, 100);
adc_val[0] = HAL_ADC_GetValue(&hadc1);
// 读取IN0(电流)通道值
HAL_ADC_PollForConversion(&hadc1, 100);
adc_val[1] = HAL_ADC_GetValue(&hadc1);
// 转换为实际电压值
*voltage_adc = (adc_val[0] * ADC_REF_VOLTAGE) / ADC_MAX_VALUE;
*current_adc = (adc_val[1] * ADC_REF_VOLTAGE) / ADC_MAX_VALUE – 2.5f; // ACS712中点电压2.5V
// 电流值取绝对值(双向电流)
if(*current_adc < 0) *current_adc = –(*current_adc);
}
/* 计算电参数(电压/电流/功率/用电量) */
void Elec_Param_Calc(void)
{
float voltage_adc, current_adc;
// 读取ADC原始值
ADC_Get_Value(&voltage_adc, ¤t_adc);
// 转换为实际市电参数
g_elec_param.voltage = voltage_adc * VOLTAGE_RATIO;
g_elec_param.current = current_adc * CURRENT_RATIO;
// 计算实时功率(W)
g_elec_param.power = g_elec_param.voltage * g_elec_param.current;
// 累计用电量(度):1度=1kWh=1000W×3600秒
if(g_sys_state.relay_state == 1) // 继电器闭合时累计
{
g_elec_param.electricity += (g_elec_param.power * 1.0f) / (1000.0f * 3600.0f);
}
}
/* TIM3中断回调函数(1秒计数) */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
g_elec_param.time_count++;
// 每秒更新一次参数
Elec_Param_Calc();
Temp_Param_Get();
// 安全检查
g_sys_state.error_flag = Safety_Check();
if(g_sys_state.error_flag != 0)
{
Relay_Control(0); // 异常时断开继电器
}
}
}
4.4 温度采集函数(main.c续)
/* DS18B20复位 */
uint8_t DS18B20_Reset(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
uint8_t retry = 0;
// 拉低总线480us
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
HAL_Delay_us(480);
// 释放总线,等待从机响应
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
HAL_Delay_us(60);
// 检测从机响应
while(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_SET && retry < 100)
{
retry++;
HAL_Delay_us(1);
}
if(retry >= 100) return 1; // 复位失败
else HAL_Delay_us(420); // 等待响应结束
return 0; // 复位成功
}
/* DS18B20写字节 */
void DS18B20_Write_Byte(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint8_t dat)
{
uint8_t i;
for(i=0; i<8; i++)
{
// 拉低总线1us
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
HAL_Delay_us(1);
// 写入1位数据
if(dat & 0x01) HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
else HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
HAL_Delay_us(60); // 保持60us
// 释放总线
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
HAL_Delay_us(2);
dat >>= 1;
}
}
/* DS18B20读字节 */
uint8_t DS18B20_Read_Byte(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
uint8_t i, dat = 0;
for(i=0; i<8; i++)
{
// 拉低总线1us
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
HAL_Delay_us(1);
// 释放总线,准备读取
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
HAL_Delay_us(10);
// 读取1位数据
if(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_SET) dat |= (1 << i);
HAL_Delay_us(50); // 等待数据结束
}
return dat;
}
/* 读取DS18B20温度值 */
float DS18B20_Read_Temp(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
uint8_t temp_h, temp_l;
float temp;
// 复位传感器
if(DS18B20_Reset(GPIOx, GPIO_Pin) != 0) return –99.0f;
// 发送跳过ROM指令
DS18B20_Write_Byte(GPIOx, GPIO_Pin, 0xCC);
// 发送转换指令
DS18B20_Write_Byte(GPIOx, GPIO_Pin, 0x44);
HAL_Delay(750); // 等待转换完成
// 复位传感器
DS18B20_Reset(GPIOx, GPIO_Pin);
// 发送跳过ROM指令
DS18B20_Write_Byte(GPIOx, GPIO_Pin, 0xCC);
// 发送读取暂存器指令
DS18B20_Write_Byte(GPIOx, GPIO_Pin, 0xBE);
// 读取温度数据
temp_l = DS18B20_Read_Byte(GPIOx, GPIO_Pin);
temp_h = DS18B20_Read_Byte(GPIOx, GPIO_Pin);
// 计算温度值
temp = (temp_h << 8) | temp_l;
if(temp > 0x7FF) // 负温度
{
temp = ~temp + 1;
temp = –temp / 16.0f;
}
else // 正温度
{
temp = temp / 16.0f;
}
return temp;
}
/* 获取温度参数 */
void Temp_Param_Get(void)
{
g_temp_param.temp1 = DS18B20_Read_Temp(DS18B20_1_PORT, DS18B20_1_PIN);
g_temp_param.temp2 = DS18B20_Read_Temp(DS18B20_2_PORT, DS18B20_2_PIN);
// 异常值过滤
if(g_temp_param.temp1 < –55 || g_temp_param.temp1 > 125) g_temp_param.temp1 = 0;
if(g_temp_param.temp2 < –55 || g_temp_param.temp2 > 125) g_temp_param.temp2 = 0;
}
4.5 OLED显示函数(main.c续)
/* OLED写命令 */
void OLED_Write_Cmd(uint8_t cmd)
{
uint8_t data[2] = {0x00, cmd};
HAL_I2C_Master_Transmit(&hi2c1, OLED_I2C_ADDR, data, 2, 100);
}
/* OLED写数据 */
void OLED_Write_Data(uint8_t data)
{
uint8_t buf[2] = {0x40, data};
HAL_I2C_Master_Transmit(&hi2c1, OLED_I2C_ADDR, buf, 2, 100);
}
/* OLED初始化 */
void OLED_Init(void)
{
HAL_Delay(100); // 上电延时
OLED_Write_Cmd(0xAE); // 关闭显示
OLED_Write_Cmd(0x00); // 设置列起始地址
OLED_Write_Cmd(0x10); // 设置列终止地址
OLED_Write_Cmd(0x40); // 设置行起始地址
OLED_Write_Cmd(0xB0); // 设置页地址
OLED_Write_Cmd(0x81); // 对比度设置
OLED_Write_Cmd(0xFF); // 最大对比度
OLED_Write_Cmd(0xA1); // 段重映射
OLED_Write_Cmd(0xA6); // 正常显示
OLED_Write_Cmd(0xA8); // 多路复用率
OLED_Write_Cmd(0x3F); // 1/64 Duty
OLED_Write_Cmd(0xC8); // COM扫描方向
OLED_Write_Cmd(0xD3); // 显示偏移
OLED_Write_Cmd(0x00); // 无偏移
OLED_Write_Cmd(0xD5); // 时钟分频
OLED_Write_Cmd(0x80); // 默认值
OLED_Write_Cmd(0xD9); // 预充电周期
OLED_Write_Cmd(0xF1); // 增强对比度
OLED_Write_Cmd(0xDA); // COM引脚配置
OLED_Write_Cmd(0x12);
OLED_Write_Cmd(0xDB); // VCOMH电压
OLED_Write_Cmd(0x40);
OLED_Write_Cmd(0x8D); // 电荷泵
OLED_Write_Cmd(0x14); // 启用电荷泵
OLED_Write_Cmd(0xAF); // 开启显示
OLED_Clear(); // 清屏
}
/* OLED清屏 */
void OLED_Clear(void)
{
uint8_t i, j;
for(i=0; i<8; i++)
{
OLED_Write_Cmd(0xB0 + i);
OLED_Write_Cmd(0x00);
OLED_Write_Cmd(0x10);
for(j=0; j<128; j++)
{
OLED_Write_Data(0x00);
}
}
}
/* OLED显示字符串 */
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str)
{
uint8_t i;
OLED_Write_Cmd(0xB0 + y);
OLED_Write_Cmd((x & 0x0F));
OLED_Write_Cmd(0x10 + ((x >> 4) & 0x0F));
for(i=0; str[i]!='\\0'; i++)
{
for(uint8_t j=0; j<8; j++)
{
OLED_Write_Data(F8x16[str[i]–' '][j]);
}
}
}
/* OLED显示数字(浮点型) */
void OLED_ShowNum(uint8_t x, uint8_t y, float num, uint8_t len, uint8_t dot)
{
uint8_t i, temp, num_buf[10];
uint32_t int_num = (uint32_t)(num * pow(10, dot));
for(i=0; i<len; i++)
{
temp = int_num % 10;
int_num /= 10;
num_buf[len–1–i] = temp + '0';
if(i == dot) num_buf[len–1–i–1] = '.';
}
OLED_ShowString(x, y, num_buf);
}
/* OLED显示所有参数 */
void OLED_Display_All(void)
{
OLED_Clear();
// 第一行:电压
OLED_ShowString(0, 0, (uint8_t*)"Voltage: ");
OLED_ShowNum(64, 0, g_elec_param.voltage, 3, 1);
OLED_ShowString(96, 0, (uint8_t*)"V");
// 第二行:电流
OLED_ShowString(0, 2, (uint8_t*)"Current: ");
OLED_ShowNum(64, 2, g_elec_param.current, 2, 2);
OLED_ShowString(96, 2, (uint8_t*)"A");
// 第三行:功率
OLED_ShowString(0, 4, (uint8_t*)"Power: ");
OLED_ShowNum(56, 4, g_elec_param.power, 4, 1);
OLED_ShowString(96, 4, (uint8_t*)"W");
// 第四行:温度/状态
OLED_ShowString(0, 6, (uint8_t*)"Temp: ");
OLED_ShowNum(48, 6, g_temp_param.temp2, 2, 1);
OLED_ShowString(80, 6, (uint8_t*)"C ");
if(g_sys_state.relay_state == 1) OLED_ShowString(96, 6, (uint8_t*)"ON");
else OLED_ShowString(96, 6, (uint8_t*)"OFF");
HAL_Delay(10);
}
// 8×16字库(简化版,仅包含数字/字母/符号)
const unsigned char F8x16[] = {
// 此处省略完整字库,实际使用需补充,可从网上下载标准8×16字库
};
4.6 继电器与安全保护函数(main.c续)
/* 继电器控制 */
void Relay_Control(uint8_t state)
{
if(state == 1) // 打开
{
// 只有参数正常时才能打开
if(g_sys_state.error_flag == 0)
{
HAL_GPIO_WritePin(RELAY_GPIO_PORT, RELAY_PIN, RELAY_ON);
g_sys_state.relay_state = 1;
}
}
else // 关闭
{
HAL_GPIO_WritePin(RELAY_GPIO_PORT, RELAY_PIN, RELAY_OFF);
g_sys_state.relay_state = 0;
}
}
/* 安全检查 */
uint8_t Safety_Check(void)
{
// 过压检测
if(g_elec_param.voltage < VOLTAGE_MIN || g_elec_param.voltage > VOLTAGE_MAX)
{
return 1;
}
// 过流检测
if(g_elec_param.current > CURRENT_MAX)
{
return 2;
}
// 过热检测
if(g_temp_param.temp2 > TEMP_MAX)
{
return 3;
}
// 正常
return 0;
}
4.7 ESP8266物联网通信函数(main.c续)
/* ESP8266发送AT指令并等待响应 */
void ESP8266_Send_AT_CMD(uint8_t *cmd, uint8_t *ack, uint16_t timeout)
{
HAL_UART_Transmit(&huart1, cmd, strlen((char*)cmd), 1000);
HAL_Delay(timeout);
// 检查响应
if(strstr((char*)g_uart_rx_buf, (char*)ack) == NULL)
{
// 指令执行失败,清空缓冲区
memset(g_uart_rx_buf, 0, 128);
g_uart_rx_len = 0;
}
}
/* ESP8266初始化(连接WiFi+OneNET) */
void ESP8266_Init(void)
{
HAL_Delay(1000); // 模块上电延时
// 重启模块
ESP8266_Send_AT_CMD((uint8_t*)"AT+RST\\r\\n", (uint8_t*)"OK", 2000);
HAL_Delay(2000);
// 关闭回显
ESP8266_Send_AT_CMD((uint8_t*)"ATE0\\r\\n", (uint8_t*)"OK", 1000);
// 设置STA模式
ESP8266_Send_AT_CMD((uint8_t*)"AT+CWMODE=1\\r\\n", (uint8_t*)"OK", 1000);
// 连接WiFi
uint8_t wifi_cmd[64];
sprintf((char*)wifi_cmd, "AT+CWJAP=\\"%s\\",\\"%s\\"\\r\\n", WIFI_SSID, WIFI_PWD);
ESP8266_Send_AT_CMD(wifi_cmd, (uint8_t*)"WIFI CONNECTED", 5000);
// 开启单连接
ESP8266_Send_AT_CMD((uint8_t*)"AT+CIPMUX=0\\r\\n", (uint8_t*)"OK", 1000);
// 连接OneNET MQTT服务器
uint8_t mqtt_cmd[128];
sprintf((char*)mqtt_cmd, "AT+MQTTCONNECT=0,\\"%s\\",\\"%s\\",\\"\\",60\\r\\n", ONENET_DEV_ID, ONENET_API_KEY);
ESP8266_Send_AT_CMD(mqtt_cmd, (uint8_t*)"CONNACK", 5000);
}
/* ESP8266上传数据至OneNET */
void ESP8266_Upload_Data(void)
{
uint8_t data_cmd[256];
// 构建JSON数据
sprintf((char*)data_cmd, "AT+MQTTPUB=0,\\"/sys/%s/%s/thing/event/property/post\\",\\"{\\\\\\"id\\\\\\":123,\\\\\\"params\\\\\\":{\\\\\\"voltage\\\\\\":%.1f,\\\\\\"current\\\\\\":%.2f,\\\\\\"power\\\\\\":%.1f,\\\\\\"electricity\\\\\\":%.4f,\\\\\\"temp\\\\\\":%.1f,\\\\\\"state\\\\\\":%d,\\\\\\"error\\\\\\":%d}}\\"\\r\\n",
ONENET_DEV_ID, ONENET_DEV_ID,
g_elec_param.voltage,
g_elec_param.current,
g_elec_param.power,
g_elec_param.electricity,
g_temp_param.temp2,
g_sys_state.relay_state,
g_sys_state.error_flag);
// 发送数据
ESP8266_Send_AT_CMD(data_cmd, (uint8_t*)"OK", 2000);
// 清空接收缓冲区
memset(g_uart_rx_buf, 0, 128);
g_uart_rx_len = 0;
}
/* ESP8266接收OneNET远程指令 */
void ESP8266_Receive_Cmd(void)
{
// 检查是否有控制指令
if(strstr((char*)g_uart_rx_buf, (uint8_t*)"\\"relay\\":\\"on\\""))
{
Relay_Control(1); // 打开插座
}
else if(strstr((char*)g_uart_rx_buf, (uint8_t*)"\\"relay\\":\\"off\\""))
{
Relay_Control(0); // 关闭插座
}
// 清空缓冲区
memset(g_uart_rx_buf, 0, 128);
g_uart_rx_len = 0;
}
/* 串口接收中断回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
g_uart_rx_len++;
// 重新启用中断
HAL_UART_Receive_IT(&huart1, &g_uart_rx_buf[g_uart_rx_len], 1);
}
}
4.8 主函数(main.c续)
/* 主函数 */
int main(void)
{
/* 初始化HAL库 */
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
/* 初始化外设 */
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
MX_I2C1_Init();
MX_TIM3_Init();
/* 初始化功能模块 */
OLED_Init(); // OLED初始化
ESP8266_Init(); // ESP8266初始化
Relay_Control(0); // 初始继电器断开
/* 主循环 */
while (1)
{
// 1. 本地OLED显示参数
OLED_Display_All();
// 2. 上传数据至OneNET(1秒一次)
if(g_elec_param.time_count % 1 == 0)
{
ESP8266_Upload_Data();
}
// 3. 接收远程控制指令
ESP8266_Receive_Cmd();
// 4. 低功耗延时
HAL_Delay(100);
}
}
五、代码烧录与调试
5.1 代码编译与烧录
- 若出现“DS18B20函数未定义”:检查HAL_Delay_us函数是否实现(需在stm32f1xx_hal.c中补充微秒延时);
- 若出现“串口中断异常”:核对USART1中断优先级配置,确保不与其他中断冲突。
- ST-Link连接STM32 SWD接口(SWDIO、SWCLK、GND),USB连接电脑;
- 确认市电采集电路无短路,继电器输出端串联保险丝后再接负载。
5.2 硬件调试步骤
步骤1:基础功能验证
- 上电检查:STM32上电后,OLED点亮并显示初始参数(电压≈220V,电流≈0A,温度≈室温),ESP8266指示灯快速闪烁(连接WiFi);
- ADC校准:接入已知负载(如100W灯泡),用万用表测量实际电压/电流,调整VOLTAGE_RATIO/CURRENT_RATIO使显示值与实际值一致。
步骤2:物联网通信调试
- 登录OneNET平台(https://open.iot.10086.cn/),进入设备管理页面,查看“数据流”是否有电压/电流/功率等数据上传;
- 若数据未上传:检查ESP8266 AT指令是否正确(用串口调试助手发送AT指令测试)、OneNET设备ID/API密钥是否匹配。
步骤3:远程控制调试
- 在OneNET平台下发“打开插座”指令(JSON格式:{"relay":"on"}),继电器应闭合,OLED显示“ON”;
- 下发“关闭插座”指令({"relay":"off"}),继电器应断开,OLED显示“OFF”。
步骤4:安全保护调试
- 过压测试:模拟市电电压超过240V(调整分压电路),继电器应立即断开,OneNET上传过压报警;
- 过流测试:接入超过10A的负载(如电热水壶),继电器断开并上传过流报警;
- 过热测试:用手握住插座温度传感器,温度超过60℃时,继电器断开并上传过热报警。
5.3 常见问题与解决
| OLED无显示 | I2C接线错误、地址错误 | 核对PB6/PB7接线,确认OLED地址为0x78/0x7A |
| 电流显示为0 | ACS712接线反、中点电压未校准 | 调换ACS712输入线,调整CURRENT_RATIO |
| ESP8266无法连接WiFi | WiFi名称/密码错误、模块未刷AT固件 | 核对WiFi参数,重新刷入ESP8266 AT固件 |
| 继电器无法闭合 | 控制电平错误、供电不足 | 确认RELAY_ON为低电平,给继电器单独供电 |
| 数据上传乱码 | 串口波特率不匹配、JSON格式错误 | 确认波特率115200,检查JSON格式是否有语法错误 |





