一、前言
最近写DHT11的代码到香橙派(全志H616)上,发现网上案例基本上都是树莓派+DHT11的居多,香橙派的少,少数找得到的代码跑起来也是不稳定或者数据相对不太准确,于是这里自己写了一篇,供大家参考和批评指正
产品概述
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,应用领域:暖通 空调;汽车;消费品;气象站;湿度调节器;除湿器;家电;医疗;自动控制
特点
- 相对湿度和温度测量
- 全部校准,数字输出
- 长期稳定性
- 超长的信号传输距离:20米
- 超低能耗:休眠
- 4 引脚安装:可以买封装好的
- 完全互换 : 直接出结果,不用转化
数据传送逻辑
只有一根数据线DATA,主控MCU发送序列指令给DHT11模块,模块一次完整的数据传输为40bit,高位先出
数据格式
8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和
关于校验:假设接收到的40位数据为:
计算:
0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 = 0101 0001(校验位)
DHT11通信时序
(1)主机发送起始信号
首先单片机将连接DHT11 DATA引脚的GPIO口输出低电平,且低电平保持时间不能小于18ms (t1)最大不能超多30ms,然后拉高数据线20~40us (t2) ,等待读取DHT11的响应信号。
(2)检测从机应答信号
DHT11的DATA引脚检测到外部信号有低电平(t1),并等待外部低电平信号结束(t2),延迟后DHT11的DATA引脚处于输出状态,之后DHT11开始输出80 us (t3)的低电平作为应答信号,紧接着输出80us(t4)的高电平通知主机准备接收数据。
主机的I/O此时处于输入状态,检测I/O有低电平(DHT11应答信号)后,等待80us的高电平后接受数据。
(3)数据传输
由DHT11的DATA引脚输出40位数据,采用高位优先方式(MSB),微处理器根据I/0电平的变化接收40位数据。
位数据“0”的格式为:50微秒的低电平和26-28us的高电平。
位数据“1”的格式为:50微秒的低电平加70us的高电平。
二、代码
主体代码主要是利用多线程,用户每发送一次数据读取请求,创建一个线程用于读取数据;利于提高代码的健壮性和扩展性;
同时引入一个blockFlag的标志位,避免子线程代码跑飞无法退出的问题;
并且在测试过程中发现有偶尔测试出温度明显错误的数据;考虑到可能是由于环境、传感器、延时误差等原因导致的数据不准确问题,所以程序中会将超过50°C的数据视为无效数据,自行重新测试,最多自行重试5次
GPIO初始化
因为发送给DHT11的起始信号是先拉低电平,所以拉低电平前先维持一个稳定的高电平状态
void GPIO_init(int gpio_pin)
{
pinMode(gpio_pin, OUTPUT); // set mode to output
digitalWrite(gpio_pin, 1); // output a high level
delay(1000);
}
起始信号
因为DHT11的触发是单次的,即每发送一次起始信号,才会检查一次温湿度,所以发送起始信号的代码必然是要多次复用,所以这里也封装成一个函数
void DHT11_Start_Sig()
{
pinMode(pinNumber,OUTPUT); //让GPIO为输出模式
digitalWrite(pinNumber,HIGH);
digitalWrite(pinNumber,LOW);
delay(25); //维持25ms的低电平
digitalWrite(pinNumber,HIGH); //转化为高电平,等待DHT11的响应信号
pinMode(pinNumber,INPUT);
//响应信号为80us电平与80us的准备信号
pullUpDnControl(pinNumber,PUD_UP); //进行上拉,增加稳定性,非必选
delayMicroseconds(35); //维持35微秒
}
读取数据
void* readSensorData(void *arg)
{
uint8 crc;
uint8 i;
int attempt = 5; //调用一次最多尝试测5次
while(attempt)
{
databuf = 0; //清空数据存储buf
crc = 0; //清空校验位数据存储buf
DHT11_Start_Sig();
if(digitalRead(pinNumber)==0) //检测DHT11是否应答,应答则继续下一步
{
while(!digitalRead(pinNumber)); //wait to high
//读取4个数据,合计32位
for(i=0;i<32;i++)
{
while(digitalRead(pinNumber)); //data clock start
while(!digitalRead(pinNumber)); //data start
delayMicroseconds(HIGH_TIME); //如果32微秒后,仍然检测到是高电平,则该数据位为1
databuf*=2; //移位到buf的更高位
if(digitalRead(pinNumber)==1) //1
{
databuf++;
}
}
//读取校验位
for(i=0;i<8;i++)
{
while(digitalRead(pinNumber)); //data clock start
while(!digitalRead(pinNumber)); //data start
delayMicroseconds(HIGH_TIME);
crc*=2;
if(digitalRead(pinNumber)==1) //1
{
crc++;
}
}
//用于校验数据的准确性,当温度大于50时,视为无效数据
if(((databuf>>8)&0xff) > 50)
{
attempt--;
delay(500); //不加这段延迟,下一次传感器来不及响应
continue;
}
else
{
//打印数据
printf("Congratulations ! Sensor data read ok!\n");
printf("RH:%lu.%lu\n",(databuf>>24)&0xff,(databuf>>16)&0xff);
printf("TMP:%lu.%lu\n",(databuf>>8)&0xff,databuf&0xff);
blockFlag = 0; //用来避免程序有时候跑飞,卡在此函数中,无法跳出
return (void*)1;
}
}
else //dht not answer
{
blockFlag = 0;
printf("Sorry! Sensor dosent ans!\n");
return (void*)0;
}
}
//如果代码执行到这里,则证明尝试读取了5次数据,都是不准确的数据
blockFlag = 0;
printf("get data fail\n");
return (void*)2;
}
整体代码
#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "contrlDevices.h"
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
#define HIGH_TIME 32
int pinNumber = 6; //use gpio1 to read data
uint32 databuf;
int blockFlag;
void GPIO_init(int gpio_pin)
{
pinMode(gpio_pin, OUTPUT); // set mode to output
digitalWrite(gpio_pin, 1); // output a high level
delay(1000);
//return;
}
void DHT11_Start_Sig()
{
pinMode(pinNumber,OUTPUT);
digitalWrite(pinNumber,HIGH);
digitalWrite(pinNumber,LOW);
delay(25);
digitalWrite(pinNumber,HIGH);
pinMode(pinNumber,INPUT);
//响应信号为80us电平与80us的准备信号
pullUpDnControl(pinNumber,PUD_UP);
delayMicroseconds(35);
}
void* readSensorData(void *arg)
{
uint8 crc;
uint8 i;
int attempt = 5; //调用一次最多尝试测5次
while(attempt)
{
databuf = 0; //清空数据存储buf
crc = 0; //清空校验位数据存储buf
DHT11_Start_Sig();
if(digitalRead(pinNumber)==0) //检测DHT11是否应答,应答则继续下一步
{
while(!digitalRead(pinNumber)); //wait to high
//读取4个数据,合计32位
for(i=0;i<32;i++)
{
while(digitalRead(pinNumber)); //data clock start
while(!digitalRead(pinNumber)); //data start
delayMicroseconds(HIGH_TIME); //如果32微秒后,仍然检测到是高电平,则该数据位为1
databuf*=2; //移位到buf的更高位
if(digitalRead(pinNumber)==1) //1
{
databuf++;
}
}
//读取校验位
for(i=0;i<8;i++)
{
while(digitalRead(pinNumber)); //data clock start
while(!digitalRead(pinNumber)); //data start
delayMicroseconds(HIGH_TIME);
crc*=2;
if(digitalRead(pinNumber)==1) //1
{
crc++;
}
}
//用于校验数据的准确性,当温度大于50时,视为无效数据
if(((databuf>>8)&0xff) > 50)
{
attempt--;
delay(500); //不加这段延迟,下一次传感器来不及响应
continue;
}
else
{
//打印数据
printf("Congratulations ! Sensor data read ok!\n");
printf("RH:%lu.%lu\n",(databuf>>24)&0xff,(databuf>>16)&0xff);
printf("TMP:%lu.%lu\n",(databuf>>8)&0xff,databuf&0xff);
blockFlag = 0; //用来避免程序有时候跑飞,卡在此函数中,无法跳出
return (void*)1;
}
}
else //dht not answer
{
blockFlag = 0;
printf("Sorry! Sensor dosent ans!\n");
return (void*)0;
}
}
//如果代码执行到这里,则证明尝试读取了5次数据,都是不准确的数据
blockFlag = 0;
printf("get data fail\n");
return (void*)2;
}
int main ()
{
pthread_t tid;
int waitTimes = 10;
char cmd[5] = {'\0'};
if (wiringPiSetup() == -1)
{
printf("Setup wiringPi failed!");
return 1;
}
printf("Enter OS-------\n");
while(1)
{
waitTimes = 10;
blockFlag = 1;
delay(1000);
printf("input y\n");
scanf("%s",cmd);
getchar();
if(strcmp(cmd,"y") == 0)
{
//创建一个线程用于读取传感器数据;
//严谨来说此处的tid并发时有bug;读者可以自行优化,可以用互斥锁或者tid设为数组等都行
//就当作留给读者的一个小作业吧
if (pthread_create(&tid, NULL, readSensorData, NULL) != 0)
{
printf("thread create fail!\n");
return -1;
}
//等待数据读取线程10s钟,如果10后blockFlag未置0,则说明读数据时跑飞卡住了
while(waitTimes && blockFlag)
{
delay(1000);
waitTimes--;
}
//强行结束跑飞的线程
if(blockFlag == 1)
{
pthread_cancel(tid);
printf("线程超时退出.....\n");
}
}
else
{
printf("go on\n");
continue;
}
}
return 0;
}