简介本资源是一个基于STM32F103微控制器的物联网温湿度数据采集与无线上传完整工程面向嵌入式初学者及IoT项目实践者解决环境参数感知、MCU外设驱动、WiFi模块AT指令通信与HTTP数据上报等典型开发问题。压缩包共224个文件含39个C源码如stm32f10x_usart.c、stm32f10x_tim.c等底层驱动、41个头文件.h、40个编译中间文件.o/.d/.crf以及Keil工程配置uvprojx/uvoptx、烧录镜像hex/axf和调试脚本keilkill.bat总大小4.05MB结构完整可直接编译下载运行。已有1901人学习下载涵盖DHT11单总线协议解析、ESP8266 Station模式配置、串口透传与JSON数据封装等关键实现配套代码注释清晰、模块划分明确特别适合掌握STM32ESP8266DHT11协同开发全流程的实战训练。 我最初想做这个项目是因为想在家里远程看到阳台花盆附近的温湿度但又不想买现成的智能传感器——一来贵二来数据封闭没法自己折腾。于是翻了翻手头的元件箱一块STM32F103C8T6最小系统板一个ESP8266模块一个DHT11温湿度传感器正好是网上那个流传很广的stm32_esp8266.rar压缩包里的经典三件套。做出来的成品虽然丑但数据能通过WiFi上报到服务器手机随时能看整个过程踩了不少坑也把STM32、ESP8266、DHT11这三样东西从“会点灯”到“能联网”彻底串通了。这篇文章就把整个项目的来龙去脉、硬件连接、代码逻辑、AT指令调度、联调排错完整记录下来给同样想入门STM32ESP8266DHT11的朋友一条能直接走通的路。1. 三件套选型拆解为什么这个组合是入门物联网的第一课1.1 三个芯片各干各的活先把这个系统里三个角色的分工理清楚。STM32F103C8T6是主控负责跑逻辑、读传感器、处理数据DHT11是采集端负责感知温度和湿度通过一根单总线把数据送给STM32ESP8266是通信端负责连WiFi把STM32处理好的数据通过TCP/IP协议栈发到互联网上。为什么不让ESP8266直接读DHT11技术上讲完全可以ESP8266也有GPIO也能模拟时序而且NodeMCU这类开发板连DHT11库都有现成的。但从学习角度讲这套组合的精髓在于“主控通信模组”的分工模式——STM32专注逻辑控制ESP8266专注网络透传两者通过串口通信这种架构在工业产品里非常常见。你以后换4G模组、换LoRa模组主控代码几乎不用动只需要改串口指令解析部分。学会这个等于学会了物联网终端设备的标准范式。1.2 通信链路全景图整个数据流是这样的DHT11采集温湿度通过单总线协议发送40bit数据给STM32的某个GPIO引脚STM32解析出温度值和湿度值整数部分小数部分暂存在结构体里STM32通过USART2或其他串口向ESP8266发送AT指令配置WiFi并建立TCP连接STM32把温湿度数据拼成HTTP请求报文通过同一个串口发给ESP8266ESP8266通过WiFi把数据包发到路由器路由器再转发到目标服务器注意STM32和ESP8266之间只有一根TX、一根RX通信全靠串口。这里有个很容易让新手懵的地方ESP8266在上电之后会通过串口输出一堆乱码或者ready之类的信息这些是ESP8266的固件启动日志不是错误不用慌。1.3 硬件连接与接线表这套系统的接线极其简单总共就六七根线但接线之前有两件事必须确认。第一确认你的ESP8266模块是哪种型号。网上最常见的是ESP-01板载PCB天线引出8个引脚但GPIO2和GPIO0被其他功能占用了可用的IO很少。还有ESP-12F引出更多引脚开发更方便。另外还有带USB转串口的NodeMCU开发板如果你只想先调试ESP8266本身用NodeMCU最省事。这里默认你用的是ESP-01或者ESP-12F这类裸模组需要外接3.3V电源和USB转TTL工具。第二ESP8266的功耗。ESP8266在WiFi发射瞬间电流可以达到200~300mA这对电源的要求很苛刻。用STM32最小系统板上的3.3V稳压芯片直接给ESP8266供电很可能会导致电压跌落、模块反复重启。这种问题表现很诡异模块一会儿能连上WiFi一会儿重启而且毫无规律。我一开始就这样干折腾了整整一个晚上后来老老实实用了一个独立的AMS1117-3.3稳压模块单独供电毛病立刻消失。接线表如下DHT11 VCC -- 3.3V或者5VDHT11供电范围3.3V~5.5V用5V时数据脚要加电阻分压这里建议直接用3.3V省事DHT11 GND -- GNDDHT11 DATA -- STM32 PA6任选GPIO注意最好选5V容忍引脚ESP8266 VCC -- 独立3.3V电源ESP8266 GND -- 系统的GND一定要共地ESP8266 TX -- STM32 PA3 (USART2_RX)ESP8266 RX -- STM32 PA2 (USART2_TX)就是这7根线没有更复杂的了。特别强调共地不共地串口通信会收到一堆乱码因为TTL电平的参考地不一致。2. DHT11单总线时序从示波器波形到HAL库代码2.1 DHT11的通信协议到底长什么样DHT11是一个典型的单总线传感器数据脚只有一根既做输入又做输出通信完全靠时序。如果把一次完整的读取过程拉开看大致是主机STM32拉低数据线至少18ms再释放这叫作“起始信号”DHT11检测到起始信号后拉低80us再拉高80us作为一个响应信号之后DHT11连续发送40bit数据每bit都以50us低电平开头高电平持续26~28us代表逻辑0高电平持续70us代表逻辑140bit数据排列为湿度整数8bit 湿度小数8bit 温度整数8bit 温度小数8bit 校验和8bit校验和等于前四个字节之和的低8位理解这个时序的关键在于读数据的本质是“测量高电平持续的时间”。低电平50us是一个固定前导决定0和1的不是低电平时长而是后面高电平持续多久。所以在代码里读bit的逻辑是先等数据线变低再等数据线变高然后计时等待数据线再次变低如果等待时间短则是0长则是1。用示波器看会非常直观逻辑分析仪也行但不用仪器也能通过代码调试确认时序正确。我第一次写完驱动读出来温度一直是0后来一查是起始信号的拉低时间不够长DHT11根本没响应。DHT11虽然便宜但时序要求不算宽松起始信号必须大于18ms而且建议用HAL_Delay(20)这种毫秒级延时别用us级别的延时在那里凑反而容易出错。2.2 HAL库实现DHT11读取的核心代码直接给一个能用的驱动写法。注意DHT11的GPIO需要在输入和输出模式之间切换HAL库里可以用GPIO_MODE_OUTPUT_PP和GPIO_MODE_INPUT来回切换或者用更取巧的方式把GPIO配置成开漏输出这样输出低电平靠引脚拉低输出高电平靠外部上拉电阻读输入时直接把引脚设为输入模式即可不用来回切换方向。初始化GPIO的代码void DHT11_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_6; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStruct.Pull GPIO_PULLUP; // 内部上拉 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); }读取时序的函数uint8_t DHT11_ReadByte(void) { uint8_t data 0; for (int i 0; i 8; i) { while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) GPIO_PIN_RESET); // 等待低电平结束 delay_us(40); // 高电平持续40us后采样 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) GPIO_PIN_SET) { data | (0x80 i); // 高电平持续超过40us判为1 } while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) GPIO_PIN_SET); // 等待高电平结束 } return data; }这段代码的思路是每个bit的低电平是50us从低电平结束开始计时到40us时读一次引脚如果还是高电平说明这个bit的高电平持续时间超过了40us判定为逻辑1否则就是0。这个采样点卡得很稳实测下来只要能精确延时正确率接近100%。delay_us函数可以用DWT实现也可以用TIM定时器实现最差用几个NOP指令凑时间也行。2.3 动不动就卡死、读到0xFF、数据不变的排查方法DHT11这种单总线传感器代码里全是while (引脚 某个电平)的等待循环一旦时序出错最简单直接的后果就是死循环程序跑飞。遇到卡死的基本可以断定是等待电平变化的那一步没等到比如DHT11没响应数据线一直保持高电平while (HAL_GPIO_ReadPin RESET)就永远等不到低电平。排查思路按以下顺序来先确认DHT11有没有供电。VCC和GND接反了的话模块会微微发烫读出来全是0xFF或者干脆没响应。检查GPIO配置。用开漏输出时外部一定要有上拉如果内部上拉不够强STM32内部上拉大概在30~50kΩ可以在数据线和VCC之间加一个4.7kΩ的外部上拉电阻。这个电阻加上之后时序会明显稳定很多。在关键等待循环里加超时退出机制。比如while (引脚RESET) { if (timeout 10000) return ERROR; }这样即使DHT11不响应程序也不会卡死还能通过返回值定位问题。我还遇到过一个比较隐蔽的问题用HAL_Delay做微秒延时。HAL_Delay的参数单位是毫秒有些人图省事直接传0.001结果实际上延时不生效或者延时巨大时序完全乱掉。微秒级的延时一定要单独实现别用HAL_Delay凑这是新手最容易踩的坑之一。3. ESP8266的AT指令通信从配置WiFi到透传数据3.1 先搞清楚手里的ESP8266处于什么状态ESP8266模块出厂时一般烧好了AT固件上电后可以通过串口发送AT指令来控制。但不同批次、不同渠道的模块固件版本可能不同有的支持AT指令有的默认进入了透传模式有的波特率是9600有的是115200。所以第一步永远是接好USB转TTL打开串口助手发送AT看是否返回OK。这一步能刷掉至少一半的“模块坏了”的假故障。我见过不少人连串口都打不开查了半天是USB转TTL模块的TX接了自己的RX、RX接了自己的TX——交叉连接是基础中的基础但真的很容易搞混。记住口诀收发交叉地线相连。如果发送AT没反应检查以下几点USB转TTL的TX接ESP8266的RXRX接ESP8266的TXGND对接ESP8266的CH_PDEN引脚必须拉高有的模块已经板载上拉有的需要手动接3.3VGPIO0要悬空或者接高电平否则模块进入的是烧录模式而不是运行模式波特率设置正确。大多数AT固件默认115200但老版本可能是9600试着切几个波特率看看3.2 配置ESP8266连WiFi的标准AT指令序列ESP8266的AT指令有新旧两套版本新版本比如AT固件版本2.x以上指令格式变化比较大但最常用的一套命令在大多数固件上是通用的。为了让STM32代码尽量简化建议先把ESP8266固件刷成官方AT固件版本比较新指令兼容性也好。配置WiFi并建立TCP连接的典型指令序列ATCWMODE1 // 设置为Station模式 ATCWJAPWiFi名称,WiFi密码 // 连接无线路由器 ATCIPSTARTTCP,192.168.1.100,8080 // 建立TCP连接 ATCIPSEND30 // 告知模块接下来要发送30字节数据注意最后这条ATCIPSEND30是ESP8266 AT指令里最容易被误解的一条。它不是一个“开始发送”的开关而是告诉模块“我接下来要发送的数据长度是多少”模块收到这个长度后会自动进入透传模式收取完指定长度的数据后自动退出透传并返回SEND OK。这个长度必须和实际发送的字节数严格一致少了或者多了都会导致数据发送不完整或者模块状态异常。如果只是做一个简单demo每次发送固定长度的数据确实不难。但实际中温湿度数据每次的长度可能不一样比如温度从25.3变成26.5字节数变化所以必须动态计算。我在STM32代码里用sprintf先把数据拼成字符串再用strlen算出长度最后拼成完整的AT指令发送这样长度始终是对的。3.3 透传模式和非透传模式的选择AT固件还支持一种透传模式通过ATCIPMODE1开启。透传模式下ESP8266把串口收到的所有数据不加解析地直接发到TCP连接的另一端数据之间不需要长度说明也不需要等待SEND OK非常适合连续传输数据的场景比如视频流、传感器连续上报。透传模式的缺点是一旦进入透传模式你就没法再发送其他AT指令了必须通过特殊的退出序列才能回到命令模式。的检测有一些时序要求——发送前后需要间隔一定时间不然会被当成普通数据处理。对于温湿度采集这种低频上报场景我不推荐透传模式。每次采集完数据用“非透传模式固定长度”的方式发送发完一条数据后模块自动回到命令模式逻辑清晰也不容易卡死。4. STM32端的串口调度策略从轮询到DMA空闲中断4.1 串口收发的架构演进STM32和ESP8266之间所有的通信都走串口所以串口代码的质量直接决定整个系统稳不稳定。最基础的做法是轮询——主循环里不停查串口接收标志位收到一个字节就存一个处理完再继续。这种做法写起来简单但有个致命问题如果STM32在等待ESP8266回复时其他任务比如DHT11的18ms起始信号插进来串口数据就会丢失因为串口接收缓冲只有一个字节第二个字节到了会把第一个字节冲掉。所以稍微正规一点可以用中断接收。每收到一个字节就进一次中断把数据放到一个环形缓冲区里主循环再从缓冲区里取数据解析。这样不管数据什么时候到只要缓冲够大都不会丢。更进阶一点的方法是用DMA 空闲中断IDLE Interrupt。DMA负责把串口接收数据寄存器的内容自动搬运到内存缓冲区CPU完全不用干预当一帧数据结束串口总线空闲时触发一次空闲中断在中断里处理完整数据帧。这种方案特别适合ESP8266回传不定长数据的情况比如它返回的OK、SEND OK、IPD开头的TCP数据长度各不相同用DMA空闲中断可以一锅端。STM32CubeMX里配置USART2开启DMA接收接收模式设为Circular循环模式然后在空闲中断回调里解析数据这是目前最推荐的方案。4.2 数据帧协议设计不要让代码去猜数据边界STM32要上传的数据很简单温度值和湿度值最多再加个设备编号。但正因为简单很多人就不做协议设计直接把sprintf(temp25.3 hum60.1)这样的字符串发给ESP8266服务器那边也能收到但解析起来很别扭而且这类原始字符串在传输过程中一旦有错位很难自恢复。建议做成一个简单的JSON字符串比如{id:dev01,temp:25.3,hum:60.1}发送前用sprintf格式化发送后服务器端用任何JSON库都能直接解析。这种做法的好处是协议是自描述的加字段只改一处将来你如果要做设备管理、历史曲线这个数据格式依然扛得住。如果觉得JSON的解析太占STM32的资源其实解析不一定要在STM32端做STM32只管发服务器端解析就行那至少也要用固定分隔符和固定顺序比如temp:25.3;hum:60.1;服务器端按分号切分就能拿出数据。4.3 状态机让通信逻辑不再是一坨面条代码STM32和ESP8266的交互过程其实是一个多步骤的流程配置WiFi、连接TCP、发送数据、确认发送结果每一步都可能失败失败后可能要重试。如果把这些逻辑全部用sif...else if串起来代码会变得非常难维护而且稍不留神就会在某个等待环节卡死。更工程化的做法是用状态机。把整个流程分解成若干个状态STATE_WIFI_CONNECT发送ATCWJAP等待WIFI CONNECTED或ERRORSTATE_TCP_CONNECT发送ATCIPSTART等待CONNECT或ERRORSTATE_SENDING计算数据长度发送ATCIPSENDxx等待提示符STATE_SEND_DATA发送实际数据等待SEND OKSTATE_IDLE空闲状态等待下次采集周期主循环里根据当前状态决定下一步操作每次串口收到新数据根据当前状态做对应的解析。这样每个状态的责任单一出了问题也容易定位到具体状态去排查。状态机看起来比顺序执行复杂但实际写下来你会发现代码结构反而更清晰而且扩展新功能比如加一个服务器心跳只需要加一个状态不用动其他逻辑。5. 数据上报流程HTTP还是MQTT这是一个值得想清楚的问题5.1 最简HTTP上报只用AT指令就能干完如果你只想先把数据送到服务器上看一眼HTTP POST是最直接的方式。用ESP8266的AT指令连上WiFi后直接向服务器IP的80端口发一条标准的HTTP POST请求服务器收到数据返回HTTP响应ESP8266再把这个响应通过串口发给STM32整个链路就闭环了。STM32端拼出的HTTP报文大致长这样POST /api/upload HTTP/1.1 Host: 192.168.1.100 Content-Type: application/json Content-Length: 36 {id:dev01,temp:25.3,hum:60.1}注意Content-Length要精确HTTP协议就靠这个字段判断请求体在哪结束。很多ESP8266上传数据失败不是因为网络不通而是因为Content-Length和实际JSON长度不一致服务器一直等不到完整请求体超时后返回4xx错误。本地测试时我在PC上跑了一个简单的Python HTTP服务器Flask或者http.server都行这样能实时看到POST请求的内容方便调试。生产环境换成云服务器上的Nginx后端接口逻辑完全一样。5.2 MQTT方案当数据量大了之后的正道HTTP上报虽然简单但有一个天然短板开销大。每次HTTP请求都要经过完整的TCP三次握手、HTTP头解析、响应返回对于高频上报或者嵌入式中低端MCU来说资源和带宽都很浪费。而且HTTP是单向主动上报想从服务器反向控制设备就很麻烦。MQTT为了解决这个问题而生。ESP8266既有运行MQTT库的方案用Arduino或者ESP-IDF也有AT固件对MQTT指令的支持不同固件有差异需要确认版本。MQTT基于发布/订阅模式设备端发布消息到Topic服务器订阅Topic就能收到服务器也可以往另一个Topic发消息设备订阅这个Topic就能收到指令。对于温湿度监测这种系统设备定时发布数据服务器端只需被动订阅逻辑更清晰且MQTT的报文头比HTTP小得多流量更省。不过MQTT需要搭建一个Broker服务器本地方案用Eclipse Mosquitto很轻量云端的免费方案有EMQX、HiveMQ Cloud等。用AT指令操作MQTT本质上还是通过串口发送命令只不过把ATCIPSTART的TCP连接目标变成了MQTT Broker的地址和端口然后在TCP连接之上跑MQTT协议。5.3 本地自测利器用手机热点和网络调试助手调ESP8266联网问题我个人的体会是永远不要一开始就接路由器特别是企业级WiFi或者需要网页认证的公共网络ESP8266基本连不上或者连上了也被踢。先用手机开一个2.4GHz热点密码设简单一点比如12345678让ESP8266连手机热点然后在手机上用一个TCP调试工具比如“网络调试助手”App监听端口ESP8266把数据发过来手机App就能直接看到。这个方法的好处是你能同时看到ESP8266侧发生了什么也能即时确认数据是否真的到了“服务器”。我在调试中发现过数据没发出去、数据发出去了但格式不对、WiFi断了自动重连失败等多种问题都是靠手机热点调试App快速定位的。5.4 断线重连必须面对的现实问题WiFi本身是不可靠的路由器重启、信号干扰、ESP8266固件bug都可能导致WiFi连接断开。一旦断开如果不做任何处理设备就永远“失联”了。所以可靠的项目里必须有断线检测和自动重连机制。最简单的断线检测方法周期性发送ATCIPSTATUS指令查询连接状态返回STATUS:3表示TCP连接正常返回其他值说明有问题。如果发现连接断了就按顺序重新建立连接先ATCWJAP重新连WiFi再ATCIPSTART重新建立TCP连接。实测下来ESP8266的WiFi重连过程一般需要2~10秒这个时间对温湿度采集来说完全可接受但要注意在重连期间数据采集任务不能停采集到的数据要缓存起来等网络恢复后再补发。这个“离线补传”功能虽然不复杂但在实际应用里非常重要——没有这个功能断网期间的数据就永远丢了。6. 联调实录从“永远连不上”到稳定运行一整天6.1 阶段一ESP8266单独调试先别扯上STM32我的联调策略是分而治之先把ESP8266当做一个独立模块用PC的串口助手和它交互确认WiFi能连上、TCP能建立、数据能收到再把STM32当成一个哑终端只负责转发串口数据最后才让STM32真正接管控制逻辑。单独调ESP8266时用串口助手的“手动发送”功能一条一条地发AT指令、看返回值这个过程能帮你确认ESP8266的固件功能是否完整、波特率是否匹配、WiFi密码是否有效。当用手动发送能把数据发到手机App端ESP8266这块就算通过了。6.2 阶段二STM32转发模式确认物理通路然后写一个最简单的STM32程序STM32把USART1调试用收到的数据原样转发到USART2接ESP8266再把USART2收到的数据转发回USART1。这样PC串口助手发指令给STM32STM32转发给ESP8266ESP8266的返回值再通过STM32回传到PC。这个阶段主要验证的是STM32和ESP8266之间的电气连接是否正常、波特率配置是否一致、电平是否匹配。如果转发模式下数据完整无乱码硬件通路就稳了。6.3 阶段三STM32自动控制重点看时序与状态机最后把状态机逻辑写进去让STM32按照预定的流程自动完成WiFi连接、TCP连接、数据上报。这个阶段最容易出的问题是STM32发送AT指令后还没等ESP8266回复就发了下一条指令。ESP8266的AT固件虽然能缓冲一部分指令但如果指令来得太快前面的指令还没执行完后面的指令就会被丢弃或者返回busy。所以发送每条AT指令之前必须等待上一条指令的响应标志位被置位超时则重发或者走错误处理。这就回到了前面讲的状态机设计——每个状态都等待特定的响应没等到就继续等或者超时重试绝不会出现“抢跑”。6.4 实测数据与运行稳定性整个系统调通之后我让它连续运行了24小时每10秒上报一次温湿度数据一共采集了8640条记录。服务器端统计下来数据完整率在99.8%左右丢失的十几条基本集中在凌晨路由器自动重启的时间窗口断线重连自动恢复了没有一次需要人工干预。温度读数在25~28度之间波动湿度在50%~60%之间波动DHT11的精度虽然一般温度±2度湿度±5%但对于环境监测这种场景完全够用。如果你需要更精确的数据可以把DHT11换成DHT22或者SHT30接口逻辑类似但精度能提升一个量级。7. 进阶思路这个项目还能往哪些方向走项目跑通之后你觉得不过瘾可以顺着这几个方向继续折腾。加OLED显示屏在STM32上挂一块0.96寸OLED实时显示温湿度和WiFi连接状态设备就从“黑盒”变成了“看得见”的实体排查问题也方便很多。加本地存储用SPI Flash或者SD卡把数据存一份在本地断网期间的数据也不丢服务器上线后再补传。加多个传感器节点如果不想把传感器都堆在一块可以试试ESP8266作为WiFi网关多个STM32节点通过485总线或CAN总线把数据汇到网关再由网关统一上传。这个架构就更接近真实的物联网系统了。用ESP8266做OTA升级通过WiFi远程更新STM32的固件这样设备部署后不用拆机就能升级是产品化必须考虑的功能。STM32的Bootloader加上ESP8266的透传通道可以实现最简版本的OTA。从DHT11的时序到ESP8266的AT指令从串口状态机到WiFi断线重连STM32ESP8266DHT11这个组合本身很简单但每一个环节背后都是一整套嵌入式物联网的通用方法论。我个人的体会是这个项目真正的价值不在于做出来一个能上报温湿度的东西而在于它把传感器采集、主控逻辑、通信模组、网络协议这四层知识串成了一条完整的链路。把这四层玩明白你再看市面上那些智能硬件眼里就不再有“黑魔法”了。本文还有配套的精品资源点击获取