1. DHT11温湿度传感器初探
第一次拿到DHT11这个小家伙时,我差点被它朴素的外表给骗了。这个比指甲盖大不了多少的传感器,居然能同时测量温度和湿度!它采用单总线通信协议,只需要一个GPIO引脚就能完成数据交互,特别适合那些IO资源紧张的嵌入式项目。
DHT11内部结构其实挺有意思,拆开外壳能看到两个关键元件:一个是电阻式感湿元件,负责检测环境湿度;另一个是NTC测温元件,用来测量温度。这两个元件的数据由一个8位单片机采集处理,最终输出校准后的数字信号。实测下来,它的温度测量范围是0~50℃,精度±2℃;湿度测量范围20%~90%RH,精度±5%RH。虽然比不上实验室级传感器的精度,但对于大多数智能家居、农业大棚这类应用场景完全够用。
这里有个实际使用中的小技巧:由于DHT11每次测量后都会自动进入低功耗模式,连续读取时需要间隔至少1秒,否则容易读取失败。我曾经在一个智能花盆项目里连续读取数据,就是因为没加延时导致传感器"罢工",排查了半天才发现这个问题。
2. 硬件连接与电路设计
给DHT11设计电路时,最容易被忽略的就是那个上拉电阻。因为它的数据口采用漏极开路输出,如果不接上拉电阻,信号线就只能输出低电平或高阻态,永远没法输出高电平。根据我的经验,当供电电压为5V时,接4.7KΩ的上拉电阻最稳定。如果传输距离超过20米,可以适当减小阻值。
具体接线非常简单,DHT11只有三个有效引脚(有的版本是4个引脚,但第3脚悬空):
- VCC接3.3V-5V电源
- GND接地
- DATA接MCU的GPIO
这里有个硬件设计上的坑要特别注意:如果传感器距离MCU较远,建议在VCC和GND之间加个100nF的去耦电容。有次我在工业现场部署时,因为电源干扰导致数据异常,就是靠这个电容解决的。完整的参考电路如下:
[VCC 3.3V-5V] │ ┌┴┐ │ │ 4.7KΩ └┬┘ │ ├───── DATA │ === 100nF │ [GND]3. 单总线通信协议详解
DHT11的通信协议看似简单,但时序要求非常严格。整个通信过程可以分为三个阶段:
首先是主机发送开始信号:把数据线拉低至少18ms,然后拉高20-40μs。这个阶段相当于"敲门",告诉DHT11该干活了。这里容易出错的是延时精度,普通延时函数可能不够准确,最好用定时器实现微秒级延时。
接着是传感器响应:DHT11会先拉低80μs,再拉高80μs,然后才开始传输数据。很多初学者容易在这里翻车,看到第一个低电平就急着读取数据,结果读到一堆乱码。我的经验是用中断或轮询方式严格检测这个响应信号。
最后是数据传输阶段:每个bit都以50μs低电平开始,然后高电平的持续时间决定数据是0还是1(26-28μs表示0,70μs表示1)。40bit数据包含:
- 湿度整数(8bit) + 湿度小数(8bit)
- 温度整数(8bit) + 温度小数(8bit)
- 校验和(8bit)
校验和是前四个字节的和,用于验证数据正确性。我曾经遇到过校验失败的情况,后来发现是导线太长导致信号畸变,换成屏蔽线就解决了。
4. STM32驱动代码实现
下面以STM32 HAL库为例,分享一个经过实战检验的驱动代码。首先在CubeMX中配置一个GPIO引脚(如PA0)和定时器(TIM5):
// dht11.h typedef struct { uint8_t humi_int; // 湿度整数 uint8_t humi_dec; // 湿度小数 uint8_t temp_int; // 温度整数 uint8_t temp_dec; // 温度小数 uint8_t checksum; // 校验和 } DHT11_Data; uint8_t DHT11_Read(DHT11_Data *data);// dht11.c #include "dht11.h" #include "tim.h" #define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 // 微秒级延时函数 void delay_us(uint32_t us) { __HAL_TIM_SET_COUNTER(&htim5, 0); HAL_TIM_Base_Start(&htim5); while(__HAL_TIM_GET_COUNTER(&htim5) < us); HAL_TIM_Base_Stop(&htim5); } uint8_t DHT11_Read(DHT11_Data *data) { uint8_t buf[5] = {0}; GPIO_InitTypeDef GPIO_Init = {0}; // 主机拉低18ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); delay_us(18000); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); // 切换输入模式 GPIO_Init.Pin = DHT11_PIN; GPIO_Init.Mode = GPIO_MODE_INPUT; GPIO_Init.Pull = GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, &GPIO_Init); // 等待传感器响应 while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)); while(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)); while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)); // 读取40bit数据 for(int i=0; i<5; i++) { for(int j=0; j<8; j++) { while(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)); delay_us(40); buf[i] <<= 1; if(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)) { buf[i] |= 1; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)); } } } // 校验数据 if(buf[0] + buf[1] + buf[2] + buf[3] == buf[4]) { >DHT11_Data data; if(DHT11_Read(&data)) { printf("温度: %d℃, 湿度: %d%%\n", data.temp_int, data.humi_int); }5. 常见问题与优化技巧
在实际项目中,DHT11最常遇到三类问题:
读取失败:多数是因为时序不准确。建议用逻辑分析仪抓取波形,检查开始信号和响应信号的持续时间是否达标。有个取巧的办法是把延时时间适当加长,比如开始信号用20ms代替18ms。
数据跳变:环境变化时数据可能突变。我的解决方案是采用滑动窗口滤波:保存最近5次读数,去掉最高最低值后取平均。代码实现如下:
#define SAMPLE_SIZE 5 int filter(int new_val) { static int buf[SAMPLE_SIZE] = {0}; static int index = 0; buf[index++] = new_val; if(index >= SAMPLE_SIZE) index = 0; // 排序找最大值和最小值 int min = buf[0], max = buf[0], sum = 0; for(int i=0; i<SAMPLE_SIZE; i++) { if(buf[i] < min) min = buf[i]; if(buf[i] > max) max = buf[i]; sum += buf[i]; } return (sum - min - max) / (SAMPLE_SIZE - 2); }- 长距离传输不稳定:当传感器与MCU距离超过3米时,建议:
- 改用屏蔽线
- 降低上拉电阻阻值(如2.2KΩ)
- 在MCU端增加施密特触发器整形信号
对于需要更高精度的场景,可以考虑DHT22(AM2302),它的温度精度±0.5℃,湿度精度±2%RH,但价格也更高。还有个选择是SHT30,精度更高但需要I2C接口。
最后分享一个项目经验:在智能农业系统中,我把DHT11放在防水盒里,通过1米长的导线引出,每隔5分钟采集一次数据,配合LoRa模块上传云端,已经稳定运行两年多。关键是要做好防潮处理,传感器周围用硅胶密封,避免凝露影响读数。