“合泰杯”——HT32F52352+DHT11笔记

“本文章用于记录参加合泰单片机过程关于DHT11温湿度传感器的一些制作过程与踩得一些坑。”

文章目录

前言

一、引脚说明

二、通信过程

三、产品参数

四、注意事项

五、DHT11驱动程序


前言

        “此文章多数转载这个大神——玩转硬件的文章,并结合DHT11的数据手册进行记录(50条消息) 玩转传感器——DHT11温湿度传感器(STM32版)_JOU_XQS的博客-CSDN博客_dht11温湿度传感器

        DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。(ps:datasheet里的原文)

        DHT11作为日常开发中较为常用到的一种模块,其实已经十分常见了。且想原子哥、野火等做32位单片机教学视频的平台也常把它作为课程中的一环,网上STM32版的相关开源代码也是数不胜数,但是作为合泰杯的参赛选手,由于主办方的要求只能使用他们提供的HT32F52352作为系统的控制器进行开发。这样就不能快乐的copy代码,这是我不能接受的,故写下这篇文章为后续需要在HT32中使用DHT11的场景提供便利。


一、引脚说明

1.接线图

 

 

      在手册中提到,在接线时建议连接线长度小于5m时,在DATA线上接4.7K的上拉电阻,大于5m时则根据实际情况降低上拉电阻的阻值。这样加上上拉电阻后,即可保证在数据总线空闲时保持为高电平状态。

2、电源引脚

         DHT11的供电电压为3.3~5.5v。电源引脚(VCC,GND)之间可增加一个100nF(104)的电容,走线时尽量使其离元件引出的电源引脚越短越好,用以去耦滤波。

3.数据接口(单线双向)

        DATA用于MCU与DHT11之间进行通信,采用单总线的通信方式, 一次通信时间4ms左右(上诉文章中提到的,本人并没有去验证过),数据分为5个部分——湿度的整数部分、湿度的小数部分、温度的整数部分、温度的小数部分、校验和部分,一次传输共包括40bit的数据,高位先出。其中每个部分数据都为8bit,且当数据传输正确时,校验和等于其余四个部分相加后的末8位,数据的读取方式如下所示。

二、通信过程

        如下图所示,为DHT11通信的一次完整时序, 在用户发送一次开始信号后,DHT11将从低功耗模式转变为高速模式,待主机开始信号结束后,DHT11发送相应信号,之后送出40bit的数据,并触发一次信息采集,用户可选择读取部分数据。如果没有接收到开始信号,模块不会主动进行数据的发送,采集完成后将会转变回低速模式。

 注:主机从DHT11读取的温湿度数据总是前一次的测量值,如果两次测量间隔时间太长,请连续读两次数据,以第二次获得的值为实时的温湿度值。

        读取一次DHT11数据的具体步骤如下:

        步骤一:

            DHT11上电后(DHT11上电后要等待1S以越过不稳定状态,在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时DHT111的DATA数据线由上拉电阻拉高一直保持高电平,此时DHT11的DATA引脚处于输入状态,时刻接受外部信号。

        步骤二:

           MCU的I/O口设置为输出状态并输出低电平,且低电平需保持不能小于18ms(最大不得超过30ms),然后MCU的I/O设置为输入模式,由于上拉电阻,MCU的I/O即DATA数据线也随之变高(拉高时间约为20~40us),等待DHT11的应答信号。

         步骤三:

             DHT11的DATA引脚检测到外部信号有低电平时,等待外部电信号低电平结束,延迟一段时间后(会呈现为20~40us的高电平),DHT11的DATA引脚变为输出模式,输出83us的低电平作为应答信号,紧接着输出87us的高电平通知MCU准备接收数据,MCU的I/O此时处于输入状态,检测到I/O有低电平(DHT11应答信号)后,等待87us的高电平后,开始接收数据。

         步骤四:

             由DHT11的DATA引脚输出40bit数据,MCU根据I/O电平的变化接收40bit数据,位数据“0”的表示方式为:54us的高电平和23~27us的高电平,位数据“1”的表示方式为:54us的低电平加68~74us的高电平,而非传统的高低电平来表示,具体如下图所示。

         步骤五:

            结束信号,当DHT11的DATA引脚输出完40bit数据后,会继续输出54us的低电平后转为输入模式,由于上拉电阻,其电平随之变为高电平。但DHT11内部会重测环境的温湿度数据,并记录,等待下一次外部信号的到来。

        时序表

 三、产品参数

1.相对湿度

 2、温度

 3.电气特性

 四、注意事项

        1.使用3.3V电压供电时连接线尽量短,接线过长会导致传感器供电不足,造成测量偏差。

        2.每次读出的温湿度数值是上一次测量的结构,欲获取实时数据,需连续读取两次,但不建议连续多次读取传感器,每次读取传感器间隔时间大于2S即可获得准确的数据。

        3.电源部分如有波动,会影响到温度。如使用开关电源,温度就会跳动。

五、DHT11驱动程序

本实验中通过HT32获取DHT11的数据并通过串口发送显示于PC端的串口调试助手上。

1DHT11.C

1.1配置输入输出GPIO

        阅读HT32F52352的datasheet发现,当GPIO引脚配置为输入引脚时,如果输入使能功能寄存器PxINER的使能位被置位,则外部引脚上的数据可读。这一点是我在使用HT32是遇到的一个坑,与STM32存在一定区别。对应下面代码中的GPIO_InputConfig()函数。

//设置GPIO为输出状态
void DHT11_GPIO_OUT(void)
{	 
	CKCU_PeripClockConfig_TypeDef CKCUClock = {{0}};
 	
 	CKCUClock.Bit.PC  = 1;	   //使能PC端口时钟
	CKCUClock.Bit.AFIO = 1;    //使能AFIO功能
    CKCU_PeripClockConfig(CKCUClock, ENABLE);	//使能时钟
	
	AFIO_GPxConfig(GPIO_PC,AFIO_PIN_5, AFIO_FUN_GPIO);      //设置PC5的AFIO功能为GPIO
    GPIO_DirectionConfig(HT_GPIOC,GPIO_PIN_5,GPIO_DIR_OUT);	//设置PC5为输出	
	GPIO_PullResistorConfig(HT_GPIOC, GPIO_PIN_5, GPIO_PR_UP);	//开启上拉电阻
	GPIO_SetOutBits(HT_GPIOC, GPIO_PIN_5);	                    //PC10输出高
			    
} 

//设置GPIO为输入状态
void DHT11_GPIO_IN(void)
{	 
	CKCU_PeripClockConfig_TypeDef CKCUClock = {{0}};
 	
 	CKCUClock.Bit.PC  = 1;	 //使能PC端口时钟
	CKCUClock.Bit.AFIO = 1;  //使能AFIO功能
    CKCU_PeripClockConfig(CKCUClock, ENABLE);	//使能时钟
	
	AFIO_GPxConfig(GPIO_PC,AFIO_PIN_5, AFIO_FUN_GPIO);      //设置PC5的AFIO功能为GPIO
    GPIO_DirectionConfig(HT_GPIOC,GPIO_PIN_5,GPIO_DIR_IN);	//设置PC5为输入
    //以下步骤与STM32不同需要特别注意
	GPIO_InputConfig(HT_GPIOC, GPIO_PIN_5,ENABLE);          //开启输入使能
	
} 

1.2读取1Byte数据

//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)    
{        
    //以下分别为循环变量、数据存储变量、位数据暂存变量、超时计数变量    
    u8 i,ReadData,temp,retry;    
    ReadData=0;
	retry=0;
	
		for(i=0;i<8;i++)    //循环8次,1Byte=8bit
		{
			//等待数据信号的低电平,约为50us,当高电平来临后跳出循环
			while(DHT11_DQ_IN==0 && retry<100)
			{
				Systick_Delay_us(1);    //延时1us
				retry++;
			}
			retry = 0;    //计数清零
			Systick_Delay_us(30);    //延时30us
            //数字信号为0时,temp=0;反之,temp=1
			temp=1;
			//数字0信号高电平持续28us,数字1信号高电平70us,延时30us以确认数字0或1
			if(DHT11_DQ_IN == 0 ){temp=0;}
			while(DHT11_DQ_IN==1 && retry<100)
			{
				Systick_Delay_us(1);
				retry++;
			}
			retry=0;
			ReadData<<=1;    //左移1位
			ReadData|=temp;	 //将temp读到的值赋予ReadData
		}
		return ReadData;
}

1.3读取一次完整数据

//从DHT11读取一次数据
//返回值:1,正常;0,读取失败
u8 DHT11_Read_Data(void)    
{        
		u8 retry;
	
		//主机设置为输出,发送开始信号低电平18ms,高电平40us
		DHT11_GPIO_OUT(); 
		DHT11_DQ_OUT0;	//拉低
		Systick_Delay_ms(18);	//拉低至少18ms
        //以下为误区,用户无需手动拉高20~40us,当DHT11接收到开始信号后会自行延时20~40us,        
        //而由于数据总线上的上拉电阻,数据线会呈现为高电平
//		DHT11_DQ_OUT1;	//拉高
//		Systick_Delay_us(30);		//主机拉高20~40us
		
		//主机设置为输入,检查并接收响应信号低电平80us,高电平80us  
		DHT11_GPIO_IN();
		Systick_Delay_us(20);
		//延时20us,低电平80us,还剩60us,检查是否是低电平以确定是否有响应信号
		if(DHT11_DQ_IN == 0)
		{
			while(DHT11_DQ_IN==0 && retry<100)//接收响应信号低电平剩余60us
			{
				Systick_Delay_us(1);
				retry++;
			}
			retry=0;//超过100us自动向下运行,以免卡死
			while(DHT11_DQ_IN==1 && retry<100)//接收响应信号高电平80us
			{
				Systick_Delay_us(1);
				retry++;				
			}
			retry=0;
			//接收8字节数据
			for(i=0;i<5;i++)
			{
				dat[i]=DHT11_Read_Byte();
			}	

			Systick_Delay_us(1);//DHT11拉低总线50us作为结束信号	
		}
		sum=dat[0]+dat[1]+dat[2]+dat[3];
		if(dat[4]==sum)		
		{
			return 1;
		}
		else
			return 0;
}

2.DHT11.H

 IO操作函数	

//数据端口	PC10输出1 										   
#define	DHT11_DQ_OUT1	 	 GPIO_WriteOutBits(HT_GPIOC,GPIO_PIN_5,SET) 	
//数据端口	PC10输出0	
#define	DHT11_DQ_OUT0	 	 GPIO_WriteOutBits(HT_GPIOC,GPIO_PIN_5,RESET) 	
//数据端口	PC10 
#define	DHT11_DQ_IN 		 GPIO_ReadInBit(HT_GPIOC, GPIO_PIN_5) 					
 
 //函数的申明
void DHT11_GPIO_OUT(void);	//设置GPIO为输出
void DHT11_GPIO_IN(void);		//设置GPIO为输入
u8 DHT11_Read_Data(void);//读取一次温湿度
u8 DHT11_Read_Byte(void);//读出一个字节

3.USART.C

/**************************实现函数********************************************
函数说明:配置usart1串口

*******************************************************************************/ 
void USART1_Configuration(void)
{
  USART_InitTypeDef USART_InitStructure;
	
	CKCU_PeripClockConfig_TypeDef CKCUClock= {{0}};
	CKCUClock.Bit.AFIO = 1;	//开启AFIO复用时钟;
	COM1_CLK(CKCUClock)  = 1;  //开启时钟		COM1_CLK(CK) (CK.Bit.USART1)	宏定义中,用这个方法开启串口的时钟;这里的COM1_CLK(CKCUClock)可以变为CKCUClock.Bit.USART1
	CKCU_PeripClockConfig(CKCUClock, ENABLE);//把上面的这些结构体成员变量都赋值给时钟初始化函数;
	
	//复用PA4、PA5为AFIO6 USART模式。(USART1)
  AFIO_GPxConfig(USART1_GPIO_GROUP, USART1_TX_PIN, AFIO_FUN_USART_UART);
  AFIO_GPxConfig(USART1_GPIO_GROUP, USART1_RX_PIN, AFIO_FUN_USART_UART);

	//USART初始化
  /*
		波特率: 9600
		长度:   8bits
		停止位: 1位
	  校验位: 无			
	  模式:   正常模式
  */
	
  USART_InitStructure.USART_BaudRate = 9600;
  USART_InitStructure.USART_WordLength = USART_WORDLENGTH_8B;
  USART_InitStructure.USART_StopBits = USART_STOPBITS_1;
  USART_InitStructure.USART_Parity = USART_PARITY_NO;
  USART_InitStructure.USART_Mode = USART_MODE_NORMAL;
  USART_Init(COM1_PORT, &USART_InitStructure);

  // 使能 COM1_PORT  发送和接收 
  USART_TxCmd(COM1_PORT, ENABLE);
  USART_RxCmd(COM1_PORT, ENABLE);
	
	 //中断设置    
   NVIC_EnableIRQ(COM1_IRQn);

   USART_IntConfig(COM1_PORT, USART_FLAG_RXDR , ENABLE);
	 USART_IntConfig(COM1_PORT, USART_FLAG_TXDE , ENABLE);
	 
	 
	 /* 设置FIFO接收发送等级                                                                                   */
  USART_RXTLConfig(COM1_PORT, USART_RXTL_01);
	USART_TXTLConfig(COM1_PORT, USART_TXTL_02);
}


/**************************实现函数********************************************
函数说明:接收中断服务函数

*******************************************************************************/ 
void COM1_IRQHandler(uint8_t *str)
{
	uint8_t i;
	
	for(i = 0;str[i] != '\0';i++)
	{
		Usart1_Sendbyte(str[i]);

	}
		COM1_PORT->FCR=COM1_PORT->FCR|0x00000003;
}

/**************************实现函数********************************************
函数说明:FIFO

*******************************************************************************/ 
void USART1_Tx(const char* TxBuffer, u32 length)
{
  int i;

  for (i = 0; i < length; i++)
  {
    while (!USART_GetFlagStatus(COM1_PORT, USART_FLAG_TXC));
    USART_SendData(COM1_PORT, TxBuffer[i]);
  }
}


/**************************实现函数********************************************
函数说明:发送一个字节

*******************************************************************************/ 
void Usart1_Sendbyte(u8 cnt)
{
	USART_SendData(COM1_PORT, cnt);
	while (USART_GetFlagStatus(COM1_PORT, USART_FLAG_TXDE) == RESET);		
}
 
/**************************实现函数********************************************
函数说明:发送数组

*******************************************************************************/ 
void Usart1_SendArray(u8 *array,u8 num)
{
	u8 i;
	for( i = 0;i < num;i++)
	{
		Usart1_Sendbyte(*array);
		array++;
	}
}
 /**************************实现函数********************************************
函数说明:发送字符串

*******************************************************************************/ 

void Usart1_SendStr( uint8_t *str)
{
	uint8_t i;
	for(i = 0;str[i] != '\0';i++)
	{
		Usart1_Sendbyte(str[i]);
	}
}

4.USART.H

#define USART1_GPIO_GROUP             (GPIO_PA)
#define USART1_TX_PIN                 (GPIO_PIN_4)
#define USART1_RX_PIN                 (GPIO_PIN_5)
//默认模式:AFIO_MODE_DEFAULT ,AFIO_MODE_1~15对应模式1~15
#define USART_AFIO_MODE              (AFIO_FUN_USART_UART) 
#define COM1_PORT                    (HT_USART1)
#define COM1_IRQn                    (USART1_IRQn)




void USART1_Configuration(void);
void COM1_IRQHandler(uint8_t *str);
void Usart1_Sendbyte(u8 data);
void Usart1_SendArray(u8 *array,u8 num);
void Usart1_SendStr(uint8_t *str);
void USART1_Tx(const char* TxBuffer, u32 length);

5.DELAY

void Systick_Delay_ms(u32 ms)
{
	u32 i;
	

	SYSTICK_ClockSourceConfig(SYSTICK_SRC_STCLK);      
	SYSTICK_SetReloadValue(SystemCoreClock / 8 / 1000); 
	SYSTICK_IntConfig(DISABLE);                        
 

	SYSTICK_CounterCmd(SYSTICK_COUNTER_CLEAR);
	SYSTICK_CounterCmd(SYSTICK_COUNTER_ENABLE);
	
	for( i = 0;i < ms;i++ )
	{
		while( !( (SysTick->CTRL) & (1<<16) ) ); 
	}
 

	SYSTICK_CounterCmd(SYSTICK_COUNTER_DISABLE);

	SYSTICK_CounterCmd(SYSTICK_COUNTER_CLEAR);
}
 
void Systick_Delay_us(u32 us)
{
	u32 i;
	

	SYSTICK_ClockSourceConfig(SYSTICK_SRC_STCLK);      
	SYSTICK_SetReloadValue(SystemCoreClock / 8 / 1000000); 
	SYSTICK_IntConfig(DISABLE);                        
 

	SYSTICK_CounterCmd(SYSTICK_COUNTER_CLEAR);
	SYSTICK_CounterCmd(SYSTICK_COUNTER_ENABLE);
 
	for( i = 0;i < us;i++ )
	{
		while( !( (SysTick->CTRL) & (1<<16) ) ); 
	}
 

	SYSTICK_CounterCmd(SYSTICK_COUNTER_DISABLE);

	SYSTICK_CounterCmd(SYSTICK_COUNTER_CLEAR);
}

7.MAIN.C

#include "ht32.h"
#include "usart1.h"
#include "DHT11.h"
#include "SysTick.h"


extern u8 dat[5];

	
int main(void)
{
	u8 a,b;
	
	USART1_Configuration();		//初始化串口1

 	while(!DHT11_Read_Data())	//DHT11初始化,等待初始化完成
	{

		Usart1_SendStr("DHT11 Error ");
		Systick_Delay_ms(10);
		Usart1_SendStr("Waiting...\r\n");
		Systick_Delay_ms(10);		

	}

	while(1)
	{
		if(DHT11_Read_Data())
		{
			printf("shidu:%d%%\r\n",dat[0]);
			printf("wendu:%dC\r\n",dat[2]);			
			Systick_Delay_ms(2000);			//测量时间间隔2S	
		}
		
	}

}

物联沃分享整理
物联沃-IOTWORD物联网 » “合泰杯”——HT32F52352+DHT11笔记

发表评论