Raspberry Pi Pico: датчик окружающей среды BME680 (MicroPython)

Узнайте, как использовать модуль датчика окружающей среды BME680 с платой Raspberry Pi Pico, запрограммированной на MicroPython, для получения данных о температуре, влажности, давлении и газе (качестве воздуха). Мы создадим базовый пример, чтобы показать вам, как подключить датчик, какую библиотеку использовать, и приведём пример кода для получения данных с датчика.

Raspberry Pi Pico с датчиком окружающей среды BME680 MicroPython

Впервые работаете с Raspberry Pi Pico? Начните знакомство с Raspberry Pi Pico здесь.

Необходимые условия — прошивка MicroPython

Для выполнения этого руководства вам необходимо, чтобы на плате Raspberry Pi Pico была установлена прошивка MicroPython. Вам также потребуется IDE для написания и загрузки кода на плату.

Рекомендуемой IDE для MicroPython на Raspberry Pi Pico является Thonny IDE. Следуйте следующему руководству, чтобы узнать, как установить Thonny IDE, прошить прошивку MicroPython и загрузить код на плату.

Знакомство с модулем датчика окружающей среды BME680

BME680 — это датчик окружающей среды, который объединяет в себе датчики газа, давления, влажности и температуры. Датчик газа способен обнаруживать широкий спектр газов, таких как летучие органические соединения (VOC). По этой причине BME680 может использоваться для контроля качества воздуха в помещениях.

BME680 датчик газа, влажности, барометрического давления, температуры окружающей среды, качества воздуха — вид спереди

Измерения BME680

BME680 — это цифровой датчик 4-в-1, который измеряет:

  • Температуру

  • Влажность

  • Барометрическое давление

  • Газ: летучие органические соединения (VOC), такие как этанол и угарный газ

Датчик газа

BME680 содержит MOX (металлооксидный) сенсор, который обнаруживает VOC в воздухе. Этот сенсор даёт вам качественное представление о сумме VOC/загрязнителей в окружающем воздухе — он не является специфичным для конкретной молекулы газа.

MOX-сенсоры состоят из поверхности из оксида металла, чувствительного чипа для измерения изменений проводимости и нагревателя. Он обнаруживает VOC путём адсорбции молекул кислорода на своём чувствительном слое. BME680 реагирует на большинство VOC, загрязняющих воздух в помещении (за исключением CO2).

Когда сенсор вступает в контакт с восстанавливающими газами, молекулы кислорода реагируют и увеличивают проводимость по поверхности. В качестве необработанного сигнала BME680 выдаёт значения сопротивления. Эти значения изменяются в зависимости от концентрации VOC:

BME680 датчик газа окружающей среды — сопротивление и принцип работы
  • Более высокая концентрация VOC » Более низкое сопротивление

  • Более низкая концентрация VOC » Более высокое сопротивление

Реакции, происходящие на поверхности сенсора (а следовательно, и сопротивление), зависят не только от концентрации VOC, но и от других параметров, таких как температура и влажность.

Важная информация о датчике газа

Датчик газа даёт вам качественное представление о газах VOC в окружающем воздухе. Таким образом, вы можете отслеживать тенденции, сравнивать результаты и определять, улучшается или ухудшается качество воздуха. Для получения точных измерений необходимо откалибровать датчик по известным источникам и построить калибровочную кривую.

Когда вы впервые получаете датчик, рекомендуется дать ему поработать 48 часов, прежде чем начинать собирать «реальные» данные. После этого также рекомендуется дать датчику поработать 30 минут перед получением показаний газа.

Точность BME680

Вот точность датчиков температуры, влажности и давления BME680:

Датчик

Точность

Температура

+/- 1,0 ºC

Влажность

+/- 3%

Давление

+/- 1 гПа

Диапазон работы BME680

В следующей таблице показан диапазон работы датчиков температуры, влажности и давления BME680.

Датчик

Диапазон работы

Температура

от -40 до 85 ºC

Влажность

от 0 до 100%

Давление

от 300 до 1100 гПа

Распиновка BME680

Вот распиновка BME680:

VCC

Питание датчика

GND

Общая земля

SCL

Вывод SCL для связи по I2C / Вывод SCK для связи по SPI

SDA

Вывод SDA для связи по I2C / Вывод SDI (MOSI) для связи по SPI

SDO

Вывод SDO (MISO) для связи по SPI

CS

Вывод выбора чипа для связи по SPI

Интерфейс BME680

BME680 поддерживает интерфейсы I2C и SPI.

BME680 датчик газа, влажности, барометрического давления, температуры окружающей среды, качества воздуха — вид сзади

BME680 I2C

Этот датчик обменивается данными по протоколу I2C, поэтому подключение простое. Вы можете использовать любую комбинацию выводов I2C Raspberry Pi Pico. Мы будем использовать GPIO 5 (SCL) и GPIO 4 (SDA). Вы можете использовать любую другую комбинацию выводов I2C, если укажете их в коде.

BME680

Raspberry Pi Pico

Vin

3.3V

GND

GND

SCL

GPIO 5

SDA

GPIO 4

Подробнее о GPIO Raspberry Pi Pico: Raspberry Pi Pico и Pico W — руководство по распиновке: пояснение GPIO

Необходимые компоненты

Raspberry Pi Pico с датчиком окружающей среды BME680

Для этого проекта вам нужно подключить модуль датчика BME680 к выводам I2C Raspberry Pi Pico. Вот список компонентов, необходимых для этого руководства:

  • Модуль датчика BME680

  • Raspberry Pi Pico или Pico W

  • Макетная плата

  • Соединительные провода

Подключение BME680 к Raspberry Pi Pico

Подключите BME680 к любой комбинации выводов I2C Pico — мы будем использовать GPIO 4 (SDA) и GPIO 5 (SCL).

Схема подключения Raspberry Pi Pico к датчику BME680

Рекомендуемая статья: Raspberry Pi Pico и Pico W — руководство по распиновке: пояснение GPIO

Библиотека BME680 для MicroPython

Пакет библиотек MicroPython по умолчанию не содержит библиотеку для BME680. Существует несколько различных модулей для чтения данных с датчика BME680. Мы будем использовать следующий модуль, адаптированный из библиотеки Adafruit BME680.

# Spaces, comments and some functions have been removed from the original file to save memory
# Original source: https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/master/adafruit_bme680.py
import time
import math
from micropython import const
from ubinascii import hexlify as hex
try:
  import struct
except ImportError:
  import ustruct as struct
_BME680_CHIPID = const(0x61)
_BME680_REG_CHIPID = const(0xD0)
_BME680_BME680_COEFF_ADDR1 = const(0x89)
_BME680_BME680_COEFF_ADDR2 = const(0xE1)
_BME680_BME680_RES_HEAT_0 = const(0x5A)
_BME680_BME680_GAS_WAIT_0 = const(0x64)
_BME680_REG_SOFTRESET = const(0xE0)
_BME680_REG_CTRL_GAS = const(0x71)
_BME680_REG_CTRL_HUM = const(0x72)
_BME280_REG_STATUS = const(0xF3)
_BME680_REG_CTRL_MEAS = const(0x74)
_BME680_REG_CONFIG = const(0x75)
_BME680_REG_PAGE_SELECT = const(0x73)
_BME680_REG_MEAS_STATUS = const(0x1D)
_BME680_REG_PDATA = const(0x1F)
_BME680_REG_TDATA = const(0x22)
_BME680_REG_HDATA = const(0x25)
_BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16)
_BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127)
_BME680_RUNGAS = const(0x10)
_LOOKUP_TABLE_1 = (2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0,
  2126008810.0, 2147483647.0, 2130303777.0, 2147483647.0, 2147483647.0,
  2143188679.0, 2136746228.0, 2147483647.0, 2126008810.0, 2147483647.0,
  2147483647.0)
_LOOKUP_TABLE_2 = (4096000000.0, 2048000000.0, 1024000000.0, 512000000.0, 255744255.0, 127110228.0,
  64000000.0, 32258064.0, 16016016.0, 8000000.0, 4000000.0, 2000000.0, 1000000.0,
  500000.0, 250000.0, 125000.0)
def _read24(arr):
  ret = 0.0
  for b in arr:
    ret *= 256.0
    ret += float(b & 0xFF)
  return ret
class Adafruit_BME680:
  def __init__(self, *, refresh_rate=10):
    self._write(_BME680_REG_SOFTRESET, [0xB6])
    time.sleep(0.005)
    chip_id = self._read_byte(_BME680_REG_CHIPID)
    if chip_id != _BME680_CHIPID:
      raise RuntimeError('Failed 0x%x' % chip_id)
    self._read_calibration()
    self._write(_BME680_BME680_RES_HEAT_0, [0x73])
    self._write(_BME680_BME680_GAS_WAIT_0, [0x65])
    self.sea_level_pressure = 1013.25
    self._pressure_oversample = 0b011
    self._temp_oversample = 0b100
    self._humidity_oversample = 0b010
    self._filter = 0b010
    self._adc_pres = None
    self._adc_temp = None
    self._adc_hum = None
    self._adc_gas = None
    self._gas_range = None
    self._t_fine = None
    self._last_reading = 0
    self._min_refresh_time = 1000 / refresh_rate
  @property
  def pressure_oversample(self):
    return _BME680_SAMPLERATES[self._pressure_oversample]
  @pressure_oversample.setter
  def pressure_oversample(self, sample_rate):
    if sample_rate in _BME680_SAMPLERATES:
      self._pressure_oversample = _BME680_SAMPLERATES.index(sample_rate)
    else:
      raise RuntimeError("Invalid")
  @property
  def humidity_oversample(self):
    return _BME680_SAMPLERATES[self._humidity_oversample]
  @humidity_oversample.setter
  def humidity_oversample(self, sample_rate):
    if sample_rate in _BME680_SAMPLERATES:
      self._humidity_oversample = _BME680_SAMPLERATES.index(sample_rate)
    else:
      raise RuntimeError("Invalid")
  @property
  def temperature_oversample(self):
      return _BME680_SAMPLERATES[self._temp_oversample]
  @temperature_oversample.setter
  def temperature_oversample(self, sample_rate):
    if sample_rate in _BME680_SAMPLERATES:
      self._temp_oversample = _BME680_SAMPLERATES.index(sample_rate)
    else:
      raise RuntimeError("Invalid")
  @property
  def filter_size(self):
    return _BME680_FILTERSIZES[self._filter]
  @filter_size.setter
  def filter_size(self, size):
    if size in _BME680_FILTERSIZES:
      self._filter = _BME680_FILTERSIZES[size]
    else:
      raise RuntimeError("Invalid")
  @property
  def temperature(self):
    self._perform_reading()
    calc_temp = (((self._t_fine * 5) + 128) / 256)
    return calc_temp / 100
  @property
  def pressure(self):
    self._perform_reading()
    var1 = (self._t_fine / 2) - 64000
    var2 = ((var1 / 4) * (var1 / 4)) / 2048
    var2 = (var2 * self._pressure_calibration[5]) / 4
    var2 = var2 + (var1 * self._pressure_calibration[4] * 2)
    var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536)
    var1 = (((((var1 / 4) * (var1 / 4)) / 8192) *
      (self._pressure_calibration[2] * 32) / 8) +
      ((self._pressure_calibration[1] * var1) / 2))
    var1 = var1 / 262144
    var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768
    calc_pres = 1048576 - self._adc_pres
    calc_pres = (calc_pres - (var2 / 4096)) * 3125
    calc_pres = (calc_pres / var1) * 2
    var1 = (self._pressure_calibration[8] * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096
    var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192
    var3 = (((calc_pres / 256) ** 3) * self._pressure_calibration[9]) / 131072
    calc_pres += ((var1 + var2 + var3 + (self._pressure_calibration[6] * 128)) / 16)
    return calc_pres/100
  @property
  def humidity(self):
    self._perform_reading()
    temp_scaled = ((self._t_fine * 5) + 128) / 256
    var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) -
      ((temp_scaled * self._humidity_calibration[2]) / 200))
    var2 = (self._humidity_calibration[1] *
      (((temp_scaled * self._humidity_calibration[3]) / 100) +
       (((temp_scaled * ((temp_scaled * self._humidity_calibration[4]) / 100)) /
         64) / 100) + 16384)) / 1024
    var3 = var1 * var2
    var4 = self._humidity_calibration[5] * 128
    var4 = (var4 + ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16
    var5 = ((var3 / 16384) * (var3 / 16384)) / 1024
    var6 = (var4 * var5) / 2
    calc_hum = (((var3 + var6) / 1024) * 1000) / 4096
    calc_hum /= 1000
    if calc_hum > 100:
      calc_hum = 100
    if calc_hum < 0:
      calc_hum = 0
    return calc_hum
  @property
  def altitude(self):
    pressure = self.pressure
    return 44330 * (1.0 - math.pow(pressure / self.sea_level_pressure, 0.1903))
  @property
  def gas(self):
    self._perform_reading()
    var1 = ((1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._gas_range])) / 65536
    var2 = ((self._adc_gas * 32768) - 16777216) + var1
    var3 = (_LOOKUP_TABLE_2[self._gas_range] * var1) / 512
    calc_gas_res = (var3 + (var2 / 2)) / var2
    return int(calc_gas_res)
  def _perform_reading(self):
    if (time.ticks_diff(self._last_reading, time.ticks_ms()) * time.ticks_diff(0, 1)
        < self._min_refresh_time):
      return
    self._write(_BME680_REG_CONFIG, [self._filter << 2])
    self._write(_BME680_REG_CTRL_MEAS,
      [(self._temp_oversample << 5)|(self._pressure_oversample << 2)])
    self._write(_BME680_REG_CTRL_HUM, [self._humidity_oversample])
    self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS])
    ctrl = self._read_byte(_BME680_REG_CTRL_MEAS)
    ctrl = (ctrl & 0xFC) | 0x01
    self._write(_BME680_REG_CTRL_MEAS, [ctrl])
    new_data = False
    while not new_data:
      data = self._read(_BME680_REG_MEAS_STATUS, 15)
      new_data = data[0] & 0x80 != 0
      time.sleep(0.005)
    self._last_reading = time.ticks_ms()
    self._adc_pres = _read24(data[2:5]) / 16
    self._adc_temp = _read24(data[5:8]) / 16
    self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0]
    self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64)
    self._gas_range = data[14] & 0x0F
    var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2)
    var2 = (var1 * self._temp_calibration[1]) / 2048
    var3 = ((var1 / 2) * (var1 / 2)) / 4096
    var3 = (var3 * self._temp_calibration[2] * 16) / 16384
    self._t_fine = int(var2 + var3)
  def _read_calibration(self):
    coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25)
    coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16)
    coeff = list(struct.unpack('<hbBHhbBhhbbHhhBBBHbbbBbHhbb', bytes(coeff[1:39])))
    coeff = [float(i) for i in coeff]
    self._temp_calibration = [coeff[x] for x in [23, 0, 1]]
    self._pressure_calibration = [coeff[x] for x in [3, 4, 5, 7, 8, 10, 9, 12, 13, 14]]
    self._humidity_calibration = [coeff[x] for x in [17, 16, 18, 19, 20, 21, 22]]
    self._gas_calibration = [coeff[x] for x in [25, 24, 26]]
    self._humidity_calibration[1] *= 16
    self._humidity_calibration[1] += self._humidity_calibration[0] % 16
    self._humidity_calibration[0] /= 16
    self._heat_range = (self._read_byte(0x02) & 0x30) / 16
    self._heat_val = self._read_byte(0x00)
    self._sw_err = (self._read_byte(0x04) & 0xF0) / 16
  def _read_byte(self, register):
    return self._read(register, 1)[0]
  def _read(self, register, length):
    raise NotImplementedError()
  def _write(self, register, values):
    raise NotImplementedError()
class BME680_I2C(Adafruit_BME680):
  def __init__(self, i2c, address=0x77, debug=False, *, refresh_rate=10):
    self._i2c = i2c
    self._address = address
    self._debug = debug
    super().__init__(refresh_rate=refresh_rate)
  def _read(self, register, length):
    result = bytearray(length)
    self._i2c.readfrom_mem_into(self._address, register & 0xff, result)
    if self._debug:
      print("\t${:x} read ".format(register), " ".join(["{:02x}".format(i) for i in result]))
    return result
  def _write(self, register, values):
    if self._debug:
      print("\t${:x} write".format(register), " ".join(["{:02x}".format(i) for i in values]))
    for value in values:
      self._i2c.writeto_mem(self._address, register, bytearray([value & 0xFF]))
      register += 1

Посмотреть исходный код

Загрузите предыдущую библиотеку на вашу плату Raspberry Pi Pico (сохраните её с именем bme680.py). Следуйте приведённым ниже инструкциям, чтобы узнать, как загрузить библиотеку с помощью Thonny IDE.

Если вы используете Thonny IDE, выполните следующие шаги:

1. Скопируйте код библиотеки в новый файл. Код библиотеки BME680 можно найти здесь.

2. Перейдите в File > Save as…

Thonny IDE MicroPython — Сохранить файл — Save as

3. Выберите сохранение на «Raspberry Pi Pico»:

Сохранить файл на Raspberry Pi Pico в Thonny IDE

4. Назовите ваш файл bme680.py и нажмите кнопку OK:

Сохранение библиотеки BME680 на Raspberry Pi Pico в Thonny IDE

Вот и всё. Библиотека загружена на вашу плату. Чтобы убедиться, что она была успешно загружена, перейдите в File > Save as… и выберите устройство MicroPython. Ваш файл должен быть в списке:

Библиотека BME680 сохранена на Raspberry Pi Pico в Thonny IDE

После загрузки библиотеки на вашу плату вы можете использовать методы библиотеки, импортировав её в начале вашего кода.

BME680: давление, температура, влажность и качество воздуха (газ) — код MicroPython

После загрузки библиотеки на Raspberry Pi Pico создайте новый файл с именем main.py и вставьте следующий код. Он выводит температуру, влажность, давление и сопротивление газа в консоль каждые 5 секунд.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details: https://RandomNerdTutorials.com/raspberry-pi-pico-bme680-micropython/

from machine import Pin, I2C
from time import sleep
from bme680 import *

# RPi Pico - Pin assignment
i2c = I2C(id=0, scl=Pin(5), sda=Pin(4))

bme = BME680_I2C(i2c=i2c)

while True:
  try:
    temp = str(round(bme.temperature, 2)) + ' C'
    #temp = (bme.temperature) * (9/5) + 32
    #temp = str(round(temp, 2)) + 'F'

    hum = str(round(bme.humidity, 2)) + ' %'

    pres = str(round(bme.pressure, 2)) + ' hPa'

    gas = str(round(bme.gas/1000, 2)) + ' KOhms'

    print('Temperature:', temp)
    print('Humidity:', hum)
    print('Pressure:', pres)
    print('Gas:', gas)
    print('-------')
  except OSError as e:
    print('Failed to read sensor.')

  sleep(5)

Посмотреть исходный код

Как работает код

Сначала необходимо импортировать нужные библиотеки, включая модуль bme680, который вы загрузили ранее.

from machine import Pin, I2C
from time import sleep
from bme680 import *

Задайте идентификатор I2C и выводы. Мы используем GPIO 5 и 4, но любые другие выводы I2C тоже должны работать.

i2c = I2C(id=0, scl=Pin(5), sda=Pin(4))

Примечание: GPIO 5 и 4 относятся к I2C id=0 — проверьте здесь другие комбинации и их идентификаторы.

Создайте объект BME680 с именем bme с определёнными ранее выводами I2C:

bme = BME680_I2C(i2c=i2c)

Считывание температуры, влажности, давления и сопротивления газа так же просто, как получение атрибутов temperature, humidity, pressure и gas из объекта bme.

temp = str(round(bme.temperature, 2)) + ' C'
#temp = (bme.temperature) * (9/5) + 32
#temp = str(round(temp, 2)) + 'F'

hum = str(round(bme.humidity, 2)) + ' %'

pres = str(round(bme.pressure, 2)) + ' hPa'

gas = str(round(bme.gas/1000, 2)) + ' KOhms'

Наконец, выведите показания в оболочку:

print('Temperature: ', temp)
print('Humidity: ', hum)
print('Pressure: ', pres)
print('Gas:', gas)

В конце мы добавляем задержку в 5 секунд:

sleep(5)

Демонстрация

Запустите код на вашей плате.

Thonny IDE — запуск кода

Новые показания датчика BME680 должны отображаться каждые 5 секунд.

Raspberry Pi Pico — получение показаний датчика BME680 — MicroPython

Примечание: если вы хотите, чтобы код запускался автоматически при загрузке Raspberry Pi Pico (например, без подключения к компьютеру), необходимо сохранить файл на плату с именем main.py.

Когда вы называете файл main.py, Raspberry Pi Pico будет автоматически запускать этот файл при загрузке. Если вы дадите файлу другое имя, он всё равно будет сохранён в файловой системе платы, но не будет запускаться автоматически при загрузке.

Заключение

Это руководство представляло собой введение в работу с датчиком окружающей среды и качества воздуха BME680 с Raspberry Pi Pico на прошивке MicroPython.

Мы надеемся, что это руководство было для вас полезным. У нас есть руководства по другим популярным датчикам окружающей среды: