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.
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.
| Pino | Recurso |
| IO1 | LDR |
| IO2 | BT6 |
| IO3 | BT5 |
| IO4 | BT4 |
| IO5 | BT3 |
| IO6 | BT2 |
| IO7 | BT1 |
| IO8 | OLED_SDA |
| IO9 | OLED_SCL |
| IO10 | TFT_DC |
| IO11 | TFT_RES |
| IO12 | LED AZUL |
| IO13 | LED VERDE |
| IO14 | LED VERMELHO |
| IO15 | DHT11 |
| IO17 | BUZZER |
| IO35 | TFT_SDA |
| IO36 | TFT_SCL |
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.
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 = 101325Apó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:
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.











