Raspbery Pi 5と2.8インチ液晶モジュール(240×320)を使ったCO2濃度、気圧、温度、湿度&WiFi時計(1):CO2濃度センサ_SCD30

Raspberry Pi 5 とBME280センサ(気圧、温度、湿度)とSCD30センサ(CO2濃度、温度、湿度) モジュールをI2C接続、その測定データをSPI接続した2.8インチTFT液晶 (240×320、ILI9341)モジュールに日本語フォントで表示した際の作業メモです。

Raspberry Pi 5とSCD30、BME280センサで測定したCO2濃度、気圧、気温、湿度を2.8インチTFT液晶に表示。
OS起動時にpython3プログラムを自動起動。
目次

使ったパーツと結線図、OSセットアップ

2.8インチTFT液晶 (240×320)モジュールを4線式のSPIで、SCD30とBME280センサモジュールをSoC内蔵の I2C(ハードウエア I2C)で接続する結線図です。

2.8インチTFT液晶モジュール(ILI9341)をRaspberry Pi 5とSPI接続

2.8インチ液晶モジュールは、ドライバーICとしてILI9341を使った240×320の液晶モジュール。
今回、XPT2046を使ったタッチパネル、SDカードスロット部分は使っていません。SDカード側コネクタ(J4)にはピンヘッダが未実装なので手持ちのピンヘッダをはんだ付けしています。

2.8インチTFT液晶モジュールと内蔵SDカードスロット
基板裏面のピン配置とシルク印刷

参考:
ILI9341搭載2.8インチSPI制御タッチパネル付TFT液晶(akizukidenshi.com)
2.8inch SPI Module ILI9341 SKU-MSP2807 – LCD wiki(回路図)

SPI はマスタ(ここではPi 5)とスレーブ(ここでは2.8インチTFT液晶)の間をつなぐ4線(CS、CLK、MOSI、MISO)を使った双方向のクロック同期方式のシリアルインターフェース。これらのSPIピンに加えてDCピン、RESETピンを使います。
 Raspberry Pi 4BやRaspberry Pi Zero 2 W(いずれもbullseye)でCSピンとして使っていたGPIO8(CE0)では動作させることができなかったので、GPIO23に変更しています。

# 2.8インチ
TFT液晶
J2

シルク印刷
GPIO
ピン番号
GPIO
機能
備考
1VCC13V3 power
2GND30Ground
3CS16GPIO23CSピンを
GPIO8(CE0)から
GPIO23に変更
4RESET18GPIO24
5DC22GPIO25
6SDI (MOSI)19GPIO10 (MOSI)
7SCK23GPIO11 (SCLK)
8LED13V3 power
9SDO (MISO)21GPIO9 (MISO)

BME280センサモジュールをRaspberry Pi 5とI2C接続

今回購入したBME280モジュール(6ピン基板)のインターフェースはI2CとSPIの両方が使えます。今回はI2C接続(VCC、GND、SCL、SDAの4ピンのみ利用)でつなぎます。このモジュールにはI2Cバスの信号レベル変換回路や電源レギュレータ回路は入っていないので、電源電圧、信号レベルともに3.3V。Raspberry PiのGPIO信号ピンのロジックレベルは3.3Vなので利用できます。信号線は10kΩでプルアップされています。

以前購入したBME280モジュール(4ピン基板)のインターフェースはI2Cのみですが、裏面にI2Cバスの5Vと3.3Vのレベル変換回路、VIN(5V)から3.3Vへのレギュレータ回路(LDO:Low Drop Out)を搭載しています。そのため、5V系のAndroidや3.3V系のESP32やRaspbery Piの両方で使うには便利です。信号線は10kΩでプルアップされています。

BME280モジュール(右が6ピン基板、左が4ピン基板)

Sensirion(センシリオン)社のSCD30センサ

SCD30センサモジュールは、CO2 濃度測定の精度が高い非分散型赤外線(NDIR)ベースCO2センサです。
このモジュールには基板内に同じSensirion社のSHT31センサを補正用として内蔵しているので湿度や温度も測定できますが、SCD30センサモジュールとしての動作温度は0℃~+50℃の仕様です。

消費電流が比較的大きい(平均19mA、最大70mA)ので、ブレッドボードにジャンパーワイヤーで配線する際、接触不良がないように気を付けています。

SCD30 CO2 センサ モジュール基板。
※ピンヘッダ(1×4)をはんだ付けする前です。
補正用のSHT31センサ 付近の拡大
補正用のSHT31センサ 付近の拡大

SCD30センサモジュールの電源電圧は3.3V~5.5V、I2C信号の電圧レベルは 3Vなので、 Raspberry PiのGPIOピンに信号レベル変換モジュール(レベルシフター)なしで結線しています。

ピンヘッダが付属しないときは、ピンヘッダ 1列タイプ 40ピンから4ピンをニッパ(1×4)でカットしてSCD30センサモジュールにはんだ付けします。

参考:
Datasheet Sensirion SCD30 Sensor Module CO2, humidity, and temperature sensor

「Handling and Assembly Guide for SCD30」のp2に SCD30センサの組み立て と マウントの向き についての記載があります。概要は「取り付けにはコンタクトピンのみを使用」、「SCD30はホスト基板のどの部分にも接触しないように」、「SCD30とホストPCBの接触を防ぐために推奨されるスタンドオフ高さsh以下を維持」。マウントの向きは「SCD30は、上向きにも下向きにも取り付けることができます」なので、橙色に点滅する発光面が見えるように取り付けています。

参考:
Handling and Assembly Guide for SCD30

結線図

2.8インチTFT液晶 (240×320)モジュールを4線式のSPIで、BME280センサモジュール、SCD30センサモジュールをSoC内蔵の I2C(ハードウエア I2C)で接続する結線図です。

GPIOからBME280、2.8インチTFT (240×320)モジュールへの結線図

5.1V/5AのACアダプター

Raspberry Pi 5では、電源として5.1V/5AのACアダプターが求められます。対応していないACアダプターでUSBストレージから起動しようとすると警告メッセージが表示されます。
基板上のSTATUS LED(ACT LED)の横にある電源ボタンを押すとUSBポートの電流制限が解除され、USBストレージからRaspberry Pi OSを起動できます。

Raspberry Pi OSのセットアップ、I2CとSPIを有効化

Raspberry Pi 5にはRaspberry Pi Imager v1.8.5を使って、USBメモリにRaspberry Pi OS(64-bit)を書き込んでUSBブートしています。LXTerminalでOSバージョンなどを確認した結果です。

USBブートしたRaspberry Pi OS(64-bit)の情報

I2CとSPIの設定 はデフォルトでは無効です。
苺メニューの「設定」–>「Raspberry Piの設定」を開いて–>「インターフェース」でI2CとSPIを有効にチェック。

再起動するとSoC内蔵の I2C(ハードウエア I2C )とSPI通信が使えるようになります。

I2CとSPIを有効化

SCD30センサとBME280センサのデフォルトのI2Cスレーブアドレスです。

 #センサモジュール I2Cアドレス
1SCD300x61
2BME2800x76

Python3:pipでコマンドオプションを付けてライブラリをインストール

新しいラズパイ(Raspberry Pi 5、bookworm)になって、python環境が変わりました。pipからセンサや液晶モジュールなどライブラリをlocal環境にインストールしようとするとエラーとなる場合があります。

Python仮想環境(venv)を使えばよいのですが、まっさらな状態の仮想環境に必要なライブラリやソースコードを全てそろえるのは骨が折れます。

回避策の一つであるpipでインストールする際にコマンドオプションbreak-system-packagesを付けることで、ライブラリをlocal環境にインストールしています。

The installer should have a way for the user to override these rules, such as a command-line flag --break-system-packages. This option should not be enabled by default and should carry some connotation that its use is risky.

PEP 668 – Marking Python base environments as “externally managed” | peps.pyth

Pythonとpipのバージョンを、python -V、pip -Vコマンドで確認すると、
  Pythonは Python 3.11.2
  pipは、pip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11)。
組み込まれたライブラリのリストとバージョンは pip list コマンドで確認できます。

smbus2

smbus2パッケージはデフォルトでRaspberry Pi OSに組み込み済みでした。Version 0.4.2 。

2.8インチTFT液晶ライブラリ

2.8インチTFT液晶 (240×320)モジュールの液晶コントローラLSIはILI9341。Adafruitのライブラリ「Adafruit_CircuitPython_RGB_Display」をコマンドオプションを付けてインストール。

sudo pip3 install --break-system-packages adafruit-circuitpython-rgb-display

adafruit/Adafruit_CircuitPython_RGB_Display

https://github.com/adafruit/Adafruit_CircuitPython_RGB_Display

rpi-lgpioライブラリ

 Raspberry Pi 5ではRPi.GPIOやpigpioライブラリが使えなくなり、今後は gpiozeroが推奨とのこと、今回はrpi-lgpioライブラリをコマンドオプションを付けてインストール。

sudo pip3 install --break-system-packages rpi-lgpio

rpi-lgpio 0.6

rpi-lgpio 0.6

BME280ライブラリ

BME280のライブラリには「RPi.bme280 0.2.4」をコマンドオプションを付けてインストール。Python3コード上でI2Cアドレスとポート番号を指定できるで使い勝手が良いです。

sudo pip3 install --break-system-packages RPi.bme280

RPi.bme280 0.2.4

https://pypi.org/project/RPi.bme280/

SCD30ライブラリ

SCD30のライブラリには「scd30-i2c 0.0.6」をコマンドオプションを付けてインストール。利用には、Pythonバージョン >=3.7.3が必要。

sudo pip3 install --break-system-packages scd30-i2c

SCD30 CO₂ sensor I²C driver in Python 3

https://pypi.org/project/scd30-i2c/

IPAex フォント

日本語を表示するために、独立行政法人情報処理推進機構 (IPA) が提供している日本語フォント「IPAex フォント」をインストール。ipaexfont-gothic を使っています。

sudo apt install fonts-ipaexfont

Python3:BME280測定データを2.8インチTFT液晶に表示

bme280_scd30_sample_TFT_csv_pi5.pyのテスト

Raspberry Pi OSにプリインストール されているThonny Python IDEを使って動作確認しました。

ドキュメントフォルダに保存したbme280_scd30_sample_TFT_csv_pi5.pyをLXTerminalから起動すると、2.8インチTFT液晶モジュールに気圧、気温、湿度、日時(曜日)を2秒間隔で表示します。

python3 /home/pi/ドキュメント/bme280_scd30_sample_TFT_csv_pi5.py

このpython3プログラム実行時のタスクバーのCPU稼働率モニタの値は1%以下でした。

bme280_scd30_sample_TFT_csv_pi5.py
※ここをクリックするとコード表示を開閉できます。
#bme280_scd30_sample_TFT_csv_pi5.py
#coding: utf-8

#---TFT-ili9341 init-----rgb_display_pillow_stats.py---
import time
import subprocess
import digitalio
import board
from PIL import Image, ImageDraw, ImageFont
from adafruit_rgb_display import ili9341

# Configuration for CS and DC pins (these are PiTFT defaults):
cs_pin = digitalio.DigitalInOut(board.D23)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = digitalio.DigitalInOut(board.D24)

# Config for display baudrate (default max is 24mhz):
BAUDRATE = 24000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# pylint: disable=line-too-long
# Create the display:
disp = ili9341.ILI9341(
    spi,
    rotation=90,  # 2.2", 2.4", 2.8", 3.2" ILI9341
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
)
# pylint: enable=line-too-long
# Create blank image for drawing.
# Make sure to create image with mode 'RGB' for full color.
if disp.rotation % 180 == 90:
    height = disp.width  # we swap height/width to rotate it to landscape!
    width = disp.height
else:
    width = disp.width  # we swap height/width to rotate it to landscape!
    height = disp.height

image = Image.new("RGB", (width, height))

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image)

# First define some constants to allow easy positioning of text.
padding = -2
x = 0

# Load a ipaexfont-gothic font.
font1 = ImageFont.truetype("/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf", 50)
font2 = ImageFont.truetype("/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf", 20)
font3 = ImageFont.truetype("/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf", 15)


#---BME280 init---------------bme280_sample.py------------
import smbus2
import bme280

port = 1
address = 0x76
bus = smbus2.SMBus(port)

calibration_params = bme280.load_calibration_params(bus, address)

# the sample method will take a single reading and return a
# compensated_reading object
data = bme280.sample(bus, address, calibration_params)

#---SCD30 init-----------------scd30_sample.py------------
from scd30_i2c import SCD30

scd30 = SCD30()

scd30.set_measurement_interval(2)
scd30.start_periodic_measurement()


while True:
#---csv out  ------------------------------------------
    if scd30.get_data_ready():
        m = scd30.read_measurement()
        if m is not None:
          dtd = time.strftime('%Y年%m月%d日', time.localtime())
          dtt = time.strftime('%H時%M分%S秒', time.localtime())
          dta = time.strftime('%a', time.localtime())
          if dta == "Mon": 
           jdta = dta.replace("Mon", "(月)")
          elif dta == "Tue": 
           jdta = dta.replace("Tue", "(火)")
          elif dta == "Wed": 
           jdta = dta.replace("Wed", "(水)")
          elif dta == "Thu": 
           jdta = dta.replace("Thu", "(木)")
          elif dta == "Fri": 
           jdta = dta.replace("Fri", "(金)")
          elif dta == "Sat": 
           jdta = dta.replace("Sat", "(土)")
          elif dta == "Sun": 
           jdta = dta.replace("Sun", "(日)")
          else:
           jdta="ー"
           
          data = bme280.sample(bus, address, calibration_params)
          pbme280 = f"{data.pressure:.1f},{data.temperature:.1f},{data.humidity:.1f}" 
          pscd30 = f"{m[0]:.1f},{m[1]:.1f},{m[2]:.1f}"

#        P_csv = dtd + jdta + " " + dtt + "," + pscd30 + "," + pbme280
#        print(P_csv)
        
#---TFT-ili9341- Draw ---------------------------------
        # Draw a black filled box to clear the image.
        draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))

        draw.text((50, 192), dtd, font=font2, fill="#00ff00")
        draw.text((50, 212), dtt, font=font2, fill="#00ff00")
        draw.text((220, 192), jdta, font=font2, fill="#00ff00")
        
        draw.text((20, 3), "温度:", font=font2, fill="#ffffff")
        draw.text((24, 25), "( ℃ )", font=font3, fill="#ffffff")
        temp =format(float(data.temperature), '.1f')
        draw.text((90, 0), temp, font=font1, fill="#ffffff")

        draw.text((14, 50), "CO   :", font=font2, fill="#FFFF00")
        draw.text((47, 58), "2", font=font3, fill="#FFFF00")
        draw.text((14, 70), "( ppm )", font=font3, fill="#FFFF00")
        co2 =format(float(m[0]), '.1f')
        draw.text((90, 47), co2, font=font1, fill="#FFFF00")

        draw.text((20, 97), "気圧:", font=font2, fill="#ffffff")
        draw.text((18, 117), "( hpa )", font=font3, fill="#ffffff")
        pres =format(float(data.pressure), '.1f')
        draw.text((90, 93), pres, font=font1, fill="#ffffff")

        draw.text((20, 143), "湿度:", font=font2, fill="#ffffff")
        draw.text((24, 165), "( % )", font=font3, fill="#ffffff")
        humi =format(float(data.humidity), '.1f')
        draw.text((90, 140), humi, font=font1, fill="#ffffff")

        disp.image(image)
        
        time.sleep(2)

Thonny Python IDEのshell部分へのCSV表示が必要な場合は、114、115行目先頭の#を消します。
データ形式は、[日時]、SCD30センサの[CO2濃度]、[温度]、[湿度]、BME280センサの[気圧]、[温度]、[湿度]です。

測定データをCSV形式でshell部分に表示。

/etc/rc.localを編集、OS起動時にpython3プログラムを自動起動

自動起動方法は色々ありますが、「/etc/rc.local」にpython3プログラムの実行スクリプトを追加する方法が簡便です。Raspberry Pi 5に液晶モニタ、キーボード、マウスなどをつなぐことなく、ACアダプターをつないでOS起動・python3プログラムを自動実行できます。

LXTerminalからnano(エディタ)を起動して/etc/rc.localを編集。

sudo nano /etc/rc.local

今回、自動起動するpython3プログラムは「bme280_sample_TFT_csv_pi5.py」、保管先は「 /home/pi/ドキュメント」です。このpython3スクリプトを/etc/rc.localに付け加えて、上書き保存。

python3 /home/pi/ドキュメント/bme280_scd30_sample_TFT_csv_pi5.py

自動起動が不要になた時は、先頭に#をつけて上書き保存するか、行を削除します。

/etc/rc.localを編集

SCD30センサのセルフキャリブレーション

 SCD30センサは換気の良い場所で電源を入れたままで一定時間連続測定してセルフキャリブレーションします。400ppm台の数値に落ち着くと思います。

SparkFun SCD30 CO₂ センサー ライブラリ
注: SCD30 には、自動セルフキャリブレーション ルーチンがあります。Sensirion は、セルフキャリブレーションを完了するために、少なくとも 1 日 1 時間の「新鮮な空気」で 7 日間の連続測定を推奨しています。

Note: The SCD30 has an automatic self-calibration routine. Sensirion recommends 7 days of continuous readings with at least 1 hour a day of ‘fresh air’ for self-calibration to complete.

https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library

Interface Description Sensirion SCD30 Sensor Module p.13/21

1.4.6 自動セルフキャリブレーション (ASC) の (非) アクティブ化
継続的な自動セルフキャリブレーションは、次のコマンドで (非) アクティブにすることができます。 初めてアクティブ化する場合、アルゴリズムが ASC の初期パラメーター セットを見つけることができるように、最低 7 日間必要です。 センサーは、毎日少なくとも 1 時間は新鮮な空気にさらす必要があります。 また、その間、センサーを電源から切り離すことはできません。そうしないと、キャリブレーション パラメータを見つける手順が中止され、最初からやり直す必要があります。 正常に計算されたパラメータは SCD30 の不揮発性メモリに保存され、再起動後も以前に見つかった ASC のパラメータが引き続き存在するという効果があります。

1.4.6 (De-)Activate Automatic Self-Calibration (ASC)
Continuous automatic self-calibration can be (de-)activated with the following command. When activated for the first time a period of minimum 7 days is needed so that the algorithm can find its initial parameter set for ASC. The sensor has to be exposed to fresh air for at least 1 hour every day. Also during that period, the sensor may not be disconnected from the power supply, otherwise the procedure to find calibration parameters is aborted and has to be restarted from the beginning. The successfully calculated parameters are stored in non-volatile memory of the SCD30 having the effect that after a restart the previously found parameters for ASC are still present.

https://sensirion.com/media/documents/D7CEEF4A/6165372F/Sensirion_CO2_Sensors_SCD30_Interface_Description.pdf


 

 

よかったらシェアしてね!
  • URLをコピーしました!
目次