基于STM32的环境温湿度监测系统:从DHT11时序到调试实战

发布时间:2026/9/8 10:47:34
基于STM32的环境温湿度监测系统:从DHT11时序到调试实战 简介基于STM32的环境温湿度监测系统设计完整工程包面向嵌入式开发初学者、STM32课程设计及电子竞赛备赛者解决DS18B20温度与DHT11湿度采集、LCD实时显示以及越限声光报警的整套联调问题。压缩包内含195个文件主体为46个C源码与46个头文件另含Keil工程配置uvprojx、编译生成的hex/axf、链接映射map等辅助类型整个包仅4.34MB结构紧凑便于下载后直接打开工程研读。系统支持手动设置温湿度报警上下限越限时启动无源蜂鸣器播放音乐并让LED闪烁报警方式可依据不同需求灵活修改适合作为课程设计或竞赛方案的功能基底。目前已有8714人学习对于希望快速搭建环境监测原型、理解传感器驱动和中断/定时器应用的读者是一份高性价比的参考资料。 做单片机这些年我见过太多人把“基于STM32的环境温湿度监测系统”当成一个普普通通的毕业设计题目觉得不就是读个传感器、显示个数据嘛。但实际上这个项目涵盖了嵌入式开发里最核心的几个环节GPIO模拟时序协议、定时器与延时管理、串口通信、低功耗考量甚至还有PCB布线对模拟信号的影响。把它吃透了你再去碰IIC、SPI、甚至CAN总线都会顺很多。这篇博文我就拿这套系统当例子把从选型、画板、写驱动到调不通的全过程掰开揉碎讲一遍。1. 项目整体思路与设计拆解1.1 这套温湿度监测系统到底做了什么先说人话这套系统的任务就三个——采集环境温湿度、本地显示、把数据传给上位机。硬件上就是STM32最小系统板或者自己画的核心板加上一个温湿度传感器、一个OLED屏幕、一个USB转串口模块再加几个按键和LED指示灯。很多初学者容易把这类项目做成“传感器读一下屏上显示一下”就交差了。但实际工程里你需要考虑的是传感器多长时间采一次数据、采集失败怎么重试、显示刷新会不会卡住主循环、串口数据帧怎么定义才能让上位机稳定解析。这些细节才是这套系统真正值钱的地方。我在设计时把系统分成了四层感知层温湿度传感器负责把物理量变成数字信号控制层STM32主控负责时序读写、数据解析、逻辑判断交互层OLED显示 按键 LED状态指示传输层串口USART负责把数据帧送给PC端上位机这种分层思维看着简单但非常关键。比如以后你想把有线串口换成无线模块比如常见的ESP8266透传你只需要改传输层感知层和控制层完全不用动。1.2 传感器选型DHT11、SHT30还是AHT20这套题目最核心的硬件就是温湿度传感器。市面最常见的是DHT11、DHT22AM2302、SHT30、AHT20这几款我直接列个对比表方便你做选型型号接口精度温度/湿度价格难度典型场景DHT11单总线±2℃ / ±5%RH极低低入门学习、毕设DHT22单总线±0.5℃ / ±2%RH中等低精度要求稍高的场合SHT30I2C±0.3℃ / ±2%RH中等中仪表、小型气象站AHT20I2C±0.3℃ / ±2%RH较低中性价比方案你要是纯粹为了跑通功能DHT11足够但你要是想拿这套系统去参加比赛或者放到实际环境里测数据我更推荐SHT30。原因很简单DHT11的单总线时序对延时精度要求高而且传感器本身一致性差同一批货读出来的数据能差出2到3度。SHT30走I2CSTM32硬件I2C或者模拟I2C都行稳定得多。我这次用的还是DHT11就是为了把单总线时序这个难点讲透。毕竟很多学校的课程设计题目指定的就是DHT11你先把这个搞明白后面换传感器就是换个驱动的事。1.3 系统架构与数据流向整体数据流向可以这样理解传感器采集 → STM32通过GPIO读取原始数据 → 解析出温度和湿度数值 → 一方面送OLED显示另一方面组帧通过串口发给PC。主循环的逻辑我一般写成状态机而不是一坨顺序执行初始化时钟、GPIO、定时器、串口、I2C如果用SHT30每2秒触发一次传感器采集用定时器标志位而不是Delay阻塞采集成功后更新全局结构体变量OLED显示任务检测到数据更新标志刷新屏幕串口任务把数据打包成帧发送这个结构下即使某个环节卡住也不会导致整个系统死掉。比如DHT11偶尔无响应你在读时序里加了超时退出主循环照样跑OLED上显示上一次的正常数据同时LED闪烁提示异常。这种容错设计是课程设计和真实项目最大的分水岭。2. 硬件设计与核心电路要点2.1 STM32最小系统与自制板注意事项如果你用现成的开发板这一步可以跳过。但很多朋友想自己画板子我强烈建议你第一次打板还是把最小系统做出来核心就这么几个东西STM32F103C8T6主控8MHz晶振 两个20pF负载电容复位电路10k电阻上拉 100nF电容到地3.3V LDO稳压芯片AMS1117-3.3就行BOOT0/BOOT1配置电阻SWD下载接口4针SWDIO、SWCLK、GND、3.3V晶振的负载电容怎么选很多人直接抄资料用20pF其实这个值跟晶振本身的负载电容有关。常见8MHz晶振负载电容是12pF到20pF电容计算公式是CL (C1 × C2) / (C1 C2) 寄生电容。如果取C1C220pF加上大约5pF的引脚寄生电容实际CL约15pF配合负载电容12pF的晶振频偏在可接受范围。要求高的话直接用有源晶振省心太多。我踩过最大的坑是SWD接口的RESET引脚没拉出来。ST-Link在某些固件版本下需要硬件复位才能连上芯片你没拉这个引脚就只能靠断电重新上电去碰运气。所以自己画板子4针SWD我一定会加一个RESET变成5针。2.2 DHT11上拉电阻与布线细节DHT11的数据引脚是开漏输出必须在外部接一个上拉电阻典型值4.7k到10k。很多模块板上已经集成了上拉电阻和滤波电容你直接用模块、三根杜邦线连到STM32也能跑。但如果你自己做板子这个4.7k电阻千万别省否则数据线拉不高读出来的数据全是0。还有一个细节DHT11的数据线要尽量短远离电源线和电机驱动线。我实测过数据线超过20cm之后在湿度较高的环境里读到的湿度值会周期性跳变。如果你用的是开发板加杜邦线没办法缩短距离可以在数据线上并联一个100pF左右的电容到地能明显抑制高频干扰。2.3 显示与通信模块的接法OLED我推荐用0.96寸I2C接口的因为只需要两根线SCL、SDA而且STM32硬件I2C在F1系列上虽然有些坑模拟I2C非常稳。接线如下OLED SCL → PB6或者任意推挽输出GPIOOLED SDA → PB7OLED VCC → 3.3VOLED GND → GND串口部分STM32的PA9USART1_TX和PA10USART1_RX是默认的调试串口。接USB转TTL模块的时候注意交叉连接STM32的TX接模块的RXSTM32的RX接模块的TX。很多新手第一次调串口没输出十有八九是TX和RX直连了。另外如果USB转TTL模块上有3.3V和5V的跳线帽记得选3.3V别拿5V去怼STM32的引脚虽然大部分时候不烧但长期用会损坏引脚。3. 软件实现从CubeMX到驱动编写3.1 工程搭建与时钟配置开发环境我用的Keil MDK5加STM32CubeMX生成初始化代码。现在STM32CubeMX已经更新到6.x版本固件包直接在软件里下载就行不需要单独去官网翻。芯片型号选STM32F103C8T6这个型号在软件里的引脚图选LQFP48封装。配置要点RCCHSE选择Crystal/Ceramic Resonator外部8MHz晶振Clock Configuration系统时钟SYSCLK设置为72MHzAPB1分频 /236MHzAPB2不分频72MHzSYSDebug选择Serial Wire这是SWD调试必需USART1异步模式波特率1152008位数据无校验1位停止位定时器TIM2设置1ms中断用于系统时基和传感器采集调度GPIODHT11数据引脚配置为开漏输出上拉OLED和按键根据你的接线配置GPIO模式这里有个关键点DHT11的数据脚初始化阶段要配成开漏输出因为开漏模式可以直接在输入和输出之间切换不需要重新配置CRL/CRH寄存器。很多教程里用推挽输出读数据前再切换成输入模式也能跑但时序上容易因为库函数调用开销导致微秒级误差。用开漏输出加外部上拉是最稳的做法。3.2 DHT11单总线时序怎么用代码实现DHT11的单总线时序是这套系统里编码难度最高的地方也是面试官最爱问的点。先理解协议再说代码。通信流程是这样的主机拉低数据线至少18ms然后释放这是起始信号主机释放后延时20到40us检测DHT11的响应DHT11会拉低80us再拉高80us表示响应之后DHT11发送40位数据8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和每一位数据以50us低电平开始随后是高电平。高电平持续26到28us表示“0”高电平持续70us表示“1”核心代码就一句读取引脚电平同时用定时器或者空循环测量高电平持续时间。我提供一段我自己写的参考代码uint8_t DHT11_ReadByte(void) { uint8_t i, data 0; for (i 0; i 8; i) { // 等待低电平结束50us的低电平 while (DHT11_DATA_PIN 0); // 延时30us后读取电平 delay_us(30); if (DHT11_DATA_PIN 1) { data | (0x80 i); } // 等待高电平结束 while (DHT11_DATA_PIN 1); } return data; }这段代码的逻辑是每一位数据开始必然是50us低电平然后是高电平。在低电平结束之后延时30us去采样如果引脚还是高电平说明这一个是“1”否则是“0”。因为“0”的高电平只有26到28us30us之后已经变低了“1”的高电平有70us30us时仍然是高。这就是典型的时间窗口采样法。延时函数我用的是SysTick的微秒级延时而不是简单空循环。原因很简单空循环延时的实际时长跟编译器优化级别、系统主频都有关系在O0优化下调好的参数换到O2就废了。SysTick延时的代码你可以自己封装也可以用HAL库的HAL_Delay改造一个微秒版。3.3 采样策略与数据校验DHT11的数据读取频率不要太快两次读取间隔建议2秒以上。手册标称的最快响应时间是1秒你采集太快传感器还没准备好读出来就是上一次的缓存数据看起来就是“温度不变、湿度不变”。另外40位数据里的校验和一定别忽略。校验和 湿度整数 湿度小数 温度整数 温度小数取低8位。如果算出来对不上这帧数据直接丢弃。这个校验逻辑看起来简单但对于排查那种“偶尔跳一个离谱数值”的问题价值巨大。我的读取状态机大致是这样主机发送起始信号等待DHT11响应超时时间设200us超时则返回错误读取40位数据任何一位的超时都直接返回失败校验和验证失败返回错误成功则更新全局的温湿度结构体注意状态机设计要比顺序执行好维护得多。比如DHT11读失败了你不能让系统死等而是设置一个错误计数器超过3次就在OLED上显示“Sensor Error”同时继续跑主循环。3.4 OLED显示与串口上报逻辑OLED驱动我用的是0.96寸SSD1306驱动文件网上很多这里讲一下显示逻辑的架构。数据更新之后显示任务只刷新变化的区域不需要全屏重绘。全屏刷新一次大概要30ms在72MHz主频下虽然不算长但如果你一边读DHT11一边全屏刷新容易干扰DHT11的时序。我习惯把显示刷新频率控制在5Hz以内也就是200ms刷一次传感器数据2秒更新一次这样人眼看起来流畅系统也稳定。串口上报部分我自定义了一个简单的帧格式帧头数据长度温度高字节温度低字节湿度高字节湿度低字节校验和0xAA0x060x010x2B0x010x5A0xXX帧头用来做帧同步数据长度方便接收方解析校验和是所有字节的异或或者累加。用这个格式上位机哪怕收到半截数据也能在下一帧同步回来。你要是只发一个字符串比如T:25.5 H:60.2\r\n用串口助手看没问题但真正写上位机解析的时候会非常痛苦。4. 调试实录我踩过的那些坑4.1 下载报错 no stm32 target found 怎么解决这个报错应该是STM32开发里出现频率最高的错误了没有之一。我用ST-Link调试时遇到这个提示排查顺序一般是供电检查目标板有没有独立供电或者ST-Link的3.3V输出有没有接上我见过不少人只接了SWDIO、SWCLK和GND忘了接3.3V结果芯片根本没上电线路接触杜邦线接触不良或者序接反了SWDIO和SWCLK对调也会报这个错复位引脚按一下板子上的复位键让芯片从正常启动模式进入然后再尝试连接芯片锁死如果芯片里跑的程序把SWD引脚复用成普通GPIO了会导致连不上。解决办法是把BOOT0拉高上电进入ISP模式系统存储器启动这时候SWD引脚恢复默认功能连接成功后再把BOOT0拉回低电平复位我自己的经验是90%的“no stm32 target found”是因为共地问题。ST-Link和目标板必须共地否则SWD信号根本没有参考电平自然无法通信。有的ST-Link里SWD排座旁边有GND直接和目标板的GND连一根线问题立刻消失。4.2 DHT11读回数据全是0或者超时这个问题的原因非常典型排查项如下上拉电阻没接数据线开漏输出但没有外部上拉读完起始时序之后数据线一直是低电平读回来的每一位全是0。这个最常见GPIO模式配置错如果你用推挽输出读数据前要切换到输入模式。没切或者切换延时太长也会导致时序超时延时函数不准确空循环延时在优化等级不同时实际时间差出好几倍。DHT11的时序窗口非常窄起始信号必须拉低18ms以上如果实际只有5ms传感器根本不会响应供电电压不足DHT11的供电范围是3.3V到5V但3.3V下它的时序边沿会变缓如果数据线太长信号质量会进一步恶化。建议能5V供电就5V供电但是注意电平转换问题DHT11的5V供电下数据引脚输出的高电平也是5V而STM32引脚容忍5V输入但对于F1系列来说还是建议串一个1k电阻或者用3.3V供电4.3 数据跳动、OLED花屏与延时不准数据跳动的排查日志我列一个表给后来人参考现象可能原因解决方法湿度周期性跳变数据线受电源纹波干扰数据线并100pF电容到地温度偏高且稳定STM32发热传导到传感器传感器远离主控用排线引出OLED花屏或显示异常I2C时序不稳定或电源毛刺OLED供电加10uF电容降低I2C速率读数偶发离谱值校验和没做加上校验和验证错误帧直接丢弃长时间运行后卡死DHT11读时序死循环所有while等待加超时退出OLED花屏还有一个容易被忽略的原因I2C速率太快。软件模拟I2C的时候延时太短SSD1306跟不上就会出现显示错乱、花屏。把I2C时钟降到100kHz到200kHz之间问题基本解决。4.4 延时函数卡死的根因与对策STM32做延时最常用的就是SysTick。但初学者经常遇到一个问题用了HAL_Delay()之后程序卡死。原因大多数时候不是延时函数本身而是中断优先级配置不当。SysTick的中断优先级如果被设置的比某个频繁触发的中断还低那么在那个中断的处理函数里调用HAL_Delay()SysTick中断一直抢不到CPU就会一直死等。我以前在一个红外遥控解码的项目里就吃过这个亏定时器输入捕获中断太频繁我在中断里调用了延时直接导致系统卡死。所以我的原则是中断处理函数里绝对不调用延时函数一切延时放到主循环状态机里去处理。如果你真的要在中断里做时序操作用DWTData Watchpoint and Trace计数器做微秒级延时不依赖SysTick不会互相干扰。5. 这套系统还能怎么延伸把基础功能跑通之后这个项目的可玩性其实很高。我说几个我试过或者见过别人扩展的方向加一个ESP8266模块做WiFi上报。STM32通过串口把温湿度数据发给ESP8266ESP8266通过MQTT协议上报到云平台。硬件改动极小只需要多接一个串口。软件上需要做的是把上一版的上位机帧协议换成MQTT的topic消息。换成SHT30提升精度。SHT30走I2C精度高一个量级代码上把驱动文件换掉就行上层逻辑完全不用动。这也是我为什么强调分层设计——你换传感器只动驱动那一个文件。加上EEPROM或FLASH存储历史数据。STM32F103C8T6虽然有64KB Flash但随便写写就满了外挂一个AT24C02或者W25Q64可以做历史数据的断电保存再配合按键翻页查看就是一个小型气象记录仪。做一个简单的上位机。用Python的PyQt或者C#的WinForms都能做串口解析帧协议实时画温度曲线。这个扩展对于找工作来说是很好的加分项因为软件和硬件你都接触到了。我个人在实际做这个项目的时候最大的体会不是“DHT11真难调”而是“一个看起来简单的系统把它做得稳、做得可靠需要的细节远比想象中多”。如果你也正在做这个题目我的建议是不要急着改功能先把时序、校验、容错这三件事做好再用这套框架去加花活你会发现后面每一步都走得特别顺。本文还有配套的精品资源点击获取