面向学生
一、简介
本文将讲解如何使用DHT11温湿度传感器来感知环境温湿度,并且把数据在屏幕中显示出来。许多传感器的使用方法都是类似的,通过学习使用DHT11,相信读者可以做到举一反三,能够使用更多种类的传感器。
DHT11数字温湿度传感器是一款能够检测温湿度的复合传感器,其内置一个测温元件、一个电阻式感湿元件和一个单片机。以奥松公司生产的DHT11温湿度传感器为例,其有效测量范围见表。
测量项 | 有效测量范围 |
---|---|
温度 | 0~50℃ |
湿度 | 20~95% |
其实物如图所示。
可以看到,DHT11带有4个引脚,其相关说明见表。
PIN(引脚) | 名称 | 说明 |
---|---|---|
1 | VCC | 供电引脚,3~5.5VDC |
2 | DATA | 温湿度数据输出 |
3 | NC | 空置引脚 |
4 | GND | 地线引脚,接电源负极 |
根据上表说明,可以从DATA引脚来获取温湿度数据,下面简单讲解一下其通信协议。
模块的引脚说明
传感器模块把DHT11的VCC、GND和DATA引脚引出,其对应关系为:
- 模块的+引脚对应DHT11的VCC引脚
- 模块的-引脚对应DHT11的GND引脚
- 模块的out引脚对应DHT11的DATA引脚
二、DATA引脚的通信协议分析
从DHT11中获取温湿度数据的方法较比简单,首先是CC2530与DHT11配对(握手),然后按照特定的协议从DATA引脚接收数据。1.配对(握手)
在发送温湿度数据前,DHT11需要先和CC2530配对,配对的协议如下:
(1)DATA引脚在初始的默认状态时处于高电平(3.3v)。
(2)CC2530拉低DATA引脚的电平18ms毫秒以上,接着拉高电平20~40us,DHT11就会被激活。
(3)DHT11会主动拉低DATA引脚的电平,并且持续80us,表示已经收到了CC2530的指令并且配对成功。
(4)接着DHT11会再次拉高电平,80us后就开始发送温湿度数据给CC2530了。
这个配对过程如图所示。
2.接收数据
DHT11的温湿度数据是以二进制数据表示的,这些二进制数据是按照一个比特位接着一个比特位这样顺序发送到CC2530的,具体的原理如下:
(1)在发送每个比特位之前,DHT11都会把DATA引脚的电平拉低50us,以此告诉CC2530:“我接着要发送一个比特位了”。
(2)接着,DHT11把DATA引脚的电平拉高,如果持续拉高26~28us,表示发送的是数据0;如果持续拉高70us,表示发送的是数据1,如图9-3所示。
数据0
数据1
通过这个方式,温湿度数据就发送给了CC2530了。每当配对成功后,DHT11就会默认发送40个比特位,即一共5个字节,其中包含两个字节的当前温度值、两个字节是当前湿度值和一个校验值。DHT11的通信协议大致上介绍完毕,但还有多个细节还没讲解到,有兴趣的读者可查阅更多相关的资料或仔细研究一下接下来介绍的API的源代码。三、电路原理图
模块的电路原理图如图所示。
四、DHT11驱动API设计
接下来,笔者根据以上原理,为DHT11设计了2个API,定义如下:
/**
* @fn halDHT11Init
*
* @brief 初始化函数,使用DHT11前必须先调用此函数
*/
void halDHT11Init(void);
/**
* @fn halDHT11GetData
*
* @brief 获取DHT11的温湿度数据
*
* @return 温湿度数据值
*/
halDHT11Data_t halDHT11GetData(void);
上述的代码中的halDHT11Data_t是一个结构体,定义如下:
/*
* @brief 用于表示DHT11温湿度数据
*/
typedef struct {
unsigned char ok; //ok的值非0时温湿度数据才有效
unsigned char temp; //温度值,取值范围:0~50
unsigned char humi; //湿度值,取值范围:20~95
}halDHT11Data_t;
上述API的实现的参考代码如下:
/*配置DHT11的Data引脚与单片机的P0_6连接,读者可修改为其他的GPIO*/
#define HAL_DHT11_PORT 0
#define HAL_DHT11_PIN 6
/* Boolean value. */
#define HAL_DHT11_FALSE 0
#define HAL_DHT11_TRUE 1
/* DHT11 状态码定义*/
#define HAL_DHT11_SC_ERR HAL_DHT11_FALSE//测量值错误
#define HAL_DHT11_SC_OK HAL_DHT11_TRUE//测量值正确
#define HAL_DHT11_SC_HUMI_OUTOFRANGE 0xF1//湿度值超出标准测量范围
#define HAL_DHT11_SC_TEMP_OUTOFRANGE 0xF2//温度值超出标准测量范围
#define HAL_DHT11_SC_HT_OUTOFRANGE 0xF3//温度和湿度值超出测量范围
/*把GPIO设置为输入或输出模式*/
#define HAL_DHT11_IO_OUTPUT() CC2530_IOCTL(HAL_DHT11_PORT, HAL_DHT11_PIN, CC2530_OUTPUT)//设置为输出模式,读者需要根据不同的单片机来修改此函数
#define HAL_DHT11_IO_INPUT() CC2530_IOCTL(HAL_DHT11_PORT, HAL_DHT11_PIN, CC2530_INPUT_PULLDOWN)//设置为输入模式,读者需要根据不同的单片机来修改此函数
/*获取DHT11 Data引脚的电平状态*/
#define HAL_DHT11_IO_GET(port, pin) CC2530_GPIO_GET(port, pin)//获取指定GPIO的电平状态,读者需要根据不同的单片机来修改此函数
#define HAL_DHT11_IO() HAL_DHT11_IO_GET(HAL_DHT11_PORT, HAL_DHT11_PIN)//获取DHT11的Data引脚的电平状态
/*设置DHT11 Data引脚的电平状态*/
#define HAL_DHT11_IO_SET_LO() HAL_DHT11_IO_SET(HAL_DHT11_PORT, HAL_DHT11_PIN, 0)//设置地电平
#define HAL_DHT11_IO_SET_HI() HAL_DHT11_IO_SET(HAL_DHT11_PORT, HAL_DHT11_PIN, 1)//设置高电平
/* 测量结果合法性检测*/
#define HAL_DHT11_TEMP_OK(t) ((t) <= 50)//检测温度值是否合法
#define HAL_DHT11_HUMI_OK(h) ((h) >= 20 && (h) <= 95)//检测湿度值是否合法
static void halDHT11SetIdle(void);
static uint8_t halDHT11ReadByte(void);
static uint8_t halDHT11CheckData(uint8_t TempI, uint8_t HumiI);
/**
* @fn halDHT11Init
*
* @brief 初始化函数,使用DHT11前必须先调用此函数
*/
void halDHT11Init(void)
{
halDHT11SetIdle();
}
/**
* @fn halDHT11GetData
*
* @brief 获取DHT11的温湿度数据
*
* @return 温湿度数据值
*/
halDHT11Data_t halDHT11GetData(void)
{
uint8_t HumiI, HumiF, TempI, TempF, CheckSum;
halDHT11Data_t dht11Dat = { .ok = HAL_DHT11_FALSE };
/* >18ms, keeping gpio low-level */
HAL_DHT11_IO_SET_LO();//拉低Data引脚的电平
HAL_DHT11_DELAY_MS(30);//持续30ms
HAL_DHT11_IO_SET_HI();//拉高Data引脚的电平
HAL_DHT11_DELAY_US(32);//延迟32us
HAL_DHT11_IO_INPUT();//把Data引脚对应的GPIO设置为输入模式,接收DHT11的测量结果
if (!HAL_DHT11_IO()) {//如果Data引脚的电平为低电平
/*计时等待低电平结束*/
uint16_t cnt = 1070; //cnt用于在while循环中计时,1070表示约计时1ms(不同的单片的计数值会不同)
while (!HAL_DHT11_IO() && cnt--);
if(!cnt) goto Exit;//如果计时结束,表示在规定时限内DHT11仍未输出高电平
/*计时等待高电平结束*/
cnt = 1070;
HAL_DHT11_DELAY_US(80);//延时80us
while (HAL_DHT11_IO() && cnt--);
if(!cnt) goto Exit;
/* 读取数据 */
HumiI = halDHT11ReadByte();
HumiF = halDHT11ReadByte();
TempI = halDHT11ReadByte();
TempF = halDHT11ReadByte();
CheckSum = halDHT11ReadByte();//读取校验码
if (CheckSum == (HumiI + HumiF + TempI + TempF)) {//如果数据校验正确
dht11Dat.temp = TempI;//保存温度值
dht11Dat.humi = HumiI;//保存湿度值
dht11Dat.ok = halDHT11CheckData(TempI, HumiI);//检测温度和湿度是否合法
}
}
Exit:
halDHT11SetIdle();
return dht11Dat;
}
/*
* 初始化GPIO
*/
static void halDHT11SetIdle(void)
{
HAL_DHT11_IO_OUTPUT();//把GPIO配置为输出模式
HAL_DHT11_IO_SET_HI();//把GPIO口配置为高电平
}
/*
*从DHT11的Data引脚中读取一个字节
*/
static uint8_t halDHT11ReadByte(void)
{
uint8_t dat = 0;//保存读取到的字节
for (uint8_t i = 0; i < 8; i++) {//循环8次
/*计时等待低电平结束*/
uint16_t cnt = 5350;//cnt用于在while循环中计时,5350表示约计时1ms(不同的单片的计数值会不同)
while (!HAL_DHT11_IO() && cnt--);
if(!cnt) break;//如果计时结束,表示在规定时限内DHT11仍未输出高电平
/* 基于高电平的持续时间来获取的一个bit:
* 持续时间为26~28us: 0
* 持续时间>70us: 1
*/
/*延迟50us后再检测Data的电平状态*/
HAL_DHT11_DELAY_US(50);
if (HAL_DHT11_IO()) {//如果为高电平,dat的最低位输入0
dat <<= 1;
dat |= 1;
}
else {//如果为低电平,dat的最低位输入1
dat <<= 1;
continue;
}
/*计时等待高电平结束*/
cnt = 1070;//cnt用于在while循环中计时,1070表示约计时1ms(不同的单片的计数值会不同)
while(HAL_DHT11_IO() && cnt--);
if(!cnt) break;
}
return dat;
}
/*
* 测量结果合法性检测
*/
static uint8_t halDHT11CheckData(uint8_t TempI, uint8_t HumiI)
{
if (HAL_DHT11_HUMI_OK(HumiI)) {
if(HAL_DHT11_TEMP_OK(TempI)) return HAL_DHT11_SC_OK;
else return HAL_DHT11_SC_TEMP_OUTOFRANGE;
}
if (HAL_DHT11_TEMP_OK(TempI)) return HAL_DHT11_SC_HUMI_OUTOFRANGE;
else return HAL_DHT11_SC_HT_OUTOFRANGE;
}