DHT11 这款温湿度传感器 几乎是所有 MCU 入门第一个传感器, 现在看来有些不合时宜, 毕竟过于廉价,数据不太靠谱,远不如 AHT10 好用。早年买了两个,按例程读出数据后就吃灰了。某日看到有人说自己按datasheet去读,得不到需要的数据回应。于是仔细看了下DHT11的数据传输,觉得蛮有意思的,想着自己算是入门好几年的老鸟,硬核一点,不用别人的库,自己写几句代码来读一下这个传感器看看?
DHT11 数据传输详解https://blog.csdn.net/Alex_68/article/details/108730534
上文是要啃一下的,时序图并不算复杂,大概原理就是:
1.均由 MCU 发起读数据操作。没有传输时, 数据脚持续高电平。
2.MCU 发起读数据操作时,把数据脚拉低再拉高一次。
3.DHT11 收到上述电平变化后, 也把数据脚拉低再拉高一次作为响应,表示我要开始发送数据了。
4.接下来 DHT11 把数据脚拉低再拉高40次, 即为40位数据的发送。数据是 0 还是 1 由高电平持续时间决定,和低电平时间无关。 大概可以理解和 摩斯电码 一样一样的。
5.结束再次把数据脚拉低再拉高一次,高电平就一直保持了,直到 MCU 发起下一次读数据操作。
第一步,先采集下数据脚的电平情况,结果一些实测,如下代码可以看到一些01输出,这里01并不是数据,而是反映的数据脚电平情况。大概可以理解为一个简易的逻辑分析仪。
#define DHT11_PIN 11
boolean data[200];
void setup(){
pinMode(12, OUTPUT);digitalWrite(12, HIGH); // DHT11 Vcc
pinMode(9, OUTPUT);digitalWrite(9, LOW); // DHT11 Gnd
Serial.begin(115200);
}
void loop(){
/*HDT11空闲时为高电平。需要读数时,MCU先拉低30ms,再拉高30μs,注意两者时间单位 */
pinMode(DHT11_PIN, OUTPUT);
digitalWrite(DHT11_PIN, LOW); delayMicroseconds(30000);
digitalWrite(DHT11_PIN, HIGH);delayMicroseconds(30);
/*开始检测DHT的数据返回*/
pinMode(DHT11_PIN, INPUT);
for(int i=0;i<200;i++){
data[i]=digitalRead(DHT11_PIN);
delayMicroseconds(20);
}
for(int i=0;i<200;i++)Serial.print(data[i]);
Serial.println();
delay(5000);
}
从下图可以看出,1有单个的,和多个(3个或4个),分别对应长短电平,那么接下来需要做的就是“解码”
如果上面看起来有些别扭,修改下代码,把01用上下线段表示就比较像逻辑分析仪了
第二步, 获得40位数据:
#define DHT11_PIN 11
boolean data[40];
long time_stamp;
void setup(){
pinMode(12, OUTPUT);digitalWrite(12, HIGH); // DHT11 Vcc
pinMode(9, OUTPUT);digitalWrite(9, LOW); // DHT11 Gnd
Serial.begin(115200);
}
void loop(){
/*HDT11空闲时为高电平。需要读数时,MCU先拉低30ms,再拉高30μs,注意两者时间单位 */
pinMode(DHT11_PIN, OUTPUT);
digitalWrite(DHT11_PIN, LOW); delayMicroseconds(30000);
digitalWrite(DHT11_PIN, HIGH);delayMicroseconds(30);
/*开始检测DHT的数据返回*/
pinMode(DHT11_PIN, INPUT);
while (!digitalRead(DHT11_PIN)) {} //刚开始应该是低电平,等待高电平出现后继续 , 括号不能省略!
while ( digitalRead(DHT11_PIN)) {} //高电平期间继续等待,出现低电平则为数据开始传输
for(int i=0;i<40;i++){
data[i]=0;
while (!digitalRead(DHT11_PIN)){} //等待出现高电平
time_stamp = micros(); //开始计时
while ( digitalRead(DHT11_PIN)){} //等待出现低电平,即高电平结束了
if (( micros()- time_stamp) > 50) data[i]=1;
}
for(int i=0;i<40;i++)Serial.print(data[i]); Serial.println();
delay(5000);
}
第三步,40位数据拆分转换。
DHT11 DHT22 都是连续40位数据,格式也一样:
湿度高8位、湿度低8位、温度高8位、温度低8位、校验8位
DHT11 只需要处理 湿度高8位 温度高8位 , 获得的整数即为需要的读数,没有小数的。
DHT22 需要把16位全部读出来的整数除以10,数据含有1位小数的。
数据都存在data[ i ] 里面, 以DHT11湿度为例, 按位读出并乘以该位的二进制值即可:
dht11_humidity = 128*data[0] + 64*data[1] + 32*data[2] + 16*data[3] + 8*data[4] + 4*data[5] + 2*data[6] + data[7];
比如上面标黄的 00100110 按上面算是就是 0+0+32+0+0+6+2+0 = 40
需要注意的是:温度可能会产生负数值,这里暂不做处理,具体看手册,并不难。