简介这是一套基于STM32F1系列单片机的嵌入式温湿度采集与报警系统完整软件工程面向嵌入式初学者、课程设计学生及电子竞赛备赛者解决多传感器协同采集、数据转换、阈值报警与LCD界面显示等典型实践问题。资源包共203个文件涵盖52个C源文件含DS18B20驱动、DHT11协议解析、LCD图形绘制、蜂鸣器报警逻辑等核心模块、43个头文件定义硬件接口与数据结构、以及编译生成的.o、.d、.axf、.hex等调试与烧录所需文件整体压缩包大小为4.26MB。已有780人学习下载工程已集成SysTick延时、串口调试、LED状态指示、温度/湿度单位换算与实时曲线绘制等功能代码结构清晰模块划分合理含OS相关底层文件如os_core.c、os_task.c表明具备轻量级任务调度扩展潜力可直接编译运行于Keil MDK环境是理解STM32外设驱动与传感器融合应用的优质参考范例。1. 项目缘起为什么需要一个温湿度报警系统最近在整理工作室的物料仓库发现一些对温湿度敏感的电子元件和耗材比如某些电容、光敏树脂还有几卷开封了的锡膏。这些东西对环境要求不低太潮了容易氧化太干了锡膏性能也会下降。靠人工每天去记录温湿度一来麻烦二来总有疏漏。于是一个能自动监测、超限报警的本地化系统就成了刚需。市面上成品的数据记录仪不少功能强大的价格不菲功能简单的又往往缺少报警联动或者数据存储。更重要的是作为一个嵌入式开发者我更倾向于自己动手既能完全掌控数据流向和报警逻辑又能把STM32、传感器、外设驱动这些知识点串起来练练手做一个“麻雀虽小五脏俱全”的综合性项目。这个“STM32单片机DHT11DS18B20传感器的温湿度采集报警系统”就是基于这个需求诞生的。它不依赖网络成本低廉核心就是一个STM32单片机搭配DHT11采集湿度、DS18B20采集温度通过OLED屏实时显示并设置阈值进行声光报警。这个项目非常适合已经有一定STM32和C语言基础想通过一个完整项目来巩固传感器驱动、状态机编程、人机交互设计的开发者。代码结构清晰注释详细你拿到后不仅能快速搭建起系统更能理解每一个模块是如何协同工作的为后续添加更复杂的功能比如通过ESP8266上传数据到云端或者增加更多的传感器节点打下坚实的基础。2. 核心器件选型与电路设计考量一个稳定的硬件平台是软件可靠运行的前提。这个项目用到的核心器件不多但每一个的选择和连接都值得推敲。2.1 主控芯片为什么是STM32项目标题直接点名了STM32这是一个非常庞大且成功的ARM Cortex-M内核微控制器家族。我选择它而不是更简单的51单片机或者Arduino主要基于以下几点考虑性能与资源充裕即使是入门级的STM32F103C8T6常说的“蓝桥杯”最小系统板也拥有72MHz的主频、64KB Flash、20KB RAM以及丰富的外设多个定时器、USART、I2C、SPI、ADC等。这为驱动多个传感器、处理数据、管理显示和报警逻辑提供了充足的算力和硬件支持后期扩展功能如加装SD卡存储、连接蓝牙模块也游刃有余。开发生态成熟无论是标准外设库Standard Peripheral Library还是现在更主流的HAL库Hardware Abstraction LayerSTM32都有完善的软件支持。配套的STM32CubeMX工具可以图形化配置引脚、时钟和外设极大降低了底层初始化的复杂度。Keil MDK、IAR、VSCodePlatformIO等开发环境的选择也很多。成本与可获得性STM32系列芯片和开发板已经高度市场化价格非常亲民。一块核心板加基础外设总成本可以控制在很低的范围内。在具体型号上我推荐使用STM32F103C8T6或STM32F103RCT6。前者引脚数少适合做最小系统验证后者资源更丰富有更多的IO口和存储空间适合做功能更完整的版本。2.2 传感器搭档DHT11与DS18B20的互补之道这里有一个关键点项目同时使用了DHT11和DS18B20两个温度传感器。这并非冗余设计而是有明确的意图。DHT11这是一个数字式温湿度复合传感器。它通过单总线协议通信一次读取就能同时获得温度和湿度数据。其温度测量范围是0-50°C精度±2°C湿度测量范围20-90%RH精度±5%RH。对于一般的室内环境监控这个精度是可以接受的。它的核心价值在于提供了湿度数据这是DS18B20不具备的。DS18B20这是一个高精度的数字温度传感器同样采用单总线协议。它的测量范围更广-55°C ~ 125°C精度更高可达±0.5°C。在这个项目中我主要用DS18B20来提供更精确的温度读数作为系统的主要温度参考。而DHT11的温度读数可以作为辅助参考或用于交叉校验虽然精度一般。为什么不用一个SHT30之类的同时精度高的温湿度传感器当然可以SHT30I2C接口是更优秀的选择。但本项目选择DHT11DS18B20的组合一是成本考量二是教学目的。它涵盖了两种非常经典且常用的单总线器件驱动它们能让你深刻理解单总线通信的时序要求这是嵌入式开发中一项重要的基本功。在实际部署时如果你对湿度精度要求高完全可以将DHT11替换为SHT30或AHT20只需修改对应的驱动函数即可系统主框架无需大动。2.3 外围电路与连接要点电源确保整个系统供电稳定。STM32核心板通常需要3.3VDHT11和DS18B20的工作电压范围是3-5.5V因此用3.3V供电是兼容的。如果使用5V供电的OLED屏需要注意电平转换或者选择3.3V兼容的OLED模块。上拉电阻这是驱动DHT11和DS18B20最容易忽略也最关键的一点。两者的数据线DQ都是开漏输出必须在MCU的IO口与VCC3.3V之间连接一个4.7KΩ ~ 10KΩ的上拉电阻否则总线无法被拉高通信必然失败。很多开发板可能已经集成但自己布线时务必记得。OLED显示我选择了0.96寸的I2C接口OLED屏因为它引脚少仅需SCL、SDA、VCC、GND四线编程简单显示效果清晰。在代码中我们需要移植一个OLED的驱动库如ssd1306或u8g2的简化版来显示温湿度数值和报警状态。报警模块包括一个无源蜂鸣器接一个IO口通过PWM驱动发声和一个LED灯接另一个IO口。报警逻辑由软件控制。连接示意图以STM32F103C8T6为例DS18B20 DQ - PA1 (配置为上拉输入/推挽输出)DHT11 DATA - PA2 (配置为上拉输入/推挽输出)OLED SCL - PB6 (I2C1_SCL)OLED SDA - PB7 (I2C1_SDA)Buzzer - PA8 (配置为推挽输出可通过定时器产生PWM)LED - PC13 (开发板自带LED配置为推挽输出)注意STM32的IO口驱动能力有限直接驱动蜂鸣器声音可能较小。通常会在IO口和蜂鸣器之间加一个三极管如S8050进行电流放大这是更可靠的做法。3. 软件架构设计与核心驱动实现拿到一个“软件源代码.zip”我们首先要看它的骨架。一个好的项目代码应该是模块清晰、耦合度低的。下面我拆解这个系统应有的软件架构。3.1 模块化工程结构代码目录应该大致如下Project/ ├── Core/ │ ├── Src/ │ │ ├── main.c │ │ ├── stm32f1xx_it.c │ │ └── syscalls.c (可选) │ └── Inc/ │ ├── main.h │ └── stm32f1xx_it.h ├── Drivers/ │ ├── STM32F1xx_HAL_Driver/ (HAL库文件) │ └── BSP/ (板级支持包可选) ├── Middlewares/ (中间件如FreeRTOS本项目可能不用) ├── Application/ │ ├── Src/ │ │ ├── sensor_ds18b20.c │ │ ├── sensor_dht11.c │ │ ├── oled_display.c │ │ ├── alarm.c │ │ └── data_processor.c │ └── Inc/ │ ├── sensor_ds18b20.h │ ├── sensor_dht11.h │ ├── oled_display.h │ ├── alarm.h │ └── data_processor.h ├── STM32CubeMX生成的.ioc文件和初始化代码 └── README.md这种结构将硬件驱动传感器、显示、业务逻辑数据处理、报警分离开main.c主要负责调度和协调非常清晰。3.2 单总线协议驱动的精粹以DS18B20为例DS18B20和DHT11都使用单总线但协议细节不同。驱动它们的核心就是精准的时序控制。STM32的HAL库提供的HAL_Delay()函数是基于SysTick的毫秒级延时对于单总线需要的微秒级延时是不够精确的尤其是在不同主频下。因此我们必须自己实现一个微秒延时函数。实现一个不依赖SysTick的delay_us函数 通常有两种方法一是使用定时器二是使用CPU指令空跑。对于简单应用指令空跑更直接。但要注意编译器优化可能会“优化”掉我们的空循环。我们需要使用volatile关键字来防止优化并针对不同主频进行校准。// 适用于72MHz STM32F103的简单微秒延时近似 void delay_us(uint16_t us) { volatile uint32_t delay us * 8; // 这个系数需要根据实际测试调整 while(delay--); }这个系数8是需要通过示波器或者逻辑分析仪实际测量调整的。更严谨的做法是利用STM32的DWTData Watchpoint and Trace单元中的CYCCNT周期计数器来实现纳秒级精度的延时这在需要严格时序的场合如驱动WS2812灯带是必备技能。DS18B20的驱动流程初始化复位与存在脉冲主机拉低总线至少480us然后释放等待DS18B20在60-240us内拉低总线作为应答。发送命令例如发送跳过ROM命令0xCC然后发送启动温度转换命令0x44。等待转换DS18B20进行AD转换对于12位精度最多需要750ms。期间主机可以读取总线电平判断是否完成Read Bit为0表示忙或者简单延时等待。读取数据再次初始化总线发送跳过ROM命令0xCC然后发送读暂存器命令0xBE随后连续读取9个字节前两个字节是温度值。读取温度值的代码片段示例float DS18B20_ReadTemp(void) { uint8_t temp_l, temp_h; int16_t temp_raw; float temperature; DS18B20_Reset(); DS18B20_WriteByte(0xCC); // Skip ROM DS18B20_WriteByte(0xBE); // Read Scratchpad temp_l DS18B20_ReadByte(); // LSB temp_h DS18B20_ReadByte(); // MSB DS18B20_Reset(); // 读完前两个字节后可以复位终止读取也可以继续读其他字节如CRC temp_raw (temp_h 8) | temp_l; temperature temp_raw / 16.0f; // 12位精度默认分辨率0.0625°C return temperature; }3.3 DHT11的驱动与数据校验DHT11的时序与DS18B20不同。它也是单总线但数据格式固定为40位8bit湿度整数8bit湿度小数8bit温度整数8bit温度小数8bit校验和。DHT11的读取流程主机启动信号拉低总线至少18ms然后释放并等待20-40us。从机响应DHT11拉低总线80us然后拉高80us之后开始输出数据。数据位解析每一位都以50us的低电平起始随后的高电平持续时间决定数据是026-28us还是170us。这里的关键是判断高电平的持续时间。校验校验和 湿度高8位 湿度低8位 温度高8位 温度低8位。接收到的校验和与计算值一致数据才有效。驱动DHT11的常见坑点时序苛刻对微秒延时精度要求比DS18B20更高。建议在关闭中断的环境下读取一帧数据避免被其他中断打断导致时序错乱。响应失败检查上拉电阻、电源电压以及启动信号的长度是否足够。数据校验错误除了传感器本身问题最常见的原因是时序判断不准确导致某一位数据读错。可以用逻辑分析仪抓取波形对照时序图仔细调试你的delay_us和电平判断逻辑。3.4 数据融合与报警逻辑设计拿到了DS18B20的精确温度和DHT11的湿度及辅助温度后怎么处理数据滤波传感器读数可能会有微小跳动。简单的做法是连续读取N次比如5次然后去掉最大最小值取中间值的平均。更高级的可以用一阶低通滤波软件实现。// 一阶低通滤波示例 #define ALPHA 0.2f // 滤波系数越小越平滑响应越慢 float filtered_temp 0; float new_temp DS18B20_ReadTemp(); filtered_temp ALPHA * new_temp (1 - ALPHA) * filtered_temp;报警判断在data_processor.c中设定温度上限、下限和湿度上限。将滤波后的数据与阈值比较。typedef struct { float temp_high_threshold; float temp_low_threshold; float humidity_high_threshold; uint8_t alarm_status; // 用位域表示不同报警类型如0x01温度高0x02温度低0x04湿度高 } AlarmConfig_t; void CheckAlarm(float temp, float humidity, AlarmConfig_t *cfg) { cfg-alarm_status 0; if(temp cfg-temp_high_threshold) cfg-alarm_status | 0x01; else if(temp cfg-temp_low_threshold) cfg-alarm_status | 0x02; if(humidity cfg-humidity_high_threshold) cfg-alarm_status | 0x04; }报警动作alarm.c模块根据alarm_status控制蜂鸣器和LED。可以设计不同的报警模式如温度过高是急促“滴滴”声加红灯常亮湿度过高是缓慢“滴—滴—”声加黄灯闪烁。4. 主程序调度与系统状态机在main.c中我们如何组织这些模块对于这样一个功能明确的小系统一个超级循环Super Loop配合一个简单的状态机State Machine就足够了无需引入RTOS增加复杂性。4.1 主循环设计模式一个典型的主循环结构如下int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); // For OLED // ... 其他外设初始化 OLED_Init(); DS18B20_Init(); DHT11_Init(); Alarm_Init(); AlarmConfig_t alarm_cfg {30.0, 10.0, 80.0, 0}; // 初始化阈值 float temp, humidity; uint32_t last_sensor_read_tick 0; uint32_t last_display_refresh_tick 0; while (1) { uint32_t current_tick HAL_GetTick(); // 获取系统毫秒计时 // 状态1定时读取传感器例如每2秒一次 if(current_tick - last_sensor_read_tick 2000) { last_sensor_read_tick current_tick; temp DS18B20_ReadTemp(); // 读取DS18B20温度 humidity DHT11_ReadHumidity(); // 读取DHT11湿度 // 可选读取DHT11温度用于参考或校验 CheckAlarm(temp, humidity, alarm_cfg); Alarm_Execute(alarm_cfg.alarm_status); } // 状态2定时刷新显示例如每500ms一次比传感器读取快 if(current_tick - last_display_refresh_tick 500) { last_display_refresh_tick current_tick; OLED_Clear(); OLED_ShowString(0, 0, Temp:); OLED_ShowFloat(40, 0, temp, 2); // 显示温度2位小数 OLED_ShowString(0, 2, Humi:); OLED_ShowFloat(40, 2, humidity, 1); // 显示湿度1位小数 OLED_ShowString(0, 4, Alarm:); // 根据alarm_status显示不同的报警状态字符 OLED_Refresh(); } // 其他任务如按键扫描用于设置阈值 Key_Scan(alarm_cfg); } }这种基于时间戳的非阻塞式延时避免了使用HAL_Delay()导致CPU空转浪费资源让主循环可以高效地处理多个周期性任务。4.2 阈值设置与掉电保存一个实用的报警系统必须允许用户修改报警阈值。通常通过外接几个按键来实现。逻辑是长按某个键进入设置模式然后通过其他按键增减数值再次长按确认并退出。更关键的问题是设置的阈值如何保存单片机RAM掉电就丢失了。我们需要将配置保存到EEPROM或STM32内部的Flash中。内部Flash模拟EEPROMSTM32没有真正的EEPROM但可以用一部分Flash来存储数据。HAL库提供了HAL_FLASH_Program和HAL_FLASHEx_Erase函数。重要提示Flash擦除以扇区为单位且寿命有限约1万到10万次。频繁写入会损坏。因此好的策略是在Flash中划分一个专用扇区如STM32F103的最后一个扇区。采用“磨损均衡”的简单思想每次写入新数据时写到该扇区的新地址并标记旧数据无效。只有当扇区写满时才进行一次擦除。或者只在用户确认修改时写入一次而不是每次上电都写。外部EEPROM芯片如AT24C02I2C接口。这种方式不占用Flash寿命长100万次操作简单。是更推荐的做法但需要增加一块芯片。在代码中上电初始化时先从存储介质读取保存的阈值用户修改阈值后再调用存储函数将其写入。5. 项目进阶与调试心得一个基础功能跑通后我们可以思考如何让它更健壮、更实用。5.1 稳定性增强看门狗与错误恢复工业环境或长期运行的系统必须考虑程序“跑飞”的情况。STM32内置了独立看门狗IWDG和窗口看门狗WWDG。独立看门狗IWDG由一个独立的低速时钟LSI约40kHz驱动即使主时钟失效也能工作。它像一只必须定期喂食的狗如果超过设定时间如1秒没有“喂狗”即重置计数器就会强制复位整个系统。在main函数的while(1)循环里定期调用HAL_IWDG_Refresh(hiwdg)是保证系统不死机的最后防线。应用场景在传感器读取函数中如果因为硬件故障导致DS18B20或DHT11无响应程序可能会卡在while等待循环中。有了看门狗超时后系统会复位而不是永远死机。5.2 扩展性思考从本地到远程这是项目未来可以延伸的方向数据记录增加一个SPI接口的SD卡模块将温湿度数据连同时间戳需要RTC或从网络获取以CSV格式定期写入文件便于后期分析。无线报警增加一个ESP-01SESP8266Wi-Fi模块通过AT指令或直接编程在报警发生时向指定的手机APP如Blynk、点灯科技或服务器发送通知甚至发送邮件。多节点组网使用RS-485总线或CAN总线将多个STM32温湿度采集节点连接起来形成一个分布式监控网络由一个主机进行数据汇总和显示。5.3 调试过程中的血泪教训时序问题永远是单总线的头号敌人最初驱动DS18B20总是失败用HAL_Delay(1)来凑微秒延时结果完全不对。最后用逻辑分析仪抓波形才发现延时严重不准。务必使用精确的微秒延时函数并在关键时序点如等待从机应答后读取IO口状态进行判断而不是一味死等。上拉电阻不能省第一次焊接电路DS18B20和DHT11都没反应排查了半天才发现忘了加上拉电阻。没有上拉总线永远处于不确定状态。电源噪声如果蜂鸣器工作时传感器读数出现剧烈跳动很可能是电源被干扰了。尝试在单片机电源入口和传感器VCC引脚处增加一个10uF和0.1uF的电容进行退耦。代码结构规划一开始把所有代码都堆在main.c里后来想改显示方式或报警逻辑时牵一发而动全身。尽早进行模块化设计把硬件驱动、业务逻辑、显示控制分开用头文件声明接口这会为后续调试和升级节省大量时间。阈值设置的防抖按键设置阈值时必须做软件防抖处理否则一次按下可能会被识别成多次。简单的做法是检测到按键按下后延时10-20ms再次检测如果仍然按下才确认为有效按键。这个项目虽然不大但完整地覆盖了传感器驱动、实时数据采集、人机交互、状态控制和系统稳定性设计等多个嵌入式开发的核心环节。当你亲手把它从一堆元器件变成一个有实际功能的设备并且稳定运行起来时那种成就感是看多少遍教程都无法比拟的。希望这份详细的拆解能帮助你不仅“拿来就能用”更能“用了就懂懂了能改”。本文还有配套的精品资源点击获取