ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DHT11单总线协议深度解析:时序、驱动实现与踩坑指南

DHT11单总线协议深度解析:时序、驱动实现与踩坑指南 很多人第一次接触DHT11都会觉得这玩意儿太简单了便宜、四根引脚、网上例程一抓一大把。真到自己上手做项目才发现照着抄的代码就是读不出数据不是显示湿度255%就是温度乱跳更有甚者直接卡死在读取函数里连后面的逻辑都不跑了。这背后的原因其实就藏在DHT11那条“单总线”协议里——它跟你熟悉的I2C、SPI完全不是一个思路如果你没有把它吃透代码写得再漂亮也没用。这篇文章我就从协议本质、时序细节、HAL库和标准库两种实现、以及我实际踩过的坑这几个维度把DHT11彻底掰开揉碎讲清楚让新手少走弯路让老手也能查漏补缺。1. 为什么一个温湿度传感器能让这么多人翻车DHT11的硬件电路极其简单但恰恰是这个“简单”制造了无数问题。它与MCU之间只连接一根数据线所有通信都在这根线上完成所以它被称为单总线协议。你这根线要让两个设备在不同的时刻分别占用它并且保证谁在说话、谁在听不打架这比想象中要讲究得多。1.1 单总线的真实含义一根线上的双向通行单总线的基本规则是总线空闲时为高电平通信时主机和设备交替把总线拉低。听起来跟开漏输出的I2C有点像但有个关键区别——DHT11的数据引脚不是开漏而是普通的推挽输出。这意味着当DHT11主动发送数据时它会用推挽结构强力驱动总线而当主机发送开始信号时它又要驱动这根线。两边都是推挽输出如果不讲究时序硬件上就会产生电平冲突。所以主机和DHT11的交互必须遵守一套严格的“轮流说话”机制主机先把总线拉低至少18ms释放开始信号然后释放总线接着DHT11接管总线拉低响应信号再按照位流把40位数据返回。任何一步时序偏差都会导致数据错乱。很多人的翻车点在于只看了别人给的代码把GPIO_Mode设置成推挽输出发完开始信号后想读DHT11的响应却发现引脚读到的永远是自己输出的电平。原因就是你没有在发完信号后把引脚从输出模式切换成输入模式。这一步正是新手最容易忽略、也最致命的。1.2 数据手册里藏着但没人告诉你的关键点DHT11的手册写得很简单但有几个细节新手很难注意到供电范围DHT11官方标称3.3V~5.5V都可以工作但在3.3V供电时它的信号驱动能力和上升沿表现会比5V的时候差一些。如果你的STM32是3.3V供电又给DHT11也供3.3V那么数据线建议加上拉电阻到3.3V一般用4.7kΩ~10kΩ比较稳妥。很多最小系统板上的上拉电阻是可选的默认不焊这也会导致读取不稳定。数据引脚不能悬空就算MCU内部有上拉也建议外部加一个实实在在的上拉电阻。因为MCU内部上拉通常只有30kΩ~50kΩ在高速翻转时序下引脚电平上升速度偏慢容易让时序判断出错。DHT11是一次性输出40位数据没有命令集它不像I2C设备那样可以发寄存器地址去读特定内容。你只要给它一个开始信号它就把湿度整数、湿度小数、温度整数、温度小数、校验和一口气全部吐出来一次通信直接拿全。数据更新频率极慢DHT11的采样周期大约1秒一次也就是说你读得再频繁它返回的数据也基本是1秒前的缓存值。读取间隔建议不小于1秒否则容易读到中间态或不稳定的数据。这些细节单独拎出来看都不难但组合起来就成了一堵隐形的墙把第一次接触单总线的人挡在外面。2. DHT11时序拆解五十微秒级别的博弈如果你去看那些能稳定驱动DHT11的代码会发现一个共同点它们对延时极其敏感尤其是微秒级的延时。DHT11的时序窗口很窄一旦延时偏差超过十几微秒位判定就可能出错。下面我把整个通信流程拆开详细说清楚每个阶段的时间要求与原理。2.1 通信流程总览从开始信号到40位数据一次完整的DHT11读取分三个阶段顺序不能乱主机发送开始信号主机先把数据线拉低至少18ms一般建议20ms~30ms然后释放总线并保持高电平20~40us随后准备接收。DHT11响应信号DHT11检测到开始信号后会先把总线拉低约80us再拉高约80us以此告诉主机“我准备好了我要开始发数据了”。DHT11逐位发送40位数据每1位数据都以50us低电平开始之后的高电平持续时间区分0和1。高电平持续26~28us左右表示0高电平持续70us左右表示1。你可能会有疑问为什么前面的低电平都是50us后面的高电平宽窄不同就能代表0和1这正是单总线协议的精髓——靠高电平持续时间的宽窄来编码信息而不是像串口那样靠电平绝对高低来区分。2.2 每一位0和1的判定窄高电平还是宽高电平举个实际例子。主机在读取数据位时会做这样的操作等待引脚变为低电平这是每一位数据的起始标志。等到引脚变为低电平后再等待它变为高电平。引脚变为高电平后延时40us左右然后读引脚电平。如果此时引脚还是高电平说明高电平持续时间很长判定为1如果已经变回低电平说明高电平持续时间很短判定为0。判断逻辑的本质是DHT11发出的每一位低电平都是50us但0的高电平短、1的高电平长。你在高电平出现后等40us再看电平就能区分出来——短高电平早已结束长高电平还在持续。这里有个细节值得注意延时40us这个值不能太短也不能太长。太短可能把0的高电平误判成1太长可能把1的高电平也等没了。实际使用中我一般会在主频72MHz的STM32F103上用DWT精确延时40us效果非常好。如果换成其他主频或使用不精确的延时循环需要重新校准这个40us。2.3 为什么“读不到数据”往往卡在响应信号很多人调试DHT11时会卡在响应检测这一步发完开始信号读取引脚发现就是等不到DHT11把总线拉低。常见的硬件原因有几个上拉电阻没接总线悬空受环境干扰电平不稳定。接线太长导线寄生电容影响信号上升沿DHT11的80us低电平在远端被“削”得不成样子。传感器本身损坏或接触不良。供电电压低于3.3VDHT11没有可靠复位。软件原因最常见的是主机发完开始信号后没有把GPIO切回输入模式一直在读自己的输出电平自然永远等不到低电平。我之前在一个项目里用2米长的杜邦线连接DHT11结果就是读不到响应。换了短线、加上拉电阻之后问题立刻消失。DHT11不适合长线传输如果需要远程测温应该换用I2C接口的SHT30或者DS18B20这类更抗干扰的方案。2.4 微秒延时到底该怎么写三种方案实测对比DHT11对微秒延时的要求不是特别苛刻但偏差不能太大。常见的微秒延时方案有三种我分别测试过方案精度是否占用外设受中断影响推荐度HAL_Delay毫秒级差无法微秒延时依赖Systick是不推荐普通空循环for(i0;in;i);依赖编译器优化和主频否是不推荐DWT精确延时高周期级否基本不受影响强烈推荐定时器微秒延时高是否可用但浪费定时器如果你用STM32CubeMXHAL库强烈建议直接移植一个DWT延时函数。DWT是Cortex-M内核自带的调试组件用DWT-CYCCNT计数CPU周期不需要占用任何外设精度还极高。后面我会给出完整代码。3. 基于HAL库的完整驱动实现直接在F103上跑通在这一节我以STM32F103C8T6为例结合STM32CubeMX和HAL库手把手带你把DHT11驱动写好。整个工程基于标准HAL库不依赖任何第三方封装你可以直接抄作业。3.1 CubeMX引脚配置别忘了开启上拉在STM32CubeMX中将DHT11的数据引脚配置为GPIO_Output且设置为开漏输出或推挽输出外部上拉都可以但我更推荐直接用GPIO_MODE_OUTPUT_OD开漏输出这样即使忘记切换方向也不会跟DHT11的推挽输出打架。当然开漏输出必须要有外部上拉电阻否则总线无法回到高电平。如果你不想改硬件坚持用推挽输出就必须在软件里来回切换模式。代码会稍复杂一些但也是可行的。我习惯使用开漏输出模式配置如下GPIO输出模式GPIO_MODE_OUTPUT_ODGPIO速度GPIO_SPEED_FREQ_HIGH因为要跑微秒级翻转速度不能太低引脚上拉GPIO_NOPULL外部已有上拉电阻内部就不用开了配置完成后生成工程即可。如果你用的是标准库逻辑是一样的只是寄存器操作略有区别。3.2 DWT微秒延时让时序不再飘忽DWT延时函数的原理很简单利用内核调试单元中的周期计数器CYCCNT读取CPU跑了多少个时钟周期从而实现精确延时。代码不长我直接给出#include stm32f1xx_hal.h static volatile uint32_t uwTick_DWT; void DWT_Delay_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } void DWT_Delay_us(uint32_t us) { uint32_t startTick DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - startTick) ticks); }这段代码在72MHz的STM32F1上delay_us(1)大约是72个周期性能非常稳定。默认SystemCoreClock在SystemInit里已经设置为72000000无需额外配置。需要注意的是DWT延时不响应中断打断但在单总线通信这种短时序场景下基本够用。3.3 核心读取函数从开始信号到40位采集下面是基于HAL库的完整DHT11读取函数。我在这里使用开漏输出模式所以省去了来回切换方向的麻烦但要注意在读取前必须把引脚拉高让总线处于空闲状态。#define DHT11_PIN GPIO_PIN_6 #define DHT11_PORT GPIOB static uint8_t DHT11_ReadByte(void) { uint8_t i, data 0; for (i 0; i 8; i) { // 等待低电平开始每一位都以50us低电平开头 while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); // 等待高电平开始 DWT_Delay_us(40); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { data | (0x80 i); } // 等待当前位结束 while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); } return data; } uint8_t DHT11_Read_TempHumidity(uint8_t *humidity_int, uint8_t *humidity_dec, uint8_t *temp_int, uint8_t *temp_dec) { uint8_t buf[5] {0}; uint8_t i; // 1. 主机发送开始信号拉低至少18ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); // 2. 释放总线进入接收状态 HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); DWT_Delay_us(30); // 3. 等待DHT11响应先拉低80us再拉高80us while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); // 4. 连续读取40位5个字节 for (i 0; i 5; i) { buf[i] DHT11_ReadByte(); } // 5. 校验和判断 if ((uint8_t)(buf[0] buf[1] buf[2] buf[3]) buf[4]) { *humidity_int buf[0]; *humidity_dec buf[1]; *temp_int buf[2]; *temp_dec buf[3]; return 0; // 成功 } return 1; // 校验失败 }这段代码里DHT11_ReadByte读取每一位的逻辑比较关键while (ReadPin RESET)等待每一位的低电平开始。DWT_Delay_us(40)延时40us后采样。如果采样到高电平说明是1否则是0。while (ReadPin SET)等当前位结束避免把下一位的低电平当成当前位的开始。实际测下来这个函数在F103上跑得很稳。唯一要注意的是读取过程不能被打断太久如果你的系统里有高优先级中断占用时间超过几十微秒最好在读DHT11时把中断优先级调低或者关闭中断。3.4 主循环调用间隔不能太短DHT11的读取函数返回后建议在下次读取前延时至少1秒。一方面是因为DHT11内部采样周期约1秒另一方面频繁读取会增加总线冲突概率。主程序简单示例如下int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); DWT_Delay_Init(); uint8_t hum_int, hum_dec, temp_int, temp_dec; while (1) { if (DHT11_Read_TempHumidity(hum_int, hum_dec, temp_int, temp_dec) 0) { // 数据有效可以串口打印或者显示到OLED } HAL_Delay(1000); } }有时候你会发现第一次上电读取失败第二次就成功了。这是因为DHT11上电后需要一段稳定时间大约1~2秒。建议主循环在刚开始运行时先延时2秒或者做三次重试成功一次就跳出重试循环这样能明显提高上电后的读取成功率。4. 标准库实现与HAL库的差异选型前想清楚虽然现在HAL库是主流但很多老工程师和学校教材还在用标准库。DHT11的驱动逻辑与库无关但GPIO操作和延时实现有一些差异。如果你用的是标准库核心代码可以这样写。4.1 标准库下的GPIO配置直接操作寄存器的快感标准库配置引脚不需要CubeMX直接调用库函数即可GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; // 开漏输出 GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_6); // 初始化为高电平标准库的GPIO_Mode_Out_OD对应开漏输出GPIO_Mode_IPU对应输入上拉。如果你选择推挽输出模式那么在读取前需要切换模式GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOB, GPIO_InitStructure);这种切换比较繁琐所以很多标准库例程干脆用开漏输出发完开始信号后直接读引脚电平不需要切换。开漏模式相当于“输出时拉低读的时候靠上拉电阻回到高电平”逻辑上更简洁。4.2 标准库下的微秒延时用SysTick还是DWT标准库的Delay函数通常基于SysTick但官方提供的Delay只能毫秒级。你依然可以用DWT延时也可以自己写个简单延时void delay_us(uint32_t us) { SysTick-LOAD us * 72; // 72MHz主频 SysTick-VAL 0; SysTick-CTRL SysTick_CTRL_ENABLE_Msk; while (!(SysTick-CTRL SysTick_CTRL_COUNTFLAG_Msk)); SysTick-CTRL 0; }但SysTick在HAL库中通常被HAL_Init占用如果你又用HAL_Delay又用SysTick延时会冲突。所以最稳妥的方案还是DWT这套方案在标准库下同样适用。4.3 HAL库与标准库的选型建议如果你是初学者跟着课程用HAL库那就继续用HAL生态好CubeMX图形化配置方便。如果只是做一个小功能模块手头有现成的标准库工程那就直接用标准库代码量更少、更直接。两种方案没有绝对的好坏关键是DHT11的核心时序逻辑要写对。5. 踩坑实录从“读不到数据”到“卡死死机”的完整排查链路DHT11的坑几乎每个做过的工程师都踩过。我把自己遇到过的、以及帮别人排查过的问题梳理成完整排查链路按优先级排序你可以按顺序检查。5.1 问题一上电后第一次读取永远失败这个现象很典型。代码逻辑没问题上电复位后第一帧数据就是读不出来但从第二次开始就正常。原因是DHT11上电后需要1秒左右的稳定时间期间它的内部MCU还没就绪无法响应主机的开始信号。解决方案在初始化完成后先延时2秒再首次读取或者做三次重试成功就跳出。我之前一个项目里OLED显示上电时花屏排查到最后发现是DHT11在初始化阶段把总线电平拉乱了。后来改成延时2秒再初始化花屏消失。这个“仪式感”很重要。5.2 问题二数据偶尔跳变读出来的湿度是255%如果读出来的湿度整数是255温度整数也是255说明DHT11返回的位流全是1。出现这种情况大概率是主机没有收到任何有效数据或者读取引脚悬空。常见原因上拉电阻没接引脚电平漂移。接线过长信号质量差。GPIO配置错误引脚还处在高阻输入状态。DHT11供电不稳测量时内部数据出错。排查方法是先量一下引脚静态电平空闲时应为高电平3.3V或5V。如果是低电平或乱跳说明硬件连接有问题。另外最好用示波器看触发信号没有示波器的话可以用串口打印“读取失败”重试次数来判断。5.3 问题三程序在读取DHT11时卡死主逻辑不走了这其实是最容易让人崩溃的问题。卡死的本质是某个while循环条件永远不满足。比如while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET);如果DHT11损坏或没接好引脚电平一直为高这个循环就会一直等导致整个程序卡住。解决方案给每个while循环加上超时判断。标准做法是使用一个超时计数器超过一定时长就退出循环返回错误码。uint16_t timeout 0; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { if (timeout 10000) return 1; }这种超时保护在实际工程中必不可少也是专业开发者和初学者在代码风格上最大的区别之一。DHT11本身就是低可靠性器件加上超时判断你的程序才不会因为一颗传感器挂了而整个系统瘫痪。我有一个习惯凡是外部传感器通信一律做超时保护。这比祈祷传感器永远不坏要靠谱得多。5.4 问题四ST-Link连接不上报“No STM32 Target Found”这虽然不是DHT11直接导致的故障但在调试过程中因为接线错误或引脚冲突很可能把ST-Link的SWDIO/SWCLK引脚占用了导致烧录器连不上芯片报error: no stm32 target found。我有一次就是把DHT11接到了PB14/PB13附近结果不小心跟SWD引脚短路整块板子连不上电脑。解决方案先把STM32的所有GPIO配置为复位默认值确保SWD引脚不被占用。按住复位键不松点击烧录在烧录器连接瞬间松开复位键用这个“时序窗口”恢复连接。用ST-Link Utility或STM32CubeProgrammer的“Connect under reset”模式擦除芯片。这个经验在调试STM32项目时非常实用尤其是当代码里配置了PA13/PA14这些调试引脚或者程序一跑起来就进入低功耗模式。DHT11本身跟SWD没关系但你接错线就会导致这个问题。5.5 问题五数据校验总是通不过如果读到数据但校验一直失败先不要怀疑校验算法先怀疑数据位是否移位。DHT11的数据顺序是湿度整数、湿度小数、温度整数、温度小数、校验和。如果你把湿度小数和温度整数搞反了校验和自然对不上。其次检查是不是把读取到的位序搞反了——是MSB在前还是LSB在前。DHT11是高位在前也就是data | (0x80 i)这种写法。如果写成了data | (1 i)那读出来的字节就是反的校验和必挂。5.6 问题六在FreeRTOS里读DHT11偶尔失败如果你把DHT11放进FreeRTOS任务里跑可能会遇到任务被更高优先级任务抢占导致微秒级时序被中断读取失败。解决方案把DHT11的读取单独放在一个低优先级任务里避免被频繁抢占。在读取期间用taskENTER_CRITICAL()或vTaskSuspendAll()关闭调度器。更稳妥的做法是用定时器捕获DMA这类硬实时方案但DHT11本身精度不高一般不需要这么夸张。实测下来关闭调度器后读取成功率能恢复到99%以上代价是这几十微秒内系统响应变差但影响完全可以接受。6. 数据校验、刷新频率与驱动模块化让DHT11真正融入你的工程很多教程只教你读出来不教你处理数据。这一节把校验算法、刷新频率限制和代码模块化一次性讲透。6.1 校验和算法一行代码验证数据可信度DHT11的校验规则非常简单湿度整数 湿度小数 温度整数 温度小数结果的低8位应该等于校验字节。uint8_t checksum (uint8_t)(buf[0] buf[1] buf[2] buf[3]); if (checksum buf[4]) { // 数据有效 }这个校验只能防“位翻转”错误对传感器本身的漂移无能为力。如果你要绝对精准的温湿度数据DHT11本身就不是最佳选择。它定位是“低成本、可接受精度”的入门级传感器湿度精度±5%RH温度精度±2℃所以别指望它当计量级仪器用。6.2 刷新频率别把DHT11当I2C设备猛读DHT11的采样周期是1秒所以外界读得再快数据也不会更新。有些教程让你隔50ms读一次这其实是误人子弟。短于1秒的读取会导致两种情况DHT11内部还在处理上一次测量返回旧数据。连续频繁触发开始信号可能让DHT11内部状态机错乱导致数据全是0或255。我的经验是主循环里每1.5秒读一次这样既保证数据新鲜又留有足够的余量。如果你有数据变化快的需求可以考虑DHT22AM2302采样周期2秒但精度高得多。6.3 驱动模块化把DHT11封装成独立文件当你的工程越来越大传感器、屏幕、执行器都堆在一起时先把DHT11的驱动封装成一个独立模块是非常好的习惯。我的文件结构通常是这样dht11.h dht11.c头文件里只暴露三个接口uint8_t DHT11_Init(void); uint8_t DHT11_Read_TempHumidity(uint8_t *humidity_int, uint8_t *humidity_dec, uint8_t *temp_int, uint8_t *temp_dec); void DHT11_Delay_Init(void);底层用的引脚宏定义放在头文件里方便不同板子之间移植#define DHT11_GPIO_PORT GPIOB #define DHT11_GPIO_PIN GPIO_PIN_6 #define DHT11_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()这样你换一块板子只需要改这几个宏其他代码基本不用动。我还会在dht11.c里加一个DHT11_Read_With_Retry函数里面做三次重试返回最可能正确的结果这样上层调用就省心了。6.4 进阶把DHT11的数据接到OLED、ESP8266或MQTTDHT11本身只是数据源真正有意思的是把它接到可视化设备或者云平台上。你在网上看到的各种“智能温湿度计”“宿舍环境监控”本质上都是STM32读DHT11数据本地显示到OLED/LCD或者通过WIFI模块把数据上传到服务器/手机APP我建议你自己定义一个结构体把温湿度当成一个“环境数据包”来管理typedef struct { uint8_t humidity_int; uint8_t humidity_dec; uint8_t temp_int; uint8_t temp_dec; uint8_t valid_flag; } DHT11_Data_TypeDef;然后在主循环里更新这个结构体其他模块只读取不重复访问传感器这样能避免多个模块同时读DHT11导致的时序冲突。这个思路对于多传感器系统同样适用也是从“写demo”走向“做产品”很重要的一步。7. 实测验证与数据观察如何确认你的驱动是真正稳定的驱动写完之后别急着集成到项目里先做一轮独立测试。我的测试方法是把DHT11的数据通过串口打印出来以1秒间隔连续运行48小时用肉眼或脚本观察数据是否出现突变、卡死、校验失败等情况。7.1 串口打印数据帧设计推荐打印格式固定方便用串口助手或Python脚本分析[1] H: 62% T: 26C CHK: OK [2] H: 63% T: 26C CHK: OK [3] H: 65% T: 26C CHK: ERR一旦出现CHK: ERR你可以立刻知道是校验失败不是数据偏移。连续跑几万条数据如果错误率高于千分之一就该排查硬件了。7.2 用手触摸传感器验证响应速度把DHT11捏在手里温度应该缓慢上升对着它哈气湿度应该明显提高。如果数据毫无变化或者猛地跳到满量程说明要么传感器坏了要么读取逻辑没有真正拿到有效数据。实测中我还发现DHT11的湿度响应比温度慢这是正常的别急着怀疑代码。7.3 时序的“最后一公里”与官方数据手册比对当所有数据都正常但你就是不放心时可以用逻辑分析仪抓一下波形跟数据手册的时序图比对。重点看主机拉低时间是否大于18ms。DHT11响应信号是否为80us低80us高。每一位数据的低电平是否约50us0/1的高电平是否分别约26us/70us。没有逻辑分析仪也没关系用STM32的定时器输入捕获也能测量但没必要为了一个DHT11搞这么复杂。一般能读到正确校验的数据就说明时序基本OK。8. 为什么我最终建议你从DHT11入门单总线协议DHT11不是一个高精度传感器甚至在专业级温湿度采集中会被直接淘汰。但它有一个其他传感器无法替代的价值它是理解单总线协议最好的入门教材。单总线协议在工业现场非常普遍比如很多温湿度、电池电量、开关状态检测都沿用类似的单总线设计思路。你通过DHT11掌握的时序概念、超时保护、位流拼接、校验和判断在以后驱动DS18B20、甚至一些自定义单总线设备时几乎可以无缝迁移。另外DHT11的驱动代码量适中刚好能让你体会到“时序敏感”是怎么回事又不至于像调试SDRAM那样让人绝望。如果你将来去面试嵌入式岗位能被问到“怎么读取DHT11”的概率非常高能把这个单总线问题讲清楚本身就能给面试官留下“这人是真的做过”的印象。最后分享一个我自己的小习惯每拿到一款新传感器我都会先画一张时序流程图把主机拉低、释放、设备响应、数据位判定这些节点全部标出来然后再写代码。这种“先画图、再编码”的工作方式让我少踩了很多不必要的坑。DHT11虽然简单但它教会我的这套方法论我一直用到现在。
返回列表