quinta-feira, agosto 20, 2015

Sensor Barométrico BMP085

Mais um sensor com interface I2C, desta um sensor de pressão atmosférica. O módulo usado foi comprado na Deal Extreme. Este modelo de sensor já saiu de linha, tendo sido substituído pelo BMP180, que é totalmente compatível quanto ao software.



Não vou repetir aqui tudo o que já falei em posts anteriores sobre I2C, o único ponto a destacar é que o endereço é 0x77 (acrescentando a indicação de leitura ou escrita obtemos 0xEF e 0xEE)  e o clock pode ser de até 3,4MHz.

O sensor em si opera com tensão até 3.6V, mas o módulo possui um regulador para poder alimentar com 5V. Além dos tradicionais VCC, SDA, SCL e GND, o módulo tem os sinais XCLR e EOC. XCLR resseta o sensor quando em nível baixo; este sinal pode ser deixado desconectado. O pino EOC indica foi concluída uma conversão, também não vamos usá-lo.

O sensor mede tanto temperatura como pressão. Existem quatro opções de resolução, uma resolução maior requer mais leituras e leva mais tempo. Os valores lidos precisam ser corrigidos usando uma calibração que é gravada (na fábrica) no próprio sensor. A partir da pressão corrigida é possível calcular a altitude. Para dar uma ideia de precisão, a pressão obtida no modo "ultra high resolution" permite calcular (segundo o fabricante) a altitude com um erro de 25cm. Os registradores e fórmulas envolvidos podem ser vistos no datasheet. A Adafruit disponibiliza uma biblioteca para o Arduino no github.

No meu teste usei o Raspberry Pi. Os meus posts anteriores falam sobre como preparar o Rasp Pi para uso do I2C. O programa abaixo é uma adaptação da biblioteca da Adafruit para o Arduino, que eu fiz como exercício de programação Python (ou seja, não esperem alta qualidade no código). A Adafruit tinha uma biblioteca para o Rasp Pi, que foi suplantada por uma biblioteca para Rasp Pi e Beaglebone Black.

# Exemplo de uso do sensor barometrico BMP085 no Raspberry Pi
# Baseado no codigo da Adafruit para Arduino
#
# autor: Daniel Quadros

import smbus
from time import sleep

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

# Constantes para uso do BMP085
BMP085_ULTRALOWPOWER   = 0
BMP085_STANDARD        = 1
BMP085_HIGHRES         = 2
BMP085_ULTRAHIGHRES    = 3

BMP085_CAL_AC1         = 0xAA # R Calibration data (16 bits)
BMP085_CAL_AC2         = 0xAC # R Calibration data (16 bits)
BMP085_CAL_AC3         = 0xAE # R Calibration data (16 bits)
BMP085_CAL_AC4         = 0xB0 # R Calibration data (16 bits)
BMP085_CAL_AC5         = 0xB2 # R Calibration data (16 bits)
BMP085_CAL_AC6         = 0xB4 # R Calibration data (16 bits)
BMP085_CAL_B1          = 0xB6 # R Calibration data (16 bits)
BMP085_CAL_B2          = 0xB8 # R Calibration data (16 bits)
BMP085_CAL_MB          = 0xBA # R Calibration data (16 bits)
BMP085_CAL_MC          = 0xBC # R Calibration data (16 bits)
BMP085_CAL_MD          = 0xBE # R Calibration data (16 bits)

BMP085_CONTROL         = 0xF4
BMP085_TEMPDATA        = 0xF6
BMP085_PRESSUREDATA    = 0xF6
BMP085_READTEMPCMD     = 0x2E
BMP085_READPRESSURECMD = 0x34

class BMP085:
    
    # Endereco I2C
    address = None

    # Modo de operacao
    oversampling = None
    
    # Calibracao
    ac1 = None
    ac2 = None
    ac3 = None
    ac4 = None
    ac5 = None
    ac6 = None
    b1 = None
    b2 = None
    mb = None
    mc = None
    md = None

    # construtor
    def __init__(self, modo = BMP085_ULTRAHIGHRES, address = 0x77):
        self.address = address
        self.oversampling = modo
        
        # Le a calibracao
        self.ac1 = self.readS16(BMP085_CAL_AC1)
        self.ac2 = self.readS16(BMP085_CAL_AC2)
        self.ac3 = self.readS16(BMP085_CAL_AC3)
        self.ac4 = self.readU16(BMP085_CAL_AC4)
        self.ac5 = self.readU16(BMP085_CAL_AC5)
        self.ac6 = self.readU16(BMP085_CAL_AC6)
        self.b1  = self.readS16(BMP085_CAL_B1)
        self.b2  = self.readS16(BMP085_CAL_B2)
        self.mb  = self.readS16(BMP085_CAL_MB)
        self.mc  = self.readS16(BMP085_CAL_MC)
        self.md  = self.readS16(BMP085_CAL_MD)
        
    # leitura da altitude
    def leAltitude(self, sealevelPressure = 101325.0):
        pressure = self.lePressao()
        return 44330.0 * (1.0 - pow(pressure/sealevelPressure, 0.1903))
        
    # leitura da pressao barometrica
    # formulas extraidas do datasheet
    def lePressao(self):
        UT = self.readRawTemperature()
        UP = self.readRawPressure()
        B5 = self.computeB5(UT)
        B6 = B5 - 4000
        X1 = (self.b2 * ((B6 * B6) >> 12)) >> 11
        X2 = (self.ac2 * B6) >> 11
        X3 = X1 + X2
        B3 = (((self.ac1*4 + X3) << self.oversampling) + 2) / 4
        X1 = (self.ac3 * B6) >> 13
        X2 = (self.b1 * ((B6 * B6) >> 12)) >> 16
        X3 = ((X1 + X2) + 2) >> 2
        B4 = (self.ac4 * (X3 + 32768)) >> 15
        B7 = (UP - B3) * (50000 >> self.oversampling)
        if B7 < 0x80000000:
            p = (B7 * 2) / B4
        else:
            p = (B7 / B4) * 2
        X1 = (p >> 8) * (p >> 8)
        X1 = (X1 * 3038) >> 16
        X2 = (-7357 * p) >> 16
        p = p + ((X1 + X2 + 3791) >> 4)
        return p

    # leitura da pressao barometrica "bruta"
    def readRawPressure(self):
        bus.write_byte_data(self.address, BMP085_CONTROL, BMP085_READPRESSURECMD + (self.oversampling << 6))
        if self.oversampling == BMP085_ULTRALOWPOWER:
            sleep(0.005)
        elif self.oversampling == BMP085_STANDARD:
            sleep(0.008)
        elif self.oversampling == BMP085_HIGHRES:
            sleep(0.014)
        else:
            sleep(0.026)
        raw = self.readU16(BMP085_PRESSUREDATA)
        raw = raw << 8
        raw = raw | self.read8(BMP085_PRESSUREDATA+2)
        raw = raw >> (8 - self.oversampling)
        return raw
    
    # leitura da temperatura
    def leTemperatura(self):
        UT = self.readRawTemperature()
        B5 = self.computeB5(UT)
        return ((B5+8) >> 4) / 10.0
        
    # leitura da temperatura "bruta"
    def readRawTemperature(self):
        bus.write_byte_data(self.address, BMP085_CONTROL, BMP085_READTEMPCMD)
        sleep(0.005)
        return self.readU16(BMP085_TEMPDATA)
        
    # calcula B5 (temperatura corrigida)
    # formulas extraidas do datasheet
    def computeB5 (self, UT):
        X1 = ((UT - self.ac6) * self.ac5) >> 15
        X2 = (self.mc << 11) / (X1 + self.md)
        return X1 + X2        
    
    # leitura de valor de 16 bits (sem sinal)
    def readU16(self, reg):
        bytes = bus.read_i2c_block_data(self.address, reg, 2)
        ret = (bytes[0] << 8) | bytes[1]
        #print "Read (%d) = %d %d = %d" % (reg, bytes[0], bytes[1], ret)
        return ret

    # leitura de valor de 16 bits (com sinal)
    def readS16(self, reg):
        bytes = bus.read_i2c_block_data(self.address, reg, 2)
        ret = (bytes[0] << 8) | bytes[1]
        if bytes[0] > 127:
            ret = ret - 65536
        #print "Read (%d) = %d %d = %d" % (reg, bytes[0], bytes[1], ret)
        return ret

    # leitura de valor de 8 bits
    def read8(self, reg):
        return bus.read_byte_data(self.address, reg)


if __name__ == "__main__":
    # Teste simples se executado diretamente
    sensor = BMP085()
    temp = sensor.leTemperatura()
    print "Temperatura = %.1f C" % temp
    pressao = sensor.lePressao()
    print "Pressao = %d hPa" % pressao
    alt = sensor.leAltitude()
    print "Altitude = %.3f m" % alt

Abaixo o resultado do i2cdetect e um exemplo de saída do programa



Nenhum comentário: