Raspberry Pi Pico: датчик окружающей среды BME680 (MicroPython)
Узнайте, как использовать модуль датчика окружающей среды BME680 с платой Raspberry Pi Pico, запрограммированной на 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 — это цифровой датчик 4-в-1, который измеряет:
Температуру
Влажность
Барометрическое давление
Газ: летучие органические соединения (VOC), такие как этанол и угарный газ
Датчик газа
BME680 содержит MOX (металлооксидный) сенсор, который обнаруживает VOC в воздухе. Этот сенсор даёт вам качественное представление о сумме VOC/загрязнителей в окружающем воздухе — он не является специфичным для конкретной молекулы газа.
MOX-сенсоры состоят из поверхности из оксида металла, чувствительного чипа для измерения изменений проводимости и нагревателя. Он обнаруживает VOC путём адсорбции молекул кислорода на своём чувствительном слое. BME680 реагирует на большинство VOC, загрязняющих воздух в помещении (за исключением CO2).
Когда сенсор вступает в контакт с восстанавливающими газами, молекулы кислорода реагируют и увеличивают проводимость по поверхности. В качестве необработанного сигнала BME680 выдаёт значения сопротивления. Эти значения изменяются в зависимости от концентрации VOC:
Более высокая концентрация 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 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
Необходимые компоненты
Для этого проекта вам нужно подключить модуль датчика 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 и 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…
3. Выберите сохранение на «Raspberry Pi Pico»:
4. Назовите ваш файл bme680.py и нажмите кнопку OK:
Вот и всё. Библиотека загружена на вашу плату. Чтобы убедиться, что она была успешно загружена, перейдите в File > Save as… и выберите устройство MicroPython. Ваш файл должен быть в списке:
После загрузки библиотеки на вашу плату вы можете использовать методы библиотеки, импортировав её в начале вашего кода.
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)
Демонстрация
Запустите код на вашей плате.
Новые показания датчика BME680 должны отображаться каждые 5 секунд.
Примечание: если вы хотите, чтобы код запускался автоматически при загрузке Raspberry Pi Pico (например, без подключения к компьютеру), необходимо сохранить файл на плату с именем main.py.
Когда вы называете файл main.py, Raspberry Pi Pico будет автоматически запускать этот файл при загрузке. Если вы дадите файлу другое имя, он всё равно будет сохранён в файловой системе платы, но не будет запускаться автоматически при загрузке.
Заключение
Это руководство представляло собой введение в работу с датчиком окружающей среды и качества воздуха BME680 с Raspberry Pi Pico на прошивке MicroPython.
Мы надеемся, что это руководство было для вас полезным. У нас есть руководства по другим популярным датчикам окружающей среды: