stm32用HAL库驱动DHT22使用心得

这篇是本人自己总结学习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的步骤:

  1. DHT22上电后至少要延时2秒,越过不稳定状态后才能开始读取数据。
  2. 主机输出起始信号:主机与DHT22连接的IO口设置为输出模式并输出1ms的低电平。
  1. 主机输出起始信号后需要释放总线:

如何释放总线?只需主机IO口输出30us高电平即可,但这里推荐直接将IO口设置为输入模式,由于存在上拉电阻,IO口会一直处于高电平,并不需要特定输出高电平,而且后面接收DHT22的应答信号时IO口也要设置为输入。

  1. 主机接收DHT22返回的应答信号:

因为主机释放总线时主机IO口就已设置为输入模式,根据IO口的电平变化判断当前信号是否为应答信号,若IO口检测到先是80us低电平后80us高电平,说明该信号为应答信号,若超过时间超过上图10中的响应最大时间85us,则可以判断为应答超时。

  1. 主机接收DHT22返回的40bit数据:

DHT22发送完应答信号后,随即串行输出40位数据,高位在前,主机根据IO口电平变化来判断当前的数据位是'0'还是'1',若IO口检测到先是50us低电平后是26us高电平,则说明当前数据位是'0',若IO口检测到先是50us低电平后是70us高电平,则说明当前数据位是'1'。因为是40位数据,所以要循环检测IO口电平状态40次。

  1. DHT22输出40位数据后,继续输出50us低电平后自动进入休眠状态,此时DHT22会变为输入模式随时接收主机发来的起始信号,只有接收到主机发来的起始信号才能唤醒,进入工作状态。
  2. 校验接收到的40位数据:如果(bit39:31)+(bit30:23)+(bit23:16)+(bit15:8)=(bit7:0)则表示接收到的数据正确。
  1. 读取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的步骤:

  1. DHT22上电后至少要延时2秒,越过不稳定状态后才能开始读取数据。
  2. 主机输出起始信号:主机与DHT22连接的IO口设置为输出模式并输出1ms的低电平。
  1. 主机输出起始信号后需要释放总线:

如何释放总线?只需主机IO口输出30us高电平即可,但这里推荐直接将IO口设置为输入模式,由于存在上拉电阻,IO口会一直处于高电平,并不需要特定输出高电平,而且后面接收DHT22的应答信号时IO口也要设置为输入。

  1. 主机接收DHT22返回的应答信号:

因为主机释放总线时主机IO口就已设置为输入模式,根据IO口的电平变化判断当前信号是否为应答信号,若IO口检测到先是80us低电平后80us高电平,说明该信号为应答信号,若超过时间超过上图10中的响应最大时间85us,则可以判断为应答超时。

  1. 主机接收DHT22返回的40bit数据:

DHT22发送完应答信号后,随即串行输出40位数据,高位在前,主机根据IO口电平变化来判断当前的数据位是'0'还是'1',若IO口检测到先是50us低电平后是26us高电平,则说明当前数据位是'0',若IO口检测到先是50us低电平后是70us高电平,则说明当前数据位是'1'。因为是40位数据,所以要循环检测IO口电平状态40次。

  1. DHT22输出40位数据后,继续输出50us低电平后自动进入休眠状态,此时DHT22会变为输入模式随时接收主机发来的起始信号,只有接收到主机发来的起始信号才能唤醒,进入工作状态。
  2. 校验接收到的40位数据:如果(bit39:31)+(bit30:23)+(bit23:16)+(bit15:8)=(bit7:0)则表示接收到的数据正确。
  1. 读取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

生成海报
点赞 0

oldjohntangna

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

X-bot

X-bot 前言 这个项目是我在稚辉君的视频里面看见的,是一个完全开源的项目,而且对他来说是一个比较简单的项目,但对于我这种没有什么DIY经验的同学来说,还是有点难的,不过

【STM32】NUCLEO-G031K8 开发板使用注意事项

一、背景 前段时间的ST芯片大涨价,因此项目需要更换主控芯片来节约成本。正好手上有一块NUCLEO-G031K8开发板,所以就先试着替换了。结果同事小伙伴遇到了几个问题来问我,帮忙解决问题的同时正好记

STM32引脚模式:推挽、开漏、上拉、下拉、浮空

一、简介 ​ GPIO的配置种类有8种之多:模拟输入、浮空输入、下拉输入、上拉输入、 开漏输出、推挽输出、 复用开漏输出、 复用推挽输出,每次使用引脚时都需要进行配置,所以我以自己的理解&#xff0c