与DS18B20一样DHT11也是采用单总线,但所不同的是DHT11可同时实现温度和湿度的检测。在我们的产品中经常使用它来检测环境的温湿度信息。这一篇我们将设计并封装DHT11的驱动程序,以方便重复使用。
1、功能概述
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。
1.1、硬件描述
传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选择。产品为4针单排引脚封装。
DHT11的供电电压为 3-5.5V。传感器上电后,要等待 1s 以越过不稳定状态在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波。
1.2、通讯接口
DHT11传感器单总线通讯建议连接线长度短于20米时用5K上拉电阻,大于20米时根据实际情况使用合适的上拉电阻。连线图如下:
DATA用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零。一次完整的数据传输为40bit,高位在前。数据格式:8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和。
其中,在数据传送正确时,校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。通讯过程如下图所示:
从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。
2、驱动设计与实现
我们已经了解了DHT11温湿度传感器的相关信息,接下来我们将设计并实现DHT11温湿度传感器的驱动程序。
2.1、对象定义
在使用一个对象之前我们需要获得这个对象。同样的我们想要操作DHT11温湿度传感器就需要先定义DHT11温湿度传感器的对象。
2.1.1、对象的抽象
我们要得到DHT11温湿度传感器对象,需要先分析其基本特性。一般来说,一个对象至少包含两方面的特性:属性与操作。接下来我们就来从这两个方面思考一下DHT11温湿度传感器的对象。
先来考虑属性,作为属性肯定是用于标识或记录对象特征的东西。我们来考虑DHT11温湿度传感器对象属性。DHT11温湿度传感器并没有标识设备区别的特性,只有温度和湿度信息可以表示当前的工作状态我们将其作为属性。
接着我们还需要考虑DHT11温湿度传感器对象的操作问题。我们知道DHT11温湿度传感器采用的是单总线。单总线就需要控制总线的输入输出方向,而且这对这条总线在不同的输入输出方向,我们需要读数据和写数据,而这些操作都依赖于硬件平台,所以我们将他们定义为DHT11温湿度传感器对象的操作。处于时序控制的需要,我们需要延时操作函数,而在不同的软硬件平台延时操作会有差异,我们也将其作为对象的操作。
根据上述我们对DHT11温湿度传感器的分析,我们可以定义DHT11温湿度传感器的对象类型如下:
1 /* 定义DHT11对象类型 */ 2 typedef struct Dht11Object { 3 float temperature; //温度值 4 float humidity; //湿度值 5 6 uint8_t (*SetPinOutValue)(DhtPinValueType setValue);//设置DHT11引脚的输出值 7 uint8_t (*ReadPinBit)(void);//读取引脚电平 8 void (*SetPinDirection)(DHT11IOModeType mode);//设置引脚的输入输出方向 9 10 void (*Delayms)(volatile uint32_t nTime); /*实现ms延时操作*/ 11 void (*Delayus)(volatile uint32_t nTime); /*实现us延时操作*/ 12 }Dht11ObjectType;
2.1.2、对象初始化
我们知道,一个对象仅作声明是不能使用的,我们需要先对其进行初始化,所以这里我们来考虑DHT11温湿度传感器对象的初始化函数。一般来说,初始化函数需要处理几个方面的问题。一是检查输入参数是否合理;二是为对象的属性赋初值;三是对对象作必要的初始化配置。据此我们设计DHT11温湿度传感器对象的初始化函数如下:
1 /*DHT11初始化操作*/ 2 DHT11ErrorType InitializeDHT11(Dht11ObjectType *dht, //需要初始化对象 3 Dht11SetPinOutValueType setPinStatus, //设置总线输出值 4 Dht11ReadPinBitType getPinStatus, //读取总线输入值 5 Dht11SetPinModeType mode, //配置总线的输入输出模式 6 Dht11DelayType delayms, //毫秒延时 7 Dht11DelayType delayus //微秒延时 8 ) 9 { 10 if((dht==NULL)||(setPinStatus==NULL)||(getPinStatus==NULL)||(mode==NULL)||(delayms==NULL)||(delayus==NULL)) 11 { 12 return DHT11_InitError; 13 } 14 15 dht->SetPinOutValue=setPinStatus; 16 dht->ReadPinBit=getPinStatus; 17 dht->SetPinMode=mode; 18 dht->Delayms=delayms; 19 dht->Delayus=delayus; 20 21 dht->humidity=0.0; 22 dht->temperature=0.0; 23 24 ResetDHT11(dht); 25 return CheckDHT11Status(dht); 26 }
2.2、对象操作
我们已经完成了DHT11温湿度传感器对象类型的定义和对象初始化函数的设计。但我们的主要目标是获取对象的信息,接下来我们还要实现面向DHT11温湿度传感器的各类操作。
2.2.1、启动数据通讯
DHT11温湿度传感器上电后,总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号。主机发送开始信号结束后,延时等待20-40us后, 读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。启动数据通讯的时序图如下:
1 /*复位DHT11,开始通讯*/ 2 static void ResetDHT11(Dht11ObjectType *dht) 3 { 4 dht->SetPinMode(DHT11_Out); //设置为输出方式 5 dht->SetPinOutValue(DHT11_Reset); //将引脚点位拉低 6 dht->Delayms(20); //拉低至少18ms 7 dht->SetPinOutValue(DHT11_Set); //拉高 8 dht->Delayus(30); //主机拉高20至40us 9 }
DHT11传感器的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的 DATA引脚处于输出状态,输出80微秒的低电平作为应答信号,紧接着输出 80 微秒的高电平通知外设准备接收数据,微处理器的 I/O 此时处于输入状态,检测到 I/O 有低电平(DHT11 回应信号)后,等待80微秒的高电平后的数据接收。
1 /*等待DHT11的回应,返回1:未检测到DHT11的存在;返回0:存在*/ 2 static DHT11ErrorType CheckDHT11Status(Dht11ObjectType *dht) 3 { 4 uint8_t retry=0; 5 dht->SetPinMode(DHT11_In); //设置为输入方式 6 while(dht->ReadPinBit()&&(retry<100)) 7 { 8 retry++; 9 dht->Delayus(1); 10 } 11 if(retry>=100) 12 { 13 return DHT11_None; 14 } 15 retry=0; 16 while(!dht->ReadPinBit()&&(retry<100)) 17 { 18 retry++; 19 dht->Delayus(1); 20 } 21 if(retry>=100) 22 { 23 return DHT11_None; 24 } 25 return DHT11_NoError; 26 }
2.2.2、读取数据位
当主机变为输入模式后,检测到总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是“0”还是“1”。表示0”和“1”的时序图如下所示:
1 /*从DHT11读取一个位,返回值:1/0*/ 2 static uint8_t ReadBitFromDHT11(Dht11ObjectType *dht) 3 { 4 uint8_t retry=0; 5 /*等待变为低电平*/ 6 while(dht->ReadPinBit()&&(retry<100)) 7 { 8 retry++; 9 dht->Delayus(1); 10 } 11 retry=0; 12 /*等待变高电平*/ 13 while(!dht->ReadPinBit()&&(retry<100)) 14 { 15 retry++; 16 dht->Delayus(1); 17 } 18 dht->Delayus(40); //延时判断此位是0还是1 19 20 return dht->ReadPinBit(); 21 }
当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。
3、驱动的使用
我们已经实现了DHT11温湿度传感器的驱动,接下来将以此驱动为基础设计了简单的测试应用。
3.1、声明并初始化对象
使用基于对象的操作我们需要先得到这个对象,所以我们先要使用前面定义的DHT11温湿度传感器对象类型声明一个DHT11温湿度传感器对象变量,具体操作格式如下:
Dht11ObjectType dht;
声明了这个对象变量并不能立即使用,我们还需要使用驱动中定义的初始化函数对这个变量进行初始化。这个初始化函数所需要的输入参数如下:
Dht11ObjectType *dht,需要初始化对象
Dht11SetPinOutValueType setPinStatus,设置总线输出值
Dht11ReadPinBitType getPinStatus,读取总线输入值
Dht11SetPinModeType mode,配置总线的输入输出模式
Dht11DelayType delayms,毫秒延时
Dht11DelayType delayus,微秒延时
对于这些参数,对象变量我们已经定义了。剩下的输入参数就是我们操作中需要的函数,这几个函数需要我们在应用中定义,并将函数指针作为参数。这几个函数的类型如下:
1 typedef uint8_t (*Dht11SetPinOutValueType)(DhtPinValueType setValue);//设置DHT11引脚的输出值 2 typedef uint8_t (*Dht11ReadPinBitType)(void);//读取引脚电平 3 typedef void (*Dht11SetPinModeType)(DHT11IOModeType mode);//设置引脚的输入输出方向 4 typedef void (*Dht11DelayType)(volatile uint32_t nTime); /*实现ms延时操作*/
对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。我们在STM32F4及HAL库环境下使用,具体函数定义如下:
1 //设置DHT11引脚的输出值 2 uint8_t Dht11SetPinOutValue(DhtPinValueType setValue) 3 { 4 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,(GPIO_PinState)setValue); 5 } 6 7 //读取引脚电平 8 uint8_t Dht11ReadPinBit(void) 9 { 10 return (uint8_t)HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11); 11 } 12 13 //设置引脚的输入输出方向 14 void Dht11SetPinMode(DHT11IOModeType mode) 15 { 16 GPIO_InitTypeDef GPIO_InitStruct; 17 18 GPIO_InitStruct.Pin = GPIO_PIN_11; 19 if(mode==DHT11_In) 20 { 21 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 22 GPIO_InitStruct.Pull = GPIO_NOPULL; 23 } 24 else 25 { 26 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 27 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 28 } 29 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 30 }
对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。微秒延时函数则使用我们自己定义的。于是我们可以调用初始化函数如下:
InitializeDHT11(&dht,Dht11SetPinOutValue,Dht11ReadPinBit,Dht11SetPinMode,HAL_Delay,Delayus);
3.2、基于对象进行操作
我们定义了对象变量并使用初始化函数给其作了初始化。接着我们就来考虑操作这一对象获取我们想要的数据。我们在驱动中已经将获取数据并转换为转换值的比例值,接下来我们使用这一驱动开发我们的应用实例。
1 /*获取数据值*/ 2 void GetMeasureDataFromDHT11(void) 3 { 4 float temperature; //温度值 5 float humidity; //湿度值 6 7 GetProcessValueFromDHT11(&dht); 8 9 temperature=dht.temperature; 10 humidity=dht.humidity; 11 }
4、应用总结
我们已经实现了DHT11温湿度传感器的驱动,并在此基础上设计了简单的验证应用。经过测试,利用驱动我们成功的读取了温湿度数据。
根据数据手册的要求,DHT11温湿度传感器上电后要等待1S以越过不稳定状态在此期间不能发送任何指令。
单总线数据传输时,会改变总线的输入输出方向。在我们的应用中,我们修改了对应GPIO引脚的输入输出模式。事实上如果我们在STM32中使用时,我们可将该引脚配置为开漏输出模式,加上总线的上拉电阻,可以在不修改GPIO的输入输出模式的情况下实现读写。
源码下载:https://github.com/foxclever/ExPeriphDriver