Cracow Linux Users Group

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()