首页 / 资讯中心 / 文章详情

STM32F103入门实战:用DHT11温湿度传感器掌握单总线时序与GPIO

STM32F103入门实战:用DHT11温湿度传感器掌握单总线时序与GPIO ★ FEATURED ARTICLE
刚开始接触嵌入式的人问我的第一句话十有八九是“我应该从哪块板子开始”我的答案这三四年一直没变过先上STM32F1系列。并不是说它性能有多猛、功能有多新而是它实在太适合作为“第一块认真学的单片机”了。基于Cortex-M3内核、主频最高72MHz、外设覆盖面广、价格便宜到可以放心折腾再加上从入门教程到竞赛代码到量产项目全网资料多到你想避都避不开。而最近被反复提起的DHT11温湿度传感器恰好是F1系列单片机上最经典的实战组合——用它来理解时序、GPIO、超时保护这些概念几乎是嵌入式入门最顺的一条路。这篇文章我就从F1系列本身开始讲配合DHT11这个具体场景把选型、建工程、写驱动、排故障这些环节完整走一遍。1. 为什么STM32F1系列依然值得上手先搞懂它强在哪很多人会有个疑问现在F4、H7都这么便宜了G0、L4这些新系列也一大堆为什么还要回头学一个十几年前的F1这个问题其实挺关键想清楚它你后面学任何单片机都不会跑偏。1.1 Cortex-M3带来的硬实力STM32F1系列最核心的型号F103内核是基于ARM Cortex-M3架构最高工作频率72MHz。这个规格放到今天看当然不算夸张但对一个入门者或者大多数中小型嵌入式项目来说性能是绰绰有余的。Cortex-M3是ARM在M0/M0之上的经典内核支持Thumb-2指令集、单周期乘法、硬件除法指令处理逻辑运算和数值计算时效率很高。相比老一代8051或者其他8位机F103在处理复杂协议栈、浮点运算辅助、任务调度这些场景时完全是两个维度的体验。硬件上还有很多让开发变舒服的设计比如NVIC中断控制器每个外设都能用中断方式工作不必一直轮询浪费CPU比如位带操作可以对一个bit单独做置位和清零控制GPIO时特别直观再比如DMA可以把串口、SPI、ADC的数据搬运工作交给硬件CPU只需要处理结果。这些能力放到入门阶段虽然不会全用到但学会了它们以后换F4、H7等更高端的系列会发现思路是通用的无非是外设更多、频率更高、多了一些新特性。F1系列的供电范围通常是2.0V到3.6V内部集成RC振荡器、PLL锁相环、多个定时器、多个USART、I2C、SPI、ADC等常用外设。也就是说做一套带屏幕显示、按键输入、传感器采集、串口通信、PWM电机控制的小项目一块F103芯片就能全包省去了很多外部扩展芯片的麻烦。1.2 什么项目适合F1选它之前先过一遍这几点不是说所有项目都适合用F1每个人在立项时应先做个快速评估。我自己总结了几条判断标准项目是否需要丰富的通信接口。比如同时要用到两路串口、一路SPI给屏幕、一路I2C接传感器F103的多个USART和硬件SPI/I2C可以直接应付。是否需要复杂的浮点运算或DSP。如果要做音频滤波、大量浮点矩阵运算F1的Cortex-M3没有硬件FPU纯软件算会慢不少这时候上F4或H7更合适。普通的温湿度读取、ADC采样、PID控制F1完全没问题。成本和供货稳定性。F1系列出货量巨大市面上兼容品和国产替代型号很多成本很低学生党、小批量产品非常愿意选它。资料和学习曲线。这一条其实很关键。对新手来说F1的教程量、例程数量是其他系列无法比的。遇到问题一搜就有答案这个隐性价值远比性能参数重要。如果你只是做一个小型传感器数据采集节点、智能家居控制器、教学实验板、竞赛小车控制板F1系列都是很稳的选择。如果项目要跑复杂的RTOS图形界面、做海量数据运算那F1会捉襟见肘也就不用勉强了。1.3 F1与新一代M0/M4的取舍这里我多说两句选型上的取舍因为太多人卡在这一步。F1系列虽然老但它的定位是“通用增强型”比M0系列多出来的不只是主频更重要的是外设数量更多定时器、更多USART、更多GPIO、更完整的中断系统。在一些场合你可能会想G0系列或者L4系列能不能替代F1当然能但它们各自的侧重点不同。G0系列主打低成本低功耗外设裁剪得更狠L4系列在低功耗上有优势但价格更高适合电池供电产品。F1最典型的优势就是“均衡”性能比M0强外设比M0全价格比L4便宜加上大量现成代码导致它始终是很多产品的“保底选择”。很多年前我在一个项目里需要用STM32采集多路模拟量并做串口协议上报团队里有人坚持用最新系列有人坚持用F103C8T6。最后评估下来还是选了F103因为项目对功耗不敏感对成本敏感需要三路串口和几十路普通IOF103的库存和参考设计一抓一大把开发周期能缩短一半。这件事给我的启发是芯片选型不是选参数最漂亮的而是选最合适的。2. 型号命名、封装与选型拿到芯片先别急着写代码STM32F1系列内部也不是铁板一块看型号看得懂才能找到最适合自己那块。曾经有个学员拿了一块板子兴冲冲来问“是不是F103都能跑同一个程序”结果板子上的丝印是STM32F103RCT6他自己买的板子是STM32F103C8T6程序烧进去直接卡死——当然不完全是因为Flash大小但型号差异确实会影响选择启动文件、链接脚本和外设配置。2.1 看懂STM32F103C8T6这一串字符以最常见的STM32F103C8T6为例拆开来看STM32系列品牌前缀表示ST公司基于ARM内核的32位MCU。F1其中1表示产品子系列F1是最通用的增强型系列。103代表增强型外设配置较为完整。同族还有101基本型、102USB基本型、105/107带USB OTG和CAN等。C引脚数C是48引脚R是64引脚V是100引脚Z是144引脚。8Flash容量8表示64KBC表示256KBE表示512KB。T封装形式T是LQFPH是BGAU是UFQFPN等。6温度等级6表示-40到85°C7表示-40到105°C。所以“C8T6”翻译过来就是48引脚、64KB Flash、LQFP封装的工业级芯片。市面上很多经典的“最小系统板”和“Blue Pill”就是这颗芯片便宜、能跑基本外设、引脚数量对大多数传感器和屏幕来说也够用。如果想要更大Flash和更多引脚常见选择就是RCT664引脚/256KB和ZET6144引脚/512KB很多开发板厂家用ZET6来做带屏幕、带网络、带各种扩展接口的综合实验板。2.2 不同封装下的管脚分配与复用陷阱选封装时一定要看的不仅是“够不够用”还要看管脚冲突。F1系列不少引脚是多复用功能引脚比如PA9/PA10默认就是USART1_TX/RXPA2/PA3可以用来做USART2或ADC等。用STM32CubeMX或者直接查数据手册的“Alternate function mapping”表格能看出每个引脚能复用成哪些外设这是写代码前必须做的一件事。更常见的坑是调试接口占用。F103的PA13、PA14、PA15、PB3、PB4这几个引脚默认是SWD和JTAG调试接口。如果你初始化时不注意把PA15当普通GPIO用直接配置成输出会导致调试器连接不稳定甚至每次烧录都要按复位键碰运气。正确做法是如果需要用到这几个引脚应该在初始化GPIO时钟前或初始化时关闭JTAG只保留SWD模式使用复用重映射功能然后才能当普通IO使用。我见过很多人在这一步卡了整整一天最后发现就是PA15一直在跟调试器抢控制权。另外不同封装的电源脚、地脚和晶振脚位置也不一样画PCB或接线时一定要对照具体封装的数据手册。有人用STM32F103C8T6做最小系统直接把8MHz晶振接在OSC_IN/OSC_OUT上又发现PC14/PC15也被占用——这就需要看“OSC32_IN”和“OSC32_OUT”与普通IO的重映射关系避免设计冲突。2.3 按项目定位选型速查我整理了一个常用的选型参考表基本覆盖F1系列里最常见的几个核心型号型号引脚数FlashRAM典型应用方向STM32F103C4T64816KB6KB极小节点、简单传感器采集STM32F103C8T64864KB20KB入门学习、小型控制器、DHT11/OLED等小项目STM32F103RBT664128KB20KB需要较多IO的中型项目STM32F103RCT664256KB48KB带屏幕、协议栈、稍复杂逻辑的项目STM32F103ZET6144512KB64KB综合实验平台、完整产品原型RAM和Flash这两个指标是很容易被忽略的。现在有些人习惯什么都用HAL库一个工程编译下来基础代码就占几十KB Flash如果选了C4T6的16KB工程还没写几行就要砍资源了。所以选型号之前最好先估算一下有没有屏幕字库有没有协议栈是不是要跑RTOS有没有大数组这些决定Flash和RAM够不够用。入门阶段直接选C8T6起步64KB Flash和20KB RAM做温湿度计、OLED显示、按键交互这类项目绰绰有余。3. 工程搭建与最小系统绕过F1入门三道坎选好了芯片型号接下来就是建开发环境、搭工程、把程序烧进去。这个阶段看起来简单其实暗藏不少坑。有一段经典经历我帮一个朋友调他的F103板子他用的开发板商家给了别人的例程烧进去LED不亮他以为是板子坏了最后发现是工程里包含的芯片型号选错了启动文件用的启动文件不匹配导致SystemInit阶段就进死循环。这种问题对新手来说非常隐蔽。3.1 标准外设库还是HAL库别纠结按目的选很多新手的第一反应是现在STM32不是推荐用STM32CubeMXHAL库吗为什么还要学标准外设库这里我给出明确看法。做产品开发和快速原型用CubeMXHAL确实效率高图形化配置时钟树、引脚、外设参数生成初始化代码尤其对F4/H7这些外设复杂的系列非常友好。但放在F1入门阶段尤其是为了理解GPIO、时钟、定时器这些底层机制时标准外设库更合适。原因很简单F1的资料、例程、老项目绝大多数都是基于标准库写的。你网上搜“DHT11 STM32F103”出来的代码十有八九是标准库风格GPIO_InitTypeDef、RCC_APB2PeriphClockCmd这种API直接看着库函数就能猜到每一步在做什么。HAL库在一些底层操作上封装更深寄存器操作容易被藏起来一旦遇到运行时问题新手很难定位到底是在配置哪一步出了问题。我个人的建议是一条“先标准库、后HAL”的路径先用标准库把GPIO翻转、串口发送、定时器中断这几个基本功啃明白理解了寄存器层面发生了什么再切到CubeMXHAL做项目开发这时你会觉得HAL库非常像“自动生成的代码模板”出问题也能顺着HAL库的调用关系追到是哪一步配置出的错。3.2 最小系统时钟、复位、电源与BOOTF103的最小系统其实并不复杂但每一处都有讲究。电源部分3.3V供电每个电源引脚就近加一个100nF去耦电容VDD和VSS之间的电容布局直接影响高频率运行时稳定性。复位电路NRST引脚接到10kΩ电阻到3.3V再接一个100nF电容到GND用来滤波保证上电复位信号干净。启动方式BOOT0和BOOT1引脚的电平组合决定芯片从Flash启动、SRAM启动还是系统存储器内置Bootloader正常运行时BOOT0要拉低从Flash启动。如果要通过串口ISP下载程序可以把BOOT0拉高配合复位操作进入系统存储器里的Bootloader。时钟部分是最容易让人困惑的。F103内部有一个8MHz的HSI内部RC振荡器精度一般通常我们更愿意用外部8MHz晶振配合PLL倍频到72MHz主频。系统启动时启动文件会调用SystemInit完成从默认内部时钟切换到用户配置的外部晶振PLL。如果你的外部晶振没焊好、负载电容不对或者晶振起振失败程序就会卡在SystemInit的等待超时循环里表现就是LED不闪、调试器能连上但跑不起来。一个常见入门坑就是外部晶振电路没做对但代码里配置成了HSE作为PLL源。解决办法有两个一是检查晶振电路二是暂时把系统时钟配置改成用HSI同时把PLL倍频系数改成和内部时钟匹配保证芯片能跑起来后再排查硬件。实操中我见过有人直接用内部HSI把主频配到64MHz也能正常工作只不过串口波特率会受内部RC精度影响要求不高时也不是不能用。3.3 烧录的三种方式和各自坑点F103可以用三种常用方式烧录SWD调试接口、串口ISP、ST-Link的JTAG模式。SWD是现在的首选只需要SWDIO、SWCLK、GND、3.3V四根线配合ST-Link或者J-Link就能下载和在线调试。注意线长不要超过20cm否则时钟信号容易受干扰出现烧录时断连。如果遇到连接失败或烧录不稳定可以试试在调试工具里选择“Connect under Reset”模式在下电复位瞬间抢占调试接口能解决不少引脚复用引起的连接问题。串口ISP方式不需要调试器只需要一个USB转TTL模块连接到USART1的PA9/PA10然后BOOT0拉高按一下复位芯片就会进入内置Bootloader用配套软件下载固件。这个方式很适合批量烧录或者手头没有ST-Link的场景但要注意串口电平必须是3.3V的不能直接拿5V的USB转串口模块去接否则时间长了可能烧坏芯片的RX引脚。很多便宜的USB转TTL模块可以跳线切换3.3V和5V电平实测前要确认一下。还有一种很隐蔽的坑是买到的所谓“最小系统板”上的复位电路故意省掉了外部复位按键只靠上电复位。这种情况下如果程序进入死循环或者关闭了看门狗复位操作只能靠断电重新上电调起程序来很痛苦。我的建议是动手搭最小系统时复位按键和BOOT拨码开关一定要预留后面调试时你会感谢自己当初留下了这两个设计。4. 用DHT11来一次硬核实战时序驱动的完整实现接下来进入最容易让新手抓狂、也最有含金量的部分把DHT11接到F103上并且正确读出温湿度数据。之所以拿DHT11做例子是因为它几乎占全了嵌入式开发里最基础也最关键的几个概念单总线通信、微秒级延时、时序判断、超时保护。很多新手拿到这个模块照着网上的代码抄一遍也能出数据但一旦把传感器换一个牌子或者换一个GPIO口就读不出来了原因就是没搞懂协议本身。4.1 为什么DHT11最适合作为F1的第一个传感器DHT11是一个数字温湿度传感器单总线协议供电范围3.3V到5.5V测量范围是20%到90%RH湿度0到50°C温度精度约为±5%RH和±2°C。单看参数DHT11并不以精度见长比它精度高得多的还有DHT22、SHT30等但它好就好在协议简单、成本便宜、逻辑直观而且几乎没有现成库可用必须自己写时序。对学习来说这就是金子。DHT11的“单总线”意思是数据线只有一根既能输出数据又能接收主机控制信号。F103的GPIO通过切换输入输出模式可以模拟这根总线的时序。整个过程不依赖I2C、SPI之类的现成外设完全靠代码控制引脚高低电平和采样时机。这就能逼着你去理解“拉低多少微秒、释放总线、等响应信号、按时间窗口读每一位”这些底层细节。学会了DHT11以后碰DS18B20、DHT22这类单总线器件思路几乎可以平移过去。4.2 单总线时序细读起始信号、响应与40bit数据先把这个协议的完整时序掰开揉碎。第一步是主机发起通信。F103把数据引脚配置为输出先拉低至少18毫秒这个低电平时间必须足够长让DHT11检测到总线被占用然后释放总线拉高延时20到40微秒。这时DHT11会主动把总线拉低大约80微秒作为响应信号随后再拉高大约80微秒表示“我准备好了开始传数据”。第二步是数据帧。DHT11一次传输40个bit顺序是8bit湿度整数、8bit湿度小数、8bit温度整数、8bit温度小数、8bit校验和。注意F1入门时经常有人把顺序弄混以为先来的是温度整数实际是先湿度后温度。校验和的计算方式是前四个字节相加取低8位和第五个字节相等才算数据有效。每一位数据的传输方式都一样先是约50微秒的低电平表示起始位然后是高电平高电平持续的时间长短代表这一位是0还是1。高电平持续26到28微秒左右代表0持续约70微秒代表1。所以在读每一位时正确做法是等待低电平结束然后延时大约40微秒在这个时间点去采样引脚。如果此时引脚仍然为高说明是高电平持续较长的1如果已经变低说明是0。这也是DHT11驱动代码里最核心的部分。还有一个容易忽略的点校验和错误不代表整个通信失败有时候只是干扰导致某一位读错了。常见的处理方法是丢弃这一帧数据重新发起一次读取。但也不能一直死循环重读不然主程序会被阻塞住最好设置一个最大重试次数。4.3 完整驱动代码从初始化到校验下面给出一份可以直接在标准外设库工程里用的DHT11驱动框架。我用宏定义把端口和引脚抽出来方便以后换引脚时只改一处。// dht11.h #ifndef __DHT11_H #define __DHT11_H #include stm32f10x.h #define DHT11_GPIO_PORT GPIOB #define DHT11_GPIO_CLK RCC_APB2Periph_GPIOB #define DHT11_GPIO_PIN GPIO_Pin_8 void DHT11_GPIO_Config(void); uint8_t DHT11_Read_Data(uint8_t *humi, uint8_t *temp); #endif// dht11.c #include dht11.h #include delay.h static void DHT11_Set_Output(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin DHT11_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStructure); } static void DHT11_Set_Input(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin DHT11_GPIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStructure); } static uint8_t DHT11_Read_Byte(void) { uint8_t i, data 0; for (i 0; i 8; i) { while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) Bit_RESET); delay_us(40); if (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) Bit_SET) { data | (uint8_t)(0x80 i); } while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) Bit_SET); } return data; } uint8_t DHT11_Read_Data(uint8_t *humi, uint8_t *temp) { uint8_t data[5] {0}; uint8_t i; uint8_t timeout 0; DHT11_Set_Output(); GPIO_WriteBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN, Bit_RESET); delay_ms(20); GPIO_WriteBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN, Bit_SET); delay_us(30); DHT11_Set_Input(); timeout 0; while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) Bit_SET) { if (timeout 200) return 1; delay_us(1); } timeout 0; while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) Bit_RESET) { if (timeout 200) return 1; delay_us(1); } timeout 0; while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) Bit_SET) { if (timeout 200) return 1; delay_us(1); } for (i 0; i 5; i) { data[i] DHT11_Read_Byte(); } if ((data[0] data[1] data[2] data[3]) data[4]) { *humi data[0]; *temp data[2]; return 0; } return 2; }主程序里可以这样调用uint8_t humi 0, temp 0; if (DHT11_Read_Data(humi, temp) 0) { printf(Humi: %d.%d%% Temp: %d.%dC\r\n, humi / 10, humi % 10, temp / 10, temp % 10); } else { printf(Read DHT11 error\r\n); } delay_ms(500);这里有个容易踩的细节DHT11返回的整数部分实际是“扩大十倍”的数值。比如湿度读数45表示45.0%RH温度读数23表示23.0°C。这段代码里直接用整数和小数分离打印就不用去纠结浮点数格式化了省内存又直观。4.4 延时精度与关中断的坑很多从Arduino或者51单片机转过来的人写延时喜欢用for循环空转。在F103上面最直接的教训就是用for循环做微秒级延时在72MHz主频下极不稳定。不同的编译器优化级别、不同的代码上下文循环执行时间能差好几倍。DHT11的时序窗口只有几十微秒一旦延时偏了读出来的位就全乱表现就是数据校验失败或者读出来全是0xFF。正确的做法是使用Systick定时器或者通用定时器产生可靠微秒延时。Systick是Cortex-M内核自带的24位递减计数器用它做delay_us和delay_ms非常合适。给了延时函数之后在读取DHT11数据的整个过程中还有个隐藏问题中断。如果系统里开了串口中断、定时器中断在读时序时突然来了一个中断服务函数几微秒到几十微秒的响应延迟会直接破坏DHT11的位判断。所以读取DHT11前要关闭中断读完整个40bit后再恢复中断。很多例程没有这一步靠运气也能工作因为在教学环境里中断少但一旦接入真实系统就会偶发性地出现数据异常非常难排查。还有一个实际经验读DHT11不能太频繁。DHT11本身的采样周期大约1秒而且它的响应起始信号需要一定时间准备数据。读完一次后至少间隔500毫秒到1秒再读下一次否则传感器还在低功耗状态或者正在采样强行拉低起始信号会导致响应超时。这个限制不是F103的问题是传感器的物理特性。4.5 实跑效果与波形观察我把上面的驱动接在STM32F103C8T6最小系统板上数据引脚用PB8外部加了一个4.7kΩ上拉电阻到3.3V然后用逻辑分析仪抓了波形。起始信号那段能看到一个约20毫秒的低电平然后是30微秒左右的高电平之后DHT11拉低约80微秒、拉高约80微秒的响应再后面就是40个bit的低电平加不同宽度的高电平。展开看单个bit0的高电平宽度大约26微秒1的高电平宽度大约70微秒和协议手册完全对得上。串口打印的温湿度数据也稳定连续跑两个小时没有出现校验错误。这时候有个很值得注意的现象如果不加上拉电阻直接靠STM32内部上拉逻辑分析仪上看到的波形上升沿明显变缓在40微秒那个采样点电平有时还没稳定到高电平导致数据读取偶尔出错。DHT11的数据线需要一个阻值大概在4.7kΩ到10kΩ之间的外部上拉电阻。如果你用的是现成的DHT11模块大多数模块上已经自带上拉电阻可以直接接但如果用的是裸传感器一定要自己加上拉。这也是很多“昨天还能读今天读不出来”问题的根源之一。5. 排查示例DHT11与F1组合的常见翻车现场这个部分我把它做成一个实战排障案例因为我自己在带新人时发现很多问题不是无线索的玄学而是有明确规律的。遇到DHT11读不出来第一反应不应该是换传感器、换板子而是按顺序排查大部分情况下五分钟就能定位。5.1 三板斧定位法第一板斧是查电平。用万用表或者示波器测量DHT11数据引脚在空闲时的电平。如果总线空闲是0V那说明上拉电阻没接或者传感器被拉死了。如果空闲是高电平说明基本供电和上拉没问题。第二板斧是查起始信号。用示波器单次触发抓主机拉低那一下。如果看不到约20毫秒低电平说明代码没跑到输出拉低那一步或者GPIO配置有问题常见的就是没开启GPIOB的时钟RCC_APB2PeriphClockCmd没调用读写寄存器完全无效。第三板斧是查响应。如果能看到起始信号但紧接着没有DHT11拉低约80微秒的响应那问题多半出在传感器供电、接线或传感器本身坏了。我用逻辑分析仪排查过一个案例主机发完起始信号后总线就一直高电平DHT11完全不响应。检查后发现是传感器数据线接到了PA11恰好默认复用功能里这个脚是USB DMUSB外设也开着两个功能同时抢这个引脚导致电平状态混乱。换到普通GPIO口后立刻恢复。这类问题在F1的引脚复用表上都有明确标注排查时先查一遍引脚功能表能省很多力气。5.2 问题速查表这里整理一个最常见的DHT11F103故障速查表基本覆盖了新手能遇到的90%情况。现象可能原因解决方案读出来的数据全是0xFF数据线上没有上拉电阻或上拉电阻太大加4.7kΩ到10kΩ外部上拉卡在等待DHT11响应程序不往下走传感器没供电、数据线接错、GPIO未开启时钟检查供电和接线确认RCC时钟已使能数据校验和一直错误延时函数不精准、读取时有中断干扰使用Systick延时读取期间关中断隔一段时间就读不到数据读取间隔太短、电源纹波大至少间隔500ms再读电源加去耦电容换了一个GPIO口就不行引脚被复用或上拉没接查引脚复用表确认外部上拉存在串口打印乱码系统时钟配置和串口波特率不匹配确认PLL倍频值与波特率配置一致5.3 几个提升稳定性的细节如果你要让DHT11在一个需要长时间运行的设备上稳定工作还有几个额外的建议。第一是给DHT11的供电引脚加一个100nF去耦电容尽量靠近传感器安装这个电容能明显降低供电纹波对时序判断的影响。第二是在读DHT11的整个函数中加一个总超时保护防止传感器在极端情况下不响应时主循环被阻塞死。代码里我每个while等待都加了超时计数这个习惯在所有单总线传感器驱动里都适用。第三个细节很多人不重视不要直接拿DHT11的数据线去做长距离传输超过2米就很悬。DHT11是为近距离板内通信设计的长距离应该换成I2C总线传感器或者用RS485等差分方式。我在一个小项目里为了布线方便把DHT11放在了离主板30厘米的位置用的普通杜邦线结果干扰抖动导致偶发读错。后来把线缩短到10厘米以内问题立刻消失。这个小节其实可以延伸出很多经验但核心原则就一句话单总线协议的传感器对电压、时序、干扰都非常敏感所有问题都可以从供电、接线、时序三个维度去排查。先把这三项确认一遍再怀疑代码和芯片。最后再分享一个个人习惯每个F103工程我都会在板上预留一个GPIO测试点调试DHT11这类时序敏感器件时把它接到逻辑分析仪上观察波形。很多时候代码逻辑觉得没问题但波形一看上升沿慢得离谱、脉冲宽度忽宽忽窄问题原因直接就暴露了。用STM32F1做嵌入式开发最忌“我觉得没问题”永远要让数据说话。DHT11这个小小传感器看着不起眼但它教给你的时序分析和排查思路能用到你以后所有单总线、I2C、SPI器件的驱动开发里。
阅读完成 · 觉得有帮助?
咨询建站