DHT11自定义总线通信协议实例详解(附详细示波器实测波形图)

  • 格式:doc
  • 大小:197.50 KB
  • 文档页数:7

DHT11详解

有的时候,外围器件与MCU之间的通信并不是常见通用的通信方式,而是厂家自定义的一种通信格式。所以,最重要的应该是掌握根据时序图编写驱动程序的能力,本文通过对一种利用厂家自定义的通信格式和MCU之间通信的详细讲解,以期让读者掌握这种能力。在本文就以DHT11数字温湿度传感器为例来讲解。

一、 DHT11与MCU的通信协议

首先需要得到DHT11的Datasheet,然后根据Datasheet编写程序。DHT11的Datasheet前面部分是该传感器的介绍和一些接口及其相关性能参数的说明,这部分读者可以自己去看,我们关注的是改手册的第4部分:串行接口(单线双向)。接下来详细讲解这一部分。

DHT11的DATA引脚用于微处理器与DHT11之间的通讯和同步,采用单总线的数据格式,一次通信时间为4ms左右。先来看一下MCU发送开始信号和DHT11响应信号这部分的时序图:

图1 开始信号和响应信号时序图

从图1可以看出,总线空闲的时候为高电平(图1第1段的前面所示),MCU把总线拉低至少18ms(图1第1段),然后拉高总线(图1第2段)等待DHT11响应。DHT11接收到MCU的开始信号后,等待MCU开始信号结束,然后发送80us低电平响应信号(图1第3段),然后DHT11拉高总线80us(图1第4段)准备输出数据。图1第5段和图1第6段分别是数据1和数据0的格式。

根据图1编写的MCU开始信号如下:

#define DHT_IO PORTA_PA0

void DHT11_init(void)

{

DHT_IO=1;

DDRA_DDRA0=0xFF; //总线空闲高电平

DHT_IO = 0; //总线拉低

DDRA_DDRA0 = 0xFF; //设置为输出

Dlyms(18); //主机至少拉低18ms

DHT_IO = 1; //释放总线

delay_us(19); //主机拉高20~40us

DDRA_DDRA0 = 0x00; //设置为输入,等待DHT11响应

}

实测的MCU发出开始信号的波形如下:

图二 MCU发出开始信号

从图2可以看出,MCU把总线拉低18.2ms(图2中两光标之间的部分,对应于图1的第1部分),满足Datasheet中规定的数值。红框中的包括DHT11的响应信号和输出的40bit数据。接下来将红框中的波形放大进行详细的分析。先来看下Datasheet中对MCU发出的开始信号和DHT11的响应信号的详细说明,如图3所示:

图3 MCU开始信号和DHT11响应信号时序图

实测的波形为:

图4 MCU发出开始信号和DHT11响应信号实测波形

图4中下方的数字2、3、4是和图一中的数字一一对应的,上方的时间是实测的时间,和图三中的时间进行比较可以发现,图四中的数据并不是完全和Datasheet上的一致,实践表明,误差是允许的。

开始信号和响应信号结束后就是数据的传输,从DHT11的Datasheet上可以看出其对数据0和数据1的定义如下:

图5 数据0格式

图6 数据1格式

总线为低电平说明DHT11发送响应信号,DHT11发送响应信号后再把总线拉高80us准备发送数据,没一bit数据都是以50us低电平时序开始,紧跟着的高电平的长短决定了数据是0还是1,格式如图5和图6,实测的如图7和图8。

图7 数据0格式 图8 数据1格式

从实测的2张波形图可以看出,跟Datasheet数据还是存在一定的误差的,但这个不影响正常的通信。

有了以上的知识就可以编写函数从DHT11中读取温湿度数据了。一次完整的数据传输为40bit,高位先出。

数据格式:8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和。数据传输正确是校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据”所得结果的末8位。先传输高位。当前小数部分用于以后扩展,现读出为0。

根据以上的说明编写的从DHT11读取数据的函数如下:

unsigned char get_DHT11_value(void)

{

(1) unsigned int bit;

unsigned char p=0,time_count=0;

temperature=0x0000;

humidity=0x0000;

bit=0x8000;

DHT_IO = 0; //总线拉低

DDRA_DDRA0 = 0xFF; //设置为输出

Dlyms(18); //主机至少拉低18ms

DHT_IO = 1; //释放总线

delay_us(19); //主机拉高20~40us

DDRA_DDRA0 = 0x00; //设置为输入,等待DHT11响应

delay_us(30);

//无应答信号,退出

if(DHT_IO!=0) return 0;

//等待电平变高,设置200us超时控制

time_count=0;

while(DHT_IO==0)

{

delay_us(10);

if(++time_count==20) return 0;

}

//等待电平变低,设置150us超时控制

time_count=0;

while(DHT_IO==1)

{

delay_us(10);

if(++time_count==15) return 0;

}

PORTA_PA1=~PORTA_PA1; //调试语句

(2) //数据开始

while(p!=2)

{

//低电平宽度50us

//等待电平变高,设置100us超时控制

(3) time_count=0;

while(DHT_IO==0)

{

delay_us(10); if(++time_count==10) return 0;

}

(4) time_count=0;

do //计算高电平持续的时间

{

delay_us(10);

time_count++;

if(time_count==10) return 0;

}while(DHT_IO==1);

PORTA_PA1=~PORTA_PA1; //调试语句

//若高电平超过30us,说明数据位为1,否则为0

(5) if(time_count>=3)

{

if(p==0) humidity|=bit;

if(p==1) temperature|=bit;

}

bit>>=1; //为接收下一位做准备

//2字节(16位)结束

if(bit==0x0000)

{

p++;

bit=0x8000;

}

}

return 1;

}

其中的temperature和humidity定义为unsigned int型全景变量。程序中已经有了详细的中文注释,为了让读者对整个读函数的结构有一个更为清晰的认识,在这里将这个函数分成5部分,每一部分的功能如下:(1):MCU发出开始信号,DHT11响应开始信号,等待响应信号结束,数据部分开始。(2):从这部分开始就是MCU从DHT11中读数据的过程。(3):每1bit数据都是以50us低电平开始的,这部分的功能是等待50us低电平过去。(4):计算50us后面所跟的高电平程序的时间,该时间以time_count的值来表征。(5)根据time_count的值来(是否大于等于3)判断数据位为1还是为0。这里需要注意的一点是,程序中的注释里说“若高电平超过30us,说明数据位为1,否则为0”,30us这个值是根据time_count的阈值(>=3)乘以延时函数delay_us(10);延时的10us得出的,实际测试中发现delay_us(10)的值略大于10us,再加上其中的 time_count++;

if(time_count==10) return 0; 两个语句,实际上其值不止30us的,实测的为40us。也就是说当高电平持续的时间大于40us时既认定其数据为1.其中的调试语句是为了确定采样的点是否正确而设置的。程序运行的截图如图:

图9 程序运行结果

由于波形图一共有40bit,一个屏幕放不了,所以这里分为分为三部分逐一进行介绍:

第一个字节的值(湿度值,对于图图九中的humidity变量)如下图所示:

图10 读出的第一个字的值

图10中,两个光标之间的为第一个字的实测波形图,从图上可以看出,其值为:0011 1010 0000 0000(0x3a00),跟图9中的humidity变量的值是一样的。至于如何根据图10的波形图中读出数据,读者可以这样看,低电平持续的时间都是一样的,所以只要看高电平的长短,短的为数据0,长的为数据1。

第二个字节的值(温度值,对于图9中的temperature变量)如下图所示:

图11 读出的第二个字的值

从图11可以看出,其值为0001 1100 0000 0000(0x1c00),跟图9中的temperature变量的值是一样的。

上面的get_DHT11_value()函数只是读出前32位的值,最后的8bit校验和没有读出。但是我们可以从示波器上看出其结果:

图12 校验和

从图12可以看出其校验和为0101 0110,读者可以按照前面给出的说明进行计算验证下,可以证明其数据传输过程是正确的。