
1. 为什么STM32F1到今天还值得花时间学1.1 一颗老芯片的生存逻辑如果你最近在电子论坛或者开源硬件社区里逛会发现一个挺有意思的现象一边是各种高性能新片子层出不穷一边是大量的人在讨论STM32F1系列尤其是配上DHT11温湿度传感器做环境监测的项目。这看起来有点矛盾但仔细想想又特别合理。STM32F1是意法半导体基于ARM Cortex-M3内核推出的微控制器系列最经典的型号比如STM32F103C8T6也就是大家常说的“蓝板”或者“最小系统板”。它的主频通常跑在72MHz拥有64KB到128KB的Flash、20KB的SRAM外设资源包括多个定时器、USART、SPI、I2C、ADC等等。这些参数放在今天看确实不算亮眼但它的价格、生态和资料丰富程度让它在入门级嵌入式开发里依然占据着不可替代的位置。我个人的判断是STM32F1系列是学习嵌入式开发性价比最高的平台之一。原因很简单——它的参考手册、标准外设库、HAL库、各种例程和社区问答已经积累到了一个恐怖的程度。你遇到的几乎任何一个基础问题都能在中文或英文社区里找到现成的答案。对于新手来说这意味着学习曲线会平缓很多对于有经验的工程师来说这意味着做快速原型验证时不用在底层驱动上浪费时间。1.2 DHT11为什么总跟STM32F1绑在一起DHT11是一款非常基础的温湿度复合传感器它内部集成了一个电阻式湿度测量元件和一个NTC测温元件输出的是经过校准的数字信号。它的测量范围是湿度20%到90%RH、温度0到50摄氏度精度分别是±5%RH和±2摄氏度。这个精度放在工业场景里肯定不够看但对于室内环境监测、农业大棚简易监控、学生实验这类应用来说完全够用。DHT11和STM32F1之所以经常被放在一起核心原因是两者的定位高度匹配。DHT11是单总线通信只需要一根数据线就能完成温湿度读取对MCU的引脚资源占用极少。而STM32F1的GPIO操作灵活配合微秒级延时可以轻松实现DHT11的时序要求。更重要的是这个组合的成本极低——一块STM32F103最小系统板加上一个DHT11模块总花费可能不到二十块钱但你能学到的知识点覆盖了GPIO控制、精确延时、单总线协议解析、串口输出、LCD显示等多个方面。1.3 这篇文章能帮你解决什么问题接下来我会从硬件选型、开发环境搭建、DHT11时序解析、代码实现、常见问题排查这几个维度把STM32F1驱动DHT11这件事彻底讲透。不管你是刚接触单片机的学生还是想快速验证一个环境监测方案的工程师都能从里面找到可以直接复用的内容。我不会只贴一段代码就完事而是会把每个关键决策背后的原因讲清楚比如为什么用SysTick做微秒延时而不是随便写个for循环为什么DHT11的起始信号要拉低至少18毫秒这些细节才是决定项目能不能跑通的关键。2. 硬件选型与开发环境搭建2.1 核心器件选型对比在动手之前先把硬件方案确定下来。STM32F1系列型号很多DHT11也有不同的封装形式选对了能省不少事。器件类型推荐型号关键参数选型理由主控芯片STM32F103C8T672MHz, 64KB Flash, 20KB SRAM资料最多价格最低最小系统板遍地都是温湿度传感器DHT11湿度20-90%RH, 温度0-50°C单总线协议简单成本极低适合入门调试器ST-Link V2SWD接口支持在线调试和下载比串口下载方便太多显示模块OLED 0.96寸 I2C128x64分辨率比LCD1602接线少显示内容丰富电源3.3V或5VDHT11支持3.3V-5.5V和STM32F1的3.3V电平兼容这里重点说一下调试器的选择。很多人刚开始为了省钱用USB转TTL模块通过串口下载程序但这种方式每次下载都要手动切换BOOT引脚而且没法在线调试。ST-Link V2的价格现在也就十几块钱支持SWD接口可以直接在IDE里打断点、看变量、单步执行调试效率完全不是一个级别。我强烈建议在这个环节不要省。DHT11模块通常有三种形式裸传感器、带PCB板载上拉电阻的模块、带LED指示灯的模块。建议直接买带PCB的模块因为它已经集成了4.7kΩ到10kΩ的上拉电阻省去了自己焊接的麻烦。裸传感器也不是不能用但需要在数据线和VCC之间外接一个上拉电阻否则信号可能不稳定。2.2 开发环境搭建的两种路线STM32F1的开发环境主要有两条路线Keil MDK和STM32CubeIDE。两者各有优劣选择哪个取决于你的具体需求。Keil MDK是国内用户最多的嵌入式开发环境它的优势是编译速度快、调试功能成熟、中文资料丰富。缺点是需要手动安装器件支持包而且标准外设库的配置比较繁琐。如果你打算用标准外设库来开发Keil是首选。STM32CubeIDE是ST官方推出的免费IDE基于Eclipse和GCC工具链。它的最大优势是集成了STM32CubeMX配置工具可以通过图形化界面配置引脚、时钟、外设然后自动生成初始化代码。对于新手来说这种方式能大幅降低入门门槛。缺点是Eclipse的界面响应速度不如Keil流畅编译大型工程时速度偏慢。我个人的建议是如果你之前没有嵌入式开发经验直接从STM32CubeIDE入手用HAL库开发。虽然HAL库的效率比标准库略低但对于DHT11这种低速传感器来说完全没影响。如果你已经熟悉了标准库继续用Keil加标准库也没问题网上关于STM32F1标准库的例程更多。2.3 工程创建的关键配置项不管用哪种环境创建工程时有几个配置项必须注意。时钟配置方面STM32F103C8T6的外部晶振通常是8MHz经过PLL倍频到72MHz作为系统时钟。在CubeMX里配置时HSE选择Crystal/Ceramic ResonatorPLL Source选择HSE然后PLL MUL设置为9倍频这样8MHz乘以9就是72MHz。APB1总线最高36MHzAPB2总线最高72MHz这些在时钟树里都能直观看到。调试接口配置方面一定要在SYS选项卡里把Debug设置为Serial Wire否则下载一次程序后SWD引脚可能被禁用导致下次无法连接。这个坑我踩过不止一次每次都要用BOOT0拉高的方式擦除芯片才能恢复。GPIO配置方面DHT11的数据引脚需要配置为开漏输出或者推挽输出同时在读取时需要切换为输入模式。在CubeMX里可以先配置为GPIO_Output然后在代码里手动切换模式。也可以配置为GPIO_Input在需要输出时临时改为输出模式。两种方式都可以我习惯用后者因为DHT11的大部分时间都处于空闲状态数据线由上拉电阻拉高。串口配置方面建议至少启用一个USART用于打印调试信息。波特率设置为1152008位数据位无校验1位停止位。这样在调试DHT11时可以通过串口助手实时看到读取到的温湿度值比用OLED显示更直观。3. DHT11单总线协议深度解析3.1 单总线通信的物理层原理DHT11使用的单总线协议顾名思义就是只用一根数据线完成双向通信。这根线同时承担了发送命令和接收数据的任务所以通信过程必须严格分时复用。总线空闲时数据线由上拉电阻拉到高电平。主机和从机都通过拉低总线来发送信号通过释放总线来让上拉电阻把电平拉高。这种通信方式对时序的要求非常严格因为双方没有独立的时钟线来同步全靠约定的时间长度来区分不同的信号。DHT11的通信过程可以分为四个阶段主机发送起始信号、从机响应信号、从机发送40位数据、总线恢复空闲。每个阶段的时间要求都不一样下面逐一拆解。3.2 起始信号与响应信号的时序要求主机发送起始信号的流程是先把数据线拉低至少18毫秒然后释放总线拉高20到40微秒最后切换为输入模式等待从机响应。为什么是18毫秒这是DHT11数据手册里明确规定的。如果拉低时间不够DHT11可能检测不到起始信号如果拉低时间过长也不会损坏传感器但会浪费时间。我一般用20毫秒留一点余量。从机检测到起始信号后会等待主机释放总线然后拉低总线80微秒作为响应信号再拉高80微秒准备发送数据。这个80微秒的低电平加80微秒的高电平就是DHT11的“存在脉冲”告诉主机它已经准备好了。这里有个容易出错的地方主机在释放总线后必须尽快切换为输入模式。如果还保持在输出模式STM32的GPIO会继续驱动总线和DHT11的输出产生冲突。严重的话可能损坏引脚轻则导致通信失败。我在早期调试时因为忘了切换模式折腾了大半天才找到原因。3.3 40位数据的编码规则DHT11发送的40位数据分为五个字节第一个字节是湿度的整数部分第二个字节是湿度的小数部分DHT11固定为0第三个字节是温度的整数部分第四个字节是温度的小数部分DHT11固定为0第五个字节是校验和。校验和的计算规则是前四个字节相加取低8位。比如湿度整数35、湿度小数0、温度整数26、温度小数0那么校验和就是35026061。如果读取到的校验和不是61说明数据传输过程中出现了错误需要重新读取。每个数据位的表示方式是先拉低总线50微秒然后拉高总线。如果高电平持续26到28微秒表示数据“0”如果高电平持续70微秒表示数据“1”。所以区分0和1的关键就是测量高电平的持续时间。这个编码方式有个特点每个数据位都以50微秒的低电平开始所以位与位之间的间隔是固定的。接收方可以在检测到低电平后延时40微秒左右再采样总线电平如果此时是高电平就是1低电平就是0。但更可靠的方式是测量高电平的持续时间因为延时采样对时序的容错性较差。3.4 微秒级延时的实现方案DHT11的时序要求精确到微秒级所以必须有一个可靠的微秒延时函数。常见的实现方式有三种。第一种是用SysTick定时器。SysTick是Cortex-M3内核自带的一个24位递减计数器可以配置为1微秒中断一次。在中断里维护一个全局变量延时函数通过比较计数值来实现。这种方式精度高不占用额外定时器资源但需要处理好中断优先级。第二种是用通用定时器比如TIM2到TIM5。配置预分频器让计数器每微秒加一然后通过读取计数值来实现延时。这种方式也很精确但会占用一个定时器。第三种是用空循环。通过实测调整循环次数来近似微秒延时。这种方式最简单但精度受编译器优化等级和主频影响很大换个芯片或者改个优化选项就可能不准。我推荐用SysTick方案因为它是内核自带的不占用外设资源而且精度足够。具体实现时把SysTick配置为1微秒中断一次在中断服务函数里对一个全局变量做递减操作。延时函数先设置这个变量为目标值然后循环等待它减到零。注意这个变量要声明为volatile防止编译器优化掉。4. 完整代码实现与逐段解析4.1 GPIO模式切换的封装DHT11的数据引脚需要在输出和输入模式之间频繁切换所以先封装两个函数来简化操作。// 设置DHT11数据引脚为输出模式 void DHT11_SetPinOutput(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); } // 设置DHT11数据引脚为输入模式 void DHT11_SetPinInput(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); }这里用推挽输出模式来驱动总线因为需要主动拉低和拉高。输入模式配置了上拉电阻这样总线空闲时会被拉到高电平。虽然DHT11模块上已经有上拉电阻了但STM32内部的上拉可以起到辅助作用增强信号稳定性。4.2 起始信号的发送与响应检测// 发送起始信号并等待DHT11响应 // 返回值0-成功1-超时失败 uint8_t DHT11_Start(void) { uint8_t retry 0; // 配置为输出模式 DHT11_SetPinOutput(); // 拉低总线至少18ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); // 释放总线拉高20-40us HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); delay_us(30); // 切换为输入模式等待DHT11响应 DHT11_SetPinInput(); // 等待DHT11拉低总线响应信号 while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { retry; delay_us(1); if (retry 100) return 1; // 超时 } // 等待DHT11释放总线 retry 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET) { retry; delay_us(1); if (retry 100) return 1; // 超时 } // 等待DHT11再次拉低准备发送数据 retry 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { retry; delay_us(1); if (retry 100) return 1; // 超时 } return 0; }这段代码里有三个等待循环分别对应DHT11响应信号的三个阶段拉低80微秒、拉高80微秒、再拉低准备发送数据。每个循环都设置了超时退出机制防止DHT11没有响应时程序卡死。超时阈值设为100微秒因为DHT11的响应信号最长也就80微秒左右超过100微秒还没检测到变化就说明通信异常。4.3 40位数据的读取与校验// 读取一个字节8位数据 uint8_t DHT11_ReadByte(void) { uint8_t i, byte 0; for (i 0; i 8; i) { // 等待50us低电平结束 while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); // 延时40us后采样 delay_us(40); // 读取数据位 byte 1; if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { byte | 1; } // 等待高电平结束 while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); } return byte; } // 读取温湿度数据 // 返回值0-成功1-失败 uint8_t DHT11_ReadData(uint8_t *temp, uint8_t *humi) { uint8_t buf[5]; uint8_t i; if (DHT11_Start() ! 0) return 1; for (i 0; i 5; i) { buf[i] DHT11_ReadByte(); } // 校验 if (buf[0] buf[1] buf[2] buf[3] ! buf[4]) { return 1; } *humi buf[0]; *temp buf[2]; return 0; }读取字节的逻辑是每个数据位都以低电平开始所以先等待低电平结束。然后延时40微秒此时如果数据位是“1”高电平还在持续70微秒采样到高电平如果数据位是“0”高电平已经结束26微秒采样到低电平。采样完成后再等待高电平结束准备接收下一位。这个40微秒的延时是关键。DHT11的“0”高电平持续26到28微秒“1”高电平持续70微秒。40微秒正好在两者之间所以能可靠区分。如果延时太短比如20微秒那么“0”和“1”都还在高电平无法区分如果延时太长比如60微秒“1”可能已经结束导致误判为“0”。4.4 主循环与串口输出int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); uint8_t temp, humi; char msg[64]; while (1) { if (DHT11_ReadData(temp, humi) 0) { sprintf(msg, Temp: %d C, Humi: %d %%\r\n, temp, humi); HAL_UART_Transmit(huart1, (uint8_t *)msg, strlen(msg), 100); } else { HAL_UART_Transmit(huart1, (uint8_t *)Read Error\r\n, 12, 100); } HAL_Delay(2000); // DHT11采样间隔不能少于1秒 } }主循环里每2秒读取一次DHT11因为DHT11的采样周期不能少于1秒否则内部可能还没完成上一次测量导致数据不准确。串口输出格式用了sprintf来拼接字符串注意缓冲区要足够大64字节对于这个格式来说绰绰有余。5. 常见问题排查与实战避坑指南5.1 读取失败问题速查表现象可能原因排查方法解决方案始终返回超时接线错误检查VCC、GND、DATA是否接对重新接线确认DHT11引脚定义偶尔读取成功延时不准用示波器测量延时函数实际时间调整SysTick配置或改用定时器数据校验失败时序偏差检查40us采样点是否准确微调延时值尝试35-45us范围温度湿度值异常供电不足测量DHT11的VCC电压换用独立3.3V或5V供电上电后一直失败上拉电阻缺失检查数据线是否有上拉外接4.7kΩ-10kΩ上拉电阻5.2 那些文档里不会写的实操经验第一个坑是DHT11的上电等待时间。DHT11数据手册里写的是上电后需要等待1秒才能读取但实际测试下来有些批次的模块需要等待2秒甚至更久。如果你上电后立即读取很可能会失败。我的做法是在初始化完成后加一个2秒的延时然后再进入主循环。第二个坑是中断对时序的干扰。DHT11的通信过程对时序要求很严格如果在这期间发生了中断而且中断服务函数执行时间较长就可能导致时序错乱。我在调试时遇到过因为串口接收中断导致DHT11读取失败的情况。解决办法是在DHT11通信期间关闭全局中断通信完成后再打开。但要注意关闭中断的时间不能太长否则会影响其他外设的响应。第三个坑是杜邦线的质量。DHT11和STM32之间的连接线如果太长或者质量太差信号边沿会变缓导致时序判断出错。我建议连接线不要超过20厘米而且尽量用质量好一点的杜邦线。如果条件允许直接把DHT11模块插在STM32最小系统板旁边用短跳线连接。第四个坑是DHT11的响应信号检测。有些代码在等待DHT11拉低总线时没有设置超时如果DHT11没有接好或者损坏程序就会卡死在while循环里。一定要加超时机制这是保证系统稳定性的基本要求。5.3 提升读取稳定性的几个技巧第一个技巧是多次读取取有效值。DHT11偶尔会出现校验失败的情况这是正常的。可以在代码里实现重试机制如果第一次读取失败隔100毫秒后再试一次最多重试3次。这样能大幅降低读取失败的概率。第二个技巧是软件滤波。如果连续读取到的温度值波动较大可以做一个简单的滑动平均滤波。比如维护一个长度为5的数组每次读取到新值后替换最旧的值然后取平均值输出。这样显示出来的数据会平稳很多。第三个技巧是电源去耦。在DHT11的VCC和GND之间并联一个0.1微法的陶瓷电容可以滤除电源噪声提高通信稳定性。这个电容在DHT11模块上通常已经集成了但如果是裸传感器就需要自己加。第四个技巧是避免频繁读取。DHT11的采样周期不能少于1秒但也不建议读取太频繁。我一般设置2秒读取一次这样既能及时反映环境变化又不会给传感器太大压力。如果是电池供电的应用可以设置更长的间隔比如5秒或10秒。6. 从DHT11延伸到更多传感器应用6.1 单总线协议的通用性DHT11使用的单总线协议虽然简单但它的核心思想——通过时间长度来编码信息——在很多传感器中都有应用。比如DS18B20温度传感器也是单总线协议通信流程和DHT11类似只是数据格式和时序参数不同。掌握了DHT11的驱动方法后再去看DS18B20的代码会发现很多相似之处。再比如红外遥控接收头它输出的也是通过高低电平持续时间来编码的信号。虽然协议细节不同但处理思路是一样的检测边沿、测量时间、解码数据。所以DHT11这个项目虽然简单但它训练的是嵌入式开发中最基础也最重要的能力——对时序的精确控制。6.2 多传感器融合的扩展思路在实际项目中DHT11往往不是单独使用的。比如做一个环境监测节点可能还需要光照传感器、气压传感器、空气质量传感器等。STM32F1的I2C和SPI接口可以方便地连接这些传感器。以I2C为例STM32F1的I2C1可以挂载多个从设备每个设备有独立的地址。比如OLED显示屏地址通常是0x78BMP280气压传感器地址是0x76或0x77BH1750光照传感器地址是0x23。只要地址不冲突就可以共用一条I2C总线。这样整个系统只需要两根线就能连接多个传感器接线非常简洁。如果传感器数量更多还可以考虑用SPI接口。SPI的速度比I2C快但需要更多的引脚。STM32F1的SPI1和SPI2可以分别连接不同的设备通过片选引脚来区分。6.3 数据上云与远程监控DHT11采集到的温湿度数据除了本地显示还可以通过无线模块上传到服务器。常见的方案是加一个ESP8266或ESP32模块通过串口和STM32F1通信。STM32F1负责采集传感器数据ESP模块负责网络通信两者分工明确。具体实现时STM32F1通过USART发送AT指令给ESP模块ESP模块连接到WiFi后通过MQTT协议把数据发布到服务器。服务器端可以用各种方式接收和存储数据比如用数据库记录历史数据用Web页面展示实时曲线。这个方案的好处是STM32F1的负担很轻只需要处理传感器读取和串口通信复杂的网络协议栈由ESP模块处理。而且ESP模块的价格也很低整体成本增加不多。6.4 低功耗设计的考虑如果项目是电池供电的低功耗就是一个必须考虑的问题。STM32F1支持多种低功耗模式包括睡眠模式、停止模式和待机模式。对于DHT11这种间歇性工作的传感器可以在两次读取之间让MCU进入停止模式把功耗降到微安级别。具体做法是读取完DHT11数据后关闭不必要的外设时钟配置一个定时器或RTC在指定时间后唤醒MCU然后进入停止模式。唤醒后重新初始化时钟和外设再次读取DHT11。这样平均功耗可以做到很低用两节AA电池就能运行几个月。不过要注意的是DHT11本身的工作电流在测量时大约1毫安待机时大约60微安。如果对功耗要求特别严格可以考虑用更低功耗的传感器比如SHT30它的待机电流只有0.2微安。但SHT30是I2C接口价格也比DHT11贵不少需要根据具体需求权衡。7. 调试工具与效率提升方法7.1 逻辑分析仪的妙用调试DHT11这种时序敏感的传感器逻辑分析仪是神器。它可以把数据线上的电平变化完整记录下来然后以波形图的形式展示。你可以直观地看到起始信号的拉低时间、DHT11的响应脉冲、每个数据位的高电平持续时间从而判断时序是否符合预期。市面上几十块钱的八通道逻辑分析仪就够用了配套的软件可以设置采样率、触发条件、协议解码。有些软件甚至自带DHT11协议解码器能直接把波形翻译成温湿度值非常方便。如果没有逻辑分析仪用示波器也可以但示波器通常只有两到四个通道而且存储深度有限不如逻辑分析仪适合看长时间的时序。不过示波器看信号质量更直观比如上升沿是否陡峭、有没有振铃等。7.2 串口打印的调试技巧串口打印是最基础的调试手段但用好了效率也很高。我习惯在DHT11驱动的关键步骤加上打印语句比如起始信号发送完成、收到响应、每个字节读取完成等。这样如果通信失败能快速定位到是哪一步出了问题。但要注意的是串口打印本身会消耗时间如果在DHT11通信过程中插入打印语句可能会干扰时序。所以调试完成后要把这些打印语句删掉或者用宏定义控制编译。我一般用条件编译来实现#define DHT11_DEBUG 0 #if DHT11_DEBUG #define DHT11_LOG(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define DHT11_LOG(fmt, ...) #endif这样在调试时把DHT11_DEBUG设为1发布时设为0既方便调试又不影响最终代码。7.3 用OLED实时显示数据除了串口输出用OLED显示屏实时显示温湿度也很实用。0.96寸的I2C OLED价格便宜接线简单只需要SCL和SDA两根线。显示内容可以包括温度、湿度、读取状态等。OLED的驱动库推荐用u8g2或者SSD1306的专用库。u8g2功能强大支持多种字体和图形但占用Flash较多。SSD1306的库更轻量适合资源紧张的STM32F103C8T6。如果Flash空间不够可以只显示数字和简单字符用最小的字体。显示刷新频率不用太高和DHT11的读取频率一致就行每2秒更新一次。这样既不会闪烁也不会占用太多CPU时间。8. 项目扩展与进阶方向8.1 加入数据记录功能如果想把温湿度数据保存下来可以加一个SD卡模块或者EEPROM芯片。SD卡通过SPI接口连接可以存储大量数据适合长时间记录。EEPROM比如AT24C02通过I2C接口连接容量小但读写简单适合存储配置参数和少量历史数据。数据记录格式可以用CSV每行包含时间戳、温度、湿度。时间戳可以用RTC实时时钟提供STM32F1内部有RTC外设配合32.768kHz晶振可以保持准确的时间。如果没有RTC也可以用开机后的运行时间作为相对时间戳。8.2 添加报警功能环境监测系统通常需要报警功能比如温度超过阈值时触发蜂鸣器或者发送通知。STM32F1的定时器可以输出PWM驱动蜂鸣器GPIO可以驱动LED指示灯。报警阈值可以通过按键设置存储在EEPROM里掉电不丢失。如果需要远程报警可以结合前面提到的无线模块通过MQTT发布报警消息。服务器端收到消息后可以推送通知到手机或者触发其他动作。8.3 多节点组网监测单个DHT11的监测范围有限如果要覆盖更大的区域可以部署多个节点每个节点用一个STM32F1加一个DHT11通过无线模块组网。常见的组网方式有WiFi、蓝牙、ZigBee、LoRa等。WiFi适合有现成路由器的场景成本低但功耗高。LoRa适合远距离低功耗的场景传输距离可达几公里但需要额外的网关。ZigBee适合自组网多跳传输但协议栈复杂开发门槛较高。对于入门级的组网需求我建议先用WiFi方案因为ESP8266模块便宜且资料多。每个节点通过WiFi连接到同一个路由器然后通过MQTT协议把数据发布到服务器。服务器端用一个简单的程序订阅所有节点的主题就能汇总显示所有节点的数据。8.4 从裸机到RTOS的升级当项目功能越来越复杂裸机的前后台架构可能会变得难以维护。这时候可以考虑引入RTOS比如FreeRTOS。FreeRTOS在STM32F1上运行很流畅RAM占用也不大。用RTOS的好处是可以把不同的任务分开比如DHT11读取任务、显示刷新任务、通信任务、报警检测任务等。每个任务独立运行通过队列或信号量通信代码结构更清晰。而且RTOS的延时函数会自动让出CPU不会像裸机那样空转等待。不过RTOS也带来了新的复杂性比如任务优先级设置、栈大小分配、资源共享等问题。如果项目规模不大裸机架构反而更简单直接。我的建议是先用裸机把功能跑通确实觉得维护困难了再考虑上RTOS。9. 我在这类项目里踩过的坑和总结的经验9.1 时序调试的耐心比技术更重要DHT11的驱动代码本身并不复杂但调试过程往往很磨人。有时候代码看起来完全正确但就是读不出数据。这时候不要急着改代码先用逻辑分析仪或者示波器看看实际波形确认硬件层面的时序是否正确。我遇到过好几次是因为杜邦线接触不良导致信号时有时无换了线就好了。另外DHT11的时序参数在不同批次之间可能有细微差异。数据手册上写的是典型值实际模块可能需要微调。比如40微秒的采样延时如果读出来全是0或者全是1可以尝试在35到45微秒之间调整找到最稳定的值。9.2 代码的健壮性要从一开始就考虑很多人在写DHT11驱动时只考虑正常情况没有加超时和重试机制。结果一旦传感器没接好或者出现干扰程序就卡死了。我的习惯是每个等待循环都加超时每次读取失败都重试最多重试3次。这样即使偶尔出现异常系统也能自动恢复。还有一点是校验和检查。DHT11的校验和虽然简单但能有效发现传输错误。如果校验失败直接丢弃这次数据不要试图“修正”。因为温湿度数据变化很慢丢一次数据对整体影响不大但用错误的数据可能导致误判。9.3 文档和注释要写给自己看嵌入式项目的代码往往过几个月就要修改或者移植如果当时没有写清楚注释回头再看就要花很多时间重新理解。我习惯在DHT11驱动的每个函数前面写清楚功能、参数、返回值在关键时序的地方标注时间要求。比如“拉低至少18ms”这样的注释能提醒自己不要随意改动。另外把硬件接线方式也记录在代码注释里比如“DHT11 DATA接PA0VCC接3.3VGND接GND”。这样换一块板子或者重新接线时不用翻原理图就能快速恢复。9.4 从简单项目里学到的通用能力DHT11这个项目看起来很简单但它涵盖的知识点很全面GPIO输入输出模式切换、微秒级延时实现、单总线协议解析、校验和计算、串口通信、状态机设计、超时处理、重试机制。这些能力在后续做更复杂的项目时都会用到。比如GPIO模式切换在驱动其他单总线设备或者模拟时序协议时都会用到。微秒级延时在驱动WS2812灯带或者红外发射时也是基础。校验和计算在通信协议里随处可见。所以不要觉得DHT11太简单就不认真做把每个细节都搞清楚后面做复杂项目时就能少走很多弯路。9.5 关于选型和替代方案的思考虽然DHT11很便宜但它的精度和响应速度确实有限。如果项目对测量精度有要求比如需要0.5摄氏度以内的精度DHT11就不够用了。这时候可以考虑DHT22它的精度是±0.5摄氏度价格比DHT11贵一些但仍在可接受范围内。如果需要更快的响应速度比如每秒读取多次DHT11的1秒采样周期就不满足要求。这时候可以选SHT30或者SHT31它们通过I2C接口通信采样速率可以到每秒10次以上而且精度更高。当然价格也更高需要根据项目预算和需求来权衡。我的建议是学习阶段用DHT11完全够用因为它的协议简单适合理解单总线通信的原理。实际产品中如果对精度和速度有要求再换更高级的传感器。STM32F1的代码框架不用大改只需要替换传感器驱动部分即可。