quinta-feira, julho 11, 2013

Sensor de Temperatura DS18B20 ligado ao Arduino

Neste post vamos fazer algumas experiências ligando sensores de temperatura DS18B20 a um Arduino.


Como vimos no post anterior, a comunicação com o sensor utiliza o complexo protocolo 1-Wire. Felizmente já existem bibliotecas prontas para isto.

O nosso ponto de partida é o site oficial do Arduino, que tem uma página sobre o assunto. Lá encontramos o link para a biblioteca OneWire. E daí que fazemos o download da biblioteca (OneWire.zip), já que o site do Arduino não está atualizado.

Para instalarmos a biblioteca, basta expandir o zip no subdiretório libraries do diretório onde são gravados os sketches. Não sabe qual diretório é este? Veja em File Preferences. Instalada a biblioteca, a próxima vez que você entrar na IDE do Arduino os exemplos correspondentes estão em Files Examples.




Vamos usar o primeiro exemplo, DS18x20_Temperature. Para isto montei um circuito de teste em uma protoboard: um Arduino Nano, um resistor de 4.7K e três sensores DS18B20, um deles encapsulado. Carregando o exemplo no Arduino, observamos na serial que os sensores são reconhecidos e as temperaturas são lidas.

Vamos examinar isto com mais cuidado. Do ponto de vista do hardware, é muito simples. O pino de terra e a alimentação (Vdd) dos sensores são ligado ao terra do Arduino. Os pinos de dados (DQ) dos sensores estão ligados ao mesmo pino do Arduino e, através de um resistor de 4.7K, à alimentação. A reparar que não está presente (nem é suportado pela biblioteca) o mosfet para fornecer um pull-up mais forte; apesar disto não tive problemas. Por outro lado, é importante não deixar desconectado o pino de alimentação dos sensores, isto causou leituras errôneas.

Do lado do software, um objeto OneWire encapsula todo o protocolo. O construtor deste objeto recebe o número do pino ligado à linha de dados (no exemplo 10). A busca pelos dispositivos é feita pelo método search, que procura o próximo dispositivo. reset_search reinicia a busca. O primeiro byte do endereço identifica o modelo do dispositivo, o exemplo trata somente os sensores de temperatura DS18x20.

Encontrado um sensor, ele é selecionado (método select). O comando 0x44 dispara uma leitura de temperatura; durante a leitura é preciso manter a linha de dados em nível alto, para alimentar o sensor. Enquanto isto, nenhuma comunicação pode ser feita. Este tempo dependa precisão da leitura, quanto menos bits menor o tempo. O resultado da leitura está em um registrador, que é lido através do comando 0xBE. Este resultado é um número de 16 bits com sinal, que é convertido para graus centígrados e fahrenheit.
#include <OneWire.h>

// OneWire DS18S20, DS18B20, DS1822 Temperature Example
//
// http://www.pjrc.com/teensy/td_libs_OneWire.html
//
// The DallasTemperature library can do all this work for you!
// http://milesburton.com/Dallas_Temperature_Control_Library

OneWire  ds(10);  // on pin 10 (a 4.7K resistor is necessary)

void setup(void) {
  Serial.begin(9600);
}

void loop(void) {
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  
  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  
  Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(addr[i], HEX);
  }

  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      return;
  }
  Serial.println();
 
  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      Serial.println("  Chip = DS18S20");  // or old DS1820
      type_s = 1;
      break;
    case 0x28:
      Serial.println("  Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      Serial.println("  Chip = DS1822");
      type_s = 0;
      break;
    default:
      Serial.println("Device is not a DS18x20 family device.");
      return;
  } 

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end
  
  delay(1000);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
  
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad

  Serial.print("  Data = ");
  Serial.print(present, HEX);
  Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }
  Serial.print(" CRC=");
  Serial.print(OneWire::crc8(data, 8), HEX);
  Serial.println();

  // Convert the data to actual temperature
  // because the result is a 16 bit signed integer, it should
  // be stored to an "int16_t" type, which is always 16 bits
  // even when compiled on a 32 bit processor.
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time
  }
  celsius = (float)raw / 16.0;
  fahrenheit = celsius * 1.8 + 32.0;
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.print(" Celsius, ");
  Serial.print(fahrenheit);
  Serial.println(" Fahrenheit");
}

No próximo post vamos ver uma biblioteca específica para o DS18B20 mencionada no código acima.

Nenhum comentário: