DHT11温湿度传感器,使用定时器输入捕获+DMA硬件自动搬运数据(基于STM32F10xxx标准库开发)

本文介绍了作者针对STM32F10x平台设计的DHT11温湿度传感器驱动,使用输入捕获和DMA技术处理信号,确保数据接收的稳定性和低干扰。代码详细展示了如何配置GPIO、定时器、DMA以及处理起始信号和接收数据的过程。
摘要由CSDN通过智能技术生成

设计思路

        设计初心:商家提供的驱动特别难用,我不仅需要加两个DHT11的文件,还要加很多其他文件,看着就头疼,于是自己搞了一个,代码纯手工制作,放心食用,至于一些有关DHT11的数据资料之类的就不在这里放了。

        DHT11温湿度传感器属于单线传输方式,因此需要用主机给DHT11模块一个起始信号(18-30ms的低电平与20-40um的高电平),而DHT11返回的信息为40个高电平一共40位分别为:湿度高位,湿度低位,温度高位,温度低位,校验位,校验方式也很简单,湿度高位+湿度低位+温度高位+温度低位 =校验位。

        我们需要判断DHT11给主机的返回信号的高电平持续时间,从而知道每一位的高低电平,因此我选择使用输入捕获的方式来得到一组高低电平的时间,使用PWMI模式,获得低电平的持续时间与一个周期的时间,而一个周期的时间-低电平持续时间=高电平持续的时间,我们就可以获得每一位的值。然而,我们想在传输过程中不影响其他程序,并且不被程序中其他中断打断接收信号,还需要一个DMA在每次事件完成后搬运走我们的数据,并且存放到一个足够大的数组中,传输完成后,我们通过这个数组来判断每一位的高低电平,这个过程由于不要求实时,因此并不担心被中断等程序打断。

       下面话不多说上代码。

代码讲解

输入捕获配置

void IC_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
	
	TIM_InternalClockConfig(TIM1);
	TIM_TimeBaseInitTypeDef TIM_TimBaseInitStructure;
	TIM_TimBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimBaseInitStructure.TIM_Period=65535-1;
	TIM_TimBaseInitStructure.TIM_Prescaler=72-1;
	TIM_TimBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM1,&TIM_TimBaseInitStructure);
	
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter=0x5;
	TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Falling;
	TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI ;
	TIM_ICInit(TIM1,&TIM_ICInitStructure);
	TIM_PWMIConfig(TIM1,&TIM_ICInitStructure);
	TIM_SelectInputTrigger(TIM1,TIM_TS_TI1FP1);
	TIM_SelectSlaveMode(TIM1,TIM_SlaveMode_Reset);
	
	
	TIM_Cmd(TIM1,DISABLE);
}

        由于我们要接收的数据一个周期在70-120um左右,因此PSC设置为71,即ARR里每增加一个值代表1um,时基单元就选择内部时钟的72M,因为DHT11在收到起始信号后会返回给主机一个80um左右的一个低电平与高电平,程序是在低电平结束后配置这输入捕获通道和DMA,因此DMA会多搬运一个值,这个值我们后面会将它跳过。

        至于PWMI模式,前面说过,我们得到的值是一整个周期(下降沿开始,到下降沿结束),因此还需要一个从下降沿上升沿的值,因为每一个周期都要清零CNT,因此我们需要配置一下TRGI的触发源与从模式。

GPIO配置

void GPIO_Output_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_DHT11;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
}
void GPIO_IPU_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin=GPIO_DHT11;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
}

GPIO配置无非两种,一个是在给DHT11输出信号时选用的推挽输出,一个是接收信号用的上拉输入,这里不做过多解释

DMA配置

void DHT11_DMA_Init(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&TIM1->CCR1;
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)oneValue;
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize=41;
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;
	DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;
	DMA_InitStructure.DMA_Priority=DMA_Priority_High;
	DMA_Init(DMA1_Channel2,&DMA_InitStructure);
	
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&TIM1->CCR2;
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)twoValue;
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize=40;
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;
	DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;
	DMA_InitStructure.DMA_Priority=DMA_Priority_High;
	DMA_Init(DMA1_Channel3,&DMA_InitStructure);
	DMA_Cmd(DMA1_Channel2,DISABLE);
	DMA_Cmd(DMA1_Channel3,DISABLE);
	TIM_DMACmd(TIM1,TIM_DMA_CC1,DISABLE);
	TIM_DMACmd(TIM1,TIM_DMA_CC2,DISABLE);
}

        DMA在本程序中一共需要两个,如果资源紧张也可以使用一个(DHT11传输的低电平永远为50um左右)。

        两个DMA的源地址分别为TIM定时器的CCR1与CCR2的地址,目的地址为两个数组,这两个数组我随手起的名字,oneValue和twoValue两个值哈。上面说到过,由于DHT11会给主机一个低电平和一个高电平的反馈,在高电平结束后就会开始传输数据。但是我们的定时器在ENABLE之后就会开始工作,第一个值(高电平到开始传输数据前的下降沿)还是会给到oneValue,DMA也会搬运这个值,因此我选择将这个多留一位,即DMA1_Channel2为41,当然如果你觉得不美观可以再设置一个while等待这个过程,我主要时考虑其他程序随时可能发生的中断,如果在这个while中程序被中断,那么将接收不到DHT11发送的数据

        地址是否自增这个也好理解哈,DMA每次都会搬运同一个源地址(CCR寄存器),但是目的地址是要自增的,数据宽度我选择的HalfWord,由于CCR的值有时会达到120多,防止任何可能的意外造成,因此我的两个数组的数据类型也是无符号16位整型。

        这里我要说明一下,上面的定时器和这里的Cmd的参数都是DISABLE,因为这里只需要初始化,并不使用这个(需要先给DHT11发送一个信号)。

启动信号

void StartSignal(void)
{
	temint=0;
	temdec=0;
	humiint=0;
	humidec=0;
	check=0;
	GPIO_Output_Init();
	GPIO_ResetBits(GPIOA,GPIO_DHT11);
	Delay_ms(20);
	GPIO_SetBits(GPIOA,GPIO_DHT11);
	Delay_us(30);
	GPIO_IPU_Init();
	while(GPIO_ReadInputDataBit(GPIOA,GPIO_DHT11)==Bit_RESET);
	IC_Start();
}

        上面提到过的,要先给DHT11一个起始信号,先设置推挽输出,之后改成上拉输入,启动之前将几个用于存放值的变量清零。

void IC_Start(void)
{
	DMA_SetCurrDataCounter(DMA1_Channel2,41);
	DMA_SetCurrDataCounter(DMA1_Channel3,40);
	DMA_Cmd(DMA1_Channel2,ENABLE);
	DMA_Cmd(DMA1_Channel3,ENABLE);
	TIM_DMACmd(TIM1,TIM_DMA_CC1,ENABLE);
	TIM_DMACmd(TIM1,TIM_DMA_CC2,ENABLE);
	TIM_Cmd(TIM1,ENABLE);
}

这里给DMA的传输计数器赋值是为了之后重复使用设计的,在第一次的时候其实并不需要赋值。

接收信号

void ReadSignal(void)
{
	while(DMA_GetFlagStatus(DMA1_FLAG_TC2)==RESET);
	TIM_Cmd(TIM1,DISABLE);
	DMA_Cmd(DMA1_Channel2,DISABLE);
	DMA_Cmd(DMA1_Channel3,DISABLE);
	TIM_DMACmd(TIM1,TIM_DMA_CC1,DISABLE);
	TIM_DMACmd(TIM1,TIM_DMA_CC2,DISABLE);
	for(int i=1;i<=8;i++)
		{
			if(oneValue[i]-twoValue[i-1]>40)
			{
				humiint|=1;
			}
			if(oneValue[i+8]-twoValue[i+7]>40)
			{
				humidec|=1;
			}
			if(oneValue[i+16]-twoValue[i+15]>40)
			{
				temint|=1;
			}
			if(oneValue[i+24]-twoValue[i+23]>40)
			{
				temdec|=1;
			}
			if(oneValue[i+32]-twoValue[i+31]>40)
			{
				check|=1;
			}
			if(i!=8)
			{
			temint=temint<<1;
			temdec=temdec<<1;
			humiint=humiint<<1;
			humidec=humidec<<1;
			check=check<<1;
			}
		}
		if(temint+temdec+humidec+humiint!=check)
		{
			temint=99;
			temdec=99;
			humiint=99;
			humidec=99;
			check=99;
		}
		DMA_ClearFlag(DMA1_FLAG_TC2);
}

先保证DMA已经传输完毕,然后失能DMA和TIM,由于oneValue的第一个值不正确(表示DHT11的反馈信号高电平),因此两个括号的 i 要移动一位。最后的99相当于报错,这个可以删掉,可以将上次获取的值存放起来然后赋值给这次的变量

主函数与DHT11.h头文件

测试用代码

#include "stm32f10x.h"  
#include "dht11.h"
uint16_t oneValue[41]={0};
uint16_t twoValue[40]={0};
uint8_t temint=0;
uint8_t temdec=0;
uint8_t humiint=0;
uint8_t humidec=0;
uint8_t check=0;
int main()
{
	DHT11_Init();
	while(1)
	{
	StartSignal();
	ReadSignal();
	Delay_ms(1000);		
	}
}

DHT11_Init()其实就是初始化但不使能输入捕获与DMA,内容如下:

void DHT11_Init(void)
{
	IC_Init();
	DHT11_DMA_Init();
}

DHT11.h中声明一下main.c中用到的全局变量和数组,以便在DHT11.c的函数中可以使用。

#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h"
#include "Delay.h"
#define GPIO_DHT11 GPIO_Pin_8 //默认使用PA8引脚
extern uint16_t oneValue[41];
extern uint16_t twoValue[40];
extern uint8_t temint;
extern uint8_t temdec;
extern uint8_t humiint;
extern uint8_t humidec;
extern uint8_t check;
void GPIO_Output_Init(void);
void GPIO_IPU_Init(void);
void IC_Init(void);
void DHT11_DMA_Init(void);
void StartSignal(void);
void ReadSignal(void);
void IC_Start(void);
void DHT11_Init(void);
#endif

以上为用到的变量与函数,delay这个就不在这里放了。

END

        如果你发现还有更好的方法写这个驱动,或者你发现我的代码有需要改进的地方,欢迎联系我,咱们可以探讨一下相互学习。

### 回答1: 基于STM32单片机DHT11湿度传感器OLED显示程序可以实现以下功能: 首先,需要连STM32单片机DHT11湿度传感器以及OLED显示屏。 然后,编写程序读取DHT11传感器的湿度数值。可以通过引脚连使用相应的库函数来实现数据读取。 下来,使用OLED显示屏库函数将湿度数据显示在OLED屏幕上。可以在屏幕上创建相应的文本框或者图标来显示湿度值。可以使用合适的库函数调用,将湿度数据转换为字符串格式并在屏幕上显示出来。 同时,可以设计一个定时器来定时更新湿度数据的显示。可以设置一个适当的时间间隔来实现数据的定时更新,并使用相应的库函数来控制定时器的启动和停止。 此外,为了增加用户体验,还可以添加一些额外的功能,比如在某个湿度阈值超过一定值时,显示警告信息或者触发报警器等。 最后,将编写好的程序下载到STM32单片机中进行测试。通过观察OLED显示屏是否能够正确显示湿度数值,以及数据是否能够定时更新,来验证程序的正确性。 总体来说,基于STM32单片机DHT11湿度传感器OLED显示程序需要通过串口和I2C总线连硬件设备,并使用相应的库函数来读取传感器数据和控制OLED显示屏,以实现湿度数据的实时显示。 ### 回答2: 基于STM32单片机DHT11湿度传感器和OLED显示程序可以实现如下功能。 首先,我们需要连DHT11湿度传感器STM32单片机的GPIO口。DHT11传感器的信号线单片机的输入GPIO口,供电线单片机的5V电源口,地线单片机的地线。 着,需要通过STM32的GPIO口读取DHT11传感器发送的湿度数据。通过向DHT11传感器发送一个读取请求信号,然后在适当的时间间隔后读取传感器发送的数据,包括度和湿度值。 下来,我们需要将读取到的湿度数据通过I2C或SPI协议发送到连的OLED显示屏上显示出来。首先,需要初始化I2C或SPI口,然后将湿度数据传送到OLED显示屏的适当位置进行显示。可以使用相应的OLED显示屏库函数来帮助实现这一功能。 此外,为了更好地呈现湿度数据,还可以添加一些额外的功能。例如,可以设置一个度和湿度的阈值,当度或湿度超过阈值时,通过OLED显示屏进行警告或提示。还可以添加一个实时钟表显示当前的时间,并将当前湿度数据显示在时钟表上。 需要注意的是,在编写程序时,应根据单片机型号和开发环境选择相应的库函数和配置参数,确保程序正确运行。 以上是基于STM32单片机DHT11湿度传感器和OLED显示程序的简要说明。具体的实现细节和代码可以根据具体的需求和硬件平台进行调整和开发。 ### 回答3: 基于STM32单片机DHT11湿度传感器OLED显示程序主要实现了以下功能。 首先,我们需要通过STM32单片机DHT11传感器进行通信。我们可以通过引脚连和编程设置来实现数据的读取。在程序中,我们需要配置引脚输入/输出模式,并通过适当的延时来与DHT11发送和数据下来,我们需要解析从DHT11传感器收到的数据DHT11传感器会发送40位二进制数据,其中包含度和湿度信息。我们可以根据协议来解析这些数据,并将其存储到相应的变量中。 然后,我们需要将解析后的数据通过OLED显示屏进行显示。在STM32单片机中,我们可以使用相应的库函数来控制OLED显示屏。我们需要将度和湿度信息转换为字符串,并使用适当的字符函数来显示在OLED屏幕上。 最后,我们可以通过循环来实现数据的持续更新和显示。以一定的时间间隔读取DHT11传感器的数据,并将其显示在OLED屏幕上。这样,我们就实现了基于STM32单片机DHT11湿度传感器OLED显示程序。 需要注意的是,为了确保程序的正常运行,我们还需要根据实际情况对程序进行优化和调试。这可能包括校准湿度传感器、处理错误情况和调整程序逻辑等。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值