STM32+ESP8266+DHT11+OLED实战:打造WiFi温湿度监控系统
2026/9/9 3:05:20 网站建设 项目流程

最近把家里阳台的温湿度监控改成了 WiFi 无线版,核心用的就是 STM32 + ESP8266 + DHT11 + OLED 这套经典组合。整体做下来最大的感受是:这套方案成本不高、原理清晰、每个模块单独拎出来都有很多可以深入的点,特别适合作为物联网入门到进阶的过渡项目。这篇文章就把整个制作过程完整拆开,从硬件选型、接线、驱动代码到 ESP8266 网络上传,再到日常调试中踩过的坑,全部记录下来,希望能给正在做类似项目的朋友省点时间。

这套东西能做什么?传感器负责每分钟采集一次温湿度,OLED 屏幕上实时刷新显示,ESP8266 通过 WiFi 把数据上报到局域网服务端或者云平台,这样即使人不在现场,也能在手机或电脑上看到数据变化。它的定位很明确:是一个可以稳定跑通的无线传感器节点方案,而不是玩具级 demo。适合作的人包括刚学完 STM32 基础外设想找一个完整项目的同学,也包括想给家里做环境监控的嵌入式爱好者。

1. 整体方案与硬件选型思路

1.1 为什么是 STM32 + ESP8266 + DHT11 + OLED

很多类似项目会用 ESP8266 直接连 DHT11 和 OLED,毕竟 ESP8266 本身也有 GPIO,逻辑上完全可行。但我这次刻意保留 STM32 作为主控,原因有三:一是 STM32 的生态更偏“单片机工程师日常”,定时器、中断、I2C、串口等外设配合 HAL 库非常成熟,适合把这个项目当成外设综合练习;二是 ESP8266 只干通信这一件事,模块本身的无线协议栈处理已经比较复杂,如果再把传感器时序和显示刷新都压给它,调试时很难分清问题是出在数据采集还是网络传输上;三是这套架构天然可以扩展到多传感器,后续换 SHT30、BMP280,或者加继电器控制设备,主控逻辑完全不用动。

DHT11 的精度确实一般,温度误差 ±2°C,湿度误差 ±5%RH,但胜在便宜、协议简单,用来学习单总线时序非常合适。如果做实际产品,我建议换成 DHT22 / SHT30,但教程逻辑完全一致,驱动函数只有时序参数略有不同。OLED 选 0.96 寸 I2C 接口的 SSD1306,128x64 分辨率,只需要两根数据线就能驱动,省 GPIO 也省事。

1.2 硬件清单与功能拆解

  • 主控:STM32F103C8T6 最小系统板(蓝色 pill 板),价格低,资料多,HAL 库支持完善。
  • WiFi 模块:ESP8266-01S 或 ESP-12F 转接板,我用的是 ESP-01S,性价比高,但只有 GPIO0/GPIO2 引出,做纯透传足够。
  • 传感器:DHT11 蓝色三针模块,板载上拉电阻,直接插杜邦线就能用。
  • 显示:0.96 寸 SSD1306 OLED,I2C 接口,四针(VCC/GND/SCL/SDA)。
  • 电源:USB-TTL 模块的 5V 供电,或者 AMS1117-3.3 稳压模块。STM32 板载 LDO 可以输出 3.3V,但 ESP8266 峰值电流可能到 200-300mA,最好独立供电,避免 OLED 显示闪烁。
  • 调试工具:USB-TTL(CH340),用于给 ESP8266 烧 AT 固件和看日志。

整个系统的数据流是:DHT11 通过单总线传给 STM32,STM32 处理后一方面送 OLED 刷新,另一方面通过 UART 把数据帧发给 ESP8266,ESP8266 以 AT 指令模式连上 WiFi,用 TCP 或 MQTT 上报到远端。

2. 硬件接线与模块调试前的准备

2.1 各模块接线表

接线是整个项目最容易翻车的第一关,我整理了一张自己的接线表,主机是 STM32F103C8T6,串口调试信息走 USART1,ESP8266 走 USART2,OLED 走硬件 I2C1。

STM32 引脚连接目标说明
3.3VOLED VCC、ESP8266 VCC(通过外部稳压)逻辑电源
GNDOLED GND、ESP8266 GND、DHT11 GND共地必须接
PB7OLED SCL(I2C1_SCL)硬件 I2C
PB6OLED SDA(I2C1_SDA)硬件 I2C
PB8DHT11 DATA单总线双向引脚
PA2ESP8266 RX(通过分压)USART2_TX
PA3ESP8266 TXUSART2_RX
PA9 / PA10USB-TTL 的 RX/TX调试日志 USART1

注意 PB8 作为 DHT11 数据线,建议加一个 4.7k 上拉电阻到 3.3V。虽然很多模块板载上拉,但如果买的是单独的传感器而不是蓝色小板,不加的话时序高电平会偏弱,偶尔读到 0。

2.2 电平匹配与供电注意事项

ESP8266 是 3.3V 逻辑,但 UART 引脚并不都是 5V 耐受,所以 STM32 的 3.3V 供电系统下其实可以直接互连。不过如果用的是 5V 供电的 STM32 板子(比如某些开发板通过 VIN 供电后逻辑还是 3.3V),发现通信不稳定时要检查一下 TX 电平。最稳妥的方式是 STM32 TX 到 ESP8266 RX 之间串一个 1k 电阻分压,或者用两个电阻组成 1.8k/3.3k 分压。

供电上我的经验是:ESP8266 与 STM32 分成两路供电。USB-TTL 输出 5V 进 STM32 的 5V 引脚,同时 5V 给 AMS1117-3.3 输入端,然后 AMS1117 输出单独给 ESP8266 供电。不要直接从 STM32 的 3.3V 引脚给 ESP8266 供电,因为板载 LDO 在 WiFi 发射瞬间压降明显,会出现 OLED 亮度突变甚至 MCU 复位。

2.3 ESP8266 AT 固件检查与网络配置

拿到 ESP8266 第一步不是忙着接线,而是先用 USB-TTL 单独测试模块能否响应 AT 指令。把 ESP-01S 的 VCC、GND、TX、RX、GPIO0 接好,GPIO0 接高电平进入运行模式,然后在串口工具里发送AT,返回OK说明固件正常。

接下来配置 WiFi 参数。我在调试阶段最常用这几条指令:

AT+CWMODE=1 AT+CWJAP="你的SSID","你的密码" AT+CIPSTART="TCP","192.168.8.100",8080 AT+CIPMODE=1 AT+CIPSEND

AT+CWMODE=1 是 Station 模式,只连接路由不创建热点。AT+CWJAP 连上 WiFi 后,用 AT+CIFSR 查询模块拿到的 IP,确认已经拿到地址。AT+CIPSTART 建立 TCP 连接,AT+CIPMODE=1 进入透传模式,之后单片机发给串口的数据都会直接通过 TCP 发出去。退出透传的方法是发送+++,注意前后不要跟回车,否则会被当成数据发出去。

如果模块一直返回 ERROR,我建议先用 AT+CWMODE=1 和 AT+RST 复位,然后再试。老固件可能不支持 AT+CWMODE=3 和透传同时使用,所以配置顺序很有讲究。

3. 关键驱动代码与协议实现

3.1 DHT11 单总线时序与 HAL 库实现

DHT11 只有一根数据线,所有通信都在这一根线上完成,协议本身不复杂,但对时序敏感。裸机编程里最直接的做法是用延时函数精确控制高低电平宽度。在 STM32 HAL 库环境下,我用的是 GPIOD 读引脚 + Delay_us 微秒延时实现的。

DHT11 的通信过程分成两步:主机发起始信号,传感器响应后连续输出 40 bit 数据。起始信号是主机先把数据线拉低至少 18ms,然后释放并拉高 20-40us,之后传感器会拉低 80us 作为响应,再把总线拉高 80us,然后开始输出数据位。

每一位数据都是一个低电平 + 高电平的组合,数据 0 的高电平持续时间约 26-28us,数据 1 的高电平持续时间约 70us。读位的代码可以写成“等待低电平结束,然后统计高电平持续时间,根据时间判断 0/1”。

uint8_t DHT11_ReadByte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) { while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_RESET); delay_us(40); if (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET) { data |= (1 << (7 - i)); while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET); } } return data; }

这段代码的思路是:先等待低电平结束,也就是数据位开始后的低电平部分。接着延时 40us,如果此时引脚还是高电平,说明是数据 1(高电平持续长),否则是数据 0。这个技巧在逻辑分析仪上验证过,只要主频 72MHz 下延时函数足够准,就能稳定读取。

读完整帧后,5 个字节分别是湿度整数、湿度小数、温度整数、温度小数、校验和。校验和等于前四个字节之和的低 8 位,如果校验失败,我直接丢弃这帧数据,不更新 OLED 和网络值。DHT11 手册要求两次读取间隔大于 1 秒,推荐 2 秒一次,我最终改成每 5 秒采集一次,避免温度上升时传感器自身发热影响读数。

3.2 OLED 显示驱动与自定义汉字显示

OLED 部分我直接用 STM32 硬件 I2C1 驱动 SSD1306。初始化流程主要是发送 SSD1306 的配置序列:关闭显示、设置显示时钟分频、设置复用比、设置显示偏移、开启显示等。这里不贴完整初始化数组,网上 HAL 库 SSD1306 的初始化代码非常多,核心是确认 I2C 地址是 0x78(7 位地址 0x3C)。

很多人一开始 OLED 点不亮,最大的坑是 I2C 引脚复用配置不对。开启硬件 I2C 后,需要把 GPIOB 的 AF 配置为 I2C1 的复用功能,并且把时钟使能。在 CubeMX 里直接选 I2C1,然后在 GPIO 设置里选开漏输出、上拉 4.7k,这样最省事。

显示内容上,我除了显示温度湿度数值,还想显示“温度”“湿度”汉字。SSD1306 不带字库,需要自己取模。我的办法是:取模软件设置字符大小为 16x16,点阵格式选列行式,生成数组后定义一个小字库。比如:

const uint8_t tian_[] = {0x04,0x04,0xFC,0x04,0x00,0x00,...};

然后写一个OLED_ShowChinese(x, y, index)函数,按 16 像素宽度水平扫描写入显存,最后统一调用OLED_Refresh()刷新到屏幕。刷新频率不要太高,否则 I2C 会吃掉不少 CPU 时间,DHT11 时序容易受影响。我实测 5Hz 刷新完全流畅,肉眼看不见闪烁。

OLED 驱动有一个细节:写入显存后必须调用刷新函数,否则屏幕是空的。如果在刷新之前去操作传感器,显示会出现撕裂感,最好的做法是定时器中断里做“采集 + 刷新”,主循环只处理网络上报,任务分块。

3.3 STM32 与 ESP8266 的串口通信与 AT 指令控制

STM32 和 ESP8266 之间走的是 USART2,波特率我固定 115200,数据格式 8N1。STM32 作为主机,通过 HAL_UART_Transmit 发送 AT 指令,通过 HAL_UART_Receive_IT 接收 ESP8266 的回应。这里最需要处理的是“何时发指令、何时等回应”。

我的做法是封装一个简单的 AT 命令函数:

uint8_t ESP8266_SendCmd(char *cmd, char *ack, uint16_t timeout) { HAL_UART_Transmit(&huart2, (uint8_t *)cmd, strlen(cmd), 100); return ESP8266_WaitAck(ack, timeout); }

ESP8266_WaitAck会循环接收串口数据,把收到的内容暂存在缓冲区里,然后用strstr查找期望的关键字,比如OKSEND OK。如果超时还没匹配到,就返回失败。这个函数是整个通信稳定性的关键,应答超时控制的粒度尽量短,我一般用 10ms 轮询一次。

模块上电后我不会立刻发连接指令,而是先循环发送 AT 等待模块就绪,同时打印日志。实际测试中 ESP-01S 从上电到 AT 可响应,至少要 500ms-1s,如果上电后立刻发协议指令,大概率会丢第一条。

网络部分我做了两种模式:TCP 透传和 MQTT 模式。TCP 透传适合自建局域网服务端,数据格式简单。MQTT 模式是让 ESP8266 先连上 TCP 到 MQTT Broker,然后按照 MQTT 协议包格式,用 AT 指令的透传模式发送 PUBLISH 报文。如果只是本地监控,TCP 透传就够了;如果想要接入云平台,建议直接使用 ESP8266 上的 MQTT 固件,或者换 ESP32,单芯片搞定传感器+网络,省去 STM32 也能跑,但在这个项目里,我更享受把每个环节拆开调通的过程。

4. 数据上传方案与远程监控扩展

4.1 局域网 TCP 服务端接收数据

最简单的一种方案是 STM32 把 JSON 格式的温湿度数据通过 ESP8266 透传发给局域网内的服务端。我写了 Python 脚本在电脑上监听 8080 端口,手机或另一台电脑就能看到数据。

STM32 端串口发送的数据格式我定为:

{"temp": 25.6, "humi": 60.2, "id": 1}

ESP8266 在透传模式下,收到串口字节流就会原样发到 TCP 服务端。要注意的是透传模式下模块不会自动加回车换行,所以 STM32 发送时要在末尾加上\r\n作为帧结束符,方便服务端按行解析。

服务端 Python 的简化代码:

import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0', 8080)) s.listen(5) while True: conn, addr = s.accept() data = conn.recv(128) if data: print(data.decode().strip())

实测效果:模块稳定运行 7 天,偶尔会丢一两个包,但重连机制做好之后基本不影响使用。

4.2 基于 MQTT 的云平台接入思路

如果人不在同一个局域网,靠 TCP 透传就搞不定了,这时候需要走 MQTT。MQTT 是很适合低带宽设备的协议,一是消息体积小,二是有 QoS 机制,三是连接保持长连接,比 HTTP 轮询省电省流量。

接入公共 MQTT Broker 的思路是:ESP8266 AT 指令建立一个 TCP 连接,然后按照 MQTT CONNECT 报文格式发送连接请求,再发送 PUBLISH 报文。但直接用裸 AT 指令拼 MQTT 包非常痛苦,协议帧、剩余长度编码、报文标识符都要自己算。

如果只是学习,我建议先用 AT 指令连上本地部署的 Mosquitto,然后用 Wireshark 抓包对比协议格式。如果想把功能跑起来,更高效的办法是换一个刷了 MQTT 固件的 ESP8266,或者直接用 ESP32。以 STM32 + ESP8266 这套架构来说,我自己最终是把 ESP8266 刷成了 NodeMCU 固件,然后 STM32 通过串口向 ESP8266 发简单的 TCP 命令,后续再做协议封装就容易多了。

4.3 数据解析与状态判断逻辑

数据往上送之前,最好先在 MCU 里做一次合理性判断。DHT11 偶尔会因为上电瞬间或干扰读到一个特别离谱的数,比如湿度 248%,温度 -15°C。我加了一个简单的滤波:

if (temp > 50 || temp < -10 || humi > 99 || humi < 0) { // 丢弃该帧,不更新显示和上报 return; }

另外我做了简单的滑动平均,取最近 5 次有效读数的平均值再上报。这样显示界面上的数字不会忽上忽下地跳,人看起来舒服一点。

状态判断方面,我在主机端定义了一个枚举:DATA_OKSENSOR_ERRWIFI_DISCONN。OLED 第三行会实时显示当前状态。如果 WiFi 断开,屏幕会有一个小圆点闪烁提示,而传感器错误时温度湿度区域直接显示--.-。这个细节在调试时非常有用,因为系统跑在无人环境下,没有状态提示,隔天回来看数据全是 0,根本不知道是传感器挂了还是 WiFi 断了。

5. 常见问题与排查技巧实录

5.1 DHT11 读不到数据或读到固定值

这是新手最容易卡住的地方。现象有三个:数据一直为 0、数据偶尔变一次然后不变、OLED 上温度湿度永远是 0.0。

排查顺序我固定这样走:先看模块供电是不是 3.3-5V 稳定,DHT11 对电压很敏感;然后量 DATA 引脚上拉电压,正常空闲状态是 3.3V 高电平;接着用示波器或者老式逻辑分析仪看起始信号,确认主机有没有发出 18ms 低电平;最后检查两次读取时间间隔,如果循环里连续快速读,DHT11 会不响应。

我踩过一次很隐蔽的坑:初始化 DHT11 前忘记把 PB8 设成开漏输出。DHT11 数据线需要开漏输出,这样外部上拉才能把电平拉高,而我一开始用推挽输出,结果时序一直不对,读出来的数据七零八落。改成开漏之后一次通过。

5.2 OLED 不显示或花屏

OLED 不显示首先查 I2C 地址,SSD1306 通常为 0x3C,写操作地址 0x78。如果扫描不到设备,检查 SCL/SDA 是否接反,然后看有没有把 GPIO 配成开漏输出并加速上拉。

花屏多数情况是刷新时序问题。我遇到过在 OLED 刷新过程中 DHT11 的延时被打断,导致屏幕显示残缺。解决办法是刷新操作放主循环,DHT11 读取时屏蔽掉相同优先级的调度,或者用 DMA 传输 I2C 数据。SSD1306 还有一个常见要求:初始化序列不能省略,很多人直接从网上拷一段不完整的初始化代码,导致显示不正常。

5.3 ESP8266 连接不上 WiFi 或丢包

这里我建议按下面顺序自查:

现象可能原因解决办法
AT 有响应但连不上 AP路由器频段是 5G,不支持切成 2.4G 频段
上电后 AT 卡死供电不足独立 3.3V 电源供电
连上 AP 但 TCP 连接失败服务端 IP/端口错误同一局域网用 CIFSR 查一下 IP
透传模式收不到数据没有先发 AT+CIPSEND重新设置透传并确认收到OK
串口打印乱码波特率不匹配确认两边都是 115200

丢包方面,我建议不要直接用 72MHz 主频下普通延时函数来发送大缓冲区数据。USART 发送是阻塞的,如果一次发 512 字节,MCU 在发送期间无法处理中断,ESP8266 内部缓冲区如果满了就会丢。做法是拆分数据包,每包不超过 128 字节,或者在发送之间加 10ms 间隔。

5.4 STM32 频繁复位或程序跑飞

STM32F103C8T6 如果电源质量差,很容易在上电和 WiFi 通信瞬间复位。排查方法:首先是独立供电,然后检查复位引脚是否外部干扰,最后看看门狗配置。如果开了 IWDG,但主循环处理网络超时超过喂狗时间,就会不断复位。我做网络重连时会把喂狗放到独立定时器里,保证即使串口等待应答,也不会触发看门狗复位。

还有一点是中断优先级。DHT11 的延时循环如果被高优先级串口中断打断,时序会崩。我的做法是 DHT11 读取函数运行期间关闭所有中断,读取完成后恢复。虽然这会短暂打断其他事务,但也就 1ms 左右,影响很小。

6. 经验总结与可扩展的方向

这套系统从原型到稳定运行,我大概花了两天时间。如果只看最终的代码量,其实不大,核心驱动加逻辑不到 500 行,但调试过程接触到的知识点密度很高。回过头来想,几个点对后续做复杂项目特别有参考价值:硬件上电源分区处理,软件上任务调度和状态机设计,协议上 AT 指令异步应答处理,这些都是物联网节点开发的基本功。

如果还想继续扩展,有几个方向我亲测可以玩:把 DHT11 换成 SHT30 走 I2C,精度提升明显;增加一个继电器模块,根据温度和湿度自动控制加湿器或排风扇;OLED 显示界面可以增加趋势曲线,最近 24 小时的小时柱状图;云端存储用 MQTT 接入开源面板,手机上直接看折线图。另外,如果对低功耗有需求,可以让 STM32 进入 STOP 模式,ESP8266 用 AT+GSLP 睡眠,定时唤醒采集一次数据然后继续睡,这样用 18650 电池也能撑很久。

最后分享一个我在实际测试中总结的小技巧:不要一开始就追求 PCB 一体化,先把模块用杜邦线搭起来,验证每个环节的信号。很多所谓“玄学问题”,最后查出来都是杜邦线接触不良或者供电引脚氧化。等整套逻辑稳定了,再画一块小板把 DHT11、OLED 和 ESP8266 焊上去,你会有种豁然开朗的感觉。做嵌入式项目,稳定压倒一切,功能再花哨,跑一会儿就挂也没意义。这套 WiFi 温湿度监控方案,算是把稳定性和学习性平衡得特别好的一套组合了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询