通信协议之一线协议--DHT11

在学习通信协议之前,我们先介绍几个概念

一、同步与异步通信

如果收发双发,有同步信号则叫同步通信(Synchronization)

通信协议之一线协议--DHT11_第1张图片

如果收发双方在通信时,没有同步时钟信号则叫异步通信(Aysnchronization).异步通信靠通信速率来规范高/低电平要维持多长时间来表示0/1,这个速率叫做波特率(baudrate),其单位为bps(bits per second)。

通信协议之一线协议--DHT11_第2张图片

二、并行与串行通信

通信协议之一线协议--DHT11_第3张图片

举个例子,对于计算机与外设或计算机的并行通信,就是像是多条车道,所有驶向同一方向的的车辆可以并行行驶在车道上。相反,对于计算机与外设或计算机的串行通信。就像是驶向同一方向的车辆,只能通过一条道路,大家需要排队,依次通过。

三、全双工、半双工、单工

 通信协议之一线协议--DHT11_第4张图片

单工:任意时间,只能由发送器发送接收器接收,实现单向传输。像是单向车道。

半双工:在同一时间段内,发送器发送接收器接收,只能实现单向传输。像是潮汐车道,方向可以改变。

全双工:任意时间,都可以实现双向传输。像是双向车道。

四、电平信号差分信号

电平信号和差分信号是用来描述通信线路传输方式的,也就是说如何在通信线路上表示1和0电平信号的传输线中有一个参考电平线一般是GND,然后信号线上的信号值室友信号线电平是由信号线电平的参考电平线的电压差决定;差分信号的传输线中没有参考电平线,所以信号线(D+,D-),然后1和0的表达靠信号线之间的电压差。

电平信号的两根通信线(数据线和GND)之间的电平差异容易受到干扰,传输容易失败,差分信号不容易受到干扰,因此传输质量比较稳定。现代通信(如网线,usb线等)一般都使用差分信号,电平信号几乎没有了。

经过这么多年的发展,最终生出的是:异步,串行,差分,譬如usb和网络通信。

五、示例 DHT11

dht11简介 温湿度传感器

采用简单的单总线通信。单总线即只有一根数据线,系统中的数据交换,控制均由单总线完成。单总线通常要求外界一个约5.1kΩ的上拉电阻,这样,当总线闲置,其状态为高电平。由于他们是主从结构,只有主机呼叫从机时,从机才能应答,因此从机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主句。

通信协议之一线协议--DHT11_第5张图片

 

采用一线协议

定义:主机和从机通过一根数据线进行通信,在一条总线上可以挂多个器件。

通信方式:异步通信  串行 半双工 电平信号

发送特点:单总线上所有的命令或数据的发送都是遵循低位先发送的原则

dht11通信总时序

用户主机(stm32单片机)发送一次开始信号后,dht11从低功耗转换到告诉模式,代主机开始信号结束后,dht11发送响应信号,并送出5个字节的采样数据,之后结束采集任务。

通信协议之一线协议--DHT11_第6张图片

 

主机发送起始信号

首先单片机将DTH11连接单片机的GPIO口输出低电平,且低电平保持时间不能小于18ms(t1),然后拉高数据线20~40us(t2),等待读取DHT11的响应信号。

检测从机应答信号

DHT11的DATA引脚检测到外部信号有低电平(t1),并等待外部低电平信号结束(t2),之后DHT11开始输出80us(t3)的低引脚作为应答信号,紧着着输出80us(t4)的高电平通知主机准备接收数据。

数据传输

DHT11在传输数据时,一次性传输4字节温湿度值数据和一字节数据校验。其数据格式为

1B 湿度整数数据 + 1B 湿度小数数据 + 1B温度整数数据 + 1B温度小数数据 + 1B 校验位

在发送每个字节8个位时,采用高位优先方式(MSB),其中对于数据为0/1d的电平定义如下:

数据位“0”:50微秒的低电平外加26-28W微秒的高电平;

数据位“1”:50微秒的低电平加70微秒的高电平;

 通信协议之一线协议--DHT11_第7张图片

 单片机在处理数据接收时可以先等待低电平过去,即等待数据线拉高,再延时40u因为40us大于28us且小于70us,再检测此时数据线是否为高,如果为高,则判定数据为1,否则为0;

DHT11温湿度采样实现

dht11单片机的连接

通信协议之一线协议--DHT11_第8张图片

创建DHT11温湿度传感器驱动源文件dht11.c

通信协议之一线协议--DHT11_第9张图片

 

 编写dht11的驱动源文件dht11.c

 

#include "dht11.h"

/*创建一个结构体,用来存引脚的组别和引脚号*/
typedef struct w1_gpio_S
{
	GPIO_TypeDef	*group;
	uint16_t		pin;
}w1_gpio_t;

/*结构体变量*/
static w1_gpio_t W1Dat =
{
	.group = GPIOA,//组别A
	.pin = GPIO_PIN_5,//引脚5,即PA5
};

//定义宏,将引脚设置为特定的输入模式,至于为什么没有封装成函数,是因为调用函数过程中的变量等数据需要进栈出栈,消耗大
#define W1DQ_Input()	\
{\
	GPIO_InitTypeDef GPIO_InitStruct = {0};\
 	GPIO_InitStruct.Pin = W1Dat.pin;\
 	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;\
 	GPIO_InitStruct.Pull = GPIO_PULLUP;\
 	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;\
 	HAL_GPIO_Init(W1Dat.group, &GPIO_InitStruct);\
}

//定义宏,将引脚设置为特定的输出模式
#define W1DQ_Output()	\
{\
	GPIO_InitTypeDef GPIO_InitStruct = {0};\
 	GPIO_InitStruct.Pin = W1Dat.pin;\
 	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;\
 	GPIO_InitStruct.Pull = GPIO_NOPULL;\
 	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;\
 	HAL_GPIO_Init(W1Dat.group, &GPIO_InitStruct);\
}

/*运用了三目运算符,如果x为1,则HAL_GPIO_WritePin的第三个参数level为GPIO_PIN_SET(1) ,否则为GPIO_PIN_RESET(0),*/
#define W1DQ_Write(x)	HAL_GPIO_WritePin(W1Dat.group, W1Dat.pin,\
		(x==1)?GPIO_PIN_SET : GPIO_PIN_RESET)
		
/*用于读取引脚的电平*/
#define W1DQ_Read()	HAL_GPIO_ReadPin(W1Dat.group, W1Dat.pin)

/*向主机发送起始信号*/
static void DHT11_StartSignal(void)
{
	W1DQ_Output();//设置引脚为输出模式

	W1DQ_Write(0);//输出20ms低电平
	HAL_Delay(20);
	
	W1DQ_Write(1);//输出30us高电平
	delay_us(30);

	W1DQ_Input();//设置引脚为输入模式
}

/*主机检测从机应答信号*/
uint8_t DHT11_RespondSignal(void)
{
	uint8_t retry = 0;//用于判断是否超市

	/*读取引脚状态,如果是高电平,则说明从机DHT11还没有发送低电平信号给主机,将一直循环,直到retry==100*/
	while(W1DQ_Read() && retry < 100)
	{
		retry++;
		delay_us(1);
	}
	
	/*retry>=100时,说明从机DHT11没有响应主机*/
	if(retry >= 100)
	{
		printf("The host receives no response signal!\r\n");
		return 1;
	}

	retry = 0;

	while(!W1DQ_Read() && retry < 100)
	{
		retry++;
		delay_us(1);
	}

	if(retry >= 100)
	{
		printf("DHT11 has not received a start signal!\r\n");
		return 1;
	}

	return 0;
}

/*读取一个位的数据,即为高电平或低电平*/
uint8_t DHT11_ReadBit(void)
{
	uint8_t retry = 0;
	
	/*如果是低电平,则跳过这个while循环*/
	while(W1DQ_Read() && (retry < 100))
	{
		retry++;
		delay_us(1);
	}

	retry = 0;
	while( !W1DQ_Read() && retry < 100)
	{
		retry++;
		delay_us(1);
	}

	delay_us(40);
	if(W1DQ_Read())
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

/*读取一个字节的数据*/
uint8_t DHT11_ReadByte(void)
{
	uint8_t i, dat;

	dat = 0;
	for(i=0; i<8; i++)
	{
		dat <<=1;//大端字节序,每循环一次左移一位,将最新的一位数据放到最右一位
		dat |= DHT11_ReadBit();
	}

	return dat;
}

/*一次采样*/
int DHT11_SampleData(float *temperature, float *humidity)
{
		uint8_t humi_H8bit;
		uint8_t humi_L8bit;
		uint8_t temp_H8bit;
		uint8_t temp_L8bit;
		uint8_t check_sum;

		if(!temperature || !humidity)
			return -1;

		DHT11_StartSignal();

		if(0 != DHT11_RespondSignal())
			return -2;

		humi_H8bit = DHT11_ReadByte();//湿度整数
		humi_L8bit = DHT11_ReadByte();//湿度小数
		temp_H8bit = DHT11_ReadByte();//温度整数
		temp_L8bit = DHT11_ReadByte();//温度小数
		check_sum  = DHT11_ReadByte();//校验和

		if((humi_H8bit + humi_L8bit + temp_H8bit + temp_L8bit) != check_sum)
			return -3;

		*humidity = (humi_H8bit*1000 + humi_L8bit)/1000;//湿度,整数+小数(保留三位小数)
		*temperature = (temp_H8bit*1000 + temp_L8bit)/1000;

		return 0;
}

创建头文件dht11.h

通信协议之一线协议--DHT11_第10张图片

 

 

#ifndef INC_DHT11_H_
#define INC_DHT11_H_

#include "main.h"

extern int DHT11_SampleData(float *temperature, float *humidity);

#endif /* INC_DHT11_H_ */

 main.h文件需要在main()里面添加的代码

while(1)
{
	  int flag = DHT11_SampleData(&temperature, &humidity);

	  if( flag < 0 )
	  {
	  printf("ERROR: DHT11 Sample Data failure:%d\r\n", flag);
	  }
	  else
	  {
	  printf("DHT11 Sample Temperature: %.3f	Relative Humidity: %.3f\r\n", temperature,humidity);
	  }

	  HAL_Delay(3000);
}

 

你可能感兴趣的:(学习,linux,运维)