DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。其成本低、长期稳定、可以测量相对湿度和温度测量,并可以只使用一根数据线进行温湿度采集。
1.模块来源
模块实物展示:
资料下载链接:https://pan.baidu.com/s/1HQEL699-Yl5Jh3Hp87_FlQ
资料提取码:2sgq
2. 规格参数
模块的厂家资料下载见百度网盘链接
工作电压:3-5.5V
工作电流:1MA
测量分辨率:8 bit
湿度量程: 20 - 90 %RH
湿度精度:±5 %RH
温度量程: 0 - 50 ℃
温度精度:±2 ℃
通信协议:单总线
管脚数量:3 Pin(2.54mm间距排针)
3.移植过程
我们的目标是在立创·地文星·CW32F030C8T6开发板上实现读取温湿度的功能。首先要获取资料,查看数据手册应如何实现读取数据,再移植至我们的工程。
3.1查看资料
DHT11使用的是单总线通信,即发送数据与接收数据都在一根数据线上,通过规定的时序进行控制。
从左向右看,时序一开始,主机信号就保持着高电平,所以引脚初始化完毕时,及时给引脚输出高电平。因为 模块的数据线要求空闲时,要保持高电平状态。(其实模块上已经接了上拉电阻,使数据线一直保持高电平)
根据时序图可以知道,主机(开发板)发送一次开始信号,待主机开始信号结束后,DHT11 发送响应信号,送出 温湿度数据,并触发一次数据采集给下一次数据读取作准备。因此完成一次数据读取需要进行起始信号、响应信号、数据接收、结束信号。
读取数据步骤:
- 起始信号:主机(开发板)接入数据线的I/O输出低电平,且低电平保持时间不能小于 18ms
DATA_GPIO_OUT(0); //数据线输出低电平
delay_1ms(19); //起始信号保持时间19ms
DATA_GPIO_OUT(1); //主机释放总线
delay_uus( 20 ); //拉高等待
2.响应信号:等待模块的响应信号到来。将数据线改为输入模式,如果接入到低电平,说明接收到模块的响应。
DHT11_GPIO_Mode_IN();//数据线转为输入模式
//如果前面没有错误,则模块会发出低电平的应答信号,
//所以直接等待DHT11拉高,83us
timeout = 5000;
while( (! DATA_GPIO_IN ) && ( timeout >0 ) )
{
timeout--; //等待高电平的到来
}
//模块当前处于拉高准备输出数据,
//所以直接等待DHT11拉低,87us
timeout = 5000;//设置超时时间
while( DATA_GPIO_IN && ( timeout >0 ) )
{
timeout-- ; //等待低电平的到来
}
3.数据传输:主机接收模块发送的40位数据,其中,位数据 ‘0’ 表示54us的低电平,27us的高电平;位数据 ‘1’ 表示54us的低电平,74us的高电平。两个格式的分辨主要是高电平的输出时长不同。
#define CHECK_TIME 28 //超过0值的高电平时间
for(i=0;i<40;i++)//循环接收40位数据
{
timeout = 5000;
//等待低电平过去
while( ( !DATA_GPIO_IN ) && (timeout > 0) ) timeout--; //54us
delay_uus(CHECK_TIME);//等待超过位数据0值的高电平时间
if ( DATA_GPIO_IN )//如果还是高电平,说明是1值
{
val=(val<<1)+1;
}
else //如果是低电平,说明是0值
{
val<<=1;
}
timeout = 5000;
//如果当前还是高电平,等待高电平过去,准备接收下一位数据
while( DATA_GPIO_IN && (timeout > 0) ) timeout-- ;
}
4.结束信号:模块的数据线输出 40 位数据后,是以低电平结束,它会继续输出低电平 54 微秒后转为输入状态,主机需要转为输出状态,输出高电平释放总线。
DHT11_GPIO_Mode_OUT();//转为输出模式
DATA_GPIO_OUT(1);//主机释放总线
数据接收完成,但是这40位数据要如何转化为温湿度数据?并如何保证传输的数据没有错误?
DHT11模块一次完整的数据传输为40bit,高位先出。数据格式:
8bit湿度整数数据 + 8bit湿度小数数据 + 8bi温度整数数据 + 8bit温度小数数据 + 8bit校验和
注意
湿度小数部分数据一直为0。
数据传送正确时,校验和数据等于“8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据”所得结果的末8位。举几个例子。
示例一:接收的40位数据分别为:
0011 0101 | 0000 0000 | 0001 1000 | 0000 0100 | 0101 0001 |
湿度高8位 | 湿度低8位 | 温度高8位 | 温度低8位 | 校验位 |
校验和为 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 = 0101 0001,与接收的数据一致
湿度为 0011 0101 + 0000 0000 = 35 + 0 = 35%RH
温度为 0001 1000 0000 0100 = 24 + 4 = 24.4℃
示例二:接收的40位数据分别为:
0011 0101 | 0000 0000 | 0001 1000 | 0000 0100 | 0100 1001 |
湿度高8位 | 湿度低8位 | 温度高8位 | 温度低8位 | 校验位 |
校验和为 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 = 0101 0001,与接收的数据不一致 计算的数据为0101 0001,接收的数据为0100 1001,两者不一致说明数据不准确,丢弃这次数据,重新接收。
以下为数据处理的实现代码:
//val为接收到的40位数据。
// 湿高8 + 湿低8 + 温高8 + 温低8
verify_num = (val>>32) + (val>>24) + (val>>16) + (val>>8);
//计算的校验和 与 接收的校验和 的差为0说明一致,不为0说明不一致
//(val&0xff)是因为val的大小为64位,我们只需要val的最后8位校验和
verify_num = verify_num - (val&0xff);
//进行校验
if( verify_num )//如果不为0,说明校验失败
{
// 校验错误
return 0;
}
else //校验成功
{
//数据处理
humidity = (val>>32)&0xff; //湿度前8位(小数点前数据)
small_point = (val>>24)&0x00ff; //湿度后8位(小数点后数据)
small_point = small_point * 0.1; //换算为小数点
humidity = humidity + small_point; //小数前+小数后
printf("湿度:%.2f\r\n",humidity);
temperature = (val>>16)&0x0000ff; //温度前8位(小数点前数据)
small_point = (val>>8)&0x000000ff; //温度后8位(小数点后数据)
small_point = small_point * 0.1; //换算为小数点
temperature = temperature + small_point;//小数前+小数后
printf("温度:%.2f\r\n",temperature);
return val>>8; //返回未处理的数据
}
3.2引脚选择
该模块有3个引脚,具体引脚连接见 表 各引脚连接。
3.3移植至工程
工程模板下载请查看入门手册百度链接
然后我们打开空白工程,新建两个文件dht11.c和dht11.h
在文件dht11.c中,编写如下代码。
/*
* Change Logs:
* Date Author Notes
* 2024-06-19 LCKFB-LP first version
*/
#include "dht11.h"
#include "stdio.h"
float temperature = 0;
float humidity = 0;
/******************************************************************
* 函 数 名 称:DHT11_GPIO_Init
* 函 数 说 明:DHT11温湿度传感器初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void DHT11_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体
RCC_DHT11_GPIO_ENABLE(); // 使能GPIO时钟
GPIO_InitStruct.Pins = GPIO_DHT11; // GPIO引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 输出速度高
GPIO_Init(PORT_DHT11, &GPIO_InitStruct); // 初始化
DATA_GPIO_OUT(1);
}
/******************************************************************
* 函 数 名 称:DHT11_GPIO_Mode_OUT
* 函 数 说 明:配置DHT11的数据引脚为输出模式
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void DHT11_GPIO_Mode_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体
GPIO_InitStruct.Pins = GPIO_DHT11; // GPIO引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 输出速度高
GPIO_Init(PORT_DHT11, &GPIO_InitStruct); // 初始化
}
/******************************************************************
* 函 数 名 称:DHT11_GPIO_Mode_IN
* 函 数 说 明:配置DHT11的数据引脚为输入模式
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void DHT11_GPIO_Mode_IN(void)
{
GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体
GPIO_InitStruct.Pins = GPIO_DHT11; // GPIO引脚
GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP; // 上拉输入
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 输出速度高
GPIO_Init(PORT_DHT11, &GPIO_InitStruct); // 初始化
}
/******************************************************************
* 函 数 名 称:DHT11_Read_Data
* 函 数 说 明:根据时序读取温湿度数据
* 函 数 形 参:无
* 函 数 返 回:0=数据校验失败 其他=温湿度未处理的数据
* 作 者:LC
* 备 注:无
******************************************************************/
unsigned int DHT11_Read_Data(void)
{
int i;
long long val=0;
int timeout=0;
float small_point=0;
unsigned char verify_num = 0;//验证值
DATA_GPIO_OUT(0);//数据线输出低电平
delay_ms(19); //起始信号保持时间19ms
DATA_GPIO_OUT(1);//主机释放总线
delay_us( 20 );//拉高等待
DHT11_GPIO_Mode_IN();//数据线转为输入模式
//如果前面没有错误,则模块会发出低电平的应答信号,所以直接等待DHT11拉高,80us
timeout = 5000;
while( (! DATA_GPIO_IN ) && ( timeout >0 ) )timeout--; //等待高电平的到来
//模块当前处于拉高准备输出数据,所以直接等待DHT11拉低,80us
timeout = 5000;//设置超时时间
//DATA_GPIO_IN=0时,while条件不成立退出while 说明接收到响应信号
//当timeout<=0时,while条件不成立退出while 说明超时
while( DATA_GPIO_IN && ( timeout >0 ) )timeout-- ; //等待低电平的到来
#define CHECK_TIME 28 //实测发现超过0值的高电平时间
for(i=0;i<40;i++)//循环接收40位数据
{
timeout = 5000;
while( ( !DATA_GPIO_IN ) && (timeout > 0) ) timeout--; //等待低电平过去
delay_us(CHECK_TIME);//超过0值的高电平时间
if ( DATA_GPIO_IN )//如果还是高电平,说明是1值
{
val=(val<<1)+1;
}
else //如果是低电平,说明是0值
{
val<<=1;
}
timeout = 5000;
while( DATA_GPIO_IN && (timeout > 0) ) timeout-- ; //如果还是高电平
}
DHT11_GPIO_Mode_OUT();//转为输出模式
DATA_GPIO_OUT(1);//主机释放总线
// 湿高8 + 湿低8 + 温高8 + 温低8
verify_num = (val>>32) + (val>>24) + (val>>16) + (val>>8);
//计算的校验和 与 接收的校验和 的差为0说明一致,不为0说明不一致
verify_num = verify_num - (val&0xff);
//进行校验
if( verify_num )
{
// 校验错误
return 0;
}
else //校验成功
{
//数据处理
humidity = (val>>32)&0xff;//湿度前8位(小数点前数据)
small_point = (val>>24)&0x00ff;//湿度后8位(小数点后数据)
small_point = small_point * 0.1;//换算为小数点
humidity = humidity + small_point;//小数前+小数后
// printf("湿度:%.2f\r\n",humidity);
temperature = (val>>16)&0x0000ff;//温度前8位(小数点前数据)
small_point = (val>>8)&0x000000ff;//温度后8位(小数点后数据)
small_point = small_point * 0.1;//换算为小数点
temperature = temperature + small_point;//小数前+小数后
// printf("温度:%.2f\r\n",temperature);
return val>>8; //返回未处理的数据
}
}
/******************************************************************
* 函 数 名 称:Get_temperature
* 函 数 说 明:获取温度数据
* 函 数 形 参:无
* 函 数 返 回:温度值
* 作 者:LC
* 备 注:使用前必须先调用 DHT11_Read_Data 读取有数据
******************************************************************/
float Get_temperature(void)
{
return temperature;
}
/******************************************************************
* 函 数 名 称:Get_humidity
* 函 数 说 明:获取湿度数据
* 函 数 形 参:无
* 函 数 返 回:湿度值
* 作 者:LC
* 备 注:使用前必须先调用 DHT11_Read_Data 读取有数据
******************************************************************/
float Get_humidity(void)
{
return humidity;
}
在文件dht11.h中,编写如下代码。
/*
* Change Logs:
* Date Author Notes
* 2024-06-19 LCKFB-LP first version
*/
#ifndef _BSP_DHT11_H_
#define _BSP_DHT11_H_
#include "board.h"
/**************引脚修改此处****************/
#define RCC_DHT11_GPIO_ENABLE() __RCC_GPIOB_CLK_ENABLE()
#define PORT_DHT11 CW_GPIOB
#define GPIO_DHT11 GPIO_PIN_0
//设置DHT11输出高或低电平
#define DATA_GPIO_OUT(x) GPIO_WritePin(PORT_DHT11, GPIO_DHT11, x ? GPIO_Pin_SET : GPIO_Pin_RESET)
//获取DHT11数据引脚高低电平状态
#define DATA_GPIO_IN GPIO_ReadPin(PORT_DHT11, GPIO_DHT11)
extern float temperature;
extern float humidity;
void DHT11_GPIO_Init(void);//引脚初始化
unsigned int DHT11_Read_Data(void);//读取模块数据
float Get_temperature(void);//返回读取模块后的温度数据
float Get_humidity(void);//返回读取模块后的湿度数据
#endif
4.移植验证
在自己工程中的main主函数中,编写如下。
/*
* Change Logs:
* Date Author Notes
* 2024-06-19 LCKFB-LP first version
*/
#include "board.h"
#include "stdio.h"
#include "bsp_uart.h"
#include "dht11.h"
int32_t main(void)
{
board_init(); // 开发板初始化
uart1_init(115200); // 串口1波特率115200
DHT11_GPIO_Init(); //DHT11引脚初始化
delay_ms(1000);
printf("DHT11 demo start\r\n");
while(1)
{
//读取模块数据
DHT11_Read_Data();
//显示读取后的温度数据
printf("temperature = %.2f\r\n", Get_temperature() );
//显示读取后的湿度数据
printf("humidity = %.2f\r\n", Get_humidity() );
delay_ms(1000);
}
}
上电效果:
模块移植成功案例代码:
链接:https://pan.baidu.com/s/10WX784WnNQeMiwbLH8Yt7g?pwd=LCKF
提取码:LCKF