
简介这是一套面向嵌入式初学者与课程设计者的STM32温湿度采集报警系统完整软件工程基于STM32F103系列单片机融合DHT11湿度温度与DS18B20高精度单总线温度双传感器协同采集方案解决环境监测中多源数据融合、阈值报警触发及LCD可视化显示等典型实践问题。压缩包共203个文件含52个C源文件如stm32f10x_tim.c、os_task.c等驱动与任务逻辑、43个头文件定义接口与宏、以及编译中间文件.o/.d/.crf和可执行输出.axf/.hex结构完整支持Keil MDK直接编译下载整体大小为4.26MB。已有780人学习下载资源包含初始化配置UART、LCD、LED、蜂鸣器、传感器协议解析单总线时序、DHT11校验、温度/湿度数值转换、实时曲线绘制DrawGraph及报警逻辑Warning_Beep等核心模块代码注释清晰主循环逻辑直观适合作为STM32外设驱动开发与多传感器融合项目的入门参考范例。1. 项目概述一个拿来就能用的温湿度监控报警方案最近在整理资料时翻到了一个几年前做的老项目——“STM32单片机DHT11DS18B20传感器的温湿度采集报警系统”。这个项目虽然技术栈不算新潮但胜在结构完整、逻辑清晰从传感器驱动到报警逻辑一应俱全特别适合刚接触STM32和传感器的新手朋友或者需要一个稳定可靠的本地环境监控方案的开发者。项目核心就是通过STM32同时读取DHT11温湿度和DS18B20高精度温度的数据在温湿度超过预设阈值时通过声光等方式进行报警并将数据通过串口发送到上位机。这听起来像是毕业设计或课程设计的经典题目但其中涉及的时序控制、传感器特性、中断处理以及系统稳定性设计都是嵌入式开发中非常实在的“基本功”。今天我就把这个项目的软件设计思路、关键代码实现以及我踩过的那些“坑”详细拆解一遍希望能给你带来一个“开箱即用”的参考。2. 系统整体设计与核心思路拆解2.1 为什么选择DHT11和DS18B20组合很多朋友可能会问DHT11本身就能测温度和湿度为什么还要额外加一个DS18B20来测温度这不是多此一举吗这恰恰是这个项目设计中的一个关键考量点。DHT11是一款经典的复合型数字温湿度传感器采用单总线通信成本低集成度高能同时提供温度和湿度数据。但其温度测量精度为±2°C分辨率只有1°C响应速度也较慢。在需要快速反应或对温度精度要求稍高的场合它的表现就有些力不从心了。DS18B20则是单总线数字温度传感器的“明星产品”它的精度可达±0.5°C分辨率可配置为9~12位最高0.0625°C并且抗干扰能力强支持“一线挂多机”的独特功能。在这个报警系统中我的设计思路是用DHT11的湿度数据用DS18B20的温度数据。湿度方面DHT11的±5%RH精度对于大多数环境监控如仓库、机房、温室已经足够。而温度方面则交给更精准的DS18B20。这样既保证了湿度监测的便利性无需额外传感器又获得了高精度的温度数据使得报警判断更为准确可靠。这种组合方案在成本、精度和复杂度上取得了很好的平衡。2.2 系统架构与工作流程整个系统的软件架构可以看作一个典型的前后台系统或基于裸机状态机虽然没有上RTOS但通过合理的中断和主循环调度实现了多任务的协同。初始化系统上电后首先初始化STM32的GPIO用于控制LED、蜂鸣器、传感器数据线、定时器用于精确定时和产生PWM驱动蜂鸣器、串口用于向上位机打印数据以及ADC如果后续需要扩展其他模拟传感器。传感器数据采集在主循环中以固定的周期例如每2秒轮流读取DHT11和DS18B20的数据。这里必须注意DHT11的读取间隔不能小于2秒否则读取会失败。数据处理与阈值判断读取到的原始数据经过校验如DHT11的校验和和换算将数字量转换为实际的温湿度值后与用户预设的报警阈值高温、低温、高湿、低湿进行比较。报警触发与执行一旦有任何一项数据超限系统立即触发报警状态。报警动作通常包括点亮红色LED、启动蜂鸣器鸣叫可以用PWM驱动产生“滴滴”声并通过串口向上位机发送包含具体超标信息的报警报文。数据上报与显示无论是否报警系统都会周期性地将当前的温湿度数据通过串口发送出去格式可以是简单的文本如TEMP:25.6C, HUMI:45%方便上位机如电脑串口助手、C#/Python编写的监控软件接收、显示和记录。报警解除当所有监测数据都恢复到正常范围内并持续一段时间即“回差”或“迟滞”时间如10秒后系统自动解除报警关闭声光提示。这个流程的核心在于时序的严格把控和状态的无冲突管理。比如在读取DS18B20的微妙级时序时必须关闭总中断而蜂鸣器鸣叫如果用PWM驱动则不能影响主循环和其他定时器的正常运行。3. 核心驱动解析与实操要点3.1 DHT11驱动单总线时序的“软”实现DHT11的通信协议是典型的单总线协议主机STM32发起通信传感器返回40位数据。其难点在于对时序的精确控制特别是微秒(μs)级的延时。通信时序解析主机起始信号拉低数据线至少18ms然后拉高20-40μs随后释放总线转为输入模式等待传感器响应。传感器响应传感器会拉低总线80μs作为响应信号接着拉高80μs通知主机准备发送数据。数据位传输每一位数据都以一个50μs的低电平起始位开始随后的高电平持续时间决定数据是026-28μs还是170μs。需要在高电平开始后延时约30-40μs再去采样总线电平。代码实现关键与避坑指南// 示例读取一位数据的函数 uint8_t DHT11_Read_Bit(void) { while(DHT11_IO_READ() 0); // 等待50μs低电平起始位过去 Delay_us(40); // 延时40μs这个值是关键太早或太晚都会采样错误 if(DHT11_IO_READ() 1) { while(DHT11_IO_READ() 1); // 等待高电平结束 return 1; } else { return 0; } }注意这里的Delay_us(40)是经验值需要根据你的STM32主频和代码优化等级进行微调。一个常见的“坑”是使用SysTick或定时器实现的微秒延时函数在中断频繁的系统中可能被干扰导致时序错乱。建议在读取DHT11的整个40位数据期间短暂关闭总中断__disable_irq()读完后立即开启以确保时序绝对准确。校验DHT11返回的40位数据中前16位是湿度整数和小数中间16位是温度整数和小数最后8位是校验和前四个字节之和的低8位。每次读取数据后必须进行校验和计算校验失败则丢弃本次数据等待下一次读取。这是保证数据可靠性的第一道关卡。3.2 DS18B20驱动精确的“硬”时序与搜索ROMDS18B20的时序要求比DHT11更为严苛复位脉冲、存在脉冲、读写时隙的宽度都以微秒计且允许误差范围很小。操作流程初始化复位与存在脉冲主机拉低总线480-960μs后释放然后切换到输入模式检测DS18B20是否在60-240μs内拉低总线作为应答存在脉冲。ROM操作命令对于单节点系统可以直接发送0xCC跳过ROM命令。如果是多节点则需要先发送0xF0搜索ROM命令配合复杂的搜索算法来获取每个传感器的唯一64位ROM ID。我们这个项目按单节点设计跳过此步。功能命令发送0x44命令启动温度转换。转换时间与分辨率有关9位约93.75ms12位约750ms。转换期间如果以强上拉方式给总线供电可以在此期间进行其他操作。读取暂存器再次初始化传感器发送0xBE命令可以连续读出9个字节的暂存器内容其中前两个字节就是温度值。精度配置与温度计算 DS18B20的精度分辨率通过配置寄存器来设置。通常我们设置为12位分辨率以获得最高精度0.0625°C/LSB。// 设置分辨率为12位 DS18B20_Write_Byte(0x4E); // 写暂存器命令 DS18B20_Write_Byte(TH); // 报警上限通常不用写0 DS18B20_Write_Byte(TL); // 报警下限通常不用写0 DS18B20_Write_Byte(0x7F); // 配置寄存器0x7F对应12位分辨率温度计算时读取到的16位数据是补码形式。需要先判断符号位bit15负数则取反加一。然后将整数部分和小数部分分别计算出来。int16_t temp_raw (data[1] 8) | data[0]; // 合成16位数据 float temperature; if (temp_raw 0x8000) { // 负数 temp_raw ~temp_raw 1; temperature -(temp_raw * 0.0625); } else { // 正数 temperature temp_raw * 0.0625; }实操心得DS18B20对电源噪声比较敏感。如果发现读数偶尔跳变或初始化失败可以在其VCC和GND之间并联一个4.7uF-10uF的电解电容效果立竿见影。另外单总线最好接一个4.7KΩ的上拉电阻到VCC。3.3 报警逻辑设计与状态机实现报警不是简单的“超限就响恢复就停”那样在阈值边界容易产生频繁的开关抖动。一个健壮的报警逻辑需要包含阈值判断、回差迟滞和报警延时/解除延时。我通常用一个结构体来管理每个监控通道的状态typedef struct { float value; // 当前值 float high_threshold; // 高报警阈值 float low_threshold; // 低报警阈值 float hysteresis; // 回差值例如2.0 uint32_t alarm_start_time; // 触发报警的时刻 uint32_t alarm_delay; // 报警触发延时(ms)低于此时间不报警 uint32_t recover_delay; // 报警解除延时(ms) bool is_in_alarm; // 当前是否处于报警状态 } SensorChannel_t;状态迁移逻辑正常状态当value持续超过high_threshold达到alarm_delay时间则进入高报警状态。低于low_threshold同理。报警状态当value从报警状态恢复到high_threshold - hysteresis以下对于高报或low_threshold hysteresis以上对于低报并且持续recover_delay时间后才解除报警状态。这个回差机制非常重要。例如设置高温报警阈值为30°C回差为2°C。那么温度升到30°C触发报警后必须等到温度降到28°C以下并保持一段时间报警才会解除。这有效防止了在29.5°C附近因微小波动导致的报警器频繁启停。报警执行单元报警状态机判断出需要报警后会设置一个全局的alarm_flag。主循环或一个专用的定时器中断服务程序中会检查这个标志并控制LED闪烁比如快闪和蜂鸣器鸣叫可以用PWM产生断续的“嘀-嘀-嘀”声比长鸣更人性化。串口报警信息也在这里组包发送。4. 系统整合与主循环设计4.1 外设初始化与任务调度系统的稳定性始于正确的初始化。我的初始化顺序通常是先初始化系统时钟如果有时钟配置然后初始化GPIO、USART用于调试和通信接着是定时器用于提供毫秒/微秒延时基准以及PWM输出最后初始化传感器。主循环的设计采用时间片轮询的方式这是裸机编程中实现多任务感的核心。int main(void) { // 1. 系统初始化 SystemClock_Config(); GPIO_Init(); USART1_Init(115200); TIM2_Init(); // 用于delay_ms TIM3_Init(); // 用于PWM驱动蜂鸣器 printf(System Boot OK.\r\n); // 2. 传感器初始化与自检 if(DHT11_Init() ! 0) printf(DHT11 Init Fail!\r\n); if(DS18B20_Init() ! 0) printf(DS18B20 Init Fail!\r\n); // 3. 主循环 while(1) { // 任务1每2000ms读取一次传感器 if(Get_Tick_ms() - sensor_tick 2000) { sensor_tick Get_Tick_ms(); DHT11_Read_Data(temp_dht, humi); DS18B20_Get_Temperature(temp_ds18); // 数据校验、处理、阈值判断... Check_Alarm_Thresholds(); } // 任务2每500ms处理一次报警输出闪烁、鸣叫 if(Get_Tick_ms() - alarm_tick 500) { alarm_tick Get_Tick_ms(); Alarm_Process(); } // 任务3每1000ms通过串口上报一次数据 if(Get_Tick_ms() - report_tick 1000) { report_tick Get_Tick_ms(); USART_Send_Report(); } // 其他任务如按键扫描、显示刷新等... Key_Scan(); // 空闲时可以考虑进入低功耗模式 // __WFI(); } }这里Get_Tick_ms()是一个基于SysTick或基本定时器中断的毫秒计时函数。每个任务都有自己的时间戳独立运行互不阻塞。4.2 串口通信协议设计一个好的串口协议能让上位机开发事半功倍。我倾向于设计一个简单明了的文本协议方便人眼阅读和调试。数据上报帧周期1秒[TEMP:25.62, HUMI:45.3, TEMP_DS18:25.63, ALARM:0]\r\nTEMPDHT11温度备用可忽略HUMIDHT11湿度TEMP_DS18DS18B20温度主温度ALARM报警状态0正常1高温2低温3高湿4低湿5复合报警报警触发帧立即发送[ALARM:1, VAL:32.5, TH:30.0, TIME:12345678]\r\nALARM报警类型VAL触发报警的实际值TH触发的阈值TIME从系统启动开始的毫秒时间戳用于记录命令下发帧上位机-STM32[SET_TH:HIGH_TEMP:35.0]\r\n[GET_TH]\r\n[ALARM_SWITCH:OFF]\r\n在STM32端使用串口空闲中断IDLE Interrupt加DMA接收不定长数据是高效且可靠的方式。当一帧数据接收完毕在空闲中断服务函数中解析接收缓冲区里的命令字符串并执行相应操作如修改阈值。4.3 低功耗与稳定性考量虽然这个项目对功耗不敏感但养成良好的编程习惯很重要。在主循环的while(1)末尾如果没有任务需要执行可以调用__WFI()指令让CPU进入睡眠模式等待下一个中断如定时器中断、串口中断将其唤醒能有效降低系统功耗。稳定性方面除了之前提到的传感器数据校验还需要加入看门狗IWDG。初始化一个独立看门狗定时喂狗。如果程序跑飞或陷入死循环看门狗超时会导致系统复位这是一种最后的保护手段。// 初始化独立看门狗约1秒超时 IWDG_HandleTypeDef hiwdg; hiwdg.Instance IWDG; hiwdg.Init.Prescaler IWDG_PRESCALER_32; hiwdg.Init.Reload 4095; // 重载值 HAL_IWDG_Init(hiwdg); // 在主循环中定期喂狗 while(1) { // ... 各种任务 HAL_IWDG_Refresh(hiwdg); // 喂狗 }5. 常见问题排查与调试技巧实录5.1 传感器无响应或数据全为0这是新手最常遇到的问题可以从以下方面排查硬件连接这是第一嫌疑点。用万用表确认VCC3.3V、GND连接正确且电压稳定。特别注意单总线数据线是否接了上拉电阻4.7KΩ-10KΩ无论是DHT11还是DS18B20这个电阻必不可少。检查杜邦线是否接触不良。电源噪声尤其是DS18B20在长导线连接时电源噪声会导致通信失败。尝试在传感器VCC和GND引脚就近并联一个10uF电解电容和一个0.1uF瓷片电容。时序问题99%的驱动问题源于时序不准确。检查延时函数用示波器或逻辑分析仪测量单片机发出的起始信号波形看低电平时间是否满足传感器要求DHT11 18ms DS18B20复位脉冲480-960μs。如果没有仪器可以尝试在延时函数前后翻转一个测试用的GPIO引脚用普通示波器查看脉冲宽度来校准你的Delay_us和Delay_ms函数。关闭中断在发送起始信号和读取每一位数据的整个关键时序期间务必使用__disable_irq()和__enable_irq()关闭和开启总中断。任何中断的插入都可能拉长某个微秒级延时导致时序错乱。IO口模式切换单总线协议要求主机在发送输出模式和接收输入模式间快速切换。确保你的DHT11_IO_IN()和DHT11_IO_OUT()函数正确配置了GPIO的模式寄存器如GPIOx-MODER。5.2 数据偶尔跳变或明显错误校验和错误首先检查代码中的校验和计算是否正确。如果校验和经常失败说明数据传输过程受到严重干扰。加强硬件滤波总线加小电容或降低通信速率虽然单总线速率固定但可以尝试在每位数据读取后增加一点保护延时。电源电压不足确保单片机和工作电压稳定。DHT11的工作电压范围是3.3V-5.5VDS18B20是3.0V-5.5V。使用3.3V系统时确保LDO输出电流足够且纹波小。电磁干扰如果系统中有继电器、电机等大电流感性负载其开关会在电源和地线上产生巨大噪声。务必为这些负载单独供电并在其线圈两端并联续流二极管同时做好单片机的电源退耦每个电源引脚附近接0.1uF电容。代码优化等级如果你使用了高等级的编译器优化如-O2 -Os可能会将一些你认为必要的Delay_us()循环优化掉。尝试在延时函数前加上volatile关键字或者将优化等级暂时调整为-O0进行测试。5.3 报警逻辑异常不报警或误报阈值设置不合理检查代码中阈值比较的逻辑符号还是。确认阈值变量的类型float还是int与比较操作匹配。回差迟滞未生效检查你的报警状态机代码确保在判断“恢复正常”时使用的是“阈值 - 回差”进行比较而不是直接使用原阈值。并且“恢复正常”的判断一定要在“报警状态”下进行。延时未起作用检查alarm_delay和recover_delay的单位是否为毫秒以及在状态判断中是否正确地使用了Get_Tick_ms()进行时间差计算。注意Get_Tick_ms()的溢出问题使用无符号数减法可自然处理。传感器数据异常导致误报在阈值判断之前最好加入一层数据合理性滤波。例如温度值如果突然跳变到-40°C或125°CDS18B20的极限值这很可能是读取错误应该丢弃这包数据而不是用它去触发报警。可以做一个简单的滑动平均滤波或限幅滤波。5.4 串口通信问题上位机收不到数据首先用USB-TTL工具直接连接STM32的TX引脚用串口助手查看是否有数据发出。如果没有检查STM32串口初始化代码波特率、停止位等以及printf重定向或自定义发送函数是否正确。如果有数据但乱码肯定是波特率不匹配。上位机发送命令无反应检查STM32的串口接收是否开启中断或DMA是否配置正确。在接收中断服务函数或空闲中断回调函数里设置一个断点或翻转一个LED看看命令是否真的被接收到。然后检查命令解析函数字符串比较是否因换行符\r\n而失败。通信一段时间后死机检查串口接收缓冲区是否溢出。确保在解析完一帧数据后正确重置了DMA或接收缓冲区索引。使用空闲中断时在中断服务函数中清除空闲中断标志位至关重要。这个项目麻雀虽小五脏俱全。它几乎涵盖了单片机应用开发的所有基础环节GPIO控制、精密延时、外部中断、定时器、PWM、串口通信、状态机设计、数据滤波、抗干扰设计等等。把这里面的每一个环节吃透、调稳你对嵌入式开发的理解就会上升一个实实在在的台阶。源代码包里的每一行代码都对应着实际硬件上的一个电平和一段时序这种从软件到硬件的直接映射正是嵌入式开发的魅力所在。希望这份详细的拆解能帮你更快地搭建起自己的环境监控系统或者至少在调试DHT11和DS18B20时能少走一些我曾经走过的弯路。本文还有配套的精品资源点击获取