基于STM32的DHT11功能实现(操作时序)

1.引脚定义

Pin名称注释
1VDD供电 3-5.5V
2GND接地,电源负极
3DATA串行数据,单总线
4NC空脚,请悬空

 2.数据格式

  • DHT11 采用单总线协议与单片机通信,单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。
  • 一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。
  • 数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和,一共 5 字节(40bit)数据。
  • 由于 DHT11 分辨率只能精确到个位,所以小数部分是数据全为 0。校验和为前 4 个字节数据相加,校验的目的是为了保证数据传输的准确性。

3.操作时序

通过研究时序来实现对DHT11读取数据和通信的功能。下面将时序拆解,有助于更好地理解和封装功能。

3.1 配置DHT11为输入模式(即32单片机配置为输出)

void DHT11_Init_Out(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启时钟
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
}

这里通信数据线选择为PA5

3.2 配置DHT11为输出模式(即32单片机配置为输入)

void DHT11_Init_In(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
}

3.3 实现通信开始时序

DHT11时序图

 分析该时序可知,总线空闲状态为高电平,通信开始信号为主机将总线拉低至少18ms,之后再拉高20-40us;之后的总线控制权交由DHT11控制。功能实现代码如下:

void DHT11_Start(void)
{
	DHT11_Init_Out();//主机输出模式
	DHT11_BitValue(0);//主机拉低总线
	Delay_ms(20);//延时20ms
	DHT11_BitValue(1);//主机再次拉高总线
	Delay_us(22);//延时20ms
	DHT11_Init_In();//开始时序结束,总线交于DHT11控制
}

为了方便更改电位,封装函数

void DHT11_BitValue(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_5, (BitAction)BitValue);//强转为BitAction类型
}

3.4 读取一个字节

时序图

首先关于数字0和数字1信号的时序区分

数字0信号

数字1信号

由时序图可以得知,数字1信号时序高电平持续时间为70us,远大于数字0信号的26us-38us,因此我们可以采用在30us时读取该电平信号,若为高电平,则为数字信号1。另外其余的7个bit操作完全相同,所以我们可以用for把他们套起来。

功能实现代码如下:

uint8_t DHT11_ReadByte(void)
{
	uint8_t data = 0x00;//给数据付一个初始值
	uint8_t i = 0;
	for(i=0;i<8;i++)//循环8次,接受一个字节
	{
		while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==RESET);//等待电平跳变为高电平
		Delay_us(40);
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==SET)//此时检测电平高低,若为高电平
	    {
			data |= (0x80>>i);//将该位置一
			while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==SET);//等待高电平结束,进入下一位
	    }	
	}
	return data;//返回一个字节的数据
}

3.5 接收完整数据

此时我们实现了通信开始,传输一个字节的数据,这样的字节我们一共有5个,每个的操作与上一步相同,所以我们依然可以用for循环五次,来接收数据

 注意在接收数据之前,我们要等待DHT响应 ,所以要判断是否响应

为了方便编写和阅读代码我们用宏定义替换GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)

#define  dht11_get  GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)

void DHT11_Data(uint8_t *temp,uint8_t *shidu)
{
	int8_t i = 0;
	int8_t arr[5];
	
	DHT11_Start();//通信开始时序
	DHT11_BitValue(1);//拉高电平,方便检测DHT进入低电平
	//检测响应信号
	if(dht11_get == 0)
	{
		
	    while(dht11_get == 0);//低电平等待跳变高电平
		while(dht11_get == 1);//高电平等待跳变低电平
		
		for(i=0;i<5;i++)
		{
			arr[i] = DHT11_ReadByte();
		}
		DHT11_BitValue(0);//通信结束,DHT11拉低总线
		Delay_us(50);
        DHT11_Init_Out();//总线交给主机
		DHT11_BitValue(1);//拉高总线进入空闲
		if(arr[0]+arr[1]+arr[2]+arr[3]==arr[4])//校验位检测
		{
			*temp = arr[2];
			*shidu = arr[0];
			
		}
	}
}

3.6 DHT11.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h" 


#define  dht11_get  GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)

//电平高低翻转
void DHT11_BitValue(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_5, (BitAction)BitValue);
}



//stm32对dht11输出
void DHT11_Init_Out(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
}
//DHT11对stm32输入
void DHT11_Init_In(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
}

//通信开始时序
void DHT11_Start(void)
{
	DHT11_Init_Out();
	//DHT11_BitValue(1);
	DHT11_BitValue(0);
	Delay_ms(20);
	DHT11_BitValue(1);
	Delay_us(22);
	DHT11_Init_In();
}
//读取一个字节
uint8_t DHT11_ReadByte(void)
{
	uint8_t data = 0x00;
	int i = 0;
	
	for(i=0;i<8;i++)
	{
		while(dht11_get==RESET);
		Delay_us(40);
		if(dht11_get  == SET)
	    {
			data |= (0x80>>i);
			while(dht11_get==SET);
	    }	
	}
	return data;
}



//接受完整数据
void DHT11_Data(uint8_t *temp,uint8_t *shidu)
{
	int8_t i = 0;
	int8_t arr[5];
	
	DHT11_Start();
	DHT11_BitValue(1);
	//检测响应信号
	if(dht11_get==0)
	{
		
	    while(dht11_get==0);
		while(dht11_get==1);
		
		for(i=0;i<5;i++)
		{
			arr[i] = DHT11_ReadByte();
		}
		DHT11_BitValue(0);
		Delay_us(50);
		DHT11_Init_Out();
		DHT11_BitValue(1);
		if(arr[0]+arr[1]+arr[2]+arr[3]==arr[4])
		{
			*temp = arr[2];
			*shidu = arr[0];

		}
	}
}

3.7 DHT11.h

#ifndef __DHT11_H
#define __DHT11_H

uint8_t DHT11_Data(uint8_t *temp,uint8_t *shidu);

#endif

 3.8 main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "DHT11.h"

uint8_t temp,shidu;

int main(void)
{

	
	
	OLED_Init();
	
	Serial_Init();

	while (1)
	{
	
	    Delay_s(1);
		DHT11_Data(&temp,&shidu);
	    Serial_Printf("\r\ntemp=%d", temp);
	    Serial_Printf("\r\nshidu=%d", shidu);
	    Serial_Printf("\r\n");		
	
	}
}

3.9 serial.c(串口输出)

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)//发送一个字节
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)//发送多字节
{
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char *String)//发送字符串
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)
	{
		Serial_SendByte(String[i]);
	}
}

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

3.10 serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_Printf(char *format, ...);

#endif

4.功能实现

oled显示屏实现

5.不足

 该程序目前还有更进一步的可能,比如在不同的不同时序单元内加入状态返回值,方便判断和调试,欢迎各位与我交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值