ARTICLE DETAIL

资讯详情

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

基于STM32F103ZET6的DHT11单总线驱动开发与实战解析

基于STM32F103ZET6的DHT11单总线驱动开发与实战解析 简介本资源是一套基于STM32F103ZET6微控制器与DHT11数字温湿度传感器的嵌入式检测项目完整工程面向嵌入式初学者、单片机课程设计学生及物联网入门开发者解决环境温湿度实时采集、协议解析与串口通信等典型实践问题。压缩包共141个文件含33个头文件.h定义外设与传感器接口、32个C源文件.c实现GPIO时序驱动、DHT11单总线协议解析、UART数据发送及系统初始化逻辑另有.o、.d、.axf、.hex等编译中间与输出文件以及Keil MDK工程配置.uvprojx、.uvoptx、启动脚本.bat和内存映射文件.sct整体大小为3.85MB。已有7354人学习下载资源结构规范工程可直接编译烧录配套完整底层驱动与校验重试机制涵盖DHT11脉宽识别、数据校验、温度湿度阈值判断等关键实现细节是掌握STM32基础外设编程与传感器应用的高实用性学习范例。 开头先聊点实在的。DHT11这块传感器但凡玩过STM32的几乎人手一块便宜、资料多、接线简单看起来随便搞搞就能出数。但真正自己从零写驱动、用HAL库调时序、把数据稳定跑出来中间那点门道比想象中多。尤其是搭配STM32F103ZET6这种经典大容量芯片很多人第一步就卡在“GPIO模拟单总线时序”上——不是读回全0就是偶尔跳变要么就是湿度永远不变。这篇就把我从原理图到代码、从时序到实测踩过的坑一次讲清楚。这个项目适合刚学完STM32基础外设、想上手真实传感器的同学也适合准备做课程设计或小产品原型的朋友。全文围绕STM32F103ZET6和DHT11展开讲透单总线通信协议、HAL库驱动写法、数据校验与实测排错最后给几个可以直接抄的扩展方案。1. 主控与传感器选型为什么是F103ZET6配DHT11先说说这套组合的逻辑。STM32F103ZET6是STM32F1系列里的高配型号144脚封装512KB Flash64KB SRAM片上资源非常丰富5个USART、3个SPI、2个I2C、1个FSMC、2个高级定时器、3个普通定时器还有SDIO、CAN、USB等等。对绝大多数入门到中级的嵌入式项目来说这个配置属于“性能严重过剩但用着踏实”的类型。选它做温湿度检测不是因为DHT11需要这么强的算力而是因为这个板子能让你把后续的扩展屏幕显示、WiFi上传、按键菜单、多传感器融合全部跑起来不用换平台。DHT11这边很多人一上来就嫌它精度低、响应慢这其实是不公平的。温湿度检测场景分很多种实验室精密测量那是SHT30、SHT35的活但大棚环境监测、机房温湿度告警、智能家居的舒适度判断DHT11的±2℃温度和±5%RH湿度精度完全够用。而且它的优势非常明确——数字信号输出直接进GPIO不需要ADC不需要模拟信号调理电路成本几块钱坏了随便换。对于刚接触传感器通信协议的人来说DHT11的单总线协议比I2C、SPI更原始、更直观能逼着你把时序这件事彻底搞明白。选型上还有一个关键点DHT11的供电范围是3.3V到5.5VSTM32F103ZET6的GPIO是3.3V电平两者可以直接对接。如果用的是5V单片机的板子反而要在数据线上加一个电阻分压或者电平转换。这一点很多教程没强调导致有人拿着51开发板的经验往STM32上套结果逻辑电平不匹配读数飘得没法看。而在STM32上直接把DHT11的VCC接3.3V、GND接GND、DATA接任意一个GPIO我习惯用推挽输出模式就能开始干活了。再补一句关于“为什么不选DHT22”的坑。DHT22精度确实高不少但时序比DHT11复杂且价格翻好几倍。如果你是第一次调单总线建议先用DHT11把协议跑通代码结构设计好之后想换DHT22只需要改时序参数和数据处理部分驱动框架不用推倒重来。2. DHT11单总线协议的核心时序看懂这张时间图才算入门DHT11走的是单总线协议一根数据线既做发送又做接收主机和传感器之间靠严格的时间长短来区分0和1。很多人的代码读不出数据根源就是没把时序图吃透照抄的代码只知其然不知其所以然一旦环境变了比如换了引脚、换了主频就直接翻车。正常一次完整通信的流程是这样的主机把数据线拉低持续至少18ms这叫起始信号。此时DHT11被唤醒。主机释放数据线由于线上有上拉电阻STM32的GPIO内部可以开上拉电平回到高。等待20~40usDHT11开始响应先把数据线拉低80us再拉高80us表示“我准备好了”。然后DHT11连续发送40位数据8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。发完后DHT11释放总线之后如果需要再次读取得间隔1~2秒以上。这里每一个”位“的传输方式是这样的数据线先被DHT11拉低50us然后拉高。拉高的时间长短决定这一位是0还是1——高电平持续26~28us判为0持续70us左右判为1。整个读位过程主机需要在高电平区间内快速采样采早了采晚了都会误判。收到时序图先说这么多具体到STM32代码上最大的挑战不是协议本身而是”微秒级的延时“怎么实现。STM32F103ZET6最高主频72MHz一个CPU周期大约是13.9ns。HAL库自带的HAL_Delay只能做到毫秒级用来看LED闪烁没问题但DHT11的时序好多地方是几十微秒级别毫秒延时根本没法用。你当然可以用定时器做微秒延时不过最直接、最省事的办法是用一个空的for循环来凑时间。关键是要知道你的空循环大概多少遍能凑出1us这个后面在代码部分详细说。DHT11另外有一个硬性要求两次读取间隔至少要1秒最好1.5秒以上。因为传感器内部采样周期就是1秒左右你读得太频繁它上一次的数据还没更新读到的永远是旧值而且有可能让传感器进入异常状态。单总线协议本身不复杂但“没上拉电阻”这个细节害了不少人。DHT11的数据线是开漏输出的必须要外部上拉或内部上拉才能保证高电平正确传输。STM32的GPIO可以配置成内部上拉省一个电阻但是内部上拉电阻值通常在30~50k欧姆偏大在长线传输时波形边缘会变差。如果是模块化DHT11板子上一般已经带了上拉电阻如果是裸传感器自己接建议在数据线上加一颗4.7k~10k欧姆的上拉电阻到VCC这样波形干净得多。3. 用HAL库写DHT11驱动的完整思路避开的坑和直接能用的代码网上关于DHT11的代码大部分是标准库版本用HAL库反而不多见。不是说标准库不行而是现在ST官方主推HALCubeMX生成的工程模板更通用换芯片型号也方便迁移。下面这套驱动是我基于HAL库从零写的已经跑过F103ZET6稳定运行无压力。3.1 CubeMX基础配置引脚、时钟、串口打开CubeMX选择芯片STM32F103ZET6做这几步配置RCCHSE设为Crystal/Ceramic Resonator这是外部晶振板子上一般有8MHz晶振。Clock Configuration把系统时钟配到72MHzHCLK设为72MHz。APB1和APB2的分频影响外设时钟但不影响GPIO翻转速度测试按默认来就行。GPIO选一个引脚做DHT11的数据脚比如PC13注意和板上LED是否冲突模式设为GPIO_OUTPUT初始电平设为HighGPIO模式选Open Drain或者Push-Pull都行——推荐Push-Pull加内部上拉代码里自己切换输入输出方向。USART1如果你需要把温湿度打印到串口调试助手开一个串口异步模式115200-8-N-1。生成工程后在main.c的while循环之前加串口打印初始化然后就可以开始写DHT11驱动了。3.2 微秒延时的两种实现方式定时器法和空循环法强烈建议用定时器法准确且不依赖编译器优化等级。用TIM4做一个1MHz的计数定时器也就是每个tick是1usvoid DHT11_Delay_Init(void) { __HAL_RCC_TIM4_CLK_ENABLE(); TIM_HandleTypeDef htim4; htim4.Instance TIM4; htim4.Init.Period 0xFFFF; htim4.Init.Prescaler 72 - 1; // 72MHz/72 1MHz, 即1us计一次 htim4.Init.CounterMode TIM_COUNTERMODE_UP; htim4.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim4); HAL_TIM_Base_Start(htim4); } void DHT11_Delay_us(uint16_t us) { __HAL_TIM_SET_COUNTER(htim4, 0); while(__HAL_TIM_GET_COUNTER(htim4) us); }注意htim4这个句柄必须定义成全局变量不然在另一个函数里没法操作。另外Timer的时钟源是APB1APB1在72MHz主频下通常是36MHz定时器时钟还要再乘2所以Prescaler设72-1正好得到1MHz。空循环法简单但危险。我实测在-O2优化下一个空的for(i0;i8;i);大概不是1us具体要看编译器和优化等级。不同优化等级下循环耗时差异很大经常出现“Debug模式能读、Release模式全0”的情况。所以如果你图省事空循环请务必用逻辑分析仪或示波器校准别拍脑袋定循环次数。这也是为什么我后来全部改用定时器延时一劳永逸。3.3 GPIO方向切换HAL库和寄存器二选一DHT11是单总线同一根线既要输出起始信号又要读取传感器的电平所以GPIO必须在输出和输入之间来回切换。HAL库的实现方式是修改GPIO MODER寄存器或者用HAL_GPIO_Init重新初始化但后者太重每次调用要重新配置一大串结构体时序会乱。推荐用寄存器直接改#define DHT11_DATA_PIN GPIO_PIN_13 #define DHT11_DATA_PORT GPIOC #define DHT11_OUT_1() HAL_GPIO_WritePin(DHT11_DATA_PORT, DHT11_DATA_PIN, GPIO_PIN_SET) #define DHT11_OUT_0() HAL_GPIO_WritePin(DHT11_DATA_PORT, DHT11_DATA_PIN, GPIO_PIN_RESET) void DHT11_Set_Output_Mode(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_DATA_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_DATA_PORT, GPIO_InitStruct); } void DHT11_Set_Input_Mode(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_DATA_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_DATA_PORT, GPIO_InitStruct); }每次切换方向都调用HAL_GPIO_Init会重新设置整个GPIO的配置实际测试下来大约耗时几个微秒对协议来说可以接受但严格追求时序的话直接操作寄存器更快#define DHT11_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE() // 输出模式 #define DHT11_MODE_OUT() do{ \ GPIO_InitTypeDef GPIO_InitStruct {0}; \ GPIO_InitStruct.Pin DHT11_DATA_PIN; \ GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; \ GPIO_InitStruct.Pull GPIO_PULLUP; \ GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; \ HAL_GPIO_Init(DHT11_DATA_PORT, GPIO_InitStruct); \ }while(0) // 输入模式 #define DHT11_MODE_IN() do{ \ GPIO_InitTypeDef GPIO_InitStruct {0}; \ GPIO_InitStruct.Pin DHT11_DATA_PIN; \ GPIO_InitStruct.Mode GPIO_MODE_INPUT; \ GPIO_InitStruct.Pull GPIO_PULLUP; \ HAL_GPIO_Init(DHT11_DATA_PORT, GPIO_InitStruct); \ }while(0)这两种写法效果相同区别只在代码风格。我自己的工程里用的是HAL_GPIO_Init方式因为CubeMX生成的初始化结构体都在读起来清晰。如果你是做产品、对时序有极致要求可以考虑用寄存器。3.4 核心函数复位、读取响应、逐位采样先看完整流程代码。主函数里只需要调用一次DHT11_Read_Temperature_Humidity就能得到温湿度。uint8_t DHT11_Read_Byte(void) { uint8_t i, data 0; for(i 0; i 8; i) { while(HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET); // 等待50us低电平结束 DHT11_Delay_us(40); // 高电平中段采样 if(HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { data (data 1) | 0x01; // 高电平超过40us判为1 } else { data (data 1); // 高电平很短判为0 } while(HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_SET); // 等待剩余的级别结束 } return data; } uint8_t DHT11_Read_Data(uint8_t *humidity, uint8_t *temperature) { uint8_t data[5] {0}; uint8_t i; // 主机发送起始信号 DHT11_MODE_OUT(); DHT11_OUT_0(); DHT11_Delay_us(20000); // 拉低至少18ms这里用20ms更保险 DHT11_OUT_1(); DHT11_Delay_us(30); // 释放总线等待传感器响应 DHT11_MODE_IN(); // 检查传感器响应信号先低80us再高80us if(HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET) { while(HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET); // 等待低电平结束 DHT11_Delay_us(40); // 避开高电平前沿在中间段采样更准 if(HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { // 确认有高电平响应开始读取40位数据 for(i 0; i 5; i) { data[i] DHT11_Read_Byte(); } // 校验和 if((data[0] data[1] data[2] data[3]) data[4]) { *humidity data[0]; *temperature data[2]; return 0; // 成功 } } } return 1; // 失败 }这段代码有几个细节值得说第一个是起始信号拉低时间。DHT11规格书说至少18ms我直接用20ms留一点余量。有些代码写成18ms也能用但如果你的延时函数有误差还是20ms稳妥。第二个是读位时的采样点。DHT11的一位数据是低电平50us高电平26~28us或70us。我在高电平开始后延时40us再采样这样如果这一位是1高电平70us采的时候还在高电平区间如果是0高电平28us高电平已经结束了采到的是低电平。这个40us的采样点很关键放大了容易把1误判成0放小了容易把0误判成1。第三个是while死循环的风险。如果传感器没接好、线断了或者时序不对while(HAL_GPIO_ReadPin(...) GPIO_PIN_RESET)这个循环会永远卡住程序直接死在这里。实际产品中绝对要加超时保护。简单做法是给while加个计数上限比如uint16_t timeout 0; while(HAL_GPIO_ReadPin(DHT11_DATA_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET) { DHT11_Delay_us(1); if(timeout 200) return 1; // 200us超时 }这样传感器异常时程序不会卡死主循环还能继续跑只是返回一次读取失败。3.5 主函数调用与打印int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); DHT11_Delay_Init(); uint8_t hum 0, temp 0; char msg[64]; while(1) { if(DHT11_Read_Data(hum, temp) 0) { sprintf(msg, Humidity: %d.%d%% RH, Temperature: %d.%d C\r\n, hum / 10, hum % 10, temp / 10, temp % 10); HAL_UART_Transmit(huart1, (uint8_t*)msg, strlen(msg), 1000); } else { printf(DHT11 read failed\r\n); } HAL_Delay(2000); // 两次读取间隔至少2秒 } }这里有个常见的误区DHT11返回的温湿度是“整数小数”分开的字节。比如湿度整数是52、小数是0那实际湿度就是52.0%温度整数是24、小数是0实际温度就是24.0℃。注意DHT11的温湿度小数部分大部分时候是0只有在特定精度下才有非零值所以很多驱动直接忽略小数部分只用整数。但我建议保留万一你买到的DHT11精度稍好数据就能直接用。4. 实测数据与问题排查为什么你的DHT11读数总是异常代码能跑通只是第一步实际调试中遇到的问题比预想的多得多。我把这几个月里见到的、自己踩过的坑汇总一下基本覆盖了绝大多数DHT11异常现象。4.1 现象一读到的温度和湿度一直为0出现这个现象第一个查的不是代码而是接线。DHT11模块一般有3个引脚VCC、GND、DATA有些模块是4个引脚多一个NC空脚别接错。很多人在面包板上插反了VCC和GND感官上“传感器很烫”然后数据全是0。把VCC接3.3V或5VGND接GNDDATA接GPIO先确认供电。第二个原因是最低时序要求的GPIO速度。CubeMX里GPIO Speed如果设置成LOWGPIO翻转速率跟不上微秒级时序也会导致数据全0。建议把DHT11数据脚的GPIO Speed设为HIGH。第三个原因是微秒延时不准。如果你用的空循环法在编译优化等级不同的时候延时差异巨大。建议按前面说的定时器法重写或者用逻辑分析仪抓一下起始信号的实际低电平时间。4.2 现象二数据能读到但偶尔跳变比如湿度突然从50%跳到99%最典型的原因就是采样点太靠边缘。DHT11的高电平脉冲宽度本身就有一个范围1的脉宽是70us左右0的脉宽是28us左右如果你的采样延时设置在边界值附近一点点噪声就会导致误判。解决办法是把采样点尽量放在高电平的中间位置即延时40~50us处采样。另外一个因素是电源纹波。DHT11对电源稳定度有一定要求如果用开发板的3.3V供电而板子上的稳压芯片质量一般在传感器启动时电流波动可能导致数据异常。可以尝试在DHT11的VCC和GND之间加一个10uF或100nF的滤波电容实测对读数稳定性有帮助。4.3 现象三串口每隔一次才能读到一次数据交替成功和失败这种问题通常出在读时序上特别是响应信号的判断。DHT11的响应信号是拉低80us再拉高80us很多代码在拉低结束后直接进入数据读取但这时高电平还没有到来或者刚好在高电平上升沿附近稳定度不够。我的做法是在检测到低电平后等它拉高再延时40us才开始读数据。这样确保进入读位循环时数据线已经稳定在高电平的中间段后续逐位采样也相对从容。4.4 现象四长时间运行后传感器读数完全不更新DHT11长期跑容易“卡死”尤其是电源质量差、或者主机发送了不规范的起始信号时。解决办法有两个在复位起始信号前先给DHT11一个低电平“复位脉冲”拉低1ms再释放让传感器恢复。如果多次读取失败可以用GPIO输出一个短暂的拉低脉冲大约50us再释放强制传感器重置状态机。实际产品中更合理的方案是做到“三次读取失败才重新初始化”避免单次偶发故障直接导致系统上报错误数据。4.5 关于数据合法性必须加一个过滤校验和只能保证数据字节之和正确但无法防止传感器在异常状态下传回“合理但错误”的数据。比如湿度整数字节可能返回一个明显不合理的值比如200%。这种做法在教程里很少见到但实际做项目非常必要。我的做法很简单if(hum 100 temp 80) { // 数据在合理范围采信 } else { // 数据非法丢弃本次读取 }加上这个判断后线上运行的稳定性会明显提升不会出现某次异常读数直接触发告警或者控制逻辑的误动作。5. 画原理图的细节嘉立创EDA里DHT11符号的坑在看热词的时候发现不少人在问“DHT11原理图嘉立创怎么画”实际画过的都知道嘉立创EDA里DHT11的符号库有好几个版本选错了会让PCB打样回来对不上。先说明一个版本问题嘉立创EDA中DHT11一般有“传感器模块”和“裸元件”两种封装。如果你是买那种板载模块带电阻、带LED、带排针的原理图里接4根线VCC、GND、DATA、NC或者3根线VCC、GND、DATA都可以。但如果你是买裸传感器自己贴片要注意它的封装是4脚的L811或者类似的插件封装引脚定义是1脚VCC2脚DATA3脚NC4脚GND。这个排列和常见的3脚模块不一样千万别按模块的引脚顺序画进原理图不然打样回来焊上就冒烟。另外新手容易忽略的是DHT11的数据线上拉电阻。模块上一般已经集成了裸传感器推荐在原理图上加一个4.7k~10k欧姆的上拉电阻到VCC。如果画原理图时用的是嘉立创EDA的“DHT11模块”符号它可能自带了一个电阻的示意但如果你不用模块只用裸传感器一定要自己补上拉。画PCB时还有一个注意点DHT11尽量放在板边或者远离发热元件的地方。虽然DHT11本身测的是环境温湿度但旁边如果有大功率电阻、稳压芯片、电机驱动温度会被局部拉高湿度也会受影响读出来的数据就不是真实环境值了。这是我见过很多课程设计翻车的重灾区。原理图层面能说的就是这些重点不是“怎么画这个符号”而是“DHT11的电气连接和引脚定义一定要搞清楚”剩下的画图操作本身在嘉立创EDA里非常直观拖一个元件、连三根线、加上拉电阻、放置排针十分钟就能搞定。6. 进阶扩展从“能读温湿度”到“能用温湿度”读到了温湿度项目其实才走完一半。怎么把数据变成可用的东西才是这个项目的价值所在。6.1 显示方案OLED和LCD的选择最常用的搭配是0.96寸OLEDI2C接口。F103ZET6的I2C1可以直接驱动代码量不大。OLED的优点是功耗低、显示内容丰富可以同时显示温度、湿度、时间、状态。LCD1602则是很多课程设计的老选择但需要占用更多GPIO且屏幕本身不带字库要自己维护字符表开发效率低一些。手上资源充足的话建议直接上OLED。6.2 联网方案让温湿度“上云”如果想把数据传到手机或服务器F103ZET6可以通过USART接ESP8266或者用板载的以太网接口如果有。协议方面最简单的做法是MQTT用EMQX或者公共broker作为中转手机端用MQTT客户端订阅主题。这样大棚、机房、仓库都能远程看实时温湿度。在这个环节有一个很容易踩的坑DHT11的数据更新速率太慢1Hz如果以这个频率往云平台推数据对服务器来说没什么压力但很多免费MQTT broker对单个客户端的消息速率有限制短时间连续上报会被限流。建议本地做一下均值缓存每10秒或者每30秒上报一次这样既减少带宽也让数据曲线更平滑。6.3 控制联动超过阈值就报警温湿度检测的上层逻辑是控制。我的项目里做了这样一个简单联动温度超过30℃时打开继电器驱动风扇湿度低于40%时打开加湿器超过阈值同时蜂鸣器报警。F103ZET6的GPIO直接驱动继电器模块注意要光耦隔离别用GPIO直接驱动大电流继电器再用一个无源蜂鸣器接在另一个GPIO上代码就是在现有温湿度读取逻辑上加了几个if判断。这块我用的是极简的状态机思路避免在主循环里堆一堆嵌套iftypedef enum { STATE_IDLE, STATE_ALERT, STATE_FAN_ON, STATE_HUMIDIFIER_ON, } ControlState;每个状态里根据温湿度区间做切换同时带一个“状态保持时间”防止温湿度在阈值附近抖动导致继电器频繁开关。这个细节很关键继电器频繁动作不仅费电还会明显缩短寿命。6.4 低功耗优化做电池供电的温湿度节点如果你打算做便携式或无线节点F103ZET6的功耗其实偏大正常工作在50mA级别。但如果只是做实验室演示功耗问题可以忽略。真要考虑低功耗可以切换到STM32L0系列或者用F103ZET6的STOP模式加定时唤醒。不过这就是另一个话题了等把DHT11和主控玩熟之后再去做低功耗优化会容易很多。7. 一些让我印象深刻的实测细节最后写几个这次调试中印象比较深的点希望对你有用。第一DHT11的数据线在STM32上要用推挽输出不要用开漏输出。开漏输出需要外部上拉而STM32内部上拉的阻值偏大信号上升沿变缓读时序时容易出错。推挽输出直接输出高低电平配上内部上拉信号干净利落。第二读取DHT11时要把中断关掉。如果在读时序的过程中来了一个定时器中断或者串口中断哪怕只有十几微秒也会让采样点偏移导致数据错判。简单做法是在DHT11_Read_Data开头关闭全局中断读完再打开__disable_irq(); uint8_t ret DHT11_Read_Data(hum, temp); __enable_irq();注意这个操作会让整个读取过程大约5ms阻塞所有中断如果系统里有实时性要求很高的任务需要考虑用RTOS的信号量或者更复杂的驱动设计。第三DHT11的测量值在传感器刚上电的1秒内是不准的老驱动力强的传感器需要“预热”。所以程序上电后不要立刻读先等个2秒再进主循环或者第一次读取的结果直接丢弃第二次开始才显示。第四如果你用的开发板上有多个传感器共用同一个GPIO中断或者其他外设建议把DHT11单独放在一个空闲引脚上别跟按键、LED、调试口混用否则线上干扰多了DHT11的时序更容易崩。第五关于数据打印。用HAL_UART_Transmit打印字符串时如果不加超时参数在DHT11阻塞读时序的过程中串口可能会被堵住。所以建议把printf重定向到串口并用DMA或者中断方式发送而不是阻塞式发送。当然调试阶段用阻塞发送完全没有问题直到你开始做正式产品时再优化。DHT11这个传感器太常见了常见到很多人觉得它“没技术含量”。但真把它调稳、调出可以长期稳定运行的效果需要的是对时序、电平、电源、代码结构的综合理解。这些经验不会因为DHT11便宜而变得廉价后面换SHT30、换AHT21、甚至换I2C接口的传感器核心的调试思路都是相通的先搞懂时序再写驱动再实测排错最后加保护机制。希望这篇基于STM32F103ZET6的实际项目记录能让你少走几步弯路一次就把DHT11跑稳。本文还有配套的精品资源点击获取
返回列表