STM32+ESP8266+OneNet智能家居系统实战:MQTT远程控制与温湿度监测

发布时间:2026/9/20 19:43:45
STM32+ESP8266+OneNet智能家居系统实战:MQTT远程控制与温湿度监测 1. 项目缘起与整体设计思路1.1 为什么选STM32加OneNet这套组合这个项目最早是我帮一个学弟做毕业设计时接手的需求很明确做一个能远程控制家里灯光、风扇、窗帘还能实时看温湿度数据的智能家居系统。市面上现成的方案不少但要么太贵要么太封闭改不动。最后定下来的技术路线是STM32做本地主控ESP8266负责联网数据上OneNet云平台手机端通过MQTT协议下发指令和接收状态。为什么是STM32而不是ESP32或者树莓派这里有几个很实际的考量。STM32的GPIO操作足够底层外设资源丰富做多路继电器控制、PWM调光、ADC采集都很顺手而且工业级的稳定性是经过验证的。ESP32虽然自带WiFi但引脚资源紧张做多路控制时经常要外扩IO反而麻烦。树莓派跑Linux系统功耗高、启动慢对于“通电就要立刻工作”的家居场景并不合适。STM32加ESP8266的组合本质上是把“实时控制”和“网络通信”两个职责拆开各司其职稳定性反而更好。OneNet作为云平台的选择也很关键。它提供免费的设备接入额度MQTT协议支持完善数据可视化组件可以直接拖拽生成对于个人项目和小型商用场景完全够用。相比自己搭MQTT服务器省去了公网IP、服务器运维、证书配置这一堆麻烦事。你只需要在平台上创建产品、添加设备、拿到设备ID和鉴权信息剩下的就是让ESP8266把数据发上去。1.2 系统架构的分层设计整个系统我把它分成四层来看这样排查问题时思路会清晰很多。第一层是感知与执行层包括DHT11温湿度传感器、光敏电阻、继电器模块、LED灯带这些。它们直接和STM32的GPIO、ADC引脚相连负责采集环境数据和执行开关动作。第二层是本地控制层核心就是STM32最小系统板。它跑一个主循环定时读取传感器数据根据预设阈值或者云端下发的指令控制继电器。这里我用了状态机的方式管理各个设备避免逻辑混乱。第三层是网络通信层ESP8266通过UART和STM32通信跑AT指令集或者刷MQTT固件。我实测下来刷MQTT固件的方式更稳定因为AT指令在处理长连接和断线重连时容易出问题。第四层是云端与应用层OneNet负责数据存储、转发和可视化手机端可以用微信小程序或者MQTTX这类客户端来订阅和发布消息。注意分层设计的好处是当网络出问题时本地控制逻辑完全不受影响。我试过把ESP8266拔掉STM32依然能根据温湿度自动控制风扇这一点在演示时特别加分。1.3 关键器件的选型对比器件选型理由替代方案主控STM32F103C8T6资源够用、资料多、价格低STM32F407性能更强但成本高联网ESP8266-01S便宜、AT指令成熟、社区大ESP32但引脚少温湿度DHT11单总线、驱动简单SHT30精度更高但贵继电器5V低电平触发兼容STM32的3.3V逻辑固态继电器寿命长但贵电源AMS1117-3.3简单可靠MP1584效率更高这个选型表是我踩过几次坑之后定下来的。最早用DHT22精度确实好但价格是DHT11的三倍对于演示项目来说没必要。继电器一开始买了高电平触发的结果STM32上电瞬间引脚默认高电平继电器会“啪”地吸合一下后来换成低电平触发才解决。2. 核心细节解析与实操要点2.1 STM32的GPIO配置与继电器驱动STM32的GPIO操作是这个项目的基础但很多人在这里翻车。F103的GPIO有八种模式控制继电器要用推挽输出速度选50MHz就够了。初始化代码大概长这样GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure);这里有个细节上电瞬间的电平状态。STM32复位后GPIO默认是浮空输入但如果你在初始化之前引脚被外部电路拉高继电器就可能误动作。我的做法是在硬件上给继电器控制引脚加一个下拉电阻软件上初始化后立刻输出高电平低电平触发继电器确保上电时设备是关闭状态。驱动继电器时STM32的单个GPIO最大输出电流是20mA而继电器线圈通常需要60-80mA。所以不能直接驱动必须加三极管或者光耦。我用的是S8050三极管加1K基极电阻继电器线圈两端反并联一个1N4148续流二极管防止断电时反向电动势打坏三极管。实操心得如果你用光耦隔离记得光耦的输入端也要加限流电阻。我试过直接把STM32引脚接光耦结果光耦没坏STM32的引脚倒是烧了一个。后来算了一下光耦输入端压降约1.2VSTM32输出3.3V需要串一个(3.3-1.2)/0.01210Ω的电阻取220Ω刚好。2.2 DHT11的时序控制与数据校验DHT11是单总线器件对时序要求很严格。STM32的主频是72MHz一个时钟周期约13.9ns而DHT11的“1”和“0”是靠高电平持续时间区分的26-28us表示“0”70us表示“1”。用延时函数来卡这个时间误差不能太大。我的做法是用SysTick做微秒级延时代码如下void delay_us(uint32_t us) { uint32_t temp; SysTick-LOAD 9 * us; SysTick-VAL 0; SysTick-CTRL 0x01; do { temp SysTick-CTRL; } while ((temp 0x01) !(temp (1 16))); SysTick-CTRL 0x00; SysTick-VAL 0; }读取流程是主机拉低总线至少18ms然后拉高20-40us等待DHT11响应。DHT11会拉低80us再拉高80us然后开始传40位数据。每一位都以50us低电平开始高电平持续时间决定是0还是1。数据格式是湿度整数湿度小数温度整数温度小数校验和。校验和等于前四个字节相加的低八位。我遇到过校验失败的情况后来发现是两次读取间隔太短DHT11需要至少1秒的间隔。改成2秒读一次之后再没出过问题。注意DHT11的湿度小数部分在普通版本里一直是0只有温度小数有分辨率。如果你需要更高精度直接上SHT30I2C接口代码更简单精度±2%RH。2.3 ESP8266的固件刷写与MQTT配置ESP8266-01S出厂一般是AT固件但AT指令在处理MQTT长连接时不够稳定。我建议刷MQTT透传固件比如安信可的AT固件或者NodeMCU固件。刷写需要USB转TTL模块接线是ESP8266的TX接TTL的RXRX接TXCH_PD接3.3VGPIO0接地进入下载模式。刷写工具用官方的Flash Download Tool选择8Mbit的固件地址填0x00000。刷完之后GPIO0悬空重新上电模块就进入正常工作模式。MQTT配置的关键参数有这几个参数值说明服务器地址mqtts.heclouds.comOneNet的MQTT接入点端口1883非加密端口加密用8883设备ID平台生成在OneNet设备详情页查看产品ID平台生成同上鉴权信息自定义创建设备时设置的密钥连接指令是ATMQTTCONN0,mqtts.heclouds.com,1883,1然后订阅主题ATMQTTSUB0,$sys/产品ID/设备ID/cmd/request/#,1发布数据用ATMQTTPUB0,$sys/产品ID/设备ID/dp/post/json,1,0,0,{\temp\:25}实操心得ESP8266的AT指令对引号和逗号很敏感建议用串口助手先手动测试一遍确认每条指令都能返回OK再写到STM32的代码里。我一开始直接在代码里拼字符串结果因为少了一个逗号调试了一下午。2.4 OneNet平台的产品创建与数据流配置OneNet的界面这几年改版了好几次但核心逻辑没变先创建产品再添加设备然后定义数据流。创建产品时协议选MQTT数据格式选JSON行业选智能家居。产品创建好后进入设备管理添加设备。设备名称随便填但设备ID和鉴权信息要记下来后面ESP8266连接要用。数据流是OneNet的核心概念你可以把它理解成“一类数据的通道”。比如温度是一个数据流湿度是另一个继电器状态又是一个。在产品的“数据流模板”里提前定义好设备上报数据时就会自动匹配。数据上报的JSON格式{ temp: 25.6, humi: 60.2, relay1: 1, relay2: 0 }OneNet收到后会自动解析成对应的数据流。你可以在“数据可视化”里拖拽仪表盘、开关、折线图来展示这些数据。注意OneNet的免费版有设备数和数据存储时间的限制个人项目够用但商用要提前评估。另外数据流名称不要用中文虽然平台支持但ESP8266的AT指令处理中文容易乱码。3. 实操过程与核心环节实现3.1 硬件搭建与接线检查硬件连接是整个项目最容易出问题的环节。我整理了一份接线表照着接基本不会错STM32引脚连接对象备注PA9ESP8266 RXUART1 TXPA10ESP8266 TXUART1 RXPB0继电器1 IN低电平触发PB1继电器2 IN低电平触发PB5DHT11 DATA加4.7K上拉PA0光敏电阻ADC采集3.3VESP8266 VCC必须3.3V5V继电器 VCC继电器用5VESP8266的供电是重点。它的峰值电流能到300mA而STM32板子上的3.3V稳压器通常只能给100-200mA。我一开始直接从STM32的3.3V取电结果ESP8266一连接WiFi就重启。后来单独用一个AMS1117-3.3给它供电问题解决。实操心得ESP8266的CH_PD引脚必须接高电平否则模块不工作。我见过有人忘了接然后一直怀疑是固件问题。另外ESP8266的TX引脚是3.3V电平直接接STM32的RX没问题但STM32的TX是3.3V接ESP8266的RX也没问题两者电平兼容。3.2 STM32主程序的逻辑框架主程序我用了一个简单的状态机加定时器调度的结构。SysTick每1ms中断一次在中断里维护几个计数器分别控制传感器读取、数据上报、指令处理的节奏。volatile uint32_t tick_1s 0; volatile uint32_t tick_2s 0; volatile uint32_t tick_100ms 0; void SysTick_Handler(void) { tick_1s; tick_2s; tick_100ms; } int main(void) { SystemInit(); GPIO_Config(); USART1_Init(115200); USART2_Init(115200); DHT11_Init(); ESP8266_Init(); while (1) { if (tick_100ms 100) { tick_100ms 0; ESP8266_Process(); // 处理串口数据 } if (tick_1s 1000) { tick_1s 0; DHT11_Read(temp, humi); Local_Control(temp, humi); // 本地自动控制 } if (tick_2s 2000) { tick_2s 0; MQTT_Publish_Data(temp, humi, relay_state); } } }这个框架的好处是每个任务的时间片是独立的不会因为某个任务耗时过长而影响其他任务。比如DHT11读取需要20ms左右放在1秒的任务里完全没问题。3.3 MQTT数据上报与指令下发的完整流程数据上报的流程是STM32通过UART2给ESP8266发AT指令ESP8266把数据打包成MQTT报文发到OneNet。指令下发则是反过来的OneNet把指令推给ESP8266ESP8266通过UART2传给STM32STM32解析后执行。上报数据的AT指令拼接void MQTT_Publish_Data(float temp, float humi, uint8_t relay) { char json[128]; sprintf(json, {\temp\:%.1f,\humi\:%.1f,\relay\:%d}, temp, humi, relay); char cmd[256]; sprintf(cmd, ATMQTTPUB0,\$sys/%s/%s/dp/post/json\,1,0,0,\%s\\r\n, PRODUCT_ID, DEVICE_ID, json); USART2_SendString(cmd); }指令下发的解析void ESP8266_Process(void) { if (USART2_RxFlag) { USART2_RxFlag 0; if (strstr((char*)USART2_RxBuffer, relay1)) { if (strstr((char*)USART2_RxBuffer, \1\)) { GPIO_SetBits(GPIOB, GPIO_Pin_0); // 关闭继电器 } else { GPIO_ResetBits(GPIOB, GPIO_Pin_0); // 打开继电器 } } memset(USART2_RxBuffer, 0, USART2_RxLen); } }这里有个坑ESP8266返回的数据里可能包含多条消息如果只用一次strstr可能会漏掉。我的做法是把接收缓冲区开大一点512字节每次处理完清空并且在解析时用while循环找所有匹配项。注意OneNet下发的指令格式是固定的你可以在平台的“设备调试”里手动发一条指令测试。我试过发{relay1:1}ESP8266收到后原样传给STM32STM32解析后控制继电器整个链路延迟在200ms左右完全满足家居控制的需求。3.4 手机端控制界面的快速搭建手机端我没有自己写APP直接用OneNet的“应用管理”里的“数据可视化”功能。拖一个开关组件绑定到relay1数据流设置开值为1、关值为0。再拖两个仪表盘显示温度和湿度一个折线图显示历史数据。如果你想要更灵活的控制可以用微信小程序。OneNet提供了微信小程序的SDK你只需要在小程序里调用它的API就能读取设备数据和下发指令。我试过用MQTTX这个桌面客户端来测试连接参数和ESP8266一样订阅$sys/产品ID/设备ID/cmd/request/#主题就能收到设备上报的数据。实操心得OneNet的数据可视化组件有缓存有时候你改了数据流名称界面上还是显示旧的。这时候刷新一下页面或者退出重新登录就好了。另外开关组件的“开”和“关”对应的值要和你STM32里定义的一致我定义的是1开0关结果平台上默认是0开1关导致控制反了改过来就好了。4. 常见问题与排查技巧实录4.1 ESP8266连接OneNet失败的排查步骤这是问得最多的问题我整理了一个排查流程现象可能原因解决方法AT无返回波特率不对尝试115200和9600返回ERROR指令格式错误检查引号和逗号连接超时网络不通先用ATPING测试鉴权失败设备ID或密钥错重新复制平台信息频繁掉线供电不足单独给ESP8266供电我遇到过一次连接超时排查了半天发现是路由器开了AP隔离ESP8266和手机不在同一个网段。关掉AP隔离就好了。还有一次是OneNet的服务器地址填错了应该是mqtts.heclouds.com我填成了mqtt.heclouds.com少了s。注意ESP8266的AT固件版本不同指令可能有差异。建议用ATGMR查看版本号然后去安信可的官网下载对应的AT指令手册。我用的版本是1.7.4MQTT指令和1.6.x有些区别。4.2 DHT11数据读取失败的常见原因DHT11读失败通常有三个原因时序不对、上拉电阻缺失、读取间隔太短。时序问题最常见。STM32的延时函数如果被中断打断就会导致时序错乱。我的做法是在读取DHT11时关闭全局中断读完再打开。代码如下void DHT11_Read(float *temp, float *humi) { __disable_irq(); // 读取时序 __enable_irq(); }上拉电阻方面DHT11的DATA引脚必须接一个4.7K到10K的上拉电阻到3.3V。我试过不接有时候能读出来有时候读出来全是0。接上之后一次都没失败过。读取间隔方面DHT11的 datasheet 写的是“两次读取间隔不小于1秒”但实测下来2秒更稳。我一开始设的500ms失败率大概30%改成2秒后降到0。4.3 继电器误动作与上电抖动的解决继电器误动作是硬件调试时的经典问题。现象是STM32一上电继电器就“啪”地吸合一下然后才恢复正常。原因是STM32复位后GPIO处于浮空输入状态引脚电平不确定。如果继电器是低电平触发而引脚恰好被外部电路拉低继电器就会吸合。解决方法有两个硬件上在继电器控制引脚加一个10K的下拉电阻到地确保上电时引脚是低电平软件上在GPIO初始化之后立刻输出高电平。我两个都做了再没出现过误动作。实操心得如果你用的是高电平触发的继电器那就加上拉电阻软件初始化后输出低电平。总之原则就是上电时让继电器处于关闭状态。4.4 OneNet数据上报频率与流量控制OneNet免费版对数据上报频率有限制好像是每秒不超过1次。我一开始设的500ms上报一次结果平台返回“请求过于频繁”。改成2秒一次就正常了。另外数据包的大小也要控制。JSON格式虽然可读性好但比二进制格式占空间。如果你上报的数据项很多可以考虑用简写比如t代替temph代替humi。不过对于个人项目来说2秒一次、每次100字节左右一个月也就几百MB流量完全在免费额度内。4.5 常见问题速查表问题排查方向快速解决STM32程序不跑晶振、复位、BOOT检查BOOT0接地串口无输出波特率、TX/RX接反交换TX/RX试试ESP8266反复重启供电电流不足单独3.3V供电MQTT连接失败地址、端口、鉴权用MQTTX先测试数据上报成功但平台不显示数据流未定义在平台添加数据流手机端控制延迟大网络质量检查WiFi信号强度这个表是我在调试过程中一点点攒出来的基本上覆盖了90%的问题。剩下的10%通常是硬件虚焊或者模块本身坏了换一个就好了。5. 项目扩展与个人经验分享5.1 从单设备到多设备的扩展思路这个项目最初只控制两个继电器后来我把它扩展到了8路。扩展的方法很简单在STM32上多定义几个GPIO在OneNet上多定义几个数据流在JSON里多拼几个字段。但要注意ESP8266的AT指令有长度限制一次最多发256字节。如果你要上报的数据太多可以分两次发或者用更紧凑的格式。另一个扩展方向是场景联动。比如“回家模式”打开客厅灯、打开空调、关闭窗帘。这个逻辑可以放在STM32里也可以放在OneNet的“触发器”里。我选择放在STM32里因为响应更快而且断网也能用。5.2 本地控制与云端控制的优先级处理这是一个容易被忽略但很重要的问题当本地自动控制和云端手动控制同时发生时听谁的我的做法是云端优先。具体来说STM32维护一个“手动模式”标志位。当收到云端指令时置位这个标志并启动一个5分钟的定时器。在定时器到期之前本地自动控制不生效。定时器到期后自动恢复本地控制。这样做的好处是用户手动关灯后不会因为光线变暗又被自动打开。5分钟的时间也足够用户离开房间。5.3 我在这个项目里踩过的坑第一个坑是电源。我一开始用USB线给整个系统供电结果ESP8266一工作STM32就复位。后来用万用表测了一下USB线压降太大5V到了板子上只剩4.3V。换了一根粗一点的线就好了。第二个坑是AT指令的换行符。ESP8266的AT指令必须以\r\n结尾我只发了\r结果模块一直不返回。后来看手册才发现要\r\n。第三个坑是OneNet的设备ID和产品ID搞混。这两个都是长字符串长得有点像。我复制的时候复制错了导致鉴权一直失败。后来在平台上看清楚了产品ID是以“产品”开头的设备ID是以“设备”开头的。第四个坑是DHT11的供电。DHT11的工作电压是3.3V到5.5V我接的3.3V但数据线也接的3.3V上拉。后来发现DHT11在3.3V下响应速度会慢一点改成5V供电、3.3V上拉之后读取更稳定了。5.4 这个项目还能怎么玩如果你已经跑通了这个基础版本可以试试这几个方向接入语音助手用OneNet的API对接一些开放的语音平台实现语音控制。增加红外遥控加一个红外接收头学习家里的空调遥控码实现空调控制。做数据记录与分析把OneNet的数据导出到本地用Python做温湿度趋势分析。改用ESP32如果你需要更快的处理速度或者蓝牙功能可以把ESP8266换成ESP32代码改动不大。我个人觉得这个项目最大的价值不在于功能有多复杂而在于它打通了“传感器-主控-云端-手机”的完整链路。你把这个链路跑通了后面加什么功能都只是在这个骨架上添砖加瓦。我后来做鱼缸监控、做温室大棚用的都是同一套架构只是换了传感器和执行器。最后分享一个小技巧在调试MQTT通信时用MQTTX这个客户端先连上OneNet手动发几条指令确认平台侧没问题再去调STM32和ESP8266。这样可以排除掉一半的变量省很多时间。