欢迎光临
我们一直在努力

场景实战:基于STM32物联网的家用智能插座多参数监控解决方案解析

文章目录

    • 一、方案整体设计与原理说明
      • 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与电参数采集模块接线
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接线
STM32引脚DS18B20引脚说明
PA2 DQ 单总线数据引脚(上拉4.7kΩ)
3.3V VCC 传感器供电
GND GND 共地

第二个DS18B20(插座温度)DQ引脚接PA3,供电/接地同上述,无需额外配置。

(3)STM32与继电器模块接线
STM32引脚继电器模块引脚说明
PB0 IN 继电器控制引脚(低电平触发)
5V VCC 继电器供电
GND GND 共地

继电器输出端串联保险丝后接AC220V和插座模块,实现通断控制。

(4)STM32与ESP8266-01S接线
STM32引脚ESP8266引脚说明
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)
STM32引脚OLED引脚说明
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 环境准备

  • STM32CubeMX:下载V6.0及以上版本(官网:https://www.st.com/en/development-tools/stm32cubemx.html),用于图形化配置STM32外设,自动生成初始化代码。
  • Keil MDK-ARM:下载V5.36及以上版本,安装STM32F103C8T6对应的Device Pack(在Keil中通过“Pack Installer”搜索“STM32F1”安装),用于代码编写、编译和烧录。
  • ST-Link驱动:安装ST-Link V2驱动,确保电脑能识别下载器;若用串口调试,安装CH340驱动。
  • 辅助工具:OneNET物联网平台账号(免费注册)、串口调试助手(调试ESP8266 AT指令)、ADC校准工具(万用表)。
  • 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, &current_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[len1i] = temp + '0';
    if(i == dot) num_buf[len1i1] = '.';
    }

    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 代码编译与烧录

  • 代码补充:将上述所有代码按模块补充到Keil工程的main.c和main.h中,补充OLED 8×16字库(可从网上下载标准字库);替换WIFI_SSID/WIFI_PWD/ONENET_DEV_ID/ONENET_API_KEY为实际参数。
  • 编译代码:点击Keil工具栏的“Build”按钮,确保0 Error、0 Warning。
    • 若出现“DS18B20函数未定义”:检查HAL_Delay_us函数是否实现(需在stm32f1xx_hal.c中补充微秒延时);
    • 若出现“串口中断异常”:核对USART1中断优先级配置,确保不与其他中断冲突。
  • 硬件连接:
    • ST-Link连接STM32 SWD接口(SWDIO、SWCLK、GND),USB连接电脑;
    • 确认市电采集电路无短路,继电器输出端串联保险丝后再接负载。
  • 代码烧录:点击Keil工具栏的“Download”按钮,等待烧录完成。
  • 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格式是否有语法错误

    六、功能扩展建议

  • 定时开关功能:添加RTC实时时钟模块(DS3231),设置定时开/关插座,支持多组定时任务;
  • 电量统计与账单:增加SD卡模块,记录每日/每月用电量,生成用电账单并上传至平台;
  • 语音控制:接入阿里云/百度语音助手,通过语音指令“打开插座”“关闭插座”控制;
  • 多插座联动:扩展为多路插座,支持独立控制和联动控制(如打开空调时自动打开加湿器);
  • 故障报警:添加蜂鸣器/LED指示灯,异常时本地声光报警,同时推送报警信息至手机APP;
  • 离线控制:增加蓝牙模块,无WiFi时通过手机蓝牙本地控制插座。
  • 总结

  • 本方案以STM32F103C8T6为核心,实现了家用智能插座的电压/电流/功率/用电量/温度采集、OLED本地显示、ESP8266物联网上传、远程控制、安全保护五大核心功能,硬件接线模块化,代码注释详尽,零基础小白可按步骤落地。
  • 系统核心逻辑通过Mermaid流程图清晰呈现,关键模块(电参数采集、温度检测、OLED显示、物联网通信、安全保护)拆分为独立函数,便于理解和修改;核心参数(校准系数、安全阈值)可通过宏定义快速调整。
  • 调试时需优先验证ADC校准和物联网通信,再测试远程控制和安全保护功能,遇到问题可通过Keil变量监控查看g_elec_param、g_temp_param等关键变量,快速定位故障点。
  • 赞(0)
    未经允许不得转载:171主机测评 » 场景实战:基于STM32物联网的家用智能插座多参数监控解决方案解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址