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:
/*
 * Teste do sensor DHT11
 * 
 * Daniel Quadros - 31/07/18
 * https://dqsoft.blogspot.com
 */
static const int pinData = 2;
static const int timeout = 200;

// Resposta do sensor
typedef struct
{
  byte umidInt;
  byte umidDec;
  byte tempInt;
  byte tempDec;
  byte checksum;
} RESPOSTA;

// Iniciação
void setup() {
  pinMode (pinData, INPUT);
  Serial.begin (9600);
}

// Laço Principal
void loop() {
  RESPOSTA resp;
  delay (5000);   // ler a cada 5 segundos
  if (leDHT11(&resp)) {
    Serial.print ("Temperatura ");
    Serial.print (resp.tempInt);
    Serial.print (",");
    Serial.print (resp.tempDec);
    Serial.print ("C umidade ");
    Serial.print (resp.umidInt);
    Serial.print (",");
    Serial.print (resp.umidDec);
    Serial.println ("%");
  } else {
    Serial.println ("Falha na leitura");
  }
}

// Efetua a leitura
bool leDHT11 (RESPOSTA *pResp) {
  byte *pDados = (byte *) pResp;
  byte iByte, iBit;
  unsigned long to;

  // Solicita leitura
  pinMode (pinData, OUTPUT);
  digitalWrite (pinData, LOW);
  delay (20);  
  digitalWrite (pinData, LOW);

  // Aguarda confirmar
  pinMode (pinData, INPUT);
  to = micros() + timeout;
  while (digitalRead(pinData) == HIGH) {
    if (micros() > to) {
      return false;
    }
  }
  while (digitalRead(pinData) == LOW) {
    if (micros() > to) {
      return false;
    }
  }
  while (digitalRead(pinData) == HIGH) {
    if (micros() > to) {
      return false;
    }
  }

  // Le os dados
  iByte = iBit = 0;
  while (iByte < 5) {
    // pulso inicial
    to = micros() + timeout;
    while (digitalRead(pinData) == LOW) {
      if (micros() > to) {
        return false;
      }
    }
    // valor do bit
    to = micros() + timeout;
    while (digitalRead(pinData) == HIGH) {
      if (micros() > to) {
        return false;
      }
    }
    pDados[iByte] = pDados[iByte] << 1;
    if (((micros() + timeout) - to) > 40) {
      pDados[iByte] |= 1;
    }
    // passa para o próximo bit
    if (++iBit > 7) {
      iBit = 0;
      iByte++;
    }
  }
  
  // Confere o checksum
  return (pDados[0]+pDados[1]+pDados[2]+pDados[3]) == pDados[4];
}

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!