大家好,我是质点电子,在这里给大家介绍一下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 系列传感器。

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

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);  // 等待指定毫秒数后再执行下一次
}
  

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐