嵌入式STM32+DHT22底层驱动与时序分析全记录
pcfxAM2302就是DHT22
作为DHT11的增强版本,DHT22的精度可以到小数点后一位。而DHT11只能到达整数的精度。
作为一名早期的Arduino-Scratch 开发者。
控制这种单总线协议的设备无疑是十分简单的,都被封装好了。 如下:
1 2 3 4 5 6 7
| #include <DHT22.h> #define pinDATA SDA DHT22 dht22(pinDATA);
float t = dht22.getTemperature(); float h = dht22.getHumidity();
|
而如何和通过单总线通讯和具体的硬件设计,当时的我尚未知晓。
如果只停留在这个认知,无疑是很肤浅的。
外围电路设计
根据数据手册
我设计了如下电路
其中R7为上拉电阻
到这里,我以为这个模块已经大功告成了,觉得获取数据和写程序不过尔尔。
等到我开始使用stm开始编程,发现存在以下问题:
- hal库没有提供μs级别的delay
- 时序逻辑有问题 接下来我们来逐步解决。
delay_us的实现
我使用了DWT delay,通过逻辑分析仪实测还是比较准确的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void DWT_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; }
void delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while((DWT->CYCCNT - start) < ticks); }
|
- DEMCR寄存器:启用内核的调试和跟踪功能(必须先开,否则DWT不可用)
- DWT->CYCCNT:一个32位硬件计数器,每个CPU时钟周期自动累加1
- DWT->CTRL:控制寄存器的CYCCNTENA位使能计数
特点:不占用CPU额外资源,纯硬件自动计数,无中断开销
但是delay_us() 是 busy-wait,CPU在循环里空转,会占用CPU时间
SystemCoreClock 是系统时钟频率(单位Hz,例如72,000,000表示72MHz)
SystemCoreClock / 1,000,000 = 每微秒运行的CPU周期数
- 乘以
us = 总周期数
什么意思? 也就是系统1秒钟假设运行72,000,000次,那么1μs(\(10^-6\)秒)=72,000,000/1,000,000=72
也就是72个cpu周期,就是1μs
解决了delay的问题,接下来我们要解决时序逻辑问题
时序逻辑
这是AM2302的数据定义
当总线是闲置时,因为上拉电阻。总线此时为高电平。
- 现在,微控制器想要获取信号,此时拉低总线电平(至少800μs)也就是0.8ms
- 然后传感器拉低80μs,再拉高80μs,响应起始信号
- 输出40位数据,16位湿度,16位温度,8位校验数据
- 接受到数据后,经过校验,处理符号并把数据除以10,我们就能得到数据。
具体分析
1发送起始信号
2.响应信号

- 传感器拉低总线80μs
- 传感器拉高总线80μs
- 提示获取响应
3.数据传输
也就是当传感器相应后,发出40位数据
- 16位湿度
- 16位温度
- 8位检验 那么怎么表示0和1
- 拉高26μs(典型值)为0
- 拉高70μs(典型值)为1
从逻辑分析图上看,我们可以说,瘦的是0,胖的是1
这里我获取了一次数据,我们完整走一次数据的传输和获取。
湿度
我们结合图片 得到 0000 0010 1101
1100 换算成10进制是 732 也就是湿度是73.2%
温度
获取到数据 0000 0001 0000 1001
换算成十进制就是265 也就是温度是26.5℃ 注意
该数据第一位是符号位,0位正,1为负
校验
得到数据 11101000
我们来校验这个校验位是否正确 00000010 + 11011100 +00000001 +00001001
=11011110+00001010=11101000 也就是数据是无误的
太好了,我们现在终于获取到了实时的数据了!!!!
现在我要给你泼冷水
此时获取到的数据并不是实时的。
此时获取到的不是实时温度,而是前一次的测量值。
哈哈。。。。。
让我们再获取一次。现在我们终于得到了实时的温湿度了。
4.程序设计
最后附上这个单总线的信号特性。我们可以开始设计程序 
我分配了PA1引脚给这个DHT22,我们就以PA1为例:
- 我们要设置PA1为输出模式,推荐开漏,拉低引脚,使用HAL
Delay 1ms,然后释放总线
- 我们设置引脚为输入模式,等待传感器拉低 ->
传感器拉低 -> 传感器拉高
- 此时传感器拉高结束,开始获取数据
- 我们弄一个for循环 循环40次,每一个获取一个bit
- 我们跳过低电平
- 现在电平被拉高,我们记录高电平的时间,注意,我们要有超时保护。
- 然后我们以高电平的时长判断数据是0还是1,我们可以以40,或者50μs作为分界
- 然后存入数据
- 此时,我们获得了一个40bit的数据,此时我们需要校验和数据处理。
- 校验成功后,以指针返回或者以结构体返回数据。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| int DHT22_Read(float *humidity, float *temperature) { GPIO_InitTypeDef GPIO_InitStruct = {0}; uint8_t data[5] = {0}; uint8_t bit = 0; uint32_t tick_start; uint32_t high_time; uint32_t start_dwt; GPIO_InitStruct.Pin = dht22_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(dht22_GPIO_Port, &GPIO_InitStruct); HAL_GPIO_WritePin(dht22_GPIO_Port, dht22_Pin, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(dht22_GPIO_Port, dht22_Pin, GPIO_PIN_SET); GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(dht22_GPIO_Port, &GPIO_InitStruct); tick_start = HAL_GetTick(); while(HAL_GPIO_ReadPin(dht22_GPIO_Port, dht22_Pin) == GPIO_PIN_SET) { if((HAL_GetTick() - tick_start) > 2) return -1; } tick_start = HAL_GetTick(); while(HAL_GPIO_ReadPin(dht22_GPIO_Port, dht22_Pin) == GPIO_PIN_RESET) { if((HAL_GetTick() - tick_start) > 1) return -1; } tick_start = HAL_GetTick(); while(HAL_GPIO_ReadPin(dht22_GPIO_Port, dht22_Pin) == GPIO_PIN_SET) { if((HAL_GetTick() - tick_start) > 1) return -1; } for(int i = 0; i < 40; i++) { while(HAL_GPIO_ReadPin(dht22_GPIO_Port, dht22_Pin) == GPIO_PIN_RESET); start_dwt = DWT->CYCCNT; while(HAL_GPIO_ReadPin(dht22_GPIO_Port, dht22_Pin) == GPIO_PIN_SET) { if(((DWT->CYCCNT - start_dwt) / (SystemCoreClock / 1000000)) > 2000) return -1; } high_time = (DWT->CYCCNT - start_dwt) / (SystemCoreClock / 1000000); if(high_time > 50) { bit = 1; } else { bit = 0; } data[i / 8] <<= 1; data[i / 8] |= bit; } uint8_t checksum = data[0] + data[1] + data[2] + data[3]; if(checksum != data[4]) return -2; uint16_t raw_humidity = (data[0] << 8) | data[1]; uint16_t raw_temp = (data[2] << 8) | data[3]; if (raw_temp & 0x8000) { raw_temp = raw_temp & 0x7FFF; *temperature = -(raw_temp / 10.0f); } else { *temperature = raw_temp / 10.0f; } *humidity = raw_humidity / 10.0f; return 0; }
|
参考资料 AM2302
-PDF数据手册-参考资料-立创商城