terça-feira, setembro 11, 2018

Sensor de Temperatura LM75A

Na minha busca de um sensor de temperatura para usar com o Raspberry Pi, vamos dar uma olhada no LM75A, que possui interface I2C.


Vamos começar pelas características básicas:
  • Aceita alimentação de 2,8 a 5,5 V
  • Opera de -25 a +125 C
  • Precisão de +/- 2 C na faixa -25 a 100 C
  • Conversor AD de 11 bits, dando uma resolução de 0,125 C
  • 8 endereços I2C possíveis (0x48 a 0x4F)
A precisão deixa um pouco a desejar, mas a parte de alimentação e interface são bem flexíveis, é possível ter até 8 LM75A ligados no mesmo barramento I2C.

Após a configuração, o LM75A pode ser usado de forma autônoma, graças à saída OS que é ativada conforme a temperatura (detalhes mais para frente).

Internamente o LM75A possui 4 registradores:
  • Temperatura
  • Histerese
  • Limite
  • Configuração
O registrador de temperatura é de apenas leitura e possui 2 bytes (o byte mais significativo é enviado primeiro). A temperatura é um valor de 11 bits, alinhados à esquerda, em complemento de 2 e na unidade de 0,125 C. Confuso? vamos dar alguns exemplos:
  • Registrador com 00000000 001xxxxx: o valor da temperatura é 1 x 0,125 = 0,125 C
  • Registrador com 00000000 100xxxxx: o valor da temperatura é 8 x 0,125 = 1 C
  • Registrador com 11111111 100xxxxx: o valor da temperatura é -8 x 0,125 = -1 C
Os registradores de Histerese e Limite contém valores de temperatura que serão comparados com a temperatura lida. Estes registradores são de leitura e escrita e possuem dois bytes cada um (como o registrador de temperatura), porém apenas os 9 bits mais significativos são usados. Ou seja, nas comparações a unidade é 0,5 C. Tipicamente será programado um valor de histerese menor ou igual ao do limite.

A tabela abaixo (extraída do datasheet da NXP) mostra o significado dos bits do registrador de configuração (que pode ser lido e escrito) e o valor default:


Vamos começar pelo mais simples: o SHUTDOWN. Inicialmente ele está com 0 e o LM75A está em operação normal: a cada 100 ms ele faz uma leitura de temperatura, coloca o resultado no respectivo registrador e atualiza o estado do pino OS (conforme será detalhado adiante). Colocando este bit em 1, o LM75A passará para o modo shutdown: a comunicação I2C permanece operando porém não são feitas novas leitura. O registrador de temperatura e o pino OS mantém a situação anterior. Serve, portanto, para reduzir o consumo. Quando está comunicando via I2C o consumo pode chegar a 1mA; ativo, mas sem comunicação, são 100uS; no modo shutdown são apenas 3,5uA.

Outro pino fácil de entender é o OS_POL: ele determina qual a polaridade do sinal OS. No valor inicial (0) o sinal é ativo no nível baixo e inativo no no nível alto. Mudando este bit para 1 o sinal é invertido. Resta notar que a saída OS é do tipo "open drain": o LM75A pode forçá-la ao nível baixo ou deixar o sinal "solto". Deve-se, portanto, ligar um resistor para a alimentação para obter o nível alto se esta saída for usada como sinal lógico (o datasheet recomenda ser um valor alto, até 200K). A saída pode também ser usada para acionar pequenas cargas (corrente máxima de 10mA).

Os bits OS_F_QUE e OS_COMP_INT determinam a lógica de ativação do sinal OS. A cada leitura, a temperatura lida é comparada com os valores nos registradores de histerese e limite. Os bits OS_F_QUE permitem realizar uma filtragem no resultado desta comparação, exigindo que o mesmo resultado seja obtido consecutivamente várias vezes. Isto permite ignorar oscilações momentâneas na leitura. O comportamento do sinal OS a partir destas comparações filtradas vai depender do bit OS_COMP_INT:
  • Se o bit for 0 (valor padrão), temos o modo comparação,  apropriado para implementar um termostato. Considere que inicialmente a temperatura está abaixo do limite; a saída OS estará inativa. Ao ultrapassar o limite a saída OS será ativada e permanecerá ativa até a temperatura ficar menor que a histerese.
  • Se o bit for 1, temos o modo interrupção, apropriado para sinalizar a um microcontrolador uma mudança na temperatura. Considere novamente que a temperatura está abaixo do limite; a saída OS estará inativa. Ao ultrapassar o limite a saída OS será ativada e permanecerá ativa até que um dos registradores do LM75A seja lido. A saída OS só será novamente ativada quando a temperatura ficar abaixo da histerese (e novamente permanecerá ativa até que um registrador do LM75A seja lido).

Vamos fazer um pequeno teste? Vamos ligar um Arduino Nano e um LED ao módulo:


Se você for usar um outro modelo de Arduino, verifique em quais pinos estão os sinais SDA e SCL.

O nosso programa de teste inicia os registradores de histerese e limite a fica enviando pela serial a temperatura atual. Forçando variações de temperatura podemos observar no LED o acionamento do sinal OS.
//
// Teste do sensor de temperatura LM75A
//

#include <Wire.h>

// Endereço I2C do sensor
#define ADDR      0x48

// Registradores do sensor
#define REG_TEMP  0
#define REG_CONF  1
#define REG_THYST 2
#define REG_TOS   3

// Iniciação
// Obs: vamos usar a configuração padrão do LM75A
void setup() {
  Serial.begin (9600);
  Wire.begin();

  // Limites para teste
  WriteReg16 (REG_TOS, CodTemp(22.5));
  WriteReg16 (REG_THYST, CodTemp(21.0));
}

// Laço principal, lê e mostra a temperatura
void loop() {
  delay (200);  // dá um tempo para ler
  Serial.println (DecodTemp(ReadReg16(REG_TEMP)));
}

// Rotina para converter temperatura em celsius na
// representação usada no LM75A
int16_t CodTemp (float temp)
{
  return ((int16_t) (temp / 0.125)) << 5; 
}

// Rotina para converter temperatura da
// representação usada no LM75A para celsius
float DecodTemp (int16_t val)
{
  val = val / 32;
  return ((float) val) * 0.125; 
}

// Escreve um valor de 16 bits em um registrador
void WriteReg16 (byte reg, int16_t val)
{
  Wire.beginTransmission(ADDR);
  Wire.write(reg);
  Wire.write((val >> 8) & 0xFF);
  Wire.write(val & 0xFF);
  Wire.endTransmission();
}

// Le um valor de 16 bits de um registrador
int16_t ReadReg16 (byte reg)
{
  uint16_t val;
  
  // Seleciona o registrador
  Wire.beginTransmission(ADDR);
  Wire.write(reg);
  Wire.endTransmission();

  // Faz a leitura
  Wire.requestFrom(ADDR, 2);
  val = Wire.read() << 8;
  val |= Wire.read();
  return (int16_t) val;
}
Para concluir, um exemplo em Python de leitura da temperatura com o Raspberry Pi. As conexões são 3.3V do Rasp em Vcc no módulo, GND, SDA e SCL do Rasp nos respectivos pinos do módulo.
import smbus
from time import sleep

# Classe para simplificar o acesso ao I2C
class i2c_device:

    # construtor
    def __init__(self, addr):
        # salva o endereco
        self.addr = addr

        # seleciona o i2c conforme a versao do Raspberry Pi
        self.revision = ([l[12:-1] for l in open('/proc/cpuinfo','r').readlines() if l[:8]=="Revision"]+['0000'])[0]
        self.bus = smbus.SMBus(1 if int(self.revision, 16) >= 4 else 0)

    # escreve de valor de 16 bits (sem sinal)
    def writeU16(self, reg, val):
        bytes = [ val >> 8, val & 0xFF ]
        self.bus.write_i2c_block_data(self.addr, reg, bytes)

    # leitura de valor de 16 bits (sem sinal)
    def readU16(self, reg):
        bytes = self.bus.read_i2c_block_data(self.addr, reg, 2)
        ret = (bytes[0] << 8) | bytes[1]
        return ret

# Classe para acesso ao LM75A
class LM75A:

    # construtor
    def __init__(self, addr=0x48):
        # Inicia device
        self.device = i2c_device(addr)
        # Constantes
        self.TEMP = 0
        self.CONF = 1
        self.THYST = 2
        self.TOS = 3

    # iniciacao
    def init(self, tmax, tmin):
        self.device.writeU16(self.TOS, self.codtemp(tmax))
        self.device.writeU16(self.THYST, self.codtemp(tmin))

    # codifica temperatura para escrita em TOS e THYST
    def codtemp(self, temp):
        return int(temp / 0.125)*32

    # le a temperatura
    def read(self):
        val = self.device.readU16(self.TEMP)
        val = val / 32
        return val * 0.125

# Teste simples
if __name__ == "__main__":
    sensor = LM75A()
    sensor.init(24.5, 22)
    while(True):
        try:
            print sensor.read()
            sleep(3)
        except KeyboardInterrupt:
            exit(1)

Nenhum comentário: