大家好,我是林白柏;
希望你看完之后,能有所收获,不足请指正!
PS:本文提到的模块都使用正点原子的stm32开发板战舰驱动,模块用的某宝现成的模块。
PS:代码为正点原子的代码,整理成自己习惯的接口
模块长这个亚子
DHT11里面封装了一个8位MCU,单片机连接了一个电阻式感湿元件和一个NTC测温元件;
数据的采集处理都由这个8位MCU完成,使用时我们只需要从这个MCU读出数据即可;
传感器的外围电路也比较简单,只需要对数据线DATA上拉。
DHT11采用单线通信,数据包长度40bit(5bytes),数据包格式如下:
校验和是前面4个字节的累加和,取低八位。
主机发送开始信号(拉低DATA线时间大于18ms),DHT11在开始信号结束后发送响应信号(拉低DATA线80us),然后延时40-50us后开始传输数据。
所以主机发送开始信号后,将io设置为输入,延时20-40us后检测DATA线电平状态,即可知道DHT11是否有响应。
对应代码如下
定义了几个宏,方便配置IO输入/输出、读/写
#define DHT11_IO_IN() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=8<<12;} //IO配置为输入
#define DHT11_IO_OUT() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=3<<12;} //IO配置为输出
#define DHT11_DQ_OUT PGout(11) //写IO DHT11_DQ_OUT = 1;/DHT11_DQ_OUT = 0;
#define DHT11_DQ_IN PGin(11) //读IO
发送开始信号并延时等待
static void DHT11_Rst(void)
{
DHT11_IO_OUT(); //SET OUTPUT
DHT11_DQ_OUT=0; //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT=1; //DQ=1
delay_us(30); //主机拉高20~40us
}
检测DHT11是否有响应信号
u8 dht11_check(void)
{
u8 retry=0;
DHT11_IO_IN();//SET INPUT
while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us(响应信号)
{
retry++;
delay_us(1);
};
if(retry>=100) { return 1;}//超时,无响应信号
else retry=0;
while (!DHT11_DQ_IN&&retry<100)//DHT11拉高40-50us,结束后开始传输数据
{
retry++;
delay_us(1);
};
if(retry>=100) { return 1;}//超时,DHT11发送响应信号后,未把DATA线拉高
return 0;
}
发送“0”和“1”的区别只是高电平持续时间不一样。
从DHT11读取1个bit,此处不判断每个bit开始阶段的低电平时间,只判断由低电平变到高电平后,高电平时间持续是否超过40us(“0”传输时间接近40us,如果设置再长一点可能导致通讯失败);
static u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry<100)//等待变为低电平
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry<100)//等待变高电平
{
retry++;
delay_us(1);
}
delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
知道1个bit怎么读取之后,就可以读取1个byte
static u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
知道1个byte读取过程,也就可以读取一帧完整的数据了(数据结构体部分看下文解释)
u8 dht11_read_data( dht11_data_t * data ) //data为用户传进来的结构体指针,用于返回读取的数据
{
u8 buf[5];
u8 i;
DHT11_Rst();//开始信号
if(DHT11_Check()==0) { //响应信号
for(i=0;i<5;i++) {//读取40位数据
buf[i]=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { //数据校验
memcpy( data, buf, sizeof( dht11_data_t ) );//校验通过,将数据传回给用户
#if 0
data->humi_int = buf[0];
data->humi_decimals = buf[1];
data->temp_int = buf[2];
data->temp_decimals = buf[3];
#endif
}
}
else {
return 1;
}
return 0;
}
这个地方用结构体表示一帧数据(不包含校验和,因为用户不需要关心校验和,能传给用户的数据肯定是校验过的),因为使用了memcpy函数
拷贝(该函数需要包含头文件string.h
),dht11_data_t
的数据存储格式需要跟buf
匹配;也可以直接对结构体成员变量赋值,这样就不用关心结构体的存储格式。
typedef struct {
uint8_t humi_int;
uint8_t humi_decimals;
uint8_t temp_int;
uint8_t temp_decimals;
}dht11_data_t;
dht11_read_data
中读取数据时,数据存到buf
的格式如下
因为dht11_data_t
成员变量都是uint8_t [1byte]
,所以为单字节对齐,跟buf
是匹配的,可以直接用memcpy函数
拷贝,存储格式如下
头文件声明了两个函数:初始化函数,读取函数;DHT11采样间隔为1s,所以读取间隔需要大于1s,否则会读取失败;
uint8_t dht11_init(void);// 0:suc; 1:fail
uint8_t dht11_read_data( dht11_data_t * data );// 0:suc; 1:fail; 实测读取间隔需要大于1200ms
读取前,需要先定义一个保存数据的结构体,然后把结构体的地址传进给dht11_read_data
函数。
int main(void){
dht11_data_t data;
dht11_init();
while(1) {
if( !dht11_read_data( &data ) ) {
_LOG_DEBUG( "[dht] humi %d.%d%%, temp %d.%dC\n\n", data.humi_int, data.humi_decimals
, data.temp_int, data.temp_decimals );
}
else {
_LOG_INFO("[err] dht read\n");
}
delay_ms(2000);
}
}
共用代码:https://gitee.com/sumoting1629/mcu-practice/tree/master/common
驱动代码:https://gitee.com/sumoting1629/mcu-practice/tree/master/component