STM32F103C8T6 基于 HAL 库驱动 DHT11 温湿度传感器全教程
大家好,我是质点电子,在这里给大家介绍一下DHT11使用HAL库的方法,并且提供,源码大家按照步骤就可以实现,或者直接下载到电脑上就可以。
下载链接:https://www.houfaka.com/details/7C493C92
1、DHT11 传感器原理:基础知识储备
DHT11 是一款低成本、数字式温湿度一体化传感器,广泛应用于 Arduino、树莓派等开源硬件项目,以及智能家居、气象监测、工业环境控制等场景。其核心优势是单总线数字输出(无需复杂的模拟信号转换)、电路简单(仅需 3 根线即可连接),能直接输出温湿度的数字数据,降低了开发门槛。
一、核心组成与硬件结构
DHT11 传感器并非单一芯片,而是由 “湿度检测元件”“温度检测元件” 和 “专用数字信号处理芯片” 三部分集成封装而成,常见封装为 4 引脚直插式(部分为 3 引脚,省略了空脚)。

|
引脚编号(4 引脚封装) |
引脚名称 |
功能说明 |
典型连接方式 |
|
1 |
VCC |
电源输入 |
接 3.3V~5.5V 直流电源(需注意:超过 5.5V 会烧毁传感器) |
|
2 |
DATA |
单总线数据引脚 |
接单片机 I/O 口(需串联一个 4.7KΩ~10KΩ 上拉电阻,确保信号稳定) |
|
3 |
NC |
空脚 |
无功能,无需连接 |
|
4 |
GND |
接地 |
接系统地(GND),确保电源回路稳定 |
关键提醒:DATA 引脚必须串联上拉电阻—— 因为 DHT11 的数据传输依赖 “单总线协议”,上拉电阻能使引脚默认处于高电平状态,只有传感器主动拉低时才产生信号,避免信号干扰或误触发。
二、核心工作原理:温湿度检测 + 数字信号转换
DHT11 的工作流程可拆解为 “物理量检测” 和 “数字信号输出” 两步,核心是通过专用芯片将温湿度的 “模拟变化” 转化为 “数字数据”,再通过单总线协议传输给单片机。
1. 温湿度检测原理(物理层面)
DHT11 采用电阻式湿度传感器和NTC 热敏电阻分别检测湿度和温度,两者的阻值会随环境温湿度变化而改变,专用芯片通过检测阻值变化计算出实际温湿度。
湿度检测:电阻式湿度元件
湿度检测元件由 “高分子聚合物电阻” 构成,其阻值与环境相对湿度呈负相关:
环境湿度升高 → 高分子聚合物吸附空气中的水分子 → 元件导电性能增强 → 阻值降低;
环境湿度降低 → 高分子聚合物释放水分子 → 元件导电性能减弱 → 阻值升高。
专用芯片通过内部电路给湿度元件施加固定电压,检测流过元件的电流(或分压),再将 “电流 / 分压变化” 转化为对应的 “相对湿度数值”(范围:20% RH~90% RH,精度 ±5% RH)。
温度检测:NTC 热敏电阻
温度检测依赖 “负温度系数(NTC)热敏电阻”,其阻值与环境温度呈负相关:
环境温度升高 → 热敏电阻内部载流子运动加剧 → 阻值降低;
环境温度降低 → 热敏电阻内部载流子运动减缓 → 阻值升高。
专用芯片同样通过固定电压检测热敏电阻的分压,将 “分压变化” 转化为对应的 “温度数值”(范围:0℃~50℃,精度 ±2℃)。
注意:DHT11 的精度较低(仅适合对温湿度要求不高的场景),若需高精度检测(如实验室、医疗设备),需选择 DHT22(精度 ±0.5℃/±2% RH)或 SHT3x 系列传感器。

- 数据传输原理:单总线协议(关键核心)

DHT11 与单片机的通信依赖 “单总线协议”—— 即通过一根 DATA 线同时实现 “指令发送” 和 “数据接收”,通信过程由 “单片机发起请求” 和 “DHT11 响应并回传数据” 两阶段组成。
阶段 1:单片机发起 “起始信号”(请求数据)
单片机通过 DATA 引脚主动拉低电平,向 DHT11 发送 “需要数据” 的请求,具体时序如下:
单片机将 DATA 引脚拉低 至少 18ms(这是 DHT11 识别 “起始信号” 的关键时长,过短会导致传感器无响应);
单片机释放 DATA 引脚(此时上拉电阻将引脚拉回高电平),等待 DHT11 回应。

阶段 2:DHT11 发送 “响应信号”+“温湿度数据”
DHT11 检测到单片机的起始信号后,会先发送 “响应信号”,再连续输出 40 位(5 字节)的数字数据,具体时序如下:
响应信号:DHT11 主动将 DATA 引脚拉低 30μs,再拉回高电平 30μs—— 此过程是在告诉单片机 “我已准备好,可以接收数据”;
数据传输(40 位):DHT11 通过 “高电平的时长” 来表示二进制的 “0” 和 “1”(这是单总线协议的核心编码规则):
表示 “0”:低电平 50μs → 高电平 26~28μs(高电平时间短);
表示 “1”:低电平 50μs → 高电平 70μs(高电平时间长)。

阶段 3:数据解析(5 字节数据含义)
DHT11 回传的 40 位数据分为 5 个字节(1 字节 = 8 位),格式固定,需按顺序解析:
第 1 字节:湿度整数部分(如 50 → 50% RH);
第 2 字节:湿度小数部分(DHT11 小数部分固定为 0,因此此字节恒为 0);
第 3 字节:温度整数部分(如 25 → 25℃);
第 4 字节:温度小数部分(DHT11 小数部分固定为 0,因此此字节恒为 0);
第 5 字节:校验和(用于验证数据是否正确,计算规则:前 4 字节数值相加,取低 8 位作为校验和。若 “前 4 字节之和的低 8 位”≠“第 5 字节”,则数据无效,需重新读取)。
示例:若回传 5 字节为 [0x32, 0x00, 0x19, 0x00, 0x4B],解析如下:
湿度:0x32(十进制 50) + 0x00 → 50.0% RH;
温度:0x19(十进制 25) + 0x00 → 25.0℃;
校验和:0x32+0x00+0x19+0x00 = 0x4B(与第 5 字节一致,数据有效)。
#include "dht11.h"
#include "main.h"
#include <stdio.h>
#include "delay.h"
#include "ul_printf.h"
// 全局变量:可被外部访问的温湿度数据和状态
DHT11_DataTypedef dht_data;
DHT11_StatusTypedef dht_status;
/**
* @brief 设置DHT11传感器连接的GPIO引脚为输出模式
* @note 当主机需要向DHT11发送起始信号时调用此函数,
* 输出模式允许单片机主动控制总线电平(高/低)
*/
static void DHT11_SetOutputMode(void) {
// 定义GPIO初始化结构体,用于配置引脚参数
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置要操作的引脚(由宏DHT11_GPIO_PIN_Pin指定)
GPIO_InitStruct.Pin = DHT11_GPIO_PIN_Pin;
// 设置模式为推挽输出(GPIO_MODE_OUTPUT_PP)
// 推挽输出:可输出强高电平和强低电平,适合驱动外部设备或控制总线
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
// 设置引脚输出速度为高速(GPIO_SPEED_FREQ_HIGH)
// 高速模式确保电平切换响应迅速,满足DHT11对时序的严格要求
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
// 应用配置到指定的GPIO端口(由宏DHT11_GPIO_PIN_GPIO_Port指定)
HAL_GPIO_Init(DHT11_GPIO_PIN_GPIO_Port, &GPIO_InitStruct);
}
/**
* @brief 设置DHT11传感器连接的GPIO引脚为输入模式
* @note 当主机需要接收DHT11的响应信号或数据时调用此函数,
* 输入模式允许单片机检测总线电平变化(由传感器控制)
*/
static void DHT11_SetInputMode(void) {
// 定义GPIO初始化结构体,用于配置引脚参数
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置要操作的引脚(与输出模式为同一引脚)
GPIO_InitStruct.Pin = DHT11_GPIO_PIN_Pin;
// 设置模式为输入模式(GPIO_MODE_INPUT)
// 输入模式:引脚不主动驱动电平,仅用于检测外部信号
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
// 设置为无上下拉(GPIO_NOPULL)
// DHT11传感器内部已有上拉电阻,外部无需额外上下拉,避免电平冲突
GPIO_InitStruct.Pull = GPIO_NOPULL;
// 应用配置到指定的GPIO端口
HAL_GPIO_Init(DHT11_GPIO_PIN_GPIO_Port, &GPIO_InitStruct);
}
/**
* @brief 读取1字节数据
* @return 读取到的字节,错误时返回0xFF
*/
/**
* @brief 从DHT11传感器读取1字节(8位)数据
* @return uint8_t:成功时返回读取到的字节数据,通信错误时返回0xFF
* @note DHT11的数据传输采用单总线协议,每个二进制位通过高电平时长区分:
* - 逻辑0:高电平持续约26~28微秒
* - 逻辑1:高电平持续约70微秒
* 数据传输顺序为高位在前(bit7先传,bit0最后传)
*/
static uint8_t DHT11_ReadByte(void) {
uint8_t byte = 0; // 初始化字节变量,用于存储最终读取到的8位数据(初始值0)
// 循环8次,每次读取1位,组合成1字节(8位)
for (uint8_t i = 0; i < 8; i++) {
// 等待当前位的起始信号(传感器会先拉低总线约50us作为位起始)
uint32_t timeout = 0; // 超时计数器,用于防止硬件异常导致的无限等待
// 循环等待总线从低电平变为高电平(等待位起始信号结束)
// 说明:传感器发送每个位前,都会先输出50us低电平作为起始标记
while (HAL_GPIO_ReadPin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin) == GPIO_PIN_RESET) {
HAL_Delay_us(1); // 每1微秒检测一次总线电平
if (++timeout > 60) // 若超过60us仍为低电平,超出DHT11规范,判定为通信错误
return 0xFF; // 返回错误标志(0xFF)
}
// 关键时序判断:延时40us后检测电平状态
// 原理分析:
// - 若为逻辑0:高电平仅持续26~28us,40us后已恢复低电平
// - 若为逻辑1:高电平持续70us,40us后仍保持高电平
HAL_Delay_us(40);
// 读取当前总线电平状态,判断该位是0还是1
if (HAL_GPIO_ReadPin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin) == GPIO_PIN_SET) {
// 若为高电平,说明是逻辑1
// 位操作说明:(7 - i)表示从高位到低位存储
// 例如:i=0时操作bit7,i=1时操作bit6...i=7时操作bit0
// 通过按位或运算将当前位设置为1(byte初始为0,仅需设置1的位)
byte |= (1 << (7 - i));
// 等待该位的高电平结束(确保进入下一位的检测流程)
// 说明:逻辑1的高电平持续70us,需等待其结束才能检测下一位的起始信号
while (HAL_GPIO_ReadPin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin) == GPIO_PIN_SET);
}
// 若为低电平,说明是逻辑0,无需额外操作(byte对应位保持初始0)
}
// 8位全部读取完成,返回组合后的字节数据
return byte;
}
/**
* @brief 发送起始信号并等待传感器响应
* @return 响应状态
*/
/**
* @brief 向DHT11传感器发送起始信号并检测响应
* @retval DHT11_StatusTypedef: 操作结果(DHT11_OK表示成功,DHT11_ERROR_TIMEOUT表示超时)
* @note 该函数是DHT11通信的第一步,用于建立主机与传感器的通信连接
*/
static DHT11_StatusTypedef DHT11_StartSignal(void) {
// 1. 主机发送起始信号:主机拉低总线至少18ms(此处用20ms确保可靠性)
// 切换GPIO为输出模式,以便主机控制总线电平
DHT11_SetOutputMode();
// 拉低总线(输出低电平),告诉传感器准备通信
HAL_GPIO_WritePin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin, GPIO_PIN_RESET);
// 保持低电平20ms,满足DHT11对起始信号的最低时长要求
HAL_Delay(20);
// 2. 主机释放总线:拉高总线30us,等待传感器响应
// 拉高总线(输出高电平),表示主机起始信号结束
HAL_GPIO_WritePin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin, GPIO_PIN_SET);
// 保持高电平30us,这是DHT11协议规定的等待时间
HAL_Delay_us(30);
// 3. 切换为输入模式,准备接收传感器的响应信号
// 切换GPIO为输入模式,以便检测传感器发送的电平变化
DHT11_SetInputMode();
// 等待40us,给传感器足够的时间做出响应
HAL_Delay_us(40);
// 4. 检查传感器是否发出响应信号
// DHT11正常响应时会先拉低总线(输出低电平),此处检测该状态
if (HAL_GPIO_ReadPin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin) == GPIO_PIN_RESET) {
// 4.1 等待传感器拉低总线阶段结束(约80us)
uint32_t timeout = 0; // 超时计数器,防止程序陷入死循环
// 循环检测,直到总线由低电平变为高电平(拉低阶段结束)
while (HAL_GPIO_ReadPin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin) == GPIO_PIN_RESET) {
HAL_Delay_us(1); // 每1us检测一次电平状态
// 若超过100us仍为低电平,说明传感器异常(超时)
if (++timeout > 100)
return DHT11_ERROR_TIMEOUT; // 返回超时错误
}
// 4.2 等待传感器拉高总线阶段结束(约80us)
timeout = 0; // 重置超时计数器
// 循环检测,直到总线由高电平变为低电平(拉高阶段结束)
while (HAL_GPIO_ReadPin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin) == GPIO_PIN_SET) {
HAL_Delay_us(1); // 每1us检测一次电平状态
// 若超过100us仍为高电平,说明传感器异常(超时)
if (++timeout > 100)
return DHT11_ERROR_TIMEOUT; // 返回超时错误
}
// 若执行到此处,说明传感器完成了完整的响应序列(80us低 + 80us高)
return DHT11_OK; // 返回成功状态,表示通信可以继续
}
// 若未检测到传感器的低电平响应,说明传感器无应答
return DHT11_ERROR_TIMEOUT; // 返回超时错误
}
/**
* @brief 读取温湿度数据并更新到全局变量
*/
void DHT11_Read(void) {
dht_status = DHT11_StartSignal();
if (dht_status != DHT11_OK)
return;
// 读取5字节数据
uint8_t buffer[5] = {0};
for (uint8_t i = 0; i < 5; i++) {
buffer[i] = DHT11_ReadByte();
if (buffer[i] == 0xFF) {
dht_status = DHT11_ERROR_TIMEOUT;
return;
}
}
// 恢复总线为输出高电平
DHT11_SetOutputMode();
HAL_GPIO_WritePin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin, GPIO_PIN_SET);
// 校验数据
if ((buffer[0] + buffer[1] + buffer[2] + buffer[3]) % 256 != buffer[4]) {
dht_status = DHT11_ERROR_CHECKSUM;
return;
}
// 更新全局数据
dht_data.humidity_int = buffer[0];
dht_data.humidity_dec = buffer[1];
dht_data.temperature_int = buffer[2];
dht_data.temperature_dec = buffer[3];
dht_data.checksum = buffer[4];
dht_status = DHT11_OK;
}
/**
* @brief 打印温湿度数据到串口
*/
void DHT11_PrintData(void) {
DHT11_Read();
if (dht_status == DHT11_OK) {
ul_printf("读取成功:\r\n");
ul_printf("湿度:%d.%d %%RH\r\n",
dht_data.humidity_int, dht_data.humidity_dec);
ul_printf("温度:%d.%d °C\r\n",
dht_data.temperature_int, dht_data.temperature_dec);
} else if (dht_status == DHT11_ERROR_TIMEOUT) {
ul_printf("读取失败:传感器超时无响应\r\n");
} else if (dht_status == DHT11_ERROR_CHECKSUM) {
ul_printf("读取失败:数据校验和错误\r\n");
}
ul_printf("----------------------------------------\r\n");
}
/**
* @brief 初始化DHT11传感器
*/
void DHT11_Init(void) {
// 使能GPIOB时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 初始化为输出高电平
DHT11_SetOutputMode();
HAL_GPIO_WritePin(DHT11_GPIO_PIN_GPIO_Port, DHT11_GPIO_PIN_Pin, GPIO_PIN_SET);
HAL_Delay(100); // 等待传感器稳定
}
/**
* @brief 周期性读取温湿度数据的任务函数
* @param interval 读取间隔(毫秒),建议≥1000
*/
void DHT11_Task(uint32_t interval) {
// 循环体内直接使用HAL_Delay阻塞延时
DHT11_Read(); // 读取数据
DHT11_PrintData(); // 打印数据
HAL_Delay(interval); // 等待指定毫秒数后再执行下一次
}
更多推荐



所有评论(0)