Czujnik PASCO2 i NeoPixel z Raspberry Pi

Mierzenie koncentracji CO2 i wyświetlanie stanu na stripie LED NeoPixel z Raspberry Pi. Raspberry zostało wybrane z powodu lepszego zasilania niż ESP32. Przy lepszym zasilaniu kod po drobnych przeróbkach na MicroPython może zadziałać również na ESP8266/ESP32.
Czujnik CO2 to PASCO2 na płytce Shield2Go: https://www.infineon.com/cms/en/product/evaluation-boards/shield_pasco2_sensor/ . Sam czujnik wymaga zasilania 12V, na płytce znajduja się konwertery napięcia (do 12V oraz 3.3V) – dlatego wystarczy podłączyć do niej tylko 5V.
Przy użyciu samego czujnika https://www.infineon.com/cms/en/product/sensor/gas-sensors/co2-sensors/pasco2v01/ należy podłączyć 12V, oraz konwertować napięcia na logikę 3.3V. Taki zestaw mieliśmy na ostatnim CCC 😉
Dodatkowo podłączony jest czujnik BME280, który służy do pomiaru i ustawiania ciśnienia na czujniku PASCO2. Jest to wymagane do poprawnej kalibracji. Jeżeli nie mamy takiego czujnika, należy obliczyć wartość ciśnienia powietrza i wprowadzić ją ręcznie.
UWAGA: Czujnik do prawidłowego działania musi raz na jakiś czas zobaczyć koncentrację CO2 poniżej 450ppm. Jeżeli przez cały czas będzie znajdował się w pomieszczeniu z wysoką koncentracją CO2, to pomiary nie będą wiarygodne.
UWAGA2: Dostępne są zestawy ewaluacyjne – służą do testowania zaprogramowania czujnika, ale mogą podawać błędne wartości koncentracji CO2.
UWAGA3: Wartości graniczne z programie nie oznaczają szkodliwości i są wybrane indywidualnie.
Podłączenie
Czujnik PASCO2 na Shield2Go łączymy z Raspberry Pi:
5V – 5V
GND – GND
SDA – SDA (GPIO2)
SCL – SCL (GPIO3)
PSEL – GND (to ustawia komunikację przez I2C)
Podłączenie BME280:
VIN – 3V3
GND – GND
SDA – SDA (GPIO2)
SCL – SCL (GPIO3)
Podłączenie NeoPixel:
5V – 5V
GND – GND
SIG – D18 (lub inny PWM)
Czujniki PASCO2 oraz BME280 korzystają ze wspólnej szyny I2C
Przygotowanie Raspberry Pi.
Na Raspberry należy włączyć interfejs I2C – wchodzimy do konfiguracji komendą sudo raspi-config, następnie wybieramy Interface Options i tam włączamy I2C. Po zmianie konfiguracji należy zrestartować Raspberry.
Po zainstalowaniu paczki sudo apt install i2c-tools można uruchomić skanowanie, żeby sprawdzić czy widzimy czujniki na szynie. Przy dwóch czujnikach wygląda to tak:
sudo i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- 28 -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
Potrzebne biblioteki możemy zainstalować za pomocą virtualenva, lub bezpośrednio w systemie (jeżeli nie używamy go do niczego innego).
sudo apt install virtualenv
virtualenv pasco2
. pasco2/bin/activate
pip install bme280 smbus board neopixel
Jeżeli chcemy instalować bezpośrednio w systemie (odradzam), to należy pominąć virtualenv i dodać sudo przed pip.
Kod programu (uruchamiać z sudo!!):
#!/usr/bin/env python
import smbus
import bme280
import board
import neopixel
import time
# Wartosci graniczne koncentracji CO2
GREEN = 500
YELLOW = 1000
RED = 1500
# Jasnosc neopixel w zakresie 1-255
BRIGHTNESS = 16
# Pin danych NeoPixel
NEOPIXEL_PIN = board.D18
# Ilosc ledow w NeoPixel
NEOPIXEL_LEN = 10
# Adres czujnika PASCO2
PASCO2_ADDRESS = 0x28
# Adres czujnika BME280
BME280_ADDRESS = 0x76
# Okres pomiaru w sekundach
PERIOD = 60
# Inicjalizacja I2C (SMBus)
bus = smbus.SMBus(1)
# Inicjalizacja NeoPixela
neo = neopixel.NeoPixel(NEOPIXEL_PIN, NEOPIXEL_LEN, auto_write=True)
# Parametry kalibracji BME280
calibration_params = bme280.load_calibration_params(bus, BME280_ADDRESS)
def i2c_write_pasco2(register, value=None):
"""Funkcja do zapisywania do czujnika PASCO2"""
if value is not None:
bus.write_byte_data(PASCO2_ADDRESS, register, value)
else:
bus.write_byte(PASCO2_ADDRESS, register)
def i2c_read_pasco2(register):
"""Funkcja do czytania z czunika PASCO2"""
return bus.read_byte_data(PASCO2_ADDRESS, register)
def read_bme280():
"""Wczytuje dane z BME280"""
data = bme280.sample(bus, BME280_ADDRESS, calibration_params)
return data
ef set_pressure_to_pasco2(pressure):
"""Ustawia wartosc cisnienia na czujniku PASCO2"""
pressure_value = int(pressure) # Wartosc calkowita cisnienia
# Konwersja na MSB i LSB
msb = (pressure_value >> 8) & 0xFF
lsb = pressure_value & 0xFF
# Zapisanie wartosci cisnienia w czujniku PASCO2
i2c_write_pasco2(0x0B, msb) # PRES_REF_H
i2c_write_pasco2(0x0C, lsb) # PRES_REF_L
print(f"Set PASCO2 pressure to {pressure_value} hPa")
def calculate_color(co2_concentration):
"""Obliczenie koloru na podstawie wartosci koncentracji CO2"""
if co2_concentration <= GREEN:
return (0, BRIGHTNESS, 0) # Czysty zielony
elif co2_concentration <= YELLOW:
# Interpolacja pomiedzy zielonym a zoltym
ratio = (co2_concentration - GREEN) / GREEN
r = int(BRIGHTNESS * ratio)
g = BRIGHTNESS
b = 0
return (r, g, b)
elif co2_concentration <= RED:
# Interpolacja pomiedzy zoltym a czerwonym
ratio = (co2_concentration - YELLOW) / YELLOW
r = BRIGHTNESS
g = int(BRIGHTNESS * (1 - ratio))
b = 0
return (r, g, b)
else:
return (BRIGHTNESS, 0, 0) # Czerwony
def main():
"""Glowna petla programu"""
while True:
# Wczytuje i wyswietla dane z sensora BME280
data = read_bme280()
print(f"BME280: Pressure {data.pressure:.1f}, Temperature {data.temperature:.1f}, Humidity {data.humidity:.1f}")
try:
set_pressure_to_pasco2(data.pressure)
except OSError as e:
# Obsluga bledu I/O podczas zapisywania cisnienia w PASCO2
print(f"OSError while setting pressure: {e}, retrying in {PERIOD}s")
time.sleep(PERIOD)
continue
# Odczytaj i skonwertuj wartosc koncentracji CO2
i2c_write_pasco2(0x05) # Wczytaj CO2 MSB
co2_msb = i2c_read_pasco2(0x05)
i2c_write_pasco2(0x06) # Wczytaj CO2 LSB
co2_lsb = i2c_read_pasco2(0x06)
co2_concentration = (co2_msb << 8) | co2_lsb # Oblicz wartosc
print(f"CO2 Concentration: {co2_concentration} ppm")
# Oblicz kolor
color = calculate_color(co2_concentration)
# Przesun kolory na NeoPixelu
for i in range(len(neo) - 1, 0, -1):
neo[i] = neo[i - 1]
# Ustaw nowy kolor na pierwszym elemencie NeoPixel
neo[0] = color
# Poczekaj przed nastepnym cyklem
time.sleep(PERIOD)
if __name__ == '__main__':
main()

