这次我们来看一个基于 STM32 的温湿度检测系统。对于嵌入式开发者和电子爱好者来说,温湿度检测是一个经典且实用的入门项目,它融合了微控制器编程、传感器通信和数据显示等多个核心技能点。这个项目的核心在于,如何用一块 STM32单片机,搭配DHT11温湿度传感器,快速搭建一个稳定可靠的检测系统,并能在Proteus这样的仿真环境中进行验证,从而降低硬件调试的门槛。
本文将带你从零开始,完成一个完整的温湿度检测系统。我们会重点关注几个核心问题:如何用STM32读取DHT11的数据?如何在Proteus中搭建仿真电路并验证逻辑?如何将数据通过串口输出或显示在LCD上?整个过程会涉及硬件选型、软件编程、仿真调试和实际硬件测试。无论你是想完成课程设计、毕业设计,还是想巩固STM32和传感器应用的知识,这篇文章都能提供一套可直接复现的解决方案。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 核心控制器 | STM32系列微控制器(如STM32F103C8T6) |
| 传感器 | DHT11数字温湿度传感器 |
| 开发环境 | Keil MDK / STM32CubeIDE / VSCode + PlatformIO |
| 仿真工具 | Proteus 8 Professional 或更高版本 |
| 通信协议 | 单总线协议(Single-Bus) |
| 主要功能 | 采集环境温湿度数据,通过串口打印或LCD显示 |
| 输出方式 | UART串口输出、OLED/LCD显示屏、上位机软件 |
| 适合场景 | 嵌入式学习、课程设计、智能家居原型、环境监控节点 |
| 硬件门槛 | 低(核心为STM32最小系统板 + DHT11模块) |
| 软件门槛 | 中(需掌握基础C语言、STM32 HAL/标准库、Proteus基本操作) |
2. 适用场景与使用边界
这个基于STM32和DHT11的温湿度检测系统,主要适用于以下几类场景:
- 嵌入式入门学习与教学:作为学习STM32 GPIO操作、定时器、延时函数以及单总线通信协议的绝佳实践案例。通过它,可以深入理解传感器数据手册、时序图解析和底层驱动编写。
- 课程设计与毕业设计:是电子信息、物联网、自动化等相关专业学生完成课设或毕设的常见选题。可以在此基础上扩展,例如增加Wi-Fi/蓝牙模块上传数据到云端,或添加蜂鸣器、LED实现超限报警。
- 智能家居与农业监测原型:作为智能温室、仓库环境监控、智能加湿器等项目的核心传感单元,用于验证数据采集的可行性。
- 产品前期功能验证:在投入实际PCB打样前,利用Proteus仿真快速验证核心的传感器读取逻辑和单片机程序是否正确,节省时间和成本。
使用边界与注意事项:
- 传感器精度限制:DHT11是一款低成本数字传感器,其温度测量精度为±2°C,湿度精度为±5%RH。适用于对精度要求不高的场合。如需高精度测量,应考虑DHT22、SHT30等传感器。
- 响应速度:DHT11的采样周期相对较慢(≥2秒一次),不适合需要高速、连续采集的应用。
- 通信距离:单总线协议在长距离、强干扰环境下稳定性会下降,实际布线时建议传感器与MCU距离不要太远。
- 仿真与实物的差异:Proteus仿真中的DHT11模型行为是理想的,但实际硬件中,时序要求非常严格,微小的延时差异都可能导致通信失败。仿真通过后,必须在真实硬件上仔细调试时序。
- 供电与引脚:确保STM32和DHT11供电稳定(通常3.3V或5V)。连接DHT11数据线的GPIO引脚需要配置为开漏输出模式并启用上拉电阻,以符合单总线规范。
3. 环境准备与前置条件
在开始编码和仿真之前,需要准备好软硬件环境。
硬件准备(用于最终实物测试):
- STM32最小系统板:如STM32F103C8T6(蓝色小板),这是最常用且性价比高的型号。
- DHT11温湿度传感器模块:建议使用带PCB和上拉电阻的模块,使用更方便。
- 杜邦线:若干,用于连接。
- USB转TTL串口模块:用于程序下载和串口数据打印(如果开发板自带CH340等USB转串口芯片则不需要单独购买)。
- 可选:OLED显示屏(I2C接口):用于本地显示数据,提升项目的完整性。
软件准备:
- 集成开发环境(IDE):
- Keil MDK-ARM:传统且强大的ARM开发工具,需要安装对应的STM32器件包。
- STM32CubeIDE:ST官方推出的免费IDE,基于Eclipse,集成STM32CubeMX图形化配置工具,推荐新手使用。
- VSCode + PlatformIO:轻量级且现代的解决方案,插件丰富,适合喜欢自定义环境的开发者。
- STM32CubeMX:用于图形化配置STM32的时钟、引脚、外设等,生成初始化代码。即使使用Keil,也强烈建议用它来生成工程框架。
- 串口调试助手:如XCOM、SSCOM、Putty等,用于查看STM32串口打印的温湿度数据。
- Proteus 8 Professional:用于电路仿真。确保已安装,并且元件库中包含
STM32F103C6(或C8)和DHT11模型。
知识准备:
- 基础的C语言编程能力。
- 了解STM32的GPIO输入输出模式,特别是开漏输出模式。
- 理解单片机时序的概念,以及如何通过延时函数实现精确时序。
- 会阅读传感器数据手册(Datasheet),重点是通信时序图。
4. 系统设计与原理分析
4.1 DHT11传感器工作原理
DHT11采用单总线(Single-Bus)数据格式进行通信。单总线意味着只用一根数据线完成双向通信,这对时序同步要求极高。
一次完整的通信过程包含以下步骤:
- MCU发送开始信号:MCU将数据线拉低至少18ms,然后拉高20-40us,通知DHT11准备发送数据。
- DHT11响应信号:DHT11接收到开始信号后,会将数据线拉低80us,再拉高80us作为响应。
- 数据传输:随后DHT11开始发送40位数据。数据“0”的表示形式为:50us低电平 + 26-28us高电平。数据“1”的表示形式为:50us低电平 + 70us高电平。每一位数据都以50us的低电平起始,通过其后高电平的持续时间来区分0和1。
- 数据校验:40位数据包含:8位湿度整数 + 8位湿度小数 + 8位温度整数 + 8位温度小数 + 8位校验和。校验和等于前四个字节相加和的低8位。
4.2 STM32与DHT11的硬件连接
连接非常简单,仅需三根线(以模块为例):
- VCC-> 接STM32的3.3V或5V电源引脚。
- GND-> 接STM32的GND。
- DATA-> 接STM32的任意一个GPIO引脚(如PA0)。注意:该引脚需要配置为开漏输出模式,并启用内部上拉电阻,或者外部接一个4.7K-10K的上拉电阻到VCC。
4.3 软件驱动设计思路
驱动代码的核心是严格按照DHT11的时序图,通过控制GPIO引脚的电平变化和读取操作,完成开始信号、响应检测和数据位读取。
关键函数包括:
DHT11_GPIO_Init(): 初始化连接DHT11的GPIO引脚。DHT11_Start(): 单片机发送开始信号。DHT11_Check_Response(): 检测DHT11的响应信号。DHT11_Read_Bit(): 读取一个数据位(0或1)。DHT11_Read_Byte(): 调用Read_Bit8次,读取一个字节。DHT11_Read_Data(): 组织整个读取流程,读取5个字节(40位)数据,并进行校验。
5. 基于STM32CubeMX的工程创建与配置
我们以STM32CubeIDE(内含CubeMX)为例,展示如何快速搭建工程。
- 创建新工程:打开STM32CubeIDE,选择“Start new STM32 project”,在芯片选择器中输入
STM32F103C8,选中对应的型号,点击“Next”。 - 配置时钟:在“Pinout & Configuration”标签页的“System Core” -> “RCC”中,将“High Speed Clock (HSE)”设置为“Crystal/Ceramic Resonator”。
- 配置GPIO引脚:假设使用PA1连接DHT11的数据线。找到PA1引脚,点击将其模式设置为“GPIO_Output”。然后在左侧“System Core” -> “GPIO”中,点击PA1进行详细配置:
- GPIO output level: Low
- GPIO mode: Output Open Drain (开漏输出)
- GPIO Pull-up/Pull-down: Pull-up (上拉)
- Maximum output speed: Low
- 配置串口(用于打印数据):找到“USART1”,将其模式设置为“Asynchronous”。在“Pinout”视图上,USART1的TX(PA9)和RX(PA10)会自动配置。在“Parameter Settings”标签页,可以设置波特率(如115200)。
- 生成代码:点击“Project Manager”标签页,设置项目名称和路径,选择IDE为“STM32CubeIDE”,然后点击“GENERATE CODE”。系统会自动生成包含所有初始化代码的工程。
6. DHT11驱动代码实现
在生成的工程中,我们在Core/Src文件夹下新建dht11.c和dht11.h文件。
dht11.h头文件:
#ifndef __DHT11_H #define __DHT11_H #include "main.h" // 定义DHT11连接的GPIO端口和引脚 #define DHT11_GPIO_PORT GPIOA #define DHT11_GPIO_PIN GPIO_PIN_1 // 函数声明 void DHT11_GPIO_Init(void); uint8_t DHT11_Start(void); uint8_t DHT11_Read_Bit(void); uint8_t DHT11_Read_Byte(void); uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi); #endifdht11.c源文件(关键部分):
#include "dht11.h" #include "main.h" // 微秒级延时函数,需要根据系统时钟频率实现 // 这里是一个基于SysTick的简单实现示例 void DHT11_Delay_us(uint16_t us) { uint32_t ticks = us * (SystemCoreClock / 1000000) / 8; // 粗略计算,需校准 while(ticks--); } void DHT11_GPIO_Init(void) { // GPIO已在CubeMX中配置,此函数可留空或用于其他初始化 } uint8_t DHT11_Start(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 1. 主机拉低至少18ms HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET); DHT11_Delay_us(18000); // 18ms // 2. 主机拉高20-40us HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); DHT11_Delay_us(30); // 30us // 3. 配置引脚为输入模式,准备读取响应信号 GPIO_InitStruct.Pin = DHT11_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); // 4. 等待DHT11拉低响应 (80us) uint32_t timeout = 1000; while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_SET) { if (--timeout == 0) return 0; // 超时,响应失败 DHT11_Delay_us(1); } // 5. 等待DHT11拉高响应 (80us) timeout = 1000; while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_RESET) { if (--timeout == 0) return 0; DHT11_Delay_us(1); } // 响应成功 return 1; } uint8_t DHT11_Read_Bit(void) { uint32_t timeout = 1000; // 等待50us低电平起始信号结束 while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_RESET) { if (--timeout == 0) return 0xFF; // 错误 DHT11_Delay_us(1); } // 延时40us,此时若为高电平,则为‘1’,否则为‘0’ DHT11_Delay_us(40); if (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_SET) { // 等待高电平结束 while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_SET); return 1; } else { return 0; } } uint8_t DHT11_Read_Byte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) { data <<= 1; data |= DHT11_Read_Bit(); } return data; } uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi) { uint8_t buf[5]; uint8_t i; if (DHT11_Start() == 0) { return 0; // 启动失败 } for (i = 0; i < 5; i++) { buf[i] = DHT11_Read_Byte(); } // 将引脚重新设置为输出模式,为下一次通信做准备 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DHT11_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); // 校验数据 if (buf[4] == (buf[0] + buf[1] + buf[2] + buf[3])) { *humi = buf[0]; // 湿度整数部分 *temp = buf[2]; // 温度整数部分 // 注意:DHT11小数部分通常为0,可根据需要读取buf[1]和buf[3] return 1; // 读取成功 } return 0; // 校验失败 }注意:上述代码中的DHT11_Delay_us函数需要根据你的系统时钟频率进行精确校准。在实际项目中,更推荐使用定时器来产生精确的微秒延时。
7. 主程序逻辑与串口输出
在Core/Src/main.c的主循环中,我们调用DHT11驱动,并间隔2秒以上读取一次数据,通过串口打印出来。
/* 在main.c文件顶部包含头文件 */ #include "dht11.h" #include <stdio.h> /* 重定向printf到串口1 (USART1) */ #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } /* 在main函数初始化部分调用 */ DHT11_GPIO_Init(); /* 在while(1)主循环中 */ uint8_t temperature = 0; uint8_t humidity = 0; uint8_t ret = 0; while (1) { ret = DHT11_Read_Data(&temperature, &humidity); if (ret == 1) { printf("Temperature: %d C, Humidity: %d %%RH\r\n", temperature, humidity); } else { printf("DHT11 Read Failed!\r\n"); } HAL_Delay(2500); // DHT11采样周期需大于2秒 }8. Proteus仿真搭建与测试
在将程序下载到实物开发板之前,先用Proteus仿真验证逻辑的正确性,可以极大提高调试效率。
- 新建Proteus工程:打开Proteus ISIS,新建一个设计。
- 放置元件:
- 在元件库中搜索并放置
STM32F103C6(如果找不到C8,C6引脚兼容,仿真可用)。 - 搜索并放置
DHT11。 - 搜索并放置
VIRTUAL TERMINAL(虚拟终端,用于显示串口数据)。 - 搜索并放置
RES(电阻),选择PULLUP,阻值设为10k,用于给DHT11数据线上拉。 - 放置电源(
POWER)和地(GROUND)。
- 在元件库中搜索并放置
- 连接电路:
- 将DHT11的
VDD和DATA引脚通过上拉电阻连接到VCC(+5V或+3.3V)。 - 将DHT11的
GND接地。 - 将DHT11的
DATA引脚连接到STM32的PA1引脚。 - 将STM32的
PA9(USART1_TX)连接到虚拟终端的RXD引脚。 - 将虚拟终端的
GND与电路地连接。 - 为STM32和DHT11提供电源和地。
- 将DHT11的
- 配置元件:
- 双击STM32元件,在“Program File”一栏,选择你使用Keil或CubeIDE编译生成的
.hex文件(通常在工程目录的Debug或Build文件夹下)。 - 将STM32的
Crystal Frequency设置为8M(与你代码中配置的HSE一致)。 - 双击虚拟终端,将波特率(Baud Rate)设置为
115200,与代码中串口配置一致。
- 双击STM32元件,在“Program File”一栏,选择你使用Keil或CubeIDE编译生成的
- 运行仿真:点击Proteus左下角的运行按钮。如果程序正确,虚拟终端窗口将弹出,并每隔约2.5秒显示一次温湿度数据。在Proteus中,DHT11模型的温湿度是固定值或可通过双击元件手动设置,用于测试。
9. 实物硬件连接与调试
仿真通过后,就可以进行实物测试了。
- 硬件连接:使用杜邦线,将DHT11模块的VCC、GND、DATA分别连接到STM32开发板的3.3V/5V、GND和PA1引脚。将USB转TTL模块的TX、RX分别连接到STM32的PA10(RX)、PA9(TX),共地。
- 程序下载:通过ST-Link、J-Link或串口方式,将编译好的程序(或
.hex文件)下载到STM32开发板中。 - 串口监视:打开串口调试助手,选择正确的COM口(USB转TTL模块对应的端口),设置波特率115200,数据位8,停止位1,无校验。给开发板上电。
- 观察结果:如果一切正常,串口调试助手会周期性地打印出温湿度数据。用手捏住DHT11传感器,温度值应会缓慢上升;向传感器哈气,湿度值应会上升。
10. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Proteus仿真时虚拟终端无输出 | 1..hex文件路径错误或未重新编译。2. 串口引脚连接错误(TX接RXD)。 3. 波特率设置不匹配。 4. STM32芯片型号或晶振频率设置错误。 | 1. 检查Proteus中STM32的Program File配置。 2. 检查PA9(TX)是否接虚拟终端RXD。 3. 核对代码与虚拟终端的波特率。 4. 检查芯片型号和频率。 | 1. 重新编译工程,更新.hex文件路径。2. 更正连线。 3. 统一设置为115200。 4. 确保一致。 |
| 实物串口无输出或乱码 | 1. USB转TTL模块驱动未安装或COM口选错。 2. TX/RX接反。 3. 共地线未连接。 4. 单片机未正常启动(电源、复位电路)。 | 1. 检查设备管理器中串口设备。 2. 交换TX和RX连接。 3. 确保USB转TTL的GND与开发板GND相连。 4. 测量电源电压,检查复位引脚。 | 1. 安装驱动,选择正确COM口。 2. 正确连接:MCU.TX -> USB.RX; MCU.RX -> USB.TX。 3. 连接共地线。 4. 检查硬件电路。 |
| DHT11读取始终失败 | 1. 时序不精确,延时函数不准。 2. GPIO模式配置错误(未配置为开漏输出)。 3. 上拉电阻未启用或阻值不对。 4. 传感器损坏或供电不足。 5. 两次读取间隔小于2秒。 | 1. 用逻辑分析仪或示波器抓取DATA引脚波形,对比时序图。 2. 检查CubeMX中GPIO配置。 3. 检查硬件上拉或软件上拉是否启用。 4. 更换传感器,测量VCC电压。 5. 增加 HAL_Delay时间。 | 1. 使用定时器实现精确微秒延时。 2. 在CubeMX中设置为Open Drain并启用上拉。 3. 硬件连接一个4.7K-10K上拉电阻。 4. 确保供电稳定(3.3V/5V)。 5. 确保采样间隔≥2秒。 |
| 读取数据固定不变或为0 | 1. 数据校验失败,程序直接返回。 2. 读取数据位的函数逻辑有误,始终读成0或1。 3. 在响应信号检测阶段就超时退出了。 | 1. 在DHT11_Read_Data函数中打印出读取的5个字节原始数据,查看校验和。2. 单步调试或添加调试信息,检查 DHT11_Read_Bit函数的返回值。 | 1. 检查传感器连接,确保信号质量。 2. 仔细调试 DHT11_Read_Bit函数,调整判断高电平的延时时间(例如从40us调整为30us或50us),这个值对不同的MCU主频很敏感。 |
| 程序下载失败 | 1. 下载器(ST-Link/J-Link)驱动问题。 2. 下载器连接线接触不良。 3. 芯片进入睡眠模式或写保护。 | 1. 检查设备管理器。 2. 重新插拔接线,检查SWD接口(SWCLK, SWDIO)。 3. 尝试按住复位键再点击下载,或使用STM32CubeProgrammer解除保护。 | 1. 更新或重新安装下载器驱动。 2. 确保连接可靠。 3. 使用正确的下载算法和配置。 |
11. 功能扩展与进阶思路
一个基础的温湿度检测系统完成后,可以考虑以下方向进行扩展,使其更实用、更完整:
- 增加本地显示:接入一个OLED显示屏(SSD1306, I2C接口),将温湿度数据实时显示在屏幕上。这需要添加OLED的驱动代码,并在主循环中更新显示内容。
- 增加报警功能:设定温湿度的上下限阈值,当数据超限时,控制一个LED闪烁或蜂鸣器鸣响。这需要用到STM32的GPIO输出控制。
- 数据上传云端:增加ESP8266或ESP01S Wi-Fi模块,通过AT指令或SDK,将数据发送到云平台(如阿里云、腾讯云、OneNET)或私有服务器,实现远程监控。
- 低功耗设计:如果用于电池供电的场景,可以配置STM32进入停止(Stop)模式,定时唤醒读取传感器数据,发送后再次休眠,以极大降低功耗。
- 使用更高精度传感器:将DHT11替换为DHT22、SHT30、AHT20等精度更高、响应更快的传感器,只需修改对应的驱动时序即可。
- 多传感器网络:使用一个STM32连接多个DHT11(需不同GPIO引脚),或多个STM32通过RS485、CAN总线组网,实现大面积环境监测。
12. 总结与下一步
这个基于STM32和DHT11的温湿度检测系统,从原理分析、代码编写、仿真验证到实物调试,覆盖了一个嵌入式应用开发的全流程。项目的关键点在于精确的时序控制和稳定的硬件连接。Proteus仿真能快速验证逻辑,但最终必须回归硬件调试,因为真实的电磁环境和信号完整性是仿真无法完全模拟的。
建议你按照以下步骤实践:
- 先通读DHT11数据手册,理解其时序图,这是编写驱动的基础。
- 在Proteus中搭建仿真电路,使用提供的示例代码进行测试,确保逻辑正确。
- 将程序下载到真实的STM32开发板,用串口调试助手观察数据。这是最容易出问题的环节,请耐心对照“常见问题”部分进行排查,特别是延时函数的校准。
- 基础功能稳定后,再尝试扩展功能,如添加OLED显示或报警。
这个项目麻雀虽小,五脏俱全。掌握了它,你就掌握了STM32操作GPIO、处理单总线设备、进行串口通信等一系列核心技能,为后续开发更复杂的物联网设备打下坚实基础。建议收藏本文,在实践过程中遇到问题时,可以快速回溯到对应的章节查找解决方案。