quinta-feira, agosto 02, 2018

Sensor de Umidade e Temperatura DHT11

Algum tempo atrás eu pensei em escrever um livro sobre sensores. Comprei um monte deles, brinquei com a maioria e acabei desistindo do livro. Precisando de um sensor de temperatura para usar com o Raspberry Pi, achei um DHT11 na gaveta. É um sensor que você acha com facilidade a preços baixos.



As Limitações de Comunicação do Raspberry Pi

Existem várias formas de ligar sensores e outros dispositivos, cada uma com suas vantagens e desvantagens.

Sensores que só retornam uma informação do tipo sim/não (como um sensor de abertura) podem ser ligados facilmente a uma entrada digital. Não é o caso de sensores que precisam retornar um valor.

No caso de sensor de temperatura, uma opção simples e precisa é o LM35, que tem uma saída analógica. Infelizmente, o Raspberry Pi não tem um ADC.

As soluções seguintes são comunicações seriais padrão, como a serial assíncrona, SPI e I2C. O Raspberry possui suporte de hardware a estas comunicações (no caso da serial assíncrona, o Raspberry Zero W tem fortes limitações). Eu venho adquirindo um gosto pela comunicação I2C, por permitir facilmente a ligação de múltiplos dispositivos. Existem alguns sensores de temperatura com este tipo de comunicação (como o TMP1075 e o LM74) mas não tenho um aqui (vai para a lista de compras).

Existem ainda comunicações seriais que precisam ser implementadas por software, por não serem suportadas pelo hardware. Já vimos aqui o sensor de temperatura DS18B20 e o relógio DS1302. Dependendo de como é o protocolo, a comunicação pode ser complexa e exigir tempos pequenos e precisos (o que é complicado em um sistema operacional como o Linux). Como veremos, é aí que se encaixa o DHT11.

DHT11

O datasheet (que você pode baixar daqui) é bastante completo. Do ponto de vista de características de desempenho, ele opera com tensões de 3 a 5V e mede umidade e temperatura. A medida de temperatura pode ter um erro de até +/- 2°C, o que é bastante alto para algumas aplicações.

A comunicação é serial e bidirecional em um único pino. Para solicitar uma leitura, o mestre deve configurar o pino como saída e colocar o sinal em nível baixo por pelo menos 18ms. Em seguida, deve "virar" o pino para entrada (um pull-up vai colocar o sinal em nível alto). O DHT11 vai sinalizar a recepção do pedido, mantendo o sinal em nível zero por 80us. Em seguida o DHT11 vai enviar os 40 bits de resposta. Cada bit é composto de duas partes: primeiro um nível baixo por 50us e depois um nível alto por 26 a 28uS (se bit 0) ou 70uS (se bit 1). Estas temporizações não são muito ruins se você estiver usando um sistema dedicado e rápido, mas podem ser fatais se o seu sistema for lento ou se a rotina de leitura puder ser interrompida por coisas demoradas.

Os 40 bits devem ser interpretados como 5 bytes: 2 com a leitura de umidade, 2 com a leitura de temperatura e 1 de checksum. O primeiro byte das leituras é parte decimal e o segundo a parte inteira. O checksum é a somas dos quatro bytes anteriores. No DHT11 a parte decimal é sempre zero (existe um modelo com mais precisão, o DHT22 que usa o mesmo protocolo).

Um último cuidado é aguardar pelo menos 1 segundo antes da próxima leitura.

Do ponto de vista físico, o DHT11 possui quatro terminais:


Nos meus testes eu usei um "módulo", que contém o resistor de pullup e possui somente três terminais (atenção que outros módulos de três pinos podem ter os sinais em posição diferente):


DHT11 com Arduino

A conexão é trivial: Vcc em +5, GND no GND e Data na entrada digital 2 (poderia ser outro, basta acertar no código).

Seguindo a minha tradição, vamos implementar a leitura na raça para entender o protocolo:
  1. /* 
  2.  * Teste do sensor DHT11 
  3.  *  
  4.  * Daniel Quadros - 31/07/18 
  5.  * https://dqsoft.blogspot.com 
  6.  */  
  7. static const int pinData = 2;  
  8. static const int timeout = 200;  
  9.   
  10. // Resposta do sensor  
  11. typedef struct  
  12. {  
  13.   byte umidInt;  
  14.   byte umidDec;  
  15.   byte tempInt;  
  16.   byte tempDec;  
  17.   byte checksum;  
  18. } RESPOSTA;  
  19.   
  20. // Iniciação  
  21. void setup() {  
  22.   pinMode (pinData, INPUT);  
  23.   Serial.begin (9600);  
  24. }  
  25.   
  26. // Laço Principal  
  27. void loop() {  
  28.   RESPOSTA resp;  
  29.   delay (5000);   // ler a cada 5 segundos  
  30.   if (leDHT11(&resp)) {  
  31.     Serial.print ("Temperatura ");  
  32.     Serial.print (resp.tempInt);  
  33.     Serial.print (",");  
  34.     Serial.print (resp.tempDec);  
  35.     Serial.print ("C umidade ");  
  36.     Serial.print (resp.umidInt);  
  37.     Serial.print (",");  
  38.     Serial.print (resp.umidDec);  
  39.     Serial.println ("%");  
  40.   } else {  
  41.     Serial.println ("Falha na leitura");  
  42.   }  
  43. }  
  44.   
  45. // Efetua a leitura  
  46. bool leDHT11 (RESPOSTA *pResp) {  
  47.   byte *pDados = (byte *) pResp;  
  48.   byte iByte, iBit;  
  49.   unsigned long to;  
  50.   
  51.   // Solicita leitura  
  52.   pinMode (pinData, OUTPUT);  
  53.   digitalWrite (pinData, LOW);  
  54.   delay (20);    
  55.   digitalWrite (pinData, LOW);  
  56.   
  57.   // Aguarda confirmar  
  58.   pinMode (pinData, INPUT);  
  59.   to = micros() + timeout;  
  60.   while (digitalRead(pinData) == HIGH) {  
  61.     if (micros() > to) {  
  62.       return false;  
  63.     }  
  64.   }  
  65.   while (digitalRead(pinData) == LOW) {  
  66.     if (micros() > to) {  
  67.       return false;  
  68.     }  
  69.   }  
  70.   while (digitalRead(pinData) == HIGH) {  
  71.     if (micros() > to) {  
  72.       return false;  
  73.     }  
  74.   }  
  75.   
  76.   // Le os dados  
  77.   iByte = iBit = 0;  
  78.   while (iByte < 5) {  
  79.     // pulso inicial  
  80.     to = micros() + timeout;  
  81.     while (digitalRead(pinData) == LOW) {  
  82.       if (micros() > to) {  
  83.         return false;  
  84.       }  
  85.     }  
  86.     // valor do bit  
  87.     to = micros() + timeout;  
  88.     while (digitalRead(pinData) == HIGH) {  
  89.       if (micros() > to) {  
  90.         return false;  
  91.       }  
  92.     }  
  93.     pDados[iByte] = pDados[iByte] << 1;  
  94.     if (((micros() + timeout) - to) > 40) {  
  95.       pDados[iByte] |= 1;  
  96.     }  
  97.     // passa para o próximo bit  
  98.     if (++iBit > 7) {  
  99.       iBit = 0;  
  100.       iByte++;  
  101.     }  
  102.   }  
  103.     
  104.   // Confere o checksum  
  105.   return (pDados[0]+pDados[1]+pDados[2]+pDados[3]) == pDados[4];  
  106. }  

O resultado é algo como:


DHT11 com Raspberry

A conexão consiste em ligar o Vcc ao 3.3V, GND em GND e Data no pino 22 (GPIO25, também pode ser mudado no código).

Aqui eu vou usar a biblioteca para Python da Adafruit. Mas antes, vamos dar uma olhada sob o capô. A biblioteca está escrita em C e acessa o pino digital através de mapeamento de memória. Na hora de fazer a leitura a prioridade do processo é aumentada para minimizar a interferência dos demais processos. Possivelmente vai ter problemas se tiver muito mais coisa rodando no Pi ou se tiver alguma atividade alta de interrupções.

A instalação da biblioteca da Adafruit é descrita aqui; no Raspbian Scretch Lite eu precisei de alguns passos adicionais, pois o git não está instalado:

sudo apt-get update
sudo apt-get install build-essential python-dev
sudo apt-get install git
git clone https://github.com/adafruit/Adafruit_Python_DHT.git
cd Adafruit_Python_DHT
sudo python setup.py install

Fiz alguns testes com o exemplo no link acima e funcionou bem, mesmo quando tentei rodar outras coisas em paralelo.

Atualização 16/08/18:  a versão atual no github resolve um problema do setuptools que eu tinha mencionado anteriormente.

Conclusão

O sensor DHT11 não é exatamente o que eu queria em termos de precisão e interface, mas funcionou de forma satisfatória. Futuramente pretendo testar outros sensores, como o DHT22 e o LM75.

Um comentário:

Daniel disse...

Rara publicação onde não se usa bibliotecas para demonstrar o uso de componentes, diferente da maioria dos blogs sobre o assunto.

Obrigado!