尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DHT11温湿度采集:STM32标准库与HAL库双实现详解

DHT11温湿度采集:STM32标准库与HAL库双实现详解 简介这是一份面向 STM32 学习者和嵌入式初学者的完整示例工程以 DHT11 温湿度传感器为核心实现数据采集与串口显示并提供标准库与 HAL 库两套实现方案。相比单一版本读者可在对比中理解寄存器操作与库函数封装的差异适合课程设计、毕业设计或入门项目参考。压缩包共 1167 个文件约 30.48MB除大量 .c/.h 源码外还包含 Keil 的 uvprojx/uvoptx 工程配置、IAR 的 icf/sct 链接脚本以及 axf/hex 等编译产物既可直接打开工程编译运行也能结合中间文件了解构建流程。已有 3914 人学习下载说明该资源在 STM32 温湿度采集中具有实用参考价值。资源内两套工程分别对应标准库与 HAL 库DHT11 驱动、串口发送和延时实现都可逐模块对照阅读便于快速掌握单总线时序与调试思路包内还有 20 余个 txt 文本文件可作补充说明整体目录结构清晰适合二次开发与代码复用。1. 一个DHT11温湿度采集为什么值得同时写上标准库和HAL库一个F103最小系统板加几块钱的DHT11是许多人接触STM32后的第一个传感器项目。温湿度采集和串口显示听起来都不难真正自己动手做一次才会卡在两个地方DHT11的数据线既没有SCL也没有MISO全靠单总线上高低电平的持续时间表达一位数据而“标准库”和“HAL库”两套开发方式在GPIO操作和延时精度上的表现差异非常明显。把这两套库放在同一块DHT11上实现一遍才能绕开那些例程里“这次能读下次读不到”的坑。这篇内容按协议、标准库实现、HAL库实现、串口显示与排查四个层面展开适合刚准备做毕设的新手也适合从标准库转HAL的老工程师对照着看。2. DHT11的单总线时序与40位数据格式2.1 单总线的时序关系DHT11的数据线是单向半双工所有传输由主机发起。主机先把总线拉低约20ms再释放DHT11检测到这个起始信号后会先拉低约80us、再拉高约80us作为应答然后连续输出40位数据。每一位都以约50us的低电平开始区别在于后面的高电平持续时间高电平持续26~28us判为逻辑0持续约70us判为逻辑1。所以读DHT11的本质并不是“读电平”而是反复测量高电平的宽度并在这个宽度中间选一个安全采样点。这套时序对微秒级延时非常敏感。主机起始信号和每一位数据的读取窗口都在几十微秒量级任何一个中断、一条耗时的函数调用都可能让采样点错过判断区间。这也是为什么同一个DHT11在标准库下能稳定运行换到HAL库却读出全0xFF的常见原因。把关键时间参数列成一张表后面调整代码或排查问题时直接对照。阶段时间要求实践中采用的约定值主机起始信号拉低18 ~ 30 ms常用 20 ms起始信号结束后主机释放总线等待 20 ~ 40 us常用 30 us 左右DHT11应答低电平约 80 us用超时循环等待它结束DHT11应答高电平约 80 us用超时循环等待它结束每一位数据前的低电平约 50 us等待低电平结束逻辑0的高电平宽度26 ~ 28 us40 us 处再采样逻辑1的高电平宽度约 70 us40 us 处仍为高两次读取最小间隔1 s推荐 1.5 ~ 2 s一个容易被忽略的原则两次温湿度读取之间的间隔必须大于1秒。DHT11内部完成一次采集和校准需要时间如果上电后马上读或者连续快速读取通常表现为第一次读不到应答、或者一直输出旧数据。实际工程中我会把采集周期设为2秒给传感器留足余量。2.2 数据解析与校验DHT11一次返回5个字节顺序依次为湿度整数、湿度小数、温度整数、温度小数、校验和。校验和的算法是前四个字节相加取低8位后与第五字节比较。DHT11的湿度小数位和温度小数位在绝大多数批次上为0但按协议完整解析更稳妥将来换用DHT21、DHT22时也能复用这套解析逻辑。int dht11_parse(uint8_t buf[5], float *humidity, float *temperature) { uint8_t sum; /* 校验和前4字节相加低8位与第5字节相等 */ sum (uint8_t)(buf[0] buf[1] buf[2] buf[3]); if (sum ! buf[4]) { return -1; /* 校验失败本次采集数据直接丢弃 */ } /* 整数部分 小数部分小数部分单位为0.1 */ *humidity (float)buf[0] (float)buf[1] * 0.1f; *temperature (float)buf[2] (float)buf[3] * 0.1f; return 0; }这段解析里有几个值得注意的点。sum声明为uint8_t四字节相加后自动截断到低8位不需要再额外做 0xFF。湿度温度计算时没有采用(buf[0] 8 | buf[1]) / 10.0f这种写法因为当小数位不为0时强行拼接成16位整数再除以10会得到完全错误的结果。按“整数加小数乘0.1”的方式对不同批次传感器的兼容性更好。如果未来移植到DHT22同样适用这一套换算逻辑。2.3 接线、上拉与采样间隔的边界DHT11常见的封装是4个引脚顺序为1脚VCC、2脚DATA、3脚NC、4脚GND。在嘉立创等原理图库里直接搜索DHT11调出来的封装引脚顺序基本一致但焊接或插线之前仍然建议对着实物核对一遍尤其是3脚空脚和4脚地线接反时传感器会直接发烫损坏。数据线上需要外接一个4.7k~10k的上拉电阻因为DHT11的数据脚是开漏输出必须靠外部上拉电阻把总线恢复到高电平。F103的GPIO内部有约30k~50k的上拉电阻省掉外部上拉电阻也能工作但在连接线稍长、环境干扰大的场合内部上拉偏弱边沿容易被拉缓导致位识别不稳定。我一般直接在数据线上放一颗4.7k电阻成本很低能解决大量“偶发读错”问题。另外传感器上电后建议等1秒再发起第一次读取让内部完成上电自检否则起始信号常常得不到应答。3. 标准库实现SysTick微秒延时与裸时序读法3.1 F1标准外设库的GPIO初始化标准外设库Standard Peripheral Library没有CubeMX自动生成工程里每个外设都要手动初始化。读取DHT11之前先把数据引脚配置为开漏输出并释放总线到高电平。开漏输出在这个场景下有两个直接好处主机想拉低就写0想释放就写1总线电平完全由外部上拉和DHT11决定读取引脚时GPIO的IDR寄存器仍然反映引脚真实电平不需要在输入输出模式间来回切换。#define DHT11_PORT GPIOC #define DHT11_PIN GPIO_Pin_7 void DHT11_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin DHT11_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; /* 开漏输出 */ GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, GPIO_InitStructure); GPIO_SetBits(DHT11_PORT, DHT11_PIN); /* 释放总线由外部上拉保持高电平 */ }这里有一个不少例程容易踩的坑把GPIO配成推挽输出。主机推挽输出高电平时数据线被强驱动为高DHT11应答时要把总线拉低就会与主机的推挽输出形成电流竞争。虽然大多数情况下传感器仍能勉强工作但波形边沿和电平阈值都会受影响偶发读取失败很难排查。标准库下配置成开漏输出每次调用GPIO_ResetBits和GPIO_SetBits操作的都是BSRR/BRR寄存器延时确定性强适合这种微秒级时序的场景。3.2 用SysTick做微秒延时的实现与边界标准库工程里没有现成的微秒延时函数HAL库的HAL_Delay只到毫秒级DHT11需要的却是几十微秒的精度。用空循环for(i 0; i 100; i);做延时时如果编译器开了优化这个循环可能被整个优化掉导致时序完全失效。比较稳妥的做法是使用SysTick外设以SystemCoreClock为基准计算每微秒需要的计数值查询COUNTFLAG位判断定时是否到达。static void Delay_us(uint32_t us) { SysTick-CTRL 0; /* 关闭SysTick */ SysTick-LOAD (SystemCoreClock / 1000000U) - 1U; /* 1us对应计数值 */ SysTick-VAL 0; SysTick-CTRL SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; while (us--) { /* 等待COUNTFLAG置位读取CTRL会同时清除该标志 */ while ((SysTick-CTRL SysTick_CTRL_COUNTFLAG_Msk) 0); } SysTick-CTRL 0; /* 关闭SysTick避免影响后续配置 */ }F103默认主频72MHz时SystemCoreClock / 1000000U等于72每条ifphy度到好上电后等待1秒再开始采样若错误码为-2重点查时序确认延时函数是否被优化、SysTick是否被系统中断抢占。float humidity 0.0f, temperature 0.0f; int ret; while (1) { ret DHT11_Read(humidity, temperature); if (ret DHT11_READ_OK) { printf(RH%.1f%% Temp%.1fC\r\n, humidity, temperature); } else { printf(DHT11 error code: %d\r\n, ret); } HAL_Delay(2000); }最后补充一个容易误判的细节DHT11的负温度处理。DHT11规格书标注的工作温度范围是0~50°C但部分资料中温度字节最高位被定义为符号位负数时使用补码表示。实际项目中如果只使用DHT11可以不做负温度处理如果代码要移植到DHT12或DHT22这类传感器上再在解析函数里增加符号扩展逻辑。串口显示最终输出的是浮点温湿度配合错误码打印整条采集链路的状态一眼就能看清。本文还有配套的精品资源点击获取
返回列表