新 Nano(五)自己写个库,读 DHT11 / DHT22

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 发起下一次读数据操作。

 第一步,先采集下数据脚的电平情况看看:

实践出真知,下图为一帧数据的实测波形。按图右边设置示波器,点点左上角的运行按钮就能捕获到完美的波形。新 Nano(五)自己写个库,读 DHT11 / DHT22_第1张图片

如没有示波器,最简单就是 循环 “读数据脚、串口打印”,也能获得数据脚电平变化情况。

Serial.print(digitalRead(data_pin));

实际测试发现可能是因为串口太慢,上面的语句可以捕获到电平变化,但是不足以用来分析数据。 

嗯,办法总是有的,按存储示波器的玩法,间隔20微秒,连续采样200次存入数组(总时间4ms左右,上面示波器的截图也能大概看出来)。采样完成后再集中打印出来。( 间隔越小越精准,需要的采样次数越多,一行显示不全了,嗯,超宽带鱼显示器在这里就比较方便了。上面20微秒200次是经过一些测试总结的,不要轻易改变)

实测,如下代码可以在串口监视器看到一些010111这样的输出,这里的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个),分别对应长短电平,那么接下来需要做的就是“解码”

新 Nano(五)自己写个库,读 DHT11 / DHT22_第2张图片

 如果上面看起来有些别扭,修改下代码,把01用上下线段表示就比较像逻辑分析仪了:

新 Nano(五)自己写个库,读 DHT11 / DHT22_第3张图片

第二步, 上面证实数据传输无误,继续判断高电平时间,就获得40位数据了:

MCU 需要干其他事的可能需要用到中断比较好,这里仅仅是验证数据传输,就用 while 语句傻等了。

#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位不多不少,比较理想: 

新 Nano(五)自己写个库,读 DHT11 / DHT22_第4张图片

第三步,40位数据拆分转换。

第二步里的程序通用,能读出 DHT11 DHT22 两种传感器的数据,毕竟同一个公司的产品,格式都是连续5个8位共计40位数据:

                湿度高8位、湿度低8位、温度高8位、温度低8位、校验8位

DHT11 只需要处理  湿度高8位  温度高8位 , 获得的整数即为需要的读数,没有小数的。

                 湿度高8位、湿度低8位、温度高8位、温度低8位、校验8位

DHT22 需要把16位全部读出来的整数除以10,数据含有1位小数的。

                湿度高8位、湿度低8位温度高8位、温度低8位、校验8位

第二步里的程序把数据都存在data[ i ] 里面了。

以DHT11 湿度值为例, 按位读出 data[0 ] ~ data[7 ] 并乘以该位的二进制值即可。

这里可以用 POW函数按位循环相加,但实测与预期有些出入,暂时用笨点的办法:

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]; 

 新 Nano(五)自己写个库,读 DHT11 / DHT22_第5张图片比如上图标黄的 00100110 就是 0+0+32+0+0+6+2+0 = 40, 看到这里,二进制 基本功还是需要一些的,理解不了需要补些课。

需要注意的是:

        实验性质,校验位未作处理,实际使用中数值偶有跳动,影响不大。

        温度可能会产生负数值,这里暂不做处理,具体看手册,并不难。

新 Nano(五)自己写个库,读 DHT11 / DHT22_第6张图片

你可能感兴趣的:(单片机)