quarta-feira, janeiro 23, 2019

Sensor de Temperatura (e Umidade) HDC1080 (ou compatível) com Raspberry Pi

Depois do post anterior, parecia fácil ligar o HDC1080 ao Rasberry Pi. Bem, ligar é fácil (só ligar os sinais de mesmo nome) mas o software deu mais trabalho do que eu esperava.



Nas minhas brincadeiras com I2C no Raspberry eu tenho usado principalmente Python e a biblioteca smbus. SMbus não é a mesma coisa que I2C, mas até agora tinha dado para ignorar isto. O que pega aqui não são as diferenças elétricas (afinal, o hardware do Raspberry é I2C), mas a forma como o SMBus modela os dispositivos ligados.

Se olharmos as funções da biblioteca, vamos ver que quase todas tem um parâmetro cmd. Considerando apenas as funções de leitura, a biblioteca assume que deve ser feita uma escrita de um comando (ou número de registrador) e logo em seguida a leitura da resposta. A única leitura disponível sem informar um comando é a leitura de um byte.

No HDC1080 uma escrita de 0 ou 1 (que corresponde a selecionar o registrador de temperatura ou umidade) dispara uma amostragem dos sensores. Depois disso é preciso aguardar a amostragem completar e aí ler os bytes do resultado (2 ou 4, dependendo da configuração). Portanto não tem como fazer isto com as funções disponíveis na biblioteca.

O jeito é baixar o nível. Após da uma estudada em uma implementação alternativa da biblioteca e a documentação do kernel, sem achar uma saída, encontrei um biblioteca específica para o HDC1000,  que me mostrou o segredo. Uma vez definido (via ioctl) o endereço do escravo, as funções read() e write() do objeto obtido por io.open fazem a leitura e escrita "bruta" do I2C. Descoberto isto, não foi muito difícil fazer um programa de teste:
import struct, array, time, io, fcntl

# 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.busNumber = 1 if int(self.revision, 16) >= 4 else 0
            
        # inicia o acesso ao i2c
        self.fd = None
        self.open(self.busNumber)

    # escreve de valor de 16 bits (sem sinal) em um registrador
    def writeReg(self, reg, val):
        data = [ reg, val >> 8, val & 0xFF ]
        buf = bytearray(data)
        self.fd.write(buf)

    # leitura de valor de 16 bits (sem sinal) de um registrador
    def readReg(self, reg):
        data = [ reg ]
        buf = bytearray(data)
        self.fd.write(buf)
        time.sleep(0.0625)
        data = self.fd.read(2)
        buf = array.array('B', data)
        ret = (buf[0] << 8) | buf[1]
        return ret

    # dispara leitura de temperatura ou umidade
    def access(self, reg):
        data = [ reg ]
        buf = bytearray(data)
        self.fd.write(buf)
        
    # pega o resultado de uma leitura
    def readResult(self):
        data = self.fd.read(2)
        buf = array.array('B', data)
        ret = (buf[0] << 8) | buf[1]
        return ret

    def open(self, bus):
        self.fd = io.open("/dev/i2c-{}".format(bus), "r+b", buffering=0)
        I2C_SLAVE = 0x0703
        fcntl.ioctl(self.fd, I2C_SLAVE, self.addr)

    def close(self):
        if self.fd:
            self.fd.close()
            self.fd = None

# Classe para acesso ao HDC1080
class HDC1080(object):
    # construtor
    def __init__(self, addr=0x40):
        # Constantes
        self.REG_TEMP = 0
        self.REG_UMID = 1
        self.REG_CONF = 2
        self.CONFIG = 0 # ler separado temp/umid, resolucao alta
        # Guarda o endereco
        self.addr = addr

    # Iniciacao para uso com with
    def __enter__(self):
        self.init()
        return self

    # limpeza para uso com with
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.device.close()

    # iniciacao
    def init(self):
        # Inicia device
        self.device = i2c_device(self.addr)
        # espera acabar reset
        while((self.device.readReg(self.REG_CONF) & 0x8000) != 0):
            time.sleep(0.1)
        # ler separado temperatura e umidade
        self.device.writeReg(self.REG_CONF, self.CONFIG)

    # le a temperatura
    def readTemp(self):
        # dispara a leitura
        self.device.access(self.REG_TEMP)
        # aguarda
        time.sleep(0.05)
        # le o resultado
        val = self.device.readResult()
        val = ((val * 1650) >> 16) - 400
        return val / 10.0

    # le a umidade
    def readUmid(self):
        # dispara a leitura
        self.device.access(self.REG_UMID)
        # aguarda
        time.sleep(0.05)
        # le o resultado
        val = self.device.readResult()
        return (val * 100) >> 16

# Teste simples
if __name__ == "__main__":
    try:
        with HDC1080() as sensor:
            while(True):
                    print sensor.readTemp(), sensor.readUmid()
                    time.sleep(10)
    except KeyboardInterrupt:
        exit(1)

Nenhum comentário: