Sistema de Monitoramento da Pressão Atmosférica Com Sensor BMP180 na Franzininho WiFi Lab01

Este post faz parte da série Franzininho WiFi com MicroPython

Introdução

A Franzininho WiFi Lab01 é uma placa versátil e abrangente, que permite a conexão com dispositivos externos para abranger diversos projetos. Diante disso, faremos a integração do sensor de pressão e temperatura BMP180 com a placa Franzininho WiFi Lab01 para oferecer uma solução mais robusta para monitoramento ambiental.

Este projeto visa capturar dados de pressão, temperatura e altitude e exibi-los de maneira clara e objetiva no display OLED SSD1306. Para isso, utilizaremos a comunicação I2C e os GPIOS da Franzininho WiFi Lab01.

Sensor BMP180 

O barômetro BMP180 é um sensor capaz de medir a pressão atmosférica e a temperatura do ambiente, além de permitir determinar a altitude e realizar previsões do tempo. O componente possui uma faixa de medições entre 30.000 e 110.000 Pa, conta com uma precisão absoluta de 2,5 hPa e uma medição de temperatura na faixa entre -45ºC e 85ºC, com precisão de ± 2ºC.

O princípio de funcionamento do BMP180 baseia-se na variação da pressão atmosférica à medida que a altitude muda. Ele utiliza um sensor de pressão piezo-resistivo para converter essa variação de pressão em um sinal elétrico, que pode ser então interpretado para determinar tanto a pressão atmosférica quanto a altitude. 

 

Figura 1 – Sensor BMP180

O sensor é muito utilizado em projetos que envolvem aperfeiçoamento da navegação GPS, cálculo de posição e detecção de inclinação, pequenas estações meteorológicas, entre outras.

Comunicação I2C

O I2C, ou Inter-Integrated Circuit, é um protocolo de comunicação serial que permite a interconexão de vários dispositivos em um barramento de comunicação com apenas dois fios: um para dados (SDA – Serial Data) e outro para clock (SCL – Serial Clock). Esse protocolo foi desenvolvido pela Philips (atualmente NXP Semiconductors) e é amplamente utilizado para comunicação entre componentes eletrônicos em placas de circuito.

A principal característica do I2C é que vários dispositivos podem compartilhar o mesmo barramento de comunicação, pois cada dispositivo conectado ao barramento possui um endereço único que o identifica. Dessa forma, mesmo que vários dispositivos estejam fisicamente conectados ao mesmo par de fios, cada um deles só será utilizado quando o seu endereço específico for indicado.

Para mais informações, veja:

Recursos necessários

Para iniciar o trabalho com os GPIOs, é essencial possuir o diagrama de pinos da placa à disposição, pois isso vai permitir que você identifique tanto os nomes quanto as funções associadas a cada um deles.

PinoRecurso
IO1LDR
IO2BT6
IO3BT5
IO4BT4
IO5BT3
IO6BT2
IO7BT1
IO8OLED_SDA
IO9OLED_SCL
IO10TFT_DC
IO11TFT_RES
IO12LED AZUL
IO13LED VERDE
IO14LED VERMELHO
IO15DHT11
IO17BUZZER
IO35TFT_SDA
IO36TFT_SCL
Tabela 1 – Franzininho WiFi Lab01 pinout

Usando os GPIOS para Conexão Externa

Para esse projeto precisaremos utilizar uma protoboard, pois o BMP180 não está integrado a Franzininho WiFi Lab01. Portanto, será necessário realizar a conexão por meio dos pinos GPIOs disponíveis na placa.

O BMP180 possui 4 pinos: VCC, GND, SCL (Serial Clock) e SDA (Dados seriais), em que os dois últimos são utilizados na comunicação I2C. A identificação de cada um deles está impressa na parte inferior do sensor. Assim:

  • Os pinos VCC e GND devem ser conectados ao 3,3 V e GND da Franzininho WiFi Lab01, respectivamente;
  • O pino SCL do sensor BMP180 deve ser conectado a um dos 5 pinos SCL da Franzininho WiFi Lab01;
  • O SDA deve ser conectado  a um dos 5 pinos SDA da Franzininho WiFi Lab01.

Na Figura 2 temos a localização dos GPIOs e pinos I2C para conexão externa.

Figura 2 – Recursos da placa.

Upload da biblioteca OLED com o Thonny IDE 

A biblioteca para escrever no display OLED não faz parte da biblioteca padrão do MicroPython. Portanto, você precisa fazer o upload da biblioteca na sua placa Franzininho WiFi Lab01 para poder utilizá-la.

Para adicionar a biblioteca usando o IDE Thonny, siga os passos abaixo:

  • Crie um novo arquivo no Thonny e copie o código da biblioteca:
#MicroPython SSD1306 OLED driver, I2C and SPI interfaces created by Adafruit

import time
import framebuf

# register definitions
SET_CONTRAST        = const(0x81)
SET_ENTIRE_ON       = const(0xa4)
SET_NORM_INV        = const(0xa6)
SET_DISP            = const(0xae)
SET_MEM_ADDR        = const(0x20)
SET_COL_ADDR        = const(0x21)
SET_PAGE_ADDR       = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP       = const(0xa0)
SET_MUX_RATIO       = const(0xa8)
SET_COM_OUT_DIR     = const(0xc0)
SET_DISP_OFFSET     = const(0xd3)
SET_COM_PIN_CFG     = const(0xda)
SET_DISP_CLK_DIV    = const(0xd5)
SET_PRECHARGE       = const(0xd9)
SET_VCOM_DESEL      = const(0xdb)
SET_CHARGE_PUMP     = const(0x8d)


class SSD1306:
    def __init__(self, width, height, external_vcc):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.pages = self.height // 8
        # Note the subclass must initialize self.framebuf to a framebuffer.
        # This is necessary because the underlying data buffer is different
        # between I2C and SPI implementations (I2C needs an extra byte).
        self.poweron()
        self.init_display()

    def init_display(self):
        for cmd in (
            SET_DISP | 0x00, # off
            # address setting
            SET_MEM_ADDR, 0x00, # horizontal
            # resolution and layout
            SET_DISP_START_LINE | 0x00,
            SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
            SET_MUX_RATIO, self.height - 1,
            SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
            SET_DISP_OFFSET, 0x00,
            SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
            # timing and driving scheme
            SET_DISP_CLK_DIV, 0x80,
            SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
            SET_VCOM_DESEL, 0x30, # 0.83*Vcc
            # display
            SET_CONTRAST, 0xff, # maximum
            SET_ENTIRE_ON, # output follows RAM contents
            SET_NORM_INV, # not inverted
            # charge pump
            SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
            SET_DISP | 0x01): # on
            self.write_cmd(cmd)
        self.fill(0)
        self.show()

    def poweroff(self):
        self.write_cmd(SET_DISP | 0x00)

    def contrast(self, contrast):
        self.write_cmd(SET_CONTRAST)
        self.write_cmd(contrast)

    def invert(self, invert):
        self.write_cmd(SET_NORM_INV | (invert & 1))

    def show(self):
        x0 = 0
        x1 = self.width - 1
        if self.width == 64:
            # displays with width of 64 pixels are shifted by 32
            x0 += 32
            x1 += 32
        self.write_cmd(SET_COL_ADDR)
        self.write_cmd(x0)
        self.write_cmd(x1)
        self.write_cmd(SET_PAGE_ADDR)
        self.write_cmd(0)
        self.write_cmd(self.pages - 1)
        self.write_framebuf()

    def fill(self, col):
        self.framebuf.fill(col)

    def pixel(self, x, y, col):
        self.framebuf.pixel(x, y, col)

    def scroll(self, dx, dy):
        self.framebuf.scroll(dx, dy)

    def text(self, string, x, y, col=1):
        self.framebuf.text(string, x, y, col)


class SSD1306_I2C(SSD1306):
    def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
        self.i2c = i2c
        self.addr = addr
        self.temp = bytearray(2)
        # Add an extra byte to the data buffer to hold an I2C data/command byte
        # to use hardware-compatible I2C transactions.  A memoryview of the
        # buffer is used to mask this byte from the framebuffer operations
        # (without a major memory hit as memoryview doesn't copy to a separate
        # buffer).
        self.buffer = bytearray(((height // 8) * width) + 1)
        self.buffer[0] = 0x40  # Set first byte of data buffer to Co=0, D/C=1
        self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.temp[0] = 0x80 # Co=1, D/C#=0
        self.temp[1] = cmd
        self.i2c.writeto(self.addr, self.temp)

    def write_framebuf(self):
        # Blast out the frame buffer using a single I2C transaction to support
        # hardware I2C interfaces.
        self.i2c.writeto(self.addr, self.buffer)

    def poweron(self):
        pass


class SSD1306_SPI(SSD1306):
    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
        self.rate = 10 * 1024 * 1024
        dc.init(dc.OUT, value=0)
        res.init(res.OUT, value=0)
        cs.init(cs.OUT, value=1)
        self.spi = spi
        self.dc = dc
        self.res = res
        self.cs = cs
        self.buffer = bytearray((height // 8) * width)
        self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.low()
        self.cs.low()
        self.spi.write(bytearray([cmd]))
        self.cs.high()

    def write_framebuf(self):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.high()
        self.cs.low()
        self.spi.write(self.buffer)
        self.cs.high()

    def poweron(self):
        self.res.high()
        time.sleep_ms(1)
        self.res.low()
        time.sleep_ms(10)
        self.res.high()
  • Acesse Arquivo > Salvar como e selecione o dispositivo MicroPython.
  • Nomeie o arquivo como “ssd1306.py” e clique em OK para salvá-lo na placa.

E é apenas isso, a biblioteca foi carregada na sua placa. Agora, você pode usar suas funcionalidades no seu código, importando a biblioteca.

Upload da biblioteca bmp180 com o Thonny IDE 

A biblioteca para utilizar os recursos do sensor BMP180 não faz parte da biblioteca padrão do MicroPython. Portanto, você precisa fazer o upload da biblioteca na sua placa Franzininho WiFi Lab01 para poder utilizá-la.

Para adicionar a biblioteca usando o Thonny IDE, siga os passos abaixo:

  • Crie um novo arquivo no Thonny e copie o código da biblioteca:
'''
bmp180 is a micropython module for the Bosch BMP180 sensor. It measures
temperature as well as pressure, with a high enough resolution to calculate
altitude.
Breakoutboard: http://www.adafruit.com/products/1603  
data-sheet: http://ae-bst.resource.bosch.com/media/products/dokumente/
bmp180/BST-BMP180-DS000-09.pdf

The MIT License (MIT)
Copyright (c) 2014 Sebastian Plamauer, oeplse@gmail.com
'''

from ustruct import unpack as unp
from machine import I2C, Pin
import math
import time

# BMP180 class
class BMP180():
	'''
	Module for the BMP180 pressure sensor.
	'''

	_bmp_addr = 119         	# adress of BMP180 is hardcoded on the sensor

	# init
	def __init__(self, i2c_bus):

    	# create i2c obect
    	_bmp_addr = self._bmp_addr
    	self._bmp_i2c = i2c_bus
    	self._bmp_i2c.start()
    	self.chip_id = self._bmp_i2c.readfrom_mem(_bmp_addr, 0xD0, 2)
    	# read calibration data from EEPROM
    	self._AC1 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xAA, 2))[0]
    	self._AC2 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xAC, 2))[0]
    	self._AC3 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xAE, 2))[0]
    	self._AC4 = unp('>H', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB0, 2))[0]
    	self._AC5 = unp('>H', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB2, 2))[0]
    	self._AC6 = unp('>H', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB4, 2))[0]
    	self._B1 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB6, 2))[0]
    	self._B2 = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xB8, 2))[0]
    	self._MB = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xBA, 2))[0]
    	self._MC = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xBC, 2))[0]
    	self._MD = unp('>h', self._bmp_i2c.readfrom_mem(_bmp_addr, 0xBE, 2))[0]

    	# settings to be adjusted by user
    	self.oversample_setting = 3
    	self.baseline = 101325.0

    	# output raw
    	self.UT_raw = None
    	self.B5_raw = None
    	self.MSB_raw = None
    	self.LSB_raw = None
    	self.XLSB_raw = None
    	self.gauge = self.makegauge() # Generator instance
    	for _ in range(128):
        	next(self.gauge)
        	time.sleep_ms(1)

	def compvaldump(self):
    	'''
    	Returns a list of all compensation values
    	'''
    	return [self._AC1, self._AC2, self._AC3, self._AC4, self._AC5, self._AC6,
            	self._B1, self._B2, self._MB, self._MC, self._MD, self.oversample_setting]

	# gauge raw
	def makegauge(self):
    	'''
    	Generator refreshing the raw measurments.
    	'''
    	delays = (5, 8, 14, 25)
    	while True:
        	self._bmp_i2c.writeto_mem(self._bmp_addr, 0xF4, bytearray([0x2E]))
        	t_start = time.ticks_ms()
        	while (time.ticks_ms() - t_start) <= 5: # 5mS delay
            	yield None
        	try:
            	self.UT_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF6, 2)
        	except:
            	yield None
        	self._bmp_i2c.writeto_mem(self._bmp_addr, 0xF4, bytearray([0x34+(self.oversample_setting << 6)]))
        	t_pressure_ready = delays[self.oversample_setting]
        	t_start = time.ticks_ms()
        	while (time.ticks_ms() - t_start) <= t_pressure_ready:
            	yield None
        	try:
            	self.MSB_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF6, 1)
            	self.LSB_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF7, 1)
            	self.XLSB_raw = self._bmp_i2c.readfrom_mem(self._bmp_addr, 0xF8, 1)
        	except:
            	yield None
        	yield True

	def blocking_read(self):
    	if next(self.gauge) is not None: # Discard old data
        	pass
    	while next(self.gauge) is None:
        	pass

	@property
	def oversample_sett(self):
    	return self.oversample_setting

	@oversample_sett.setter
	def oversample_sett(self, value):
    	if value in range(4):
        	self.oversample_setting = value
    	else:
        	print('oversample_sett can only be 0, 1, 2 or 3, using 3 instead')
        	self.oversample_setting = 3

	@property
	def temperature(self):
    	'''
    	Temperature in degree C.
    	'''
    	next(self.gauge)
    	try:
        	UT = unp('>H', self.UT_raw)[0]
    	except:
        	return 0.0
    	X1 = (UT-self._AC6)*self._AC5/2**15
    	X2 = self._MC*2**11/(X1+self._MD)
    	self.B5_raw = X1+X2
    	return (((X1+X2)+8)/2**4)/10

	@property
	def pressure(self):
    	'''
    	Pressure in mbar.
    	'''
    	next(self.gauge)
    	self.temperature  # Populate self.B5_raw
    	try:
        	MSB = unp('B', self.MSB_raw)[0]
        	LSB = unp('B', self.LSB_raw)[0]
        	XLSB = unp('B', self.XLSB_raw)[0]
    	except:
        	return 0.0
    	UP = ((MSB << 16)+(LSB << 8)+XLSB) >> (8-self.oversample_setting)
    	B6 = self.B5_raw-4000
    	X1 = (self._B2*(B6**2/2**12))/2**11
    	X2 = self._AC2*B6/2**11
    	X3 = X1+X2
    	B3 = ((int((self._AC1*4+X3)) << self.oversample_setting)+2)/4
    	X1 = self._AC3*B6/2**13
    	X2 = (self._B1*(B6**2/2**12))/2**16
    	X3 = ((X1+X2)+2)/2**2
    	B4 = abs(self._AC4)*(X3+32768)/2**15
    	B7 = (abs(UP)-B3) * (50000 >> self.oversample_setting)
    	if B7 < 0x80000000:
        	pressure = (B7*2)/B4
    	else:
        	pressure = (B7/B4)*2
    	X1 = (pressure/2**8)**2
    	X1 = (X1*3038)/2**16
    	X2 = (-7357*pressure)/2**16
    	return pressure+(X1+X2+3791)/2**4

	@property
	def altitude(self):
    	'''
    	Altitude in m.
    	'''
    	try:
        	p = -7990.0*math.log(self.pressure/self.baseline)
    	except:
        	p = 0.0
    	return p
  • Acesse Arquivo > Salvar como e selecione o dispositivo MicroPython.
  • Nomeie o arquivo como “bmp180.py” e clique em OK para salvá-lo na placa.

E é apenas isso, a biblioteca foi carregada na sua placa. Agora, você pode usar suas funcionalidades no seu código, importando a biblioteca.

Código

Com a Franzininho WiFi Lab01 conectada ao seu computador, abra o Thonny e crie um novo arquivo contendo o código a seguir:

from machine import I2C, Pin
from bmp180 import BMP180
import time, ssd1306, framebuf

# atribuição de pinos da Franzininho
i2c = I2C(scl=Pin(9), sda=Pin(8), freq=100000)

# configurando BMP180
bmp180 = BMP180(i2c)
bmp180.oversample_sett = 2
bmp180.baseline = 101325

# configurando display
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)

# criando símbolo para representar graus no display
degree = bytearray([0x00, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, 0x00])
fb = framebuf.FrameBuffer(degree, 8, 8, framebuf.MONO_HLSB)


while True:
	temp = round(bmp180.temperature, 2)
	p = round(bmp180.pressure, 2)
	alt = round(bmp180.altitude, 2)
    
	temperatura_str="Temp:"+str(temp)
	pressao_str="P:"+str(p)+" Pa"
	altitude_str="Alt:"+str(alt)+" m"
    
	#coletando data e hora atual
	current_time = time.localtime()
	formatted_time = "{:02d}/{:02d}/{} {:02d}:{:02d}".format(current_time[2], current_time[1], current_time[0], current_time[3], current_time[4])

	# limpa display
	oled.fill(0)
    
	# exibição no display
	oled.text(formatted_time, 0, 0)
	oled.framebuf.blit(fb, 53, 15)
	oled.text(temperatura_str+ " " + "C", 0, 20)
	oled.text(pressao_str, 0, 35)
	oled.text(altitude_str, 0, 50)

	oled.show()
	time.sleep(60)

Vamos começar o código com “from machine import I2C, Pin”. Para poder acessar os pinos da placa e utilizar a comunicação I2C. A biblioteca “time” foi utilizada para adicionar pausas no código. Para utilizar o display OLED, importe a biblioteca “ssd1306”, que foi previamente instalada na sua placa, e a biblioteca “framebuf”. Além disso, importe a biblioteca BMP180 para ler os dados do sensor.

De acordo com a tabela de pinagem, os pinos SCL e SDA da Franzininho WiFi Lab01 são os pinos 9 e 8, respectivamente. Assim, o I2C é configurado.

i2c = I2C(scl=Pin(9), sda=Pin(8), freq=100000)

Em seguida, associamos os pinos I2C ao sensor, uma vez que este utiliza a comunicação I2C para aquisição de dados. E definimos a precisão do dado lido pelo sensor (oversample_sett) e a referência da pressão no nível do mar (baseline). Portanto, as configurações do sensor foram concluídas.

bmp180 = BMP180(i2c)
bmp180.oversample_sett = 2
bmp180.baseline = 101325

Após isso, configuramos o display OLED. Definimos a largura e altura do display, que são 128×64, e realizamos a associação ao I2C.

oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)

Como o BMP180 mede temperatura e os dados medidos serão exibidos no display, precisamos criar o símbolo de grau (º). Isso se deve ao fato de o display não suportar nativamente o símbolo de grau (º) para representar a temperatura. Por essa razão, foi necessário gerar o símbolo utilizando um bitmap.

Observe que não é problema o sensor e o display dividirem os mesmo pinos I2C, pois o protocolo I2C permite a comunicação de vários dispositivos em um mesmo barramento.

degree = bytearray([0x00, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, 0x00])
fb = framebuf.FrameBuffer(degree, 8, 8, framebuf.MONO_HLSB)

No loop principal, chamamos as funções do sensor para obter temperatura, pressão e altitude. Os valores obtidos são arredondados para duas casas decimais, para permitir a exibição adequada no display. Posteriormente, os valores medidos são convertidos em string para ser possível exibir no display.

Para obter a data e hora atuais, utilizamos a função ‘localtime’ da biblioteca “time”, que retorna o ano, mês, dia, hora, minuto, segundo, dia da semana (variando de 0 a 6, onde 0 é segunda-feira e 6 é domingo) e o dia do ano (varia de 1 a 366). Para apresentar essas informações no formato comum, foi criada a variável ‘formatted_time’ que organiza a data e hora no formato ‘dd/mm/aaaa, hh:mm’

Assim, a exibição no display inicia com a data e hora atuais, seguidas pelas leituras de temperatura, pressão e altitude. O bitmap que representa o símbolo de grau (°) é inserido entre o valor da temperatura e a unidade de medida Celsius (°C). 

A cada 60 segundos, as medições são atualizadas e exibidas novamente no display. Para garantir que a nova informação seja exibida corretamente, o display é limpo antes de cada atualização.

A saída do display é a mostrada na abaixo:

Figura 3 – Resultado

Conclusão

Neste artigo, realizamos a associação do sensor BMP180 com a placa Franzininho WiFi Lab01 para capturar dados de pressão, temperatura e altitude, e exibi-los de maneira clara no display OLED SSD1306. Para isso, foi necessário compreender o funcionamento do protocolo I2C, assegurando que sua implementação fosse realizada de maneira correta.

Franzininho WiFi com MicroPython

Sistema de Registro de Temperatura com a Franzininho WiFi Lab01 Sistema de Monitoramento da Qualidade do Ar utilizando a Franzininho WiFi Lab01
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Software » Sistema de Monitoramento da Pressão Atmosférica Com Sensor BMP180 na Franzininho WiFi Lab01

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: