保姆级教程:用STM32F103C8T6驱动DHT11,从接线到串口打印温湿度一气呵成
从零玩转STM32与DHT11:手把手实现温湿度监测系统
当你第一次拿到STM32开发板和DHT11传感器时,那种既兴奋又忐忑的心情我完全理解。本文将带你完成一个完整的温湿度监测项目,从硬件连接到代码编写,再到数据读取和显示,确保你能获得"第一次成功"的成就感。不同于普通的教程,我会分享一些实际项目中容易踩的坑和调试技巧。
1. 硬件准备与连接
在开始编程之前,正确的硬件连接是项目成功的基础。你需要准备以下组件:
- STM32F103C8T6最小系统板(蓝色药丸板)
- DHT11温湿度传感器模块
- USB转TTL串口模块(如CH340G)
- 杜邦线若干(建议使用不同颜色区分功能)
关键连接步骤:
电源连接:
- 将DHT11的VCC引脚连接到STM32的3.3V电源
- 将DHT11的GND引脚连接到STM32的地线
数据线连接:
- 将DHT11的DATA引脚连接到STM32的PA1引脚(可根据需要更改)
串口连接(用于调试输出):
- 连接STM32的PA9(TX)到USB转TTL模块的RX
- 连接STM32的PA10(RX)到USB转TTL模块的TX
- 共地连接(GND到GND)
注意:DHT11模块有上拉电阻版本和无上拉电阻版本。如果是无上拉版本,需要在DATA线和VCC之间接一个4.7kΩ的上拉电阻。
常见连接错误及排查:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| DHT11完全不响应 | 电源接反或电压不足 | 检查VCC和GND连接,确保供电3.3-5V |
| 数据不稳定 | 未接上拉电阻 | 添加4.7kΩ上拉电阻 |
| 串口无输出 | TX/RX接反 | 交换TX和RX连接 |
2. 开发环境配置
工欲善其事,必先利其器。我们需要搭建完整的开发环境:
安装Keil MDK:
- 下载并安装Keil MDK-ARM最新版本
- 安装STM32F1系列设备支持包
创建新工程:
Project → New μVision Project → 选择STM32F103C8T6设备配置工程选项:
- 在"Target"选项卡中,勾选"Use MicroLIB"(便于使用printf)
- 在"C/C++"选项卡的"Define"中添加:
USE_STDPERIPH_DRIVER
添加必要库文件:
- STM32标准外设库
- 串口通信库
- 系统时钟配置
关键配置代码示例(system_stm32f10x.c):
void SystemInit(void) { // 设置系统时钟为72MHz RCC->CFGR |= RCC_CFGR_PLLMULL9; RCC->CFGR |= RCC_CFGR_PLLSRC; RCC->CR |= RCC_CR_PLLON; while(!(RCC->CR & RCC_CR_PLLRDY)); RCC->CFGR |= RCC_CFGR_SW_PLL; while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); }3. DHT11驱动实现
DHT11采用单总线协议,通信时序是关键。让我们深入理解并实现驱动代码。
3.1 理解DHT11通信协议
DHT11的通信过程分为几个阶段:
主机启动信号:
- 拉低总线至少18ms
- 然后拉高20-40μs等待DHT11响应
DHT11响应:
- 拉低总线80μs作为响应信号
- 然后拉高80μs准备发送数据
数据传输:
- 每位数据以50μs低电平开始
- 高电平持续时间决定数据位值:
- 26-28μs表示'0'
- 70μs表示'1'
数据格式:
- 40位数据包含:
- 16位湿度数据(整数+小数)
- 16位温度数据(整数+小数)
- 8位校验和
- 40位数据包含:
3.2 关键驱动代码实现
GPIO配置函数:
// 设置PA1为输出模式 void DHT11_IO_OUT(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } // 设置PA1为输入模式 void DHT11_IO_IN(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); }复位和检测函数:
// 发送复位信号 void DHT11_Rst(void) { DHT11_IO_OUT(); // 设置为输出模式 DHT11_DQ_LOW; // 拉低DQ线 Delay_ms(20); // 保持至少18ms DHT11_DQ_HIGH; // 释放总线 Delay_us(30); // 主机等待20-40μs } // 检测DHT11响应 uint8_t DHT11_Check(void) { uint8_t retry = 0; DHT11_IO_IN(); // 设置为输入模式 // 等待DHT11拉低总线 while(DHT11_DQ_READ && retry<100) { retry++; Delay_us(1); } if(retry>=100) return 1; retry = 0; // 等待DHT11拉高总线 while(!DHT11_DQ_READ && retry<100) { retry++; Delay_us(1); } if(retry>=100) return 1; return 0; }数据读取函数:
// 读取一个位 uint8_t DHT11_Read_Bit(void) { uint8_t retry = 0; while(DHT11_DQ_READ && retry<100) // 等待低电平 { retry++; Delay_us(1); } retry = 0; while(!DHT11_DQ_READ && retry<100) // 等待高电平 { retry++; Delay_us(1); } Delay_us(40); // 等待40μs后采样 return DHT11_DQ_READ ? 1 : 0; } // 读取一个字节 uint8_t DHT11_Read_Byte(void) { uint8_t i, dat = 0; for(i=0; i<8; i++) { dat <<= 1; dat |= DHT11_Read_Bit(); } return dat; } // 读取温湿度数据 uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi) { uint8_t buf[5]; uint8_t i; DHT11_Rst(); if(DHT11_Check() == 0) { for(i=0; i<5; i++) { buf[i] = DHT11_Read_Byte(); } // 校验数据 if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { *humi = buf[0]; *temp = buf[2]; return 0; // 成功 } } return 1; // 失败 }4. 系统集成与调试
现在我们将所有部分整合起来,实现完整的温湿度监测系统。
4.1 串口配置与printf重定向
为了方便调试和数据查看,我们需要配置串口并重定向printf:
// 串口初始化 void USART1_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置TX(PA9)为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置RX(PA10)为浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = baudrate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } // printf重定向 int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return ch; }4.2 主程序实现
int main(void) { uint8_t temperature = 0; uint8_t humidity = 0; // 初始化系统时钟 SystemInit(); // 初始化DHT11 DHT11_Init(); // 初始化串口 USART1_Init(115200); printf("STM32 DHT11温湿度监测系统启动...\r\n"); while(1) { if(DHT11_Read_Data(&temperature, &humidity) == 0) { printf("温度: %d℃ 湿度: %d%%\r\n", temperature, humidity); } else { printf("读取DHT11数据失败!\r\n"); } Delay_ms(2000); // 每2秒读取一次 } }4.3 常见问题与调试技巧
问题1:DHT11无响应
- 检查电源连接是否正确
- 确认上拉电阻是否接好(如有需要)
- 检查DATA线连接是否牢固
- 尝试降低通信速度(增加延时)
问题2:数据校验失败
- 确保时序精确,特别是延时时间
- 检查电源稳定性,电压波动会影响通信
- 尝试缩短DATA线长度(最好不超过20cm)
问题3:串口无输出
- 确认TX/RX连接正确
- 检查波特率设置是否匹配
- 验证printf重定向是否正确实现
调试技巧:
使用逻辑分析仪:可以直观地观察DHT11的通信时序,帮助定位问题。
分段调试:
printf("开始复位DHT11...\r\n"); DHT11_Rst(); printf("复位完成,等待响应...\r\n"); if(DHT11_Check() == 0) { printf("DHT11响应成功\r\n"); }延时微调:不同批次的DHT11可能对时序要求略有不同,可以尝试微调延时参数。
5. 项目优化与扩展
基础功能实现后,我们可以考虑以下优化和扩展方向:
5.1 提高测量精度
虽然DHT11的精度有限,但我们可以通过软件方法提高稳定性:
多次测量取平均:
#define SAMPLE_TIMES 5 uint8_t get_average_temperature() { uint8_t sum = 0; uint8_t temp, humi; for(int i=0; i<SAMPLE_TIMES; i++) { if(DHT11_Read_Data(&temp, &humi) == 0) { sum += temp; } Delay_ms(100); } return sum / SAMPLE_TIMES; }数据滤波:实现滑动平均滤波或中值滤波算法
5.2 低功耗优化
对于电池供电的应用,可以优化功耗:
间歇工作模式:
void enter_low_power_mode() { // 关闭不必要的外设 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, DISABLE); // 配置为待机模式 PWR_EnterSTANDBYMode(); }延长测量间隔:根据应用需求调整采样频率
5.3 添加显示功能
除了串口输出,可以添加OLED或LCD显示:
void show_on_oled(uint8_t temp, uint8_t humi) { OLED_Clear(); OLED_ShowString(0, 0, "Temperature:"); OLED_ShowNum(0, 2, temp, 2, 16); OLED_ShowString(0, 4, "Humidity:"); OLED_ShowNum(0, 6, humi, 2, 16); }5.4 无线传输扩展
通过添加ESP8266或蓝牙模块实现无线数据传输:
void send_via_wifi(uint8_t temp, uint8_t humi) { char buffer[50]; sprintf(buffer, "AT+CIPSEND=%d", strlen(buffer)); ESP8266_SendCmd(buffer); sprintf(buffer, "temp=%d&humi=%d", temp, humi); ESP8266_SendData(buffer); }6. 实际应用案例
让我们看一个将本项目应用于智能家居系统的实际案例:
智能植物监控系统:
硬件组成:
- STM32F103C8T6主控
- DHT11温湿度传感器
- 土壤湿度传感器
- 继电器控制的水泵
- OLED显示屏
系统逻辑:
void plant_monitor_system() { uint8_t temp, humi; uint16_t soil_moisture; while(1) { // 读取环境数据 DHT11_Read_Data(&temp, &humi); soil_moisture = read_soil_moisture(); // 显示数据 show_on_oled(temp, humi, soil_moisture); // 控制逻辑 if(soil_moisture < THRESHOLD) { pump_on(); Delay_ms(5000); // 浇水5秒 pump_off(); } Delay_ms(60000); // 每分钟检查一次 } }扩展功能:
- 通过WiFi上传数据到云平台
- 设置手机APP远程监控
- 添加光照传感器优化植物生长环境
在完成这个项目后,我发现最关键的还是对DHT11时序的精确控制。实际测试中,不同环境温度下,延时参数可能需要微调才能获得最稳定的数据。建议在正式产品中使用更高精度的传感器,如SHT30或BME280,但对于学习和原型开发,DHT11仍然是一个经济实惠的选择。
