ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32温湿度监测报警系统:从DHT11驱动到OLED显示的完整实现

STM32温湿度监测报警系统:从DHT11驱动到OLED显示的完整实现 简介本资源是一套完整的STM32嵌入式综合实验项目源码面向嵌入式初学者与课程设计实践者聚焦环境参数采集与人机交互功能实现。项目基于STM32F1系列单片机集成DHT11温湿度传感器数据读取、SSD1306驱动OLED屏幕实时显示、蜂鸣器阈值报警触发并通过串口将结构化温湿度数据含单位与时间戳稳定发送至PC端串口调试助手覆盖传感器驱动、外设配置、中断处理与串口通信等核心知识点。压缩包共225个文件含36个C源文件如OLED.c、stm32f10x_rcc.c等、36个头文件、44个编译中间文件.o、42个依赖描述.crf及Keil工程配置文件.uvprojx/.uvoptx整体6.04MB结构规范便于理解工程组织逻辑与模块划分。已有3286人学习下载提供可直接编译运行的完整工程含硬件连接说明隐含在代码注释中适合用于课设开发、毕业设计原型验证及嵌入式入门实战训练。1. 项目概述与核心价值最近在整理以前做过的嵌入式小项目翻出来一个基于STM32的温湿度监测报警系统感觉挺有意思的。这个项目麻雀虽小五脏俱全它用STM32单片机读取DHT11传感器的温湿度数据实时显示在0.96寸的OLED屏幕上当温湿度超过预设阈值时蜂鸣器会发出报警同时所有数据还会通过串口发送到电脑的串口调试助手方便我们记录和分析。这几乎是每个嵌入式初学者都会接触到的“Hello World”级综合项目但别看它简单里面涉及到的知识点非常密集从GPIO、定时器、I2C/SPI通信到串口通信、传感器协议解析一个不少。很多朋友在复现时总会卡在DHT11的时序、OLED的驱动移植或者串口数据格式上。今天我就把这个项目的完整源代码和实现思路掰开揉碎了讲清楚不仅给你能直接编译运行的代码更重要的是把每个模块的“为什么”和“踩过的坑”都分享出来让你知其然更知其所以然以后自己搭任何传感器系统都能举一反三。2. 硬件选型与电路设计思路2.1 核心控制器STM32的型号考量这个项目对MCU的性能要求不高任何一款带有基本GPIO和至少一个UART的STM32都能胜任。我手头用的是STM32F103C8T6也就是常说的“蓝色小药丸”或最小系统板它资源丰富72MHz主频、64KB Flash、20KB RAM、价格便宜、资料巨多。选择它主要是出于生态考虑无论是标准库还是HAL库示例代码都一抓一大把社区遇到问题基本都能搜到答案。对于初学者我强烈建议从F1系列开始把基础外设用熟了再挑战更复杂的F4或H7。如果你用的是其他型号比如G系列或者F0系列代码逻辑完全通用只需要在CubeMX里根据你的芯片重新配置引脚和时钟即可。注意STM32不同系列甚至同系列不同型号的引脚复用功能可能略有差异务必以你手中开发板的原理图和数据手册为准。比如有些板子的USART1_TX引脚是PA9有的可能是PB6。2.2 传感器模块为什么是DHT11DHT11是一款经典的温湿度复合数字传感器采用单总线协议通信。它的优点是价格极其低廉、接口简单只需要一根数据线、功耗低。但缺点也很明显精度一般温度±2℃湿度±5%RH、响应速度慢每次测量间隔不得小于2秒。对于教室、仓库、家居这种对精度要求不苛刻的环境监测它完全够用。如果你需要更高精度可以考虑DHT22精度更高或SHT30I2C接口精度和稳定性更好。选择DHT11作为教学项目是因为它的单总线协议是学习嵌入式时序操作的绝佳教材你能深刻理解什么是“拉高”、“拉低”、“延时等待”和“读取数据位”。DHT11电路连接要点VCC接3.3V。虽然DHT11标称工作电压3.3V-5.5V但为了与STM32的IO电平匹配避免电平转换的麻烦强烈建议统一使用3.3V。GND接地。DATA接STM32的任何一个GPIO引脚配置为上拉输入/推挽输出。我通常接PA0。这里必须接一个4.7KΩ或10KΩ的上拉电阻到VCC这是保证单总线在空闲时处于高电平的关键很多新手忘记接这个电阻导致永远读不到数据。NC空脚不用接。2.3 显示模块OLED屏幕的驱动选择我用的是一块0.96寸的OLED分辨率128x64驱动芯片是SSD1306。这种屏幕有I2C和SPI两种接口版本。为了节省IO口只需要2根线SCL和SDA我选择了I2C接口的版本。它的优点是接线简单编程方便缺点是刷新速度比SPI慢一点但对于显示温湿度这种低频更新数据完全无感。购买时一定要确认是I2C接口通常模块背面会有标识或者有四个引脚GND, VCC, SCL, SDA。OLED电路连接VCC- 3.3VGND- GNDSCL- STM32的I2C时钟线例如PB6 需配置为复用开漏输出SDA- STM32的I2C数据线例如PB7 需配置为复用开漏输出实操心得I2C通信的引脚必须配置为“开漏输出”模式并且使能芯片内部的上拉电阻或者像DHT11一样在外部接上拉电阻通常4.7KΩ。这是因为I2C总线是“线与”逻辑靠下拉为低、释放为高来工作。STM32的GPIO内部有可配置的上拉电阻在CubeMX里勾选“Pull-up”就能启用对于短距离通信内部上拉通常就够了。2.4 报警与调试模块蜂鸣器与串口蜂鸣器我用的是一种有源蜂鸣器给电就响。接法很简单正极通过一个100Ω-1KΩ的限流电阻接到STM32的一个GPIO如PC13负极接地。GPIO输出高电平时蜂鸣器响低电平时关闭。选择有源的是因为驱动简单无需编程产生PWM频率。如果想控制音调则需要无源蜂鸣器和PWM驱动。串口通信这是项目与上位机电脑交互的桥梁。我使用USART1TXPA9接USB转TTL模块的RXRXPA10接USB转TTL模块的TX共地。波特率设置为9600或115200我习惯用115200传输速度快一些。在电脑端使用“串口调试助手”软件如SSCOM、XCOM来接收数据。3. 软件架构与核心代码解析整个工程的代码结构清晰采用模块化编程方便维护和移植。核心文件包括main.c主循环调度各任务。dht11.c/hDHT11传感器驱动。oled.c/hSSD1306 OLED驱动。usart.c/h串口初始化及发送函数。buzzer.c/h蜂鸣器控制。下面我们深入最核心的几个模块。3.1 DHT11单总线协议驱动详解DHT11的通信时序是难点必须微秒级精确。STM32的HAL库提供的HAL_Delay()是毫秒延时不适用。因此我们需要用系统滴答定时器SysTick或者通用定时器TIM来实现微秒延时函数。首先实现一个微秒延时函数delay_us()// 基于SysTick实现假设系统时钟为72MHz void delay_us(uint32_t us) { uint32_t ticks; uint32_t told, tnow, tcnt 0; uint32_t reload SysTick-LOAD; // SysTick重装载值 ticks us * 72; // 72MHz下1us需要72个周期 told SysTick-VAL; // 读取当前计数值 while(1) { tnow SysTick-VAL; if(tnow ! told) { if(tnow told) tcnt told - tnow; else tcnt reload - tnow told; told tnow; if(tcnt ticks) break; } } }DHT11读取流程代码解析// 1. 主机发起开始信号拉低DATA线至少18ms然后拉高20-40us。 DHT11_IO_OUT(); // 设置PA0为输出模式 DHT11_DQ_OUT(0); // 拉低 delay_ms(20); // 拉低18ms以上这里用20ms DHT11_DQ_OUT(1); // 释放总线拉高 delay_us(30); // 等待20-40us // 2. 切换为输入模式等待DHT11响应 DHT11_IO_IN(); // 设置PA0为输入模式 // 等待DHT11拉低80us if(DHT11_DQ_IN() 0) { // 等待低电平结束80us while(DHT11_DQ_IN() 0); // 等待高电平结束80us while(DHT11_DQ_IN() 1); // 接下来开始传输40位数据... }关键点从输出模式切换到输入模式后由于上拉电阻的存在总线会自然被拉高。等待DHT11拉低的过程就是检测它是否发出了响应信号。两个while循环用于跳过DHT11回应的两个固定长度的低电平和高电平。数据位解析 每一位数据都以一个50us的低电平开始随后的高电平长度决定数据是0还是126-28us表示‘0’70us表示‘1’。for(i0; i5; i) // 读取5个字节共40位 { for(j0; j8; j) { // 等待50us低电平开始位结束 while(DHT11_DQ_IN() 0); delay_us(40); // 延时40us后采样 // 此时如果电平为高说明高电平持续时间长是‘1’ if(DHT11_DQ_IN() 1) { data[i] | (0x80 j); // 将对应位置1 // 等待该位高电平结束 while(DHT11_DQ_IN() 1); } // 如果是‘0’高电平已经结束直接进入下一位等待即可 } }避坑指南delay_us(40)这个值非常关键。它必须大于‘0’信号的高电平时间26-28us但又远小于‘1’信号的高电平时间70us。在40us时采样可以稳定地区分0和1。太早采样可能‘1’信号还没拉高太晚采样‘0’信号的高电平可能已经结束。这个参数需要根据你的主频微调。3.2 OLED显示驱动与界面设计OLED驱动代码量较大但核心是OLED_Refresh_Gram()函数它将内存中的显存GRAM通过I2C一次性刷新到屏幕。我们采用“局部刷新”策略只更新变化的部分如数字而不是全屏刷新以提高效率。温湿度显示界面设计 在main.c中我们定义一个显示函数void OLED_Show_TempHum(float temp, float hum) { char str[20]; // 显示标题 OLED_ShowString(0, 0, (uint8_t *)Temp:, 16); OLED_ShowString(0, 2, (uint8_t *)Hum :, 16); // 第2行Y坐标是2*1632像素 // 将浮点数转换为字符串保留一位小数 sprintf(str, %.1f C, temp); OLED_ShowString(48, 0, (uint8_t *)str, 16); // 在固定位置显示温度值 sprintf(str, %.1f %%, hum); // %%用于显示一个%号 OLED_ShowString(48, 2, (uint8_t *)str, 16); // 可以再添加一个简单的图标或单位 OLED_ShowString(108, 0, (uint8_t *), 16); // 温度单位 // 湿度单位已在字符串中 }注意事项OLED的ShowString函数通常要求Y坐标是行号每行8像素或16像素而X坐标是像素列。务必清楚你使用的驱动库的坐标体系。频繁调用sprintf会占用较多栈空间和CPU时间在资源紧张的MCU上可以自己写轻量级的整数转字符串函数。3.3 串口数据发送与格式化我们需要将温湿度数据打包成一段格式良好的字符串通过串口发送。格式例如Temperature:25.6C, Humidity:60.3%。这样在串口调试助手中可以清晰阅读也便于后续用脚本解析。void USART_Send_Data(float temp, float hum) { char send_buf[64]; int len; // 格式化字符串 len sprintf(send_buf, Temperature:%.1fC, Humidity:%.1f%%\r\n, temp, hum); // 使用HAL库发送 HAL_UART_Transmit(huart1, (uint8_t*)send_buf, len, 1000); // 超时时间1000ms }在main函数的循环中可以每隔2秒满足DHT11采样间隔调用一次这个函数。串口调试助手设置选择正确的COM口设备管理器里查看。波特率设置为115200与代码中huart1.Init.BaudRate一致。数据位8停止位1无校验位。勾选“自动换行”或确保发送了新行符\r\n这样每条数据都会单独显示一行。3.4 报警逻辑与蜂鸣器控制报警逻辑很简单在主循环中判断读取到的温度或湿度是否超过预设的阈值。#define TEMP_THRESHOLD_HIGH 30.0 #define HUM_THRESHOLD_HIGH 80.0 if(temp TEMP_THRESHOLD_HIGH || hum HUM_THRESHOLD_HIGH) { Buzzer_On(); // 蜂鸣器响 // 可以在OLED上显示报警图标或文字 OLED_ShowString(90, 4, (uint8_t *)ALARM!, 16); } else { Buzzer_Off(); // 蜂鸣器关闭 // 清除报警显示 OLED_ShowString(90, 4, (uint8_t *) , 16); }优化建议为了避免阈值附近的抖动导致蜂鸣器频繁开关可以加入“回差”控制。例如温度超过30度报警但直到低于28度才解除报警。这能有效防止在阈值点反复横跳。4. 工程搭建与代码移植实战4.1 使用STM32CubeMX进行基础配置选择芯片新建工程选择你的STM32型号。配置时钟在RCC中将HSE设置为“Crystal/Ceramic Resonator”。然后在Clock Configuration标签页将系统时钟源选为HSE并配置到最高频率如72MHz。配置GPIODHT11_DATA (PA0)初始化为输出模式上拉。后续在代码中动态切换输入输出。I2C1 (PB6, PB7)配置为I2C模式速度标准模式100kHz或快速模式400kHz均可。模式选择“I2C”。USART1 (PA9, PA10)配置为异步模式Asynchronous波特率115200字长8位无校验停止位1。Buzzer (PC13)配置为推挽输出模式初始电平低。生成代码设置好工程名、路径、IDEKeil MDK或STM32CubeIDE然后生成代码。4.2 将驱动代码集成到工程将编写好的dht11.c/holed.c/hbuzzer.c/h文件复制到项目的Src和Inc文件夹。然后在main.c中包含头文件#include “dht11.h” #include “oled.h” #include “buzzer.h” #include “usart.h” #include stdio.h // 用于sprintf在main函数初始化部分调用各模块初始化函数int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); OLED_Init(); // 初始化OLED DHT11_Init(); // DHT11初始化主要是GPIO模式设置 Buzzer_Init(); // 蜂鸣器初始化 OLED_Clear(); // 清屏 OLED_ShowString(0, 0, (uint8_t *)“STM32 Monitor”, 16); // 显示标题 float temperature, humidity; uint8_t dht11_data[5]; while (1) { if(DHT11_Read_Data(dht11_data) DHT11_OK) { // 解析数据DHT11数据格式湿度整数湿度小数温度整数温度小数校验和 humidity dht11_data[0] dht11_data[1] * 0.1; temperature dht11_data[2] dht11_data[3] * 0.1; // 显示到OLED OLED_Show_TempHum(temperature, humidity); // 发送到串口 USART_Send_Data(temperature, humidity); // 报警判断 if(temperature TEMP_THRESHOLD_HIGH || humidity HUM_THRESHOLD_HIGH) { Buzzer_On(); OLED_ShowString(90, 4, (uint8_t *)“ALARM!”, 16); } else { Buzzer_Off(); OLED_ShowString(90, 4, (uint8_t *)“ ”, 16); } } else { // 读取失败处理例如显示“Error” OLED_ShowString(0, 4, (uint8_t *)“DHT11 Error!”, 16); } HAL_Delay(2000); // 至少等待2秒再进行下一次读取 } }4.3 编译与下载点击编译按钮确保0错误0警告。连接ST-Link或J-Link调试器到板子的SWD接口SWCLK SWDIO。在IDE中配置好调试器点击下载/调试按钮。程序下载完成后复位或重新上电系统开始运行。5. 调试技巧与常见问题排查5.1 DHT11无响应或数据全为0这是最常见的问题排查顺序如下检查硬件连接这是第一位的确保VCC、GND接对DATA线接好上拉电阻是否已接用万用表量一下DATA引脚在空闲时的电压应该是3.3V左右。检查电源DHT11对电源纹波比较敏感。确保3.3V电源稳定。可以尝试在VCC和GND之间并联一个100nF的电容。检查时序单总线协议对时序要求苛刻。重点检查delay_us(30)和delay_us(40)这两个延时。如果你的系统主频不是72MHz需要重新计算delay_us函数里的ticks值。可以用逻辑分析仪或示波器抓取DATA线的波形与DHT11数据手册的时序图对比这是最直接的调试方法。检查代码逻辑确保主机拉低时间足够长18ms释放后等待DHT11响应的时间窗口正确。检查GPIO模式切换输出/输入是否无误。5.2 OLED屏幕不显示或显示乱码检查I2C地址SSD1306的I2C地址通常是0x78写地址或0x7A。有些模块是0x7A。可以在初始化函数OLED_Init()中尝试修改I2C_WriteByte函数中使用的地址。检查I2C引脚配置确认SCL和SDA是否配置为开漏输出Open-Drain并且使能了内部上拉或在外部接了上拉电阻。检查初始化序列SSD1306有一长串初始化命令顺序不能错。确保你的OLED_Init()函数里的命令与屏幕规格128x64匹配。网上找的驱动代码有时不兼容所有屏幕。检查数据传输可以写一个简单的测试函数只向OLED发送清屏命令0x01看屏幕是否有反应。用逻辑分析仪抓取I2C总线数据看起始信号、地址、数据、停止信号是否完整。5.3 串口调试助手收不到数据检查接线TX接RXRX接TXGND接GND。这是最容易接反的地方。检查波特率代码里设置的波特率如115200必须和串口调试助手软件里设置的完全一致。检查USB转TTL模块模块的驱动是否安装好在设备管理器中能否看到对应的COM口尝试换一个COM口或换一个模块。检查代码发送函数是否真的调用了HAL_UART_Transmit发送的缓冲区是否有数据可以在发送函数前后加一个LED翻转的代码或者用调试器单步执行看是否执行到了发送语句。检查流控制确保串口调试助手和代码中都没有启用硬件流控制RTS/CTS。5.4 蜂鸣器不响或常响区分有源/无源有源蜂鸣器给电就响用万用表直流电压档测一下当GPIO输出高电平时蜂鸣器正负极之间是否有电压差接近3.3V。无源蜂鸣器需要PWM驱动。检查限流电阻如果没有接限流电阻直接连接GPIO可能会因电流过大损坏IO口或蜂鸣器。通常接一个200Ω-1kΩ的电阻。检查GPIO输出模式是否配置为推挽输出初始电平设置是否正确报警逻辑判断条件是否写反了6. 项目优化与扩展思路这个基础框架搭建好后你可以根据自己的需求进行无限扩展这也是学习的乐趣所在。增加按键设置阈值接入几个按键通过按键来增加或降低报警的温湿度阈值并将阈值实时显示在OLED上甚至保存到STM32的Flash中实现掉电保存。使用更精确的传感器将DHT11换成SHT30或BME280它们通过I2C通信精度和稳定性更高代码需要重写驱动部分。增加无线传输接入ESP-01S WiFi模块将温湿度数据上传到云平台如阿里云、OneNET或者自己的服务器实现远程监控。这需要学习AT指令或直接开发ESP8266的SDK。实现数据记录接入一个SD卡模块将定时采集的温湿度数据以CSV格式写入文件形成简单的数据记录仪。优化显示界面使用OLED的绘图功能绘制一个简单的温湿度趋势曲线图直观展示变化。低功耗设计如果用于电池供电可以将STM32设置为休眠模式定时唤醒比如每5分钟采集一次数据显示并发送然后继续休眠大幅降低系统功耗。这个项目就像一块很好的敲门砖打通了传感器、显示、人机交互、通信这几个嵌入式开发中最常见的环节。代码我放在了常用的代码托管平台你可以直接下载、编译、烧录。最重要的是不要只满足于让它跑起来试着去修改它、扩展它在解决问题的过程中你的能力才会真正得到提升。遇到问题别怕对照上面的排查思路多用调试工具嵌入式开发的乐趣就在于此。本文还有配套的精品资源点击获取
返回列表