文章目录
-
- 每日一句正能量
- 一、前言:为什么DHT传感器是"时序地狱"
- 二、单总线协议深度解析
-
- [2.1 通信时序全景](#2.1 通信时序全景)
-
- [阶段一:启动信号(Host → DHT)](#阶段一:启动信号(Host → DHT))
- [阶段二:传感器响应(DHT → Host)](#阶段二:传感器响应(DHT → Host))
- 阶段三:40位数据传输
- 阶段四:结束
- [2.2 F1 vs F4 的时序差异](#2.2 F1 vs F4 的时序差异)
- 三、三种位采样策略对比
-
- [策略A:固定延时采样(40μs)------ 简单但脆弱](#策略A:固定延时采样(40μs)—— 简单但脆弱)
- [策略B:脉宽测量------ 鲁棒但阻塞CPU](#策略B:脉宽测量—— 鲁棒但阻塞CPU)
- [策略C:双采样+超时保护(推荐)------ 生产级方案](#策略C:双采样+超时保护(推荐)—— 生产级方案)
- 四、完整驱动代码(F1/F4通用)
-
- [4.1 头文件 `dht_driver.h`](#4.1 头文件
dht_driver.h) - [4.2 核心实现 `dht_driver.c`](#4.2 核心实现
dht_driver.c) - [4.3 使用示例 `main.c`](#4.3 使用示例
main.c)
- [4.1 头文件 `dht_driver.h`](#4.1 头文件
- 五、超时状态机与错误恢复
-
- [5.1 状态机设计](#5.1 状态机设计)
- [5.2 错误恢复策略](#5.2 错误恢复策略)
- 六、硬件设计要点
-
- [6.1 上拉电阻](#6.1 上拉电阻)
- [6.2 线缆长度](#6.2 线缆长度)
- [6.3 电源退耦](#6.3 电源退耦)
- 七、调试技巧
-
- [7.1 逻辑分析仪抓包](#7.1 逻辑分析仪抓包)
- [7.2 软件调试](#7.2 软件调试)
- [7.3 常见故障排查](#7.3 常见故障排查)
- 八、总结

每日一句正能量
迈出脚步那一刻,难题往往迎刃而解。
绝大多数困境在头脑中被放大了。一旦你起身去做------哪怕只做最小的一步------焦虑就会让位给具体操作。更重要的是,行动会带来新信息、新反馈,原来的"死局"可能自然松动。
一、前言:为什么DHT传感器是"时序地狱"
在嵌入式开发中,DHT11和DHT22是最常见的温湿度传感器,价格亲民、接口简单。但正是这份"简单",让无数开发者在调试时摔了跟头。DHT系列采用单总线(Single-Wire)协议 ,没有独立的时钟线,所有数据通过一根DATA引脚以微秒级精度的脉冲序列传输。
从STM32F1(72MHz)迁移到F4(168MHz)时,时序问题会被进一步放大:GPIO翻转速度变化、中断延迟差异、定时器时钟源不同......任何一个环节的疏忽,都会导致读取失败、校验错误,甚至系统死锁。
本文将深入剖析DHT单总线协议的时序细节,对比F1与F4的迁移陷阱,并给出生产级的驱动代码和超时处理策略。
二、单总线协议深度解析
2.1 通信时序全景
DHT的通信分为四个阶段:启动信号 → 传感器响应 → 40位数据传输 → 结束。下图展示了完整的时序关系:

阶段一:启动信号(Host → DHT)
MCU将DATA线拉低至少1ms(DHT11建议18-20ms),然后释放(拉高)20-40μs,最后切换为输入模式等待传感器响应。
陷阱1 :F1上
GPIO_Mode_IN_FLOATING和F4上GPIO_MODE_INPUT的行为差异。F4的GPIO默认无上拉,必须显式配置GPIO_PULLUP或外接上拉电阻,否则DATA线悬空导致传感器无法拉高总线。
阶段二:传感器响应(DHT → Host)
DHT收到启动信号后,拉低DATA线80μs ,再拉高80μs,表示"我准备好了,开始发送数据"。
陷阱2 :很多教程用固定延时等待响应(如
delay_us(40)),但如果传感器未连接或响应超时,程序会死等。必须引入超时计数器。
阶段三:40位数据传输
每个数据位以50μs低电平开始,随后是高电平脉冲:
- 逻辑0 :高电平持续 26-28μs (DHT11)或 22-30μs(DHT22)
- 逻辑1 :高电平持续 70μs (DHT11)或 68-75μs(DHT22)
40位数据格式为:[湿度高8位][湿度低8位][温度高8位][温度低8位][校验和],MSB先传。
陷阱3:DHT11和DHT22的位宽不同!DHT11的湿度/温度只有整数部分(低8位为0),而DHT22是16位有符号小数(分辨率0.1)。直接复用DHT11代码读取DHT22会得到错误结果。
阶段四:结束
传输完成后,DHT拉低DATA线约50μs,然后释放总线回到高电平空闲状态。
2.2 F1 vs F4 的时序差异

| 参数 | STM32F1 (72MHz) | STM32F4 (168MHz) | 影响 |
|---|---|---|---|
| GPIO翻转延迟 | ~0.14μs | ~0.06μs | F4更快,但配置不当反而引入振铃 |
| 定时器时钟源 | APB1=72MHz | APB1=84MHz (通常/2) | 预分频器必须重新计算 |
| GPIO配置 | GPIO_Mode单一字段 |
Mode/Pull/Speed分离 |
F4需显式设置Speed为VERY_HIGH |
| 中断延迟 | 12个时钟周期 | 12个时钟周期 | 绝对时间更短,相对影响更小 |
关键迁移点 :F4的GPIO配置必须显式设置GPIO_SPEED_FREQ_VERY_HIGH,否则输出上升沿过缓,可能导致DHT误判启动信号。
三、三种位采样策略对比

策略A:固定延时采样(40μs)------ 简单但脆弱
这是网上最常见的写法:
c
while (!HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN)); // 等待上升沿
delay_us(40); // 固定延时
if (HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN)) // 采样
bit = 1;
else
bit = 0;
while (HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN)); // 等待下降沿
问题:
- 无超时保护,传感器脱落时死循环
- 40μs是DHT11和DHT22的"中间值",对温度漂移、电源波动无适应性
- 中断抢占可能导致延时偏差
策略B:脉宽测量------ 鲁棒但阻塞CPU
测量高电平持续时间,>40μs判为1,<40μs判为0:
c
while (!HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN)); // 等待上升沿
uint32_t start = __HAL_TIM_GET_COUNTER(&htim);
while (HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN)); // 等待下降沿
uint32_t width = __HAL_TIM_GET_COUNTER(&htim) - start;
bit = (width > 40) ? 1 : 0;
问题:
- 两个
while循环都无超时,任一死锁 - CPU被完全占用,RTOS环境下会导致任务饥饿
- 定时器溢出未处理(16位定时器在84MHz APB1下约780μs溢出)
策略C:双采样+超时保护(推荐)------ 生产级方案
结合策略A和B的优点,在关键时间点多次采样并引入超时:
c
/**
* @brief 读取单个位(带超时保护)
* @param timeout_us: 最大等待时间(微秒)
* @retval 0/1 或 DHT_ERR_TIMEOUT
*/
static int8_t DHT_ReadBit(uint16_t timeout_us)
{
uint16_t retry = 0;
/* 等待低电平结束(50μs低脉冲) */
while (HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN) == GPIO_PIN_RESET) {
if (++retry >= timeout_us) return DHT_ERR_TIMEOUT;
delay_us(1);
}
/* 等待上升沿后的采样窗口 */
delay_us(30); // 第一个采样点:避开上升沿毛刺
uint8_t sample1 = HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN);
delay_us(20); // 第二个采样点:50μs处
uint8_t sample2 = HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN);
/* 多数表决:两个样本中至少一个为1则判1 */
uint8_t bit = (sample1 || sample2) ? 1 : 0;
/* 等待当前位结束,防止影响下一位 */
retry = 0;
while (HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN) == GPIO_PIN_SET) {
if (++retry >= 100) return DHT_ERR_TIMEOUT; // 80μs上限
delay_us(1);
}
return bit;
}
优势:
- 噪声免疫:双采样+多数表决,抗毛刺
- 超时保护:每个等待循环都有上限
- 兼容DHT11/DHT22:30μs和50μs采样点覆盖两种传感器的0/1分界
- RTOS友好:单次位读取最长阻塞约120μs,可接受
四、完整驱动代码(F1/F4通用)
4.1 头文件 dht_driver.h
c
#ifndef __DHT_DRIVER_H
#define __DHT_DRIVER_H
#include "stm32f1xx_hal.h" /* 迁移到F4时改为 stm32f4xx_hal.h */
/* 传感器类型选择 */
typedef enum {
DHT11 = 0,
DHT22 = 1
} DHT_TypeDef;
/* 错误码 */
typedef enum {
DHT_OK = 0,
DHT_ERR_TIMEOUT = -1,
DHT_ERR_CHECKSUM = -2,
DHT_ERR_NO_RESPONSE = -3,
DHT_ERR_BUSY = -4
} DHT_StatusTypeDef;
/* 数据结构 */
typedef struct {
float temperature; /* 温度,单位°C */
float humidity; /* 湿度,单位%RH */
uint8_t valid; /* 数据有效标志 */
} DHT_DataTypeDef;
/* 外部定时器句柄声明(在main.c或tim.c中定义) */
extern TIM_HandleTypeDef htim6; /* F1用TIM3,F4用TIM6 */
/* 函数声明 */
DHT_StatusTypeDef DHT_Init(DHT_TypeDef type, GPIO_TypeDef* port, uint16_t pin);
DHT_StatusTypeDef DHT_Read(DHT_DataTypeDef* data);
const char* DHT_GetErrorString(DHT_StatusTypeDef status);
#endif /* __DHT_DRIVER_H */
4.2 核心实现 dht_driver.c
c
#include "dht_driver.h"
#include <string.h>
/* 私有宏 */
#define DHT_START_LOW_US 18000 /* DHT11: 18ms, DHT22: 1-10ms */
#define DHT_START_HIGH_US 30 /* 释放后等待时间 */
#define DHT_RESPONSE_TIMEOUT 100 /* 响应超时:100μs */
#define DHT_BIT_TIMEOUT_LOW 60 /* 位低电平超时 */
#define DHT_BIT_TIMEOUT_HIGH 80 /* 位高电平超时 */
#define DHT_READ_INTERVAL_MS 2000 /* 最小读取间隔:2s */
/* 私有变量 */
static DHT_TypeDef dht_type;
static GPIO_TypeDef* dht_port;
static uint16_t dht_pin;
static uint32_t last_read_tick = 0;
/**
* @brief 微秒级延时(基于硬件定时器)
* @note F1: TIM3@72MHz, Prescaler=71 -> 1μs/tick
* F4: TIM6@84MHz, Prescaler=83 -> 1μs/tick
*/
static void DHT_DelayUs(uint16_t us)
{
__HAL_TIM_SET_COUNTER(&htim6, 0);
while (__HAL_TIM_GET_COUNTER(&htim6) < us) {
/* 空循环,可被中断抢占 */
}
}
/**
* @brief 设置GPIO为输出模式
*/
static void DHT_SetOutput(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 根据实际端口修改 */
GPIO_InitStruct.Pin = dht_pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* F4改为 GPIO_SPEED_FREQ_VERY_HIGH */
GPIO_InitStruct.Pull = GPIO_NOPULL; /* F4建议改为 GPIO_PULLUP */
HAL_GPIO_Init(dht_port, &GPIO_InitStruct);
}
/**
* @brief 设置GPIO为输入模式(带上拉)
*/
static void DHT_SetInput(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = dht_pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP; /* 关键:必须上拉,否则总线悬空 */
HAL_GPIO_Init(dht_port, &GPIO_InitStruct);
}
/**
* @brief 发送启动信号
*/
static DHT_StatusTypeDef DHT_SendStart(void)
{
DHT_SetOutput();
/* 拉低至少18ms(DHT11)或1ms(DHT22)*/
HAL_GPIO_WritePin(dht_port, dht_pin, GPIO_PIN_RESET);
if (dht_type == DHT11) {
DHT_DelayUs(18000); /* 18ms */
} else {
DHT_DelayUs(1000); /* 1ms,DHT22要求更低 */
}
/* 释放总线 */
HAL_GPIO_WritePin(dht_port, dht_pin, GPIO_PIN_SET);
DHT_DelayUs(DHT_START_HIGH_US);
/* 切换为输入,等待响应 */
DHT_SetInput();
return DHT_OK;
}
/**
* @brief 等待传感器响应(带超时)
*/
static DHT_StatusTypeDef DHT_WaitResponse(void)
{
uint16_t retry = 0;
/* 等待DHT拉低(80μs响应低电平)*/
while (HAL_GPIO_ReadPin(dht_port, dht_pin) == GPIO_PIN_SET) {
if (++retry >= DHT_RESPONSE_TIMEOUT) {
return DHT_ERR_NO_RESPONSE;
}
DHT_DelayUs(1);
}
retry = 0;
/* 等待DHT拉高(80μs响应高电平)*/
while (HAL_GPIO_ReadPin(dht_port, dht_pin) == GPIO_PIN_RESET) {
if (++retry >= DHT_RESPONSE_TIMEOUT) {
return DHT_ERR_NO_RESPONSE;
}
DHT_DelayUs(1);
}
retry = 0;
/* 等待响应高电平结束,进入数据阶段 */
while (HAL_GPIO_ReadPin(dht_port, dht_pin) == GPIO_PIN_SET) {
if (++retry >= DHT_RESPONSE_TIMEOUT) {
return DHT_ERR_NO_RESPONSE;
}
DHT_DelayUs(1);
}
return DHT_OK;
}
/**
* @brief 读取单个位(双采样+超时)
*/
static int8_t DHT_ReadBit(void)
{
uint16_t retry = 0;
uint8_t sample1, sample2;
/* 等待低电平开始(50μs低脉冲)*/
while (HAL_GPIO_ReadPin(dht_port, dht_pin) == GPIO_PIN_SET) {
if (++retry >= DHT_BIT_TIMEOUT_LOW) return DHT_ERR_TIMEOUT;
DHT_DelayUs(1);
}
retry = 0;
/* 等待低电平结束,上升沿到来 */
while (HAL_GPIO_ReadPin(dht_port, dht_pin) == GPIO_PIN_RESET) {
if (++retry >= DHT_BIT_TIMEOUT_LOW) return DHT_ERR_TIMEOUT;
DHT_DelayUs(1);
}
/* 双采样策略:30μs和50μs */
DHT_DelayUs(30);
sample1 = HAL_GPIO_ReadPin(dht_port, dht_pin);
DHT_DelayUs(20);
sample2 = HAL_GPIO_ReadPin(dht_port, dht_pin);
/* 多数表决 */
uint8_t bit = (sample1 && sample2) ? 1 : 0;
/* 等待当前位高电平结束 */
retry = 0;
while (HAL_GPIO_ReadPin(dht_port, dht_pin) == GPIO_PIN_SET) {
if (++retry >= DHT_BIT_TIMEOUT_HIGH) return DHT_ERR_TIMEOUT;
DHT_DelayUs(1);
}
return bit;
}
/**
* @brief 读取一个字节(MSB优先)
*/
static DHT_StatusTypeDef DHT_ReadByte(uint8_t* byte)
{
uint8_t data = 0;
for (int i = 0; i < 8; i++) {
int8_t bit = DHT_ReadBit();
if (bit < 0) return (DHT_StatusTypeDef)bit; /* 错误码传递 */
data = (data << 1) | bit;
}
*byte = data;
return DHT_OK;
}
/**
* @brief 初始化DHT驱动
*/
DHT_StatusTypeDef DHT_Init(DHT_TypeDef type, GPIO_TypeDef* port, uint16_t pin)
{
dht_type = type;
dht_port = port;
dht_pin = pin;
/* 确保定时器已启动 */
if (HAL_TIM_Base_GetState(&htim6) != HAL_TIM_STATE_BUSY) {
HAL_TIM_Base_Start(&htim6);
}
/* 初始状态:输出高电平 */
DHT_SetOutput();
HAL_GPIO_WritePin(dht_port, dht_pin, GPIO_PIN_SET);
HAL_Delay(100); /* 传感器上电稳定时间 */
return DHT_OK;
}
/**
* @brief 读取温湿度数据(带完整超时和校验)
*/
DHT_StatusTypeDef DHT_Read(DHT_DataTypeDef* data)
{
uint8_t buf[5] = {0};
DHT_StatusTypeDef status;
/* 检查读取间隔(DHT要求≥2s)*/
uint32_t now = HAL_GetTick();
if (now - last_read_tick < DHT_READ_INTERVAL_MS) {
return DHT_ERR_BUSY;
}
/* 关中断,防止时序被抢占(关键!)*/
uint32_t primask = __get_PRIMASK();
__disable_irq();
/* 发送启动信号 */
status = DHT_SendStart();
if (status != DHT_OK) goto cleanup;
/* 等待响应 */
status = DHT_WaitResponse();
if (status != DHT_OK) goto cleanup;
/* 读取40位数据(5字节)*/
for (int i = 0; i < 5; i++) {
status = DHT_ReadByte(&buf[i]);
if (status != DHT_OK) goto cleanup;
}
/* 恢复中断 */
__set_PRIMASK(primask);
/* 校验和检查 */
uint8_t checksum = buf[0] + buf[1] + buf[2] + buf[3];
if (checksum != buf[4]) {
return DHT_ERR_CHECKSUM;
}
/* 解析数据 */
if (dht_type == DHT11) {
data->humidity = (float)buf[0];
data->temperature = (float)buf[2];
} else { /* DHT22 */
uint16_t hum_raw = (buf[0] << 8) | buf[1];
uint16_t temp_raw = (buf[2] << 8) | buf[3];
data->humidity = (float)hum_raw / 10.0f;
/* 温度最高位为符号位 */
if (temp_raw & 0x8000) {
data->temperature = -((float)(temp_raw & 0x7FFF) / 10.0f);
} else {
data->temperature = (float)temp_raw / 10.0f;
}
}
data->valid = 1;
last_read_tick = HAL_GetTick();
return DHT_OK;
cleanup:
__set_PRIMASK(primask);
/* 错误恢复:强制输出高电平,复位总线 */
DHT_SetOutput();
HAL_GPIO_WritePin(dht_port, dht_pin, GPIO_PIN_SET);
return status;
}
/**
* @brief 错误码转字符串
*/
const char* DHT_GetErrorString(DHT_StatusTypeDef status)
{
switch (status) {
case DHT_OK: return "OK";
case DHT_ERR_TIMEOUT: return "TIMEOUT";
case DHT_ERR_CHECKSUM: return "CHECKSUM ERROR";
case DHT_ERR_NO_RESPONSE: return "NO RESPONSE";
case DHT_ERR_BUSY: return "BUSY (read too fast)";
default: return "UNKNOWN";
}
}
4.3 使用示例 main.c
c
#include "main.h"
#include "dht_driver.h"
TIM_HandleTypeDef htim6; /* F4用TIM6,F1用TIM3 */
int main(void)
{
HAL_Init();
SystemClock_Config();
/* 初始化定时器(1μs/tick)*/
MX_TIM6_Init(); /* CubeMX生成 */
HAL_TIM_Base_Start(&htim6);
/* 初始化DHT22,接PA1 */
DHT_DataTypeDef dht_data = {0};
DHT_StatusTypeDef status = DHT_Init(DHT22, GPIOA, GPIO_PIN_1);
if (status != DHT_OK) {
printf("DHT init failed: %s\n", DHT_GetErrorString(status));
}
while (1) {
status = DHT_Read(&dht_data);
if (status == DHT_OK) {
printf("Temp: %.1f°C, Hum: %.1f%%RH\n",
dht_data.temperature, dht_data.humidity);
} else {
printf("DHT read error: %s\n", DHT_GetErrorString(status));
}
HAL_Delay(2500); /* 必须≥2s */
}
}
五、超时状态机与错误恢复

5.1 状态机设计
上图右侧展示了完整的超时状态机。每个状态都有明确的超时阈值:
| 状态 | 超时阈值 | 超时原因 |
|---|---|---|
| RESP_WAIT | 100μs | 传感器未连接/未供电 |
| RESP_LOW | 100μs | 传感器拉低后未释放 |
| RESP_HIGH | 100μs | 传感器响应异常 |
| BIT_LOW | 60μs | 位起始低电平过长(总线被占用) |
| BIT_HIGH | 80μs | 位高电平过长(噪声/传感器故障) |
5.2 错误恢复策略
当发生超时时,驱动执行以下恢复:
- 强制总线复位:将DATA线切回输出模式,拉高电平
- 等待2秒:让传感器内部状态机复位
- 返回错误码:上层应用可选择重试(建议最多3次)
- 记录错误计数:连续失败超过阈值可触发硬件复位(如重启传感器电源)
c
/* 上层应用的重试逻辑 */
DHT_StatusTypeDef DHT_ReadWithRetry(DHT_DataTypeDef* data, uint8_t max_retry)
{
DHT_StatusTypeDef status;
for (int i = 0; i < max_retry; i++) {
status = DHT_Read(data);
if (status == DHT_OK) return DHT_OK;
HAL_Delay(2000); /* 每次重试间隔2s */
}
return status; /* 返回最后一次错误 */
}
六、硬件设计要点
6.1 上拉电阻
DATA线必须外接4.7kΩ~10kΩ上拉电阻至VCC。虽然F4可配置内部上拉(约40kΩ),但阻值过大,在长线缆或高速翻转时无法提供足够的上升沿驱动能力。
6.2 线缆长度
- <30cm:直连即可
- 30cm~1m:建议使用屏蔽线,DATA与GND双绞
- >1m:必须降低上拉电阻(至3.3kΩ),并在传感器端加100nF退耦电容
- >3m:不推荐,建议改用I2C/SPI传感器(如SHT30)
6.3 电源退耦
DHT传感器在数据发送期间有突发电流(约1mA/位),若电源纹波过大,会导致时序漂移。建议在传感器VCC引脚就近放置100nF陶瓷电容。
七、调试技巧
7.1 逻辑分析仪抓包
使用Saleae/DSLogic等逻辑分析仪捕获实际波形,对比 datasheet 规格:
- 检查启动信号低电平是否≥1ms
- 测量响应低电平/高电平是否约80μs
- 测量位0高电平是否在22-30μs(DHT22)或26-28μs(DHT11)
- 测量位1高电平是否在68-75μs(DHT22)或70μs(DHT11)
7.2 软件调试
在DHT_ReadBit()中加入调试输出,打印每个位的采样值:
c
/* 调试模式宏 */
#ifdef DHT_DEBUG
printf("Bit[%d]: s1=%d, s2=%d, result=%d\n", i, sample1, sample2, bit);
#endif
7.3 常见故障排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| "NO RESPONSE" | 上拉电阻缺失/松动 | 检查4.7kΩ电阻,F4配置GPIO_PULLUP |
| "CHECKSUM ERROR" | 定时器精度不足 | 用示波器校准DHT_DelayUs() |
| 温度跳变±85°C | 数据解析错误(DHT11代码读DHT22) | 确认dht_type配置正确 |
| 随机返回0 | 读取间隔<2s | 检查HAL_GetTick()间隔 |
| RTOS下频繁超时 | 中断关闭时间太长 | 改用信号量+中断方式,或提高任务优先级 |
八、总结
从STM32F1迁移到F4驱动DHT传感器,核心挑战不在于代码量,而在于对微秒级时序的精确把控:
- GPIO配置 :F4必须显式设置
GPIO_SPEED_FREQ_VERY_HIGH和GPIO_PULLUP - 定时器校准:APB1时钟变化导致预分频器需重新计算(F1: 71, F4: 83)
- 超时保护:每个等待循环必须有上限,防止死锁
- 双采样策略:30μs+50μs双点采样,兼容DHT11/DHT22且抗噪声
- 中断管理:数据读取期间关中断,但超时后必须恢复,避免系统瘫痪
DHT传感器虽然"古老",但在成本敏感的场景中仍有不可替代的地位。掌握其单总线时序的底层原理,不仅能解决当下的驱动问题,更能培养对时序临界代码的敏感度------这是每一位嵌入式工程师的必修课。
转载自:https://blog.csdn.net/u014727709/article/details/162279385
欢迎 👍点赞✍评论⭐收藏,欢迎指正