这篇是本人自己总结学习DHT22的笔记,源码在最后。关于DHT22的工作原理有很多博主讲得很好了,本篇主要讲解DHT22的驱动代码(都在代码注释上)。DHT22是单总线通信的,驱动它需三根线:电源线、信号线(接到任意GPIO口就行)和地线,,如下图
具体的原理解释见该博主:温湿度模块DHT22详解一:基础篇_k1ang的博客-CSDN博客_dht22
以下的工作原理是从DHT22的使用说明中截取的时序图
数据格式: 40bit数据=16bit湿度数据+16bit温度数据+8bit校验和
例子: 接收40bit数据如下:
0000 0010 1000 1100 0000 0001 0101 1111 1110 1110
湿度数据 温度数据 校验和
湿度高8位+湿度低8位+温度高8位+温度低8位=的末8位=校验和
例如:0000 0010+1000 1100+0000 0001+0101 1111=1110 1110
湿度=65.2%RH 温度=35.1℃
当温度低于0℃时温度数据的最高位置1。
例如:-10.1℃表示为1000 0000 0110 0101
用户主机(MCU)发送一次开始信号后,DHT22从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT22发送响应信号,送出40bit的数据,并触发一次信号采集。(注:主机DHT22读取的温湿度数据总是前一次的测量值,如两次测量间隔时间很长,请连续读两次以获得实时的温湿度值)
图1
空闲时总线为高电平,通讯开始时主机(MCU)拉低总线500us后释放总线,延时20-40us后主机开始检测从机(DHT22)的响应信号。
从机的响应信号是一个80us左右的低电平,随后从机在拉高总线80us左右代表即将进入数据传送。
图2
高电平后就是数据位,每1bit数据都是由一个低电平时隙和一个高电平组成。低电平时隙就是一个50us左右的低电平,它代表数据位的起始,其后的高电平的长度决定数据位所代表的数值,较长的高电平代表1,较短的高电平代表0。共40bit数据,当最后一Bit数据传送完毕后,从机将再次拉低总线50us左右,随后释放总线,由上拉电阻拉高。数字1信号表示方法如图3所示
图3
数字0信号表示方法.如图4所示
图4
总结使能DHT22的步骤:
- DHT22上电后至少要延时2秒,越过不稳定状态后才能开始读取数据。
- 主机输出起始信号:主机与DHT22连接的IO口设置为输出模式并输出1ms的低电平。
- 主机输出起始信号后需要释放总线:
如何释放总线?只需主机IO口输出30us高电平即可,但这里推荐直接将IO口设置为输入模式,由于存在上拉电阻,IO口会一直处于高电平,并不需要特定输出高电平,而且后面接收DHT22的应答信号时IO口也要设置为输入。
- 主机接收DHT22返回的应答信号:
因为主机释放总线时主机IO口就已设置为输入模式,根据IO口的电平变化判断当前信号是否为应答信号,若IO口检测到先是80us低电平后80us高电平,说明该信号为应答信号,若超过时间超过上图10中的响应最大时间85us,则可以判断为应答超时。
- 主机接收DHT22返回的40bit数据:
DHT22发送完应答信号后,随即串行输出40位数据,高位在前,主机根据IO口电平变化来判断当前的数据位是'0'还是'1',若IO口检测到先是50us低电平后是26us高电平,则说明当前数据位是'0',若IO口检测到先是50us低电平后是70us高电平,则说明当前数据位是'1'。因为是40位数据,所以要循环检测IO口电平状态40次。
- DHT22输出40位数据后,继续输出50us低电平后自动进入休眠状态,此时DHT22会变为输入模式随时接收主机发来的起始信号,只有接收到主机发来的起始信号才能唤醒,进入工作状态。
- 校验接收到的40位数据:如果(bit39:31)+(bit30:23)+(bit23:16)+(bit15:8)=(bit7:0)则表示接收到的数据正确。
- 读取DHT22数据的时间间隔至少要2秒。
注意事项:使用前给串口重定向,以便于能够看到调试信息,网上很多重定向的代码,就不展示了。CUBEMX的配置也跳过了,配置一个串口打印数据以及配置数据总线IO口就可以了。整个工程我会放到最后,需要的自取
驱动代码介绍:
DHT22.H的代码介绍,主要是函数声明
#ifndef INC_DHT22_H_
#define INC_DHT22_H_
#include "stm32g4xx_hal.h"
#define CPU_FREQUENCY_MHZ 170 // STM32时钟主频
//#define DHT_PIN GPIO_PIN_11 //重命名引脚,可用可不用
//#define DHT_PORT GPIOA //重命名IO名,同上
void DHT22_Init(GPIO_TypeDef* Port, uint16_t Pin);//引脚初始化(命名)
void ONE_WIRE_Start(void);//开始启动DHT22
void ONE_WIRE_Response(void);//DTH22的响应,若不正常则返回err
void Gpio_Output(void);//把数据线IO口设置为输出
void Gpio_Input(void);//把数据线IO设置为输入
void Delay_us(__IO uint32_t delay);//利用sys系统滴答定时器实现us延时
uint8_t ONE_WIRE_Read_Data(void);//读取数据线的数据
uint8_t DHT22_Get_Data(uint16_t *Temp,uint16_t *Hum);//接收数据并对数据校验
#endif /* INC_DHT22_H_ *
/
DHT22.c的代码介绍
1.利用滴答定时器实现us延时
void Delay_us(__IO uint32_t delay)
{
int last, curr, val;
int temp;
while (delay != 0)
{
temp = delay > 900 ? 900 : delay;
last = SysTick->VAL;
curr = last - CPU_FREQUENCY_MHZ * temp;//头文件中定义CPU_FREQUENCY_MHZ为170
if (curr >= 0) //CPU_FREQUENCY_MHZ是用到开发板的时钟主频
{
do
{
val = SysTick->VAL;
}
while ((val < last) && (val >= curr));
}
else
{
curr += CPU_FREQUENCY_MHZ * 1000;
do
{
val = SysTick->VAL;
}
while ((val <= last) || (val > curr));
}
delay -= temp;
}
}
2.引脚的重命名,可以直接传参进来
//例如GPIOA GPIO_PIN_11
void DHT22_Init(GPIO_TypeDef* Port,uint16_t Pin)
{
wire_Port = Port;
wire_Pin = Pin;
}
3.设置端口为输入输出模式,用于让DHT22开始响应
void Gpio_Output(void)//设置为输出,去拉高和拉低DHT22的电平
{
GPIO_InitStruct.Pin = wire_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(wire_Port, &GPIO_InitStruct);
}
void Gpio_Input(void)//设置为输入,以致于可以读到DHT22响应后采集的数据
{
GPIO_InitStruct.Pin = wire_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(wire_Port, &GPIO_InitStruct);
}
void ONE_WIRE_START(void)//让DHT22开始响应
{
Gpio_Output();
HAL_GPIO_WritePin(wire_Port, wire_Pin, GPIO_PIN_RESET);//拉低
HAL_Delay(1);//拉低1ms
HAL_GPIO_WritePin(wire_Port, wire_Pin, GPIO_PIN_SET);
Delay_us(30);//拉高30us 具体时序见图2
Gpio_Input();
}
4.获取数据总线的电平状态,来判断是否得到了响应
void ONE_WIRE_Responsee(void)
{
Delay_us(40);
//判断是否输出低电平
if(!(HAL_GPIO_ReadPin(wire_Port, wire_Pin)))
{
Delay_us(80);
//判断是否输出高电平
if(HAL_GPIO_ReadPin(wire_Port, wire_Pin))
{
//用来判断是否正常读出了数据
err=1;
}
//等待高电平结束后退出循环
while(HAL_GPIO_ReadPin(wire_Port, wire_Pin));
}
}
5.数据总线正常工作,读取其中的数值。前16位分别为湿度的高八位和低八位,后16分别为温度的高八位和低八位
uint8_t ONE_WIRE_Read_Data(void)
{
uint8_t i,j;
for(j = 0;j < 8; j++)
{
//等待低电平结束,低电平代表1bit开始
while(!(HAL_GPIO_ReadPin(wire_Port, wire_Pin)));
//延时50us后若是低电平则代表数据是0,反之为1
Delay_us(50);
if(HAL_GPIO_ReadPin(wire_Port, wire_Pin) == 0)
{
//读出0
i &= ~(1 << (7 - j));
}
else
{ //读出 1
i |= ( 1 << (7 - j));
}
//变为低电平时退出循环,开始下一bit的数据接收
while(HAL_GPIO_ReadPin(wire_Port, wire_Pin));
}
return i;
}
uint8_t DHT22_Get_Data(uint16_t *Temp,uint16_t *Hum)
{
ONE_WIRE_START();
ONE_WIRE_Responsee();
//32位,先读湿度的高低位,再读温度的高低位
Hum_byte1 = ONE_WIRE_Read_Data();
Hum_byte2 = ONE_WIRE_Read_Data();
Temp_byte1 = ONE_WIRE_Read_Data();
Temp_byte2 = ONE_WIRE_Read_Data();
Sum = ONE_WIRE_Read_Data();
*Temp = (Temp_byte1 << 8) | Temp_byte2;
*Hum = (Hum_byte1 << 8) | Hum_byte2;
if(err == 1)
{
printf("数据检测成功\r\n");
}
}
main.c的代码介绍:
主函数中十分简单,调用一下函数,再对读出的数据进行处理,通过串口打印。CUBEMX生成的函数我就没贴上了,默认就好
int main(void)
{
uint16_t temperature;
uint16_t humidity;
//引脚对应你定义数据总线的IO引脚,我的是PA11
DHT22_Init(GPIOA, GPIO_PIN_11);
while (1)
{
DHT22_Get_Data(&temperature,&humidity);
printf("\r\n数据检测中\r\n");
printf("\r\n读取DHT22成功!\r\n\r\n湿度为%d RH,温度为 %d*C\r\n",
humidity/10,temperature/10);
HAL_Delay(2000);//
}
}
代码以及工程(包含CUBEMX配置),我用的板子是nucleo-g474
链接:百度网盘 请输入提取码
提取码:kv3t
版权声明:本文为CSDN博主「oldjohntangna」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/oldjohntangna/article/details/121545347
这篇是本人自己总结学习DHT22的笔记,源码在最后。关于DHT22的工作原理有很多博主讲得很好了,本篇主要讲解DHT22的驱动代码(都在代码注释上)。DHT22是单总线通信的,驱动它需三根线:电源线、信号线(接到任意GPIO口就行)和地线,,如下图
具体的原理解释见该博主:温湿度模块DHT22详解一:基础篇_k1ang的博客-CSDN博客_dht22
以下的工作原理是从DHT22的使用说明中截取的时序图
数据格式: 40bit数据=16bit湿度数据+16bit温度数据+8bit校验和
例子: 接收40bit数据如下:
0000 0010 1000 1100 0000 0001 0101 1111 1110 1110
湿度数据 温度数据 校验和
湿度高8位+湿度低8位+温度高8位+温度低8位=的末8位=校验和
例如:0000 0010+1000 1100+0000 0001+0101 1111=1110 1110
湿度=65.2%RH 温度=35.1℃
当温度低于0℃时温度数据的最高位置1。
例如:-10.1℃表示为1000 0000 0110 0101
用户主机(MCU)发送一次开始信号后,DHT22从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT22发送响应信号,送出40bit的数据,并触发一次信号采集。(注:主机DHT22读取的温湿度数据总是前一次的测量值,如两次测量间隔时间很长,请连续读两次以获得实时的温湿度值)
图1
空闲时总线为高电平,通讯开始时主机(MCU)拉低总线500us后释放总线,延时20-40us后主机开始检测从机(DHT22)的响应信号。
从机的响应信号是一个80us左右的低电平,随后从机在拉高总线80us左右代表即将进入数据传送。
图2
高电平后就是数据位,每1bit数据都是由一个低电平时隙和一个高电平组成。低电平时隙就是一个50us左右的低电平,它代表数据位的起始,其后的高电平的长度决定数据位所代表的数值,较长的高电平代表1,较短的高电平代表0。共40bit数据,当最后一Bit数据传送完毕后,从机将再次拉低总线50us左右,随后释放总线,由上拉电阻拉高。数字1信号表示方法如图3所示
图3
数字0信号表示方法.如图4所示
图4
总结使能DHT22的步骤:
- DHT22上电后至少要延时2秒,越过不稳定状态后才能开始读取数据。
- 主机输出起始信号:主机与DHT22连接的IO口设置为输出模式并输出1ms的低电平。
- 主机输出起始信号后需要释放总线:
如何释放总线?只需主机IO口输出30us高电平即可,但这里推荐直接将IO口设置为输入模式,由于存在上拉电阻,IO口会一直处于高电平,并不需要特定输出高电平,而且后面接收DHT22的应答信号时IO口也要设置为输入。
- 主机接收DHT22返回的应答信号:
因为主机释放总线时主机IO口就已设置为输入模式,根据IO口的电平变化判断当前信号是否为应答信号,若IO口检测到先是80us低电平后80us高电平,说明该信号为应答信号,若超过时间超过上图10中的响应最大时间85us,则可以判断为应答超时。
- 主机接收DHT22返回的40bit数据:
DHT22发送完应答信号后,随即串行输出40位数据,高位在前,主机根据IO口电平变化来判断当前的数据位是'0'还是'1',若IO口检测到先是50us低电平后是26us高电平,则说明当前数据位是'0',若IO口检测到先是50us低电平后是70us高电平,则说明当前数据位是'1'。因为是40位数据,所以要循环检测IO口电平状态40次。
- DHT22输出40位数据后,继续输出50us低电平后自动进入休眠状态,此时DHT22会变为输入模式随时接收主机发来的起始信号,只有接收到主机发来的起始信号才能唤醒,进入工作状态。
- 校验接收到的40位数据:如果(bit39:31)+(bit30:23)+(bit23:16)+(bit15:8)=(bit7:0)则表示接收到的数据正确。
- 读取DHT22数据的时间间隔至少要2秒。
注意事项:使用前给串口重定向,以便于能够看到调试信息,网上很多重定向的代码,就不展示了。CUBEMX的配置也跳过了,配置一个串口打印数据以及配置数据总线IO口就可以了。整个工程我会放到最后,需要的自取
驱动代码介绍:
DHT22.H的代码介绍,主要是函数声明
#ifndef INC_DHT22_H_
#define INC_DHT22_H_
#include "stm32g4xx_hal.h"
#define CPU_FREQUENCY_MHZ 170 // STM32时钟主频
//#define DHT_PIN GPIO_PIN_11 //重命名引脚,可用可不用
//#define DHT_PORT GPIOA //重命名IO名,同上
void DHT22_Init(GPIO_TypeDef* Port, uint16_t Pin);//引脚初始化(命名)
void ONE_WIRE_Start(void);//开始启动DHT22
void ONE_WIRE_Response(void);//DTH22的响应,若不正常则返回err
void Gpio_Output(void);//把数据线IO口设置为输出
void Gpio_Input(void);//把数据线IO设置为输入
void Delay_us(__IO uint32_t delay);//利用sys系统滴答定时器实现us延时
uint8_t ONE_WIRE_Read_Data(void);//读取数据线的数据
uint8_t DHT22_Get_Data(uint16_t *Temp,uint16_t *Hum);//接收数据并对数据校验
#endif /* INC_DHT22_H_ *
/
DHT22.c的代码介绍
1.利用滴答定时器实现us延时
void Delay_us(__IO uint32_t delay)
{
int last, curr, val;
int temp;
while (delay != 0)
{
temp = delay > 900 ? 900 : delay;
last = SysTick->VAL;
curr = last - CPU_FREQUENCY_MHZ * temp;//头文件中定义CPU_FREQUENCY_MHZ为170
if (curr >= 0) //CPU_FREQUENCY_MHZ是用到开发板的时钟主频
{
do
{
val = SysTick->VAL;
}
while ((val < last) && (val >= curr));
}
else
{
curr += CPU_FREQUENCY_MHZ * 1000;
do
{
val = SysTick->VAL;
}
while ((val <= last) || (val > curr));
}
delay -= temp;
}
}
2.引脚的重命名,可以直接传参进来
//例如GPIOA GPIO_PIN_11
void DHT22_Init(GPIO_TypeDef* Port,uint16_t Pin)
{
wire_Port = Port;
wire_Pin = Pin;
}
3.设置端口为输入输出模式,用于让DHT22开始响应
void Gpio_Output(void)//设置为输出,去拉高和拉低DHT22的电平
{
GPIO_InitStruct.Pin = wire_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(wire_Port, &GPIO_InitStruct);
}
void Gpio_Input(void)//设置为输入,以致于可以读到DHT22响应后采集的数据
{
GPIO_InitStruct.Pin = wire_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(wire_Port, &GPIO_InitStruct);
}
void ONE_WIRE_START(void)//让DHT22开始响应
{
Gpio_Output();
HAL_GPIO_WritePin(wire_Port, wire_Pin, GPIO_PIN_RESET);//拉低
HAL_Delay(1);//拉低1ms
HAL_GPIO_WritePin(wire_Port, wire_Pin, GPIO_PIN_SET);
Delay_us(30);//拉高30us 具体时序见图2
Gpio_Input();
}
4.获取数据总线的电平状态,来判断是否得到了响应
void ONE_WIRE_Responsee(void)
{
Delay_us(40);
//判断是否输出低电平
if(!(HAL_GPIO_ReadPin(wire_Port, wire_Pin)))
{
Delay_us(80);
//判断是否输出高电平
if(HAL_GPIO_ReadPin(wire_Port, wire_Pin))
{
//用来判断是否正常读出了数据
err=1;
}
//等待高电平结束后退出循环
while(HAL_GPIO_ReadPin(wire_Port, wire_Pin));
}
}
5.数据总线正常工作,读取其中的数值。前16位分别为湿度的高八位和低八位,后16分别为温度的高八位和低八位
uint8_t ONE_WIRE_Read_Data(void)
{
uint8_t i,j;
for(j = 0;j < 8; j++)
{
//等待低电平结束,低电平代表1bit开始
while(!(HAL_GPIO_ReadPin(wire_Port, wire_Pin)));
//延时50us后若是低电平则代表数据是0,反之为1
Delay_us(50);
if(HAL_GPIO_ReadPin(wire_Port, wire_Pin) == 0)
{
//读出0
i &= ~(1 << (7 - j));
}
else
{ //读出 1
i |= ( 1 << (7 - j));
}
//变为低电平时退出循环,开始下一bit的数据接收
while(HAL_GPIO_ReadPin(wire_Port, wire_Pin));
}
return i;
}
uint8_t DHT22_Get_Data(uint16_t *Temp,uint16_t *Hum)
{
ONE_WIRE_START();
ONE_WIRE_Responsee();
//32位,先读湿度的高低位,再读温度的高低位
Hum_byte1 = ONE_WIRE_Read_Data();
Hum_byte2 = ONE_WIRE_Read_Data();
Temp_byte1 = ONE_WIRE_Read_Data();
Temp_byte2 = ONE_WIRE_Read_Data();
Sum = ONE_WIRE_Read_Data();
*Temp = (Temp_byte1 << 8) | Temp_byte2;
*Hum = (Hum_byte1 << 8) | Hum_byte2;
if(err == 1)
{
printf("数据检测成功\r\n");
}
}
main.c的代码介绍:
主函数中十分简单,调用一下函数,再对读出的数据进行处理,通过串口打印。CUBEMX生成的函数我就没贴上了,默认就好
int main(void)
{
uint16_t temperature;
uint16_t humidity;
//引脚对应你定义数据总线的IO引脚,我的是PA11
DHT22_Init(GPIOA, GPIO_PIN_11);
while (1)
{
DHT22_Get_Data(&temperature,&humidity);
printf("\r\n数据检测中\r\n");
printf("\r\n读取DHT22成功!\r\n\r\n湿度为%d RH,温度为 %d*C\r\n",
humidity/10,temperature/10);
HAL_Delay(2000);//
}
}
代码以及工程(包含CUBEMX配置),我用的板子是nucleo-g474
链接:百度网盘 请输入提取码
提取码:kv3t
版权声明:本文为CSDN博主「oldjohntangna」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/oldjohntangna/article/details/121545347
暂无评论