news 2026/9/3 7:53:34

STM32温湿度报警系统:DHT11与DS18B20传感器驱动与项目实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32温湿度报警系统:DHT11与DS18B20传感器驱动与项目实战

简介:这是一套基于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单片机+DHT11+DS18B20传感器的温湿度采集报警系统”就是基于这个需求诞生的。它不依赖网络,成本低廉,核心就是一个STM32单片机,搭配DHT11采集湿度、DS18B20采集温度,通过OLED屏实时显示,并设置阈值进行声光报警。

这个项目非常适合已经有一定STM32和C语言基础,想通过一个完整项目来巩固传感器驱动、状态机编程、人机交互设计的开发者。代码结构清晰,注释详细,你拿到后不仅能快速搭建起系统,更能理解每一个模块是如何协同工作的,为后续添加更复杂的功能(比如通过ESP8266上传数据到云端,或者增加更多的传感器节点)打下坚实的基础。

2. 核心器件选型与电路设计考量

一个稳定的硬件平台是软件可靠运行的前提。这个项目用到的核心器件不多,但每一个的选择和连接都值得推敲。

2.1 主控芯片:为什么是STM32?

项目标题直接点名了STM32,这是一个非常庞大且成功的ARM Cortex-M内核微控制器家族。我选择它,而不是更简单的51单片机或者Arduino,主要基于以下几点考虑:

  1. 性能与资源充裕:即使是入门级的STM32F103C8T6(常说的“蓝桥杯”最小系统板),也拥有72MHz的主频、64KB Flash、20KB RAM,以及丰富的外设(多个定时器、USART、I2C、SPI、ADC等)。这为驱动多个传感器、处理数据、管理显示和报警逻辑提供了充足的算力和硬件支持,后期扩展功能(如加装SD卡存储、连接蓝牙模块)也游刃有余。
  2. 开发生态成熟:无论是标准外设库(Standard Peripheral Library)还是现在更主流的HAL库(Hardware Abstraction Layer),STM32都有完善的软件支持。配套的STM32CubeMX工具可以图形化配置引脚、时钟和外设,极大降低了底层初始化的复杂度。Keil MDK、IAR、VSCode+PlatformIO等开发环境的选择也很多。
  3. 成本与可获得性: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之类的同时精度高的温湿度传感器?当然可以,SHT30(I2C接口)是更优秀的选择。但本项目选择DHT11+DS18B20的组合,一是成本考量,二是教学目的。它涵盖了两种非常经典且常用的单总线器件,驱动它们能让你深刻理解单总线通信的时序要求,这是嵌入式开发中一项重要的基本功。在实际部署时,如果你对湿度精度要求高,完全可以将DHT11替换为SHT30或AHT20,只需修改对应的驱动函数即可,系统主框架无需大动。

2.3 外围电路与连接要点

  1. 电源:确保整个系统供电稳定。STM32核心板通常需要3.3V,DHT11和DS18B20的工作电压范围是3-5.5V,因此用3.3V供电是兼容的。如果使用5V供电的OLED屏,需要注意电平转换,或者选择3.3V兼容的OLED模块。
  2. 上拉电阻:这是驱动DHT11和DS18B20最容易忽略也最关键的一点。两者的数据线(DQ)都是开漏输出,必须在MCU的IO口与VCC(3.3V)之间连接一个4.7KΩ ~ 10KΩ的上拉电阻,否则总线无法被拉高,通信必然失败。很多开发板可能已经集成,但自己布线时务必记得。
  3. OLED显示:我选择了0.96寸的I2C接口OLED屏,因为它引脚少(仅需SCL、SDA、VCC、GND四线),编程简单,显示效果清晰。在代码中,我们需要移植一个OLED的驱动库(如ssd1306u8g2的简化版)来显示温湿度数值和报警状态。
  4. 报警模块:包括一个无源蜂鸣器(接一个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的DWT(Data Watchpoint and Trace)单元中的CYCCNT(周期计数器)来实现纳秒级精度的延时,这在需要严格时序的场合(如驱动WS2812灯带)是必备技能。

DS18B20的驱动流程

  1. 初始化(复位与存在脉冲):主机拉低总线至少480us,然后释放,等待DS18B20在60-240us内拉低总线作为应答。
  2. 发送命令:例如发送跳过ROM命令0xCC,然后发送启动温度转换命令0x44
  3. 等待转换:DS18B20进行AD转换,对于12位精度最多需要750ms。期间主机可以读取总线电平判断是否完成(Read Bit为0表示忙),或者简单延时等待。
  4. 读取数据:再次初始化总线,发送跳过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的读取流程

  1. 主机启动信号:拉低总线至少18ms,然后释放并等待20-40us。
  2. 从机响应:DHT11拉低总线80us,然后拉高80us,之后开始输出数据。
  3. 数据位解析:每一位都以50us的低电平起始,随后的高电平持续时间决定数据是0(26-28us)还是1(70us)。这里的关键是判断高电平的持续时间
  4. 校验:校验和 = 湿度高8位 + 湿度低8位 + 温度高8位 + 温度低8位。接收到的校验和与计算值一致,数据才有效。

驱动DHT11的常见坑点

  • 时序苛刻:对微秒延时精度要求比DS18B20更高。建议在关闭中断的环境下读取一帧数据,避免被其他中断打断导致时序错乱。
  • 响应失败:检查上拉电阻、电源电压,以及启动信号的长度是否足够。
  • 数据校验错误:除了传感器本身问题,最常见的原因是时序判断不准确,导致某一位数据读错。可以用逻辑分析仪抓取波形,对照时序图仔细调试你的delay_us和电平判断逻辑。

3.4 数据融合与报警逻辑设计

拿到了DS18B20的精确温度和DHT11的湿度(及辅助温度)后,怎么处理?

  1. 数据滤波:传感器读数可能会有微小跳动。简单的做法是连续读取N次(比如5次),然后去掉最大最小值,取中间值的平均。更高级的可以用一阶低通滤波(软件实现)。
    // 一阶低通滤波示例 #define ALPHA 0.2f // 滤波系数,越小越平滑,响应越慢 float filtered_temp = 0; float new_temp = DS18B20_ReadTemp(); filtered_temp = ALPHA * new_temp + (1 - ALPHA) * filtered_temp;
  2. 报警判断:在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; }
  3. 报警动作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掉电就丢失了。我们需要将配置保存到EEPROMSTM32内部的Flash中。

  • 内部Flash模拟EEPROM:STM32没有真正的EEPROM,但可以用一部分Flash来存储数据。HAL库提供了HAL_FLASH_ProgramHAL_FLASHEx_Erase函数。重要提示:Flash擦除以扇区为单位,且寿命有限(约1万到10万次)。频繁写入会损坏。因此,好的策略是:

    1. 在Flash中划分一个专用扇区(如STM32F103的最后一个扇区)。
    2. 采用“磨损均衡”的简单思想:每次写入新数据时,写到该扇区的新地址,并标记旧数据无效。只有当扇区写满时才进行一次擦除。
    3. 或者,只在用户确认修改时写入一次,而不是每次上电都写。
  • 外部EEPROM芯片:如AT24C02(I2C接口)。这种方式不占用Flash,寿命长(100万次),操作简单。是更推荐的做法,但需要增加一块芯片。

在代码中,上电初始化时,先从存储介质读取保存的阈值;用户修改阈值后,再调用存储函数将其写入。

5. 项目进阶与调试心得

一个基础功能跑通后,我们可以思考如何让它更健壮、更实用。

5.1 稳定性增强:看门狗与错误恢复

工业环境或长期运行的系统,必须考虑程序“跑飞”的情况。STM32内置了独立看门狗(IWDG)和窗口看门狗(WWDG)。

  • 独立看门狗(IWDG):由一个独立的低速时钟(LSI,约40kHz)驱动,即使主时钟失效也能工作。它像一只必须定期喂食的狗,如果超过设定时间(如1秒)没有“喂狗”(即重置计数器),就会强制复位整个系统。在main函数的while(1)循环里定期调用HAL_IWDG_Refresh(&hiwdg),是保证系统不死机的最后防线。
  • 应用场景:在传感器读取函数中,如果因为硬件故障导致DS18B20或DHT11无响应,程序可能会卡在while等待循环中。有了看门狗,超时后系统会复位,而不是永远死机。

5.2 扩展性思考:从本地到远程

这是项目未来可以延伸的方向:

  1. 数据记录:增加一个SPI接口的SD卡模块,将温湿度数据连同时间戳(需要RTC或从网络获取)以CSV格式定期写入文件,便于后期分析。
  2. 无线报警:增加一个ESP-01S(ESP8266)Wi-Fi模块,通过AT指令或直接编程,在报警发生时,向指定的手机APP(如Blynk、点灯科技)或服务器发送通知,甚至发送邮件。
  3. 多节点组网:使用RS-485总线或CAN总线,将多个STM32温湿度采集节点连接起来,形成一个分布式监控网络,由一个主机进行数据汇总和显示。

5.3 调试过程中的血泪教训

  1. 时序问题永远是单总线的头号敌人:最初驱动DS18B20总是失败,用HAL_Delay(1)来凑微秒延时,结果完全不对。最后用逻辑分析仪抓波形,才发现延时严重不准。务必使用精确的微秒延时函数,并在关键时序点(如等待从机应答)后读取IO口状态进行判断,而不是一味死等。
  2. 上拉电阻不能省:第一次焊接电路,DS18B20和DHT11都没反应,排查了半天才发现忘了加上拉电阻。没有上拉,总线永远处于不确定状态。
  3. 电源噪声:如果蜂鸣器工作时,传感器读数出现剧烈跳动,很可能是电源被干扰了。尝试在单片机电源入口和传感器VCC引脚处增加一个10uF和0.1uF的电容进行退耦。
  4. 代码结构规划:一开始把所有代码都堆在main.c里,后来想改显示方式或报警逻辑时牵一发而动全身。尽早进行模块化设计,把硬件驱动、业务逻辑、显示控制分开,用头文件声明接口,这会为后续调试和升级节省大量时间。
  5. 阈值设置的防抖:按键设置阈值时,必须做软件防抖处理,否则一次按下可能会被识别成多次。简单的做法是检测到按键按下后,延时10-20ms再次检测,如果仍然按下才确认为有效按键。

这个项目虽然不大,但完整地覆盖了传感器驱动、实时数据采集、人机交互、状态控制和系统稳定性设计等多个嵌入式开发的核心环节。当你亲手把它从一堆元器件变成一个有实际功能的设备,并且稳定运行起来时,那种成就感是看多少遍教程都无法比拟的。希望这份详细的拆解,能帮助你不仅“拿来就能用”,更能“用了就懂,懂了能改”。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 7:49:45

main_window.py(五):项目周期管理页面|信息化项目全流程管理系统源码逐行精讲(三十)

main_window.py(五):项目周期管理页面|信息化项目全流程管理系统源码逐行精讲(三十) 摘要:本文逐行精讲 main_window.py 中 UI 最复杂的项目周期管理页面。核心内容包括:13 列表格的构建与 QSS 样式、CycleItemDelegate 自定义委托的三态绘制与自适应对齐、基于 seq_me…

作者头像 李华
网站建设 2026/9/3 7:49:11

Python批量Web存活探测与标题提取工具:从并发请求到编码处理实战

简介&#xff1a;WebBatchRequest是一款面向网络技术初学者与个人学习者的轻量级批量探测工具&#xff0c;用于高效检测大批量网站地址的存活状态并提取HTML页面标题&#xff0c;适用于网站运维监控、开发环境验证及网络安全基础实践等场景。资源包共12个文件&#xff0c;含6个…

作者头像 李华
网站建设 2026/9/3 7:46:49

GRE over GRE特殊配置组网实现

一 组网说明与用户需求 如上图: 总部与分支1、分支2通过gre互通,但是总部与分支2互通需借助总部与分支1的通道,并且只需要总部与分支互通,分支与分支不能互通; 总部与分支1通过gre tunnel1互联互通,隧道源和目的地址为总部与分支1互联地址; 总部与分支2通过gre tunn…

作者头像 李华
网站建设 2026/9/3 7:45:09

Yolo 小白入门 51:Val 模式第一次上手——给 best.pt 做完整体检

Yolo 小白入门 51:Val 模式第一次上手——给 best.pt 做完整体检 [!NOTE] 你现在位于《Yolo 全速入门到精通【持续更新中】》的 第六章 评估错误诊断。这一篇不追求堆满参数,而是带你建立“Val完整评估”的最小可验证闭环,并能说清它在数据、模型与业务之间的位置。我们用 …

作者头像 李华
网站建设 2026/9/3 7:40:49

沥青路面缺陷检测:LabelMe工业级标注规范与养护语义落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 7:40:24

基于ThinkPHP的微商分销商城:核心架构、佣金体系与二次开发实战

简介&#xff1a;这是一套基于ThinkPHP框架开发的轻量级微商分销代理新零售商城源码&#xff0c;面向中小型电商创业者、PHP初学者及二次开发者&#xff0c;解决多级代理申请、区域权限划分与佣金自动分润等核心业务需求。资源包共含多个关键文件&#xff0c;以PHP源码为主&…

作者头像 李华