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