ESP32-DevKitCと2.8インチ240×320液晶モジュール(SDカード)をSPI接続、CO2濃度センサSCD30をI2C接続

ESP32-DevKitCとILI9341を搭載した2.8インチTFT液晶モジュールをSPI接続(液晶とタッチパネルはVSPI、SDカードはHSPI)、CO2濃度センサSCD30をI2C接続しました。
TFT_eSPIライブラリをインクルード(User_Setup.hの編集)して、グラフィック型の液晶に日本語をtft.drawXBitmap() で描画するスケッチ作成時のメモです。240×320の解像度があるので文字の描画も精細です。

ESP32-DevKitCと2.8インチ240×320液晶モジュール(SDカード)をSPI接続、CO2濃度センサSCD30をI2C接続
左下がI2C接続したSCD30センサ(橙色に点灯)。CO2濃度・温湿度をグラフィック型TFT液晶に表示、測定データをSDカード記録
目次

モジュールの組立て、結線図

昨年作ったCO2濃度・温湿度計のディスプレイを16文字x2桁のキャラクタ型LCDからグラフィック型のILI9341を搭載した240×320の2.8インチTFT液晶モジュールに置き換えて、TFT_eSPIライブラリ対応のスケッチ修正を行います。

あわせて読みたい
ESP32-DevKitCと二酸化炭素センサ SCD30モジュールを使ってCO2濃度をLCDに表示、ユニバーサル基板に実装... 窓を全開にして換気するとCO2濃度が500ppm台に下がるので、換気のタイミングを計るのに重宝しています。ESP32-DevKitCと各種センサで構築中の環境モニタにSensirion社の...

Sensirion(センシリオン)社のSCD30センサはI2C接続

CO2センサを選ぶにあたってTVOC(Total Volatile Organic Compounds 総揮発性有機化合物の総称)測定がメインのセンサ(VOCガス濃度からCO2濃度を概算)もあるので迷いましたが、CO2 濃度測定の精度が高いSensirion社の 非分散型赤外線(NDIR)ベースの SCD30センサモジュールを使っています。
このモジュールには基板内に同じSensirion社のSHT31センサを補正用として内蔵しているので湿度や温度も測定できますが、SCD30センサモジュールとしての動作温度は0℃~+50℃の仕様です。

SCD30センサモジュールとESP32-DevKitCとの結線は、I2Cの2線(SCL、SDA)と電源の2線(3.3V、GND)の4線なので容易です。このモジュールのデフォルトのI2Cアドレスは0x61です。

SCD30 CO2 センサ モジュール基板
SCD30 CO2 センサ モジュール基板
補正用のSHT31センサ 付近の拡大
補正用のSHT31センサ 付近の拡大

2.8インチ液晶モジュールはSPI接続、初期設定ファイル User_Setup.h の編集

2.8インチ液晶モジュールには、ドライバーICとしてILI9341を使った240×320の液晶モジュールに加えて、XPT2046を使ったタッチパネル、SDカードスロットが実装されています。利用するにあたり必要となるモジュール間の結線とライブラリの初期設定(User_Setup.hの編集)を下記ページに纏めました。

あわせて読みたい
ESP32-DevKitCと2.8インチ液晶モジュール(240×320、タッチパネル、SDカード)をSPI接続して動作確認 ILI9341を搭載した240×320の2.8インチTFT液晶モジュールをESP32-DevKitCにSPI接続して動作確認した際の作業メモです。液晶とタッチパネルはVSPI、SDカードはHSPI接続で...

結線図

ESP32-DevKitCと液晶モジュールは4線式SPIで接続(液晶とタッチパネルはVSPI、SDカードはHSPI)。TFT_eSPIライブラリをインクルード(User_Setup.hの編集)して、グラフィック型の液晶表示とタッチパネル制御を行います。SCD30センサモジュールはI2C接続です。

ESP32-DevKitCと2.8インチTFT液晶モジュール、SCD30の結線図
SP32-DevKitCと2.8インチTFT液晶モジュール、SCD30の結線図

・モジュールにピンヘッダが付属しているか、ピンヘッダのピンの太さがブレッドボードに適しているか、自分がはんだ付けする必要があるか等を確認します。
・モジュールがプルアップ抵抗を内蔵しているか、その抵抗値と有効化方法を確認します。無い場合は必要に応じで外部でプルアップ抵抗を実装します。
・基板内に電圧レギュレータやI2C電圧レベル変換回路を内蔵して5V系(Arduinoなど)と3.3V系(XIAO ESP32C3など)の両方で使えるモジュールもあります。一方で電圧レギュレータを内蔵して電源電圧は5V、3.3V両方で使えますがI2CやSPI等のロジックレベルは3.3V系といったモジュールもあるので仕様書で確認します。

スケッチの作成と動作確認

SCD30センサで測ったCO2濃度、温度、湿度データを2.8インチ液晶モジュールへの表示とファイル名「logdata.txt」でSDカードに書き込むスケッチを作りました。
SDカードはFAT32でフォーマットしておきます。

スケッチ1:CO2濃度、温度、湿度データを液晶表示、項目名はtft.drawXBitmap()で日本語表示

SCD30センサのライブラリには、SparkFunサイトの「sparkfun/SparkFun_SCD30_Arduino_Library (version 1.0.16)」を利用させていただきました。 サイトの「Code」プルダウンから「SparkFun_SCD30_Arduino_Library-main.zip」をダウンロード。Arduino IDEのメニュー「スケッチ」–>「ライブラリをインクルード」–>「zip形式のライブラリをインクルード」でダウンロードしたzipファイルを指定します。

ILI9341を搭載したグラフィック型の液晶モジュール表示のライブラリとして「TFT_eSPI.h」を利用させていただきました。GitHubサイトからTFT_eSPIライブラリのzipファイル( TFT_eSPI-master.zip )をPCにダウンロードして保存(「Code」ボタンをクリックして「Download ZIP」)。Arduino IDEのメニューの「スケッチ」→「ライブラリをインクルード」→「.ZIP形式の ライブラリを インストール」で ダウンロードしたZIPファイルを追加します。ライブラリをインクルードした後、初期設定ファイル(User_Setup.h)編集)を行います。

このスケッチで使った漢字と記号は「C、O、2、温、湿、度、(℃)」と少ないので、メモリ消費を抑えるために、利用する特定の文字のみをHEXデータで表示します。

最大1310720バイトのフラッシュメモリのうち、スケッチが287945バイト(21%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が17596バイト(5%)を使っていて、ローカル変数で310084バイト使うことができます。

記号や漢字のBMPファイルは、Windows標準アプリのペイント3Dで170×170ピクセル程度で作った後キャンパスを32×32ピクセル(「(℃)」のみ40×40ピクセル)に変更して圧縮します。このBMPファイルをHEXデータに変換してtft.drawXBitmap() で描画します。HEXデータへの変換にはProgramResource.netサイトの「nfBmptoHex.exe」を利用させていただきました。

測定データの表示スペースは、CO2濃度は4桁(5桁の場合は文字サイズを小さくして表示)、温湿度は整数部2桁、少数点以下1桁です。
SCD30センサモジュールの赤外線発光部が約2秒間隔で橙色に点滅。息を少し吹きかけただけでも即時CO2濃度が大きく値が変動します。応答性が良いです。

ESP32-DevKitCと2.8インチ240×320液晶モジュール(SDカード)をSPI接続、CO2濃度センサSCD30をI2C接続
スケッチ1を実行中

スケッチを起動すると初期化ステータスを表示した後、測定データの表示画面に遷移します。

接続に成功
接続に成功
接続に失敗
接続に失敗
esp32-devkitc_ili9341-lcd_scd30.ino
※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <TFT_eSPI.h>   // https://github.com/Bodmer/TFT_eSPI
#include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30

TFT_eSPI tft = TFT_eSPI();
SPIClass spiSD(HSPI);
SCD30 airSensor;

const unsigned char ccc_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0x00, 0xF0, 0xFF, 0x07, 0x00, 0xFC, 0xFF, 0x07, 0x00, 0x7E, 0x00, 0x07, 0x00, 0x1F, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xF8, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, };
const unsigned char ooo_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0x01, 0xC0, 0x0F, 0xF0, 0x03, 0xE0, 0x03, 0xC0, 0x07, 0xE0, 0x01, 0x80, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x00, 0x38, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x1C, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x01, 0x80, 0x0F, 0xE0, 0x03, 0xC0, 0x07, 0xC0, 0x0F, 0xF0, 0x03, 0x80, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xC0, 0x03, 0x00, };
const unsigned char sub_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0x70, 0x78, 0x00, 0x00, 0x30, 0x70, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x01, 0x00, 0xF8, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char onn_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0xF8, 0xFF, 0x07, 0x70, 0xF8, 0xFF, 0x07, 0xE0, 0x18, 0x00, 0x06, 0xC0, 0x18, 0x00, 0x06, 0x00, 0x18, 0x00, 0x06, 0x00, 0xF8, 0xFF, 0x07, 0x00, 0xF8, 0xFF, 0x07, 0x02, 0x18, 0x00, 0x06, 0x07, 0x18, 0x00, 0x06, 0x1E, 0x18, 0x00, 0x06, 0x38, 0x38, 0x00, 0x06, 0x70, 0xF8, 0xFF, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x8C, 0xE3, 0x18, 0x60, 0x0C, 0x63, 0x18, 0x60, 0x0C, 0x63, 0x18, 0x70, 0x0C, 0x63, 0x18, 0x30, 0x0C, 0x63, 0x18, 0x30, 0x0C, 0x63, 0x18, 0x18, 0x0C, 0x63, 0x18, 0x18, 0x0C, 0x63, 0x18, 0x1C, 0x0C, 0x63, 0x18, 0x8C, 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char doo_bmp[] PROGMEM = {0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0xF0, 0xFF, 0xFF, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0xB0, 0xFF, 0xFF, 0x7F, 0x30, 0xE7, 0xE0, 0x70, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0xE0, 0xFF, 0x00, 0x30, 0xE0, 0xFF, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0x03, 0x38, 0xFF, 0xFF, 0x07, 0x18, 0x30, 0x00, 0x03, 0x18, 0x60, 0x80, 0x01, 0x18, 0xE0, 0xC0, 0x01, 0x18, 0xC0, 0xE1, 0x00, 0x1C, 0x80, 0x7B, 0x00, 0x0C, 0x00, 0x1F, 0x00, 0x0E, 0x00, 0x3F, 0x00, 0x0E, 0xE0, 0xFB, 0x01, 0x06, 0xFE, 0xC0, 0x3F, 0xC6, 0x1F, 0x00, 0xFE, 0xC0, 0x01, 0x00, 0x60, };
const unsigned char deg_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0x04, 0x10, 0x3C, 0xC0, 0x07, 0x08, 0x18, 0x24, 0xF0, 0x1F, 0x10, 0x0C, 0x42, 0x38, 0x18, 0x30, 0x04, 0x42, 0x18, 0x30, 0x20, 0x06, 0x24, 0x0C, 0x70, 0x60, 0x06, 0x18, 0x0C, 0x00, 0x40, 0x02, 0x00, 0x0C, 0x00, 0x40, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x06, 0x00, 0xC0, 0x02, 0x00, 0x06, 0x00, 0xC0, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x0C, 0x00, 0x40, 0x06, 0x00, 0x0C, 0x60, 0x40, 0x04, 0x00, 0x1C, 0x30, 0x60, 0x0C, 0x00, 0x38, 0x38, 0x20, 0x08, 0x00, 0xF0, 0x1F, 0x30, 0x18, 0x00, 0xC0, 0x0F, 0x18, 0x30, 0x00, 0x00, 0x00, 0x08, 0x60, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char sit_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x38, 0xF8, 0xFF, 0x1F, 0x70, 0xF8, 0xFF, 0x1F, 0xE0, 0x18, 0x00, 0x18, 0xC0, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0xF8, 0xFF, 0x1F, 0x00, 0xF8, 0xFF, 0x1F, 0x06, 0x18, 0x00, 0x18, 0x1E, 0x18, 0x00, 0x18, 0x3C, 0x18, 0x00, 0x18, 0x70, 0x18, 0x00, 0x18, 0x20, 0xF8, 0xFF, 0x1F, 0x00, 0xF8, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x00, 0x0C, 0x63, 0x30, 0x60, 0x0C, 0x43, 0x30, 0x60, 0x18, 0x43, 0x18, 0x70, 0x18, 0x43, 0x18, 0x30, 0x30, 0x43, 0x0C, 0x30, 0x30, 0x43, 0x0C, 0x38, 0x30, 0x43, 0x06, 0x18, 0x00, 0x63, 0x00, 0x1C, 0x00, 0x63, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

void setup(){
  // TFT液晶初期化
  tft.init();
  tft.setRotation(1);
  tft.setTextSize(1);
  tft.fillScreen(TFT_BLACK);
// SCD30初期化
  Wire.begin();
  if (airSensor.begin() == false)  {
      tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.drawString("SCD30 not detected", 0, 20, 4);
    while (1);
  }
  //The SCD30 has data ready every two seconds
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("SCD30 detected", 0, 20, 4);
delay(2000);
  // 画面クリア
  tft.fillScreen(TFT_BLACK);
}

void loop(){
  if (airSensor.dataAvailable())  {
// ----- SCD30センサが稼働している時の処理 -----
tft.setTextColor(TFT_WHITE, TFT_BLACK);
// ----- 項目名を日本語で表示 -----
   // CO2濃度(ppm)
   tft.drawXBitmap(5, 10, ccc_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(40, 10, ooo_bmp, 32, 32, 0xFFFF); 
   tft.drawXBitmap(74, 18, sub_bmp, 32, 32, 0xFFFF); 
   tft.drawString("(ppm)", 8, 45, 4);
   // 温度(℃)
   tft.drawXBitmap(10, 92, onn_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 92, doo_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(24, 110, deg_bmp, 40, 40, 0xFFFF);
   // 湿度(%)
   tft.drawXBitmap(10, 170, sit_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 170, doo_bmp, 32, 32, 0xFFFF);
   tft.drawString("(%)", 24, 205, 4);
// ----- 液晶画面に測定値を表示 -----
   tft.setTextColor(TFT_YELLOW, TFT_BLACK);
   // CO2濃度
   int co2_tmp=airSensor.getCO2();
 if (co2_tmp <= 999 ) {
   tft.fillRect(265, 0, 55, 76, TFT_BLACK);  // 4桁から3桁に変わった時は4桁目の残像を消去
 }
 if (co2_tmp > 9999 ) {
   tft.fillRect(100, 0, 219, 76, TFT_BLACK); // 5桁表示領域の消去
   tft.drawFloat(co2_tmp, 0, 100, 20, 7);    // 5桁の場合はフォントサイズを変更
 } else {
   tft.drawFloat(co2_tmp, 0, 100, 0, 8);     // 通常は4桁表示
 }
    // 温度
    float prs_tmp = airSensor.getTemperature();
   tft.drawFloat(prs_tmp, 1, 100, 80, 8);
    // 湿度
    float hum_tmp = airSensor.getHumidity();
   tft.drawFloat(hum_tmp, 1, 100, 160, 8);
  }
  else
// ----- SCD30センサが稼働していない時の処理 -----
  delay(500);
}

スケッチ2:SCD30データ(CO2濃度、温度、湿度)のSDカードへの書き込み

2.8インチ液晶モジュールへの表示に加えて、ファイル名「logdata.txt」でSDカードに書き込むスケッチ2です。SDカードに約2秒毎に4つのデータをカンマセパレータ形式で記録します。測定(記録)間隔は、スケッチ126行の「delay(2000);」で調整します。
・Arduino IDE実行後の(1)経過時間(millis()を秒変換、カウントアップ)
・SCD30センサ の(2)CO2濃度、(3)温度、(4)湿度

CO2濃度は整数、温度・湿度は少数点以下2桁です。

SCD30センサに近づいて息を吹きかけたので60秒あたりからCO2濃度が急上昇しています。

logdata.txtファイル
logdata.txtファイル

millis()は「Arduino ボードが現在のプログラムを実行し始めてから経過したミリ秒数を返します。この数は、約 50 日後にオーバーフロー (ゼロに戻る) になります」とArduino Referenceに記載があります。
長期間の測定には、I2C接続のリアルタイムクロック(RTC)を設けて取得時刻を記録Tera Termのログに時刻(タイムスタンプ)を記録といった手段もあります。

esp32-devkitc_ili9341-lcd_scd30_SD.ino
※ここをクリックするとコード表示を開閉できます。
#include <SD.h>
#include <Wire.h>
#include <TFT_eSPI.h>   // https://github.com/Bodmer/TFT_eSPI
#include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30

TFT_eSPI tft = TFT_eSPI();
SPIClass spiSD(HSPI);
SCD30 airSensor;

const unsigned char ccc_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0x00, 0xF0, 0xFF, 0x07, 0x00, 0xFC, 0xFF, 0x07, 0x00, 0x7E, 0x00, 0x07, 0x00, 0x1F, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xF8, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, };
const unsigned char ooo_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0x01, 0xC0, 0x0F, 0xF0, 0x03, 0xE0, 0x03, 0xC0, 0x07, 0xE0, 0x01, 0x80, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x00, 0x38, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x1C, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x01, 0x80, 0x0F, 0xE0, 0x03, 0xC0, 0x07, 0xC0, 0x0F, 0xF0, 0x03, 0x80, 0xFF, 0xFF, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xC0, 0x03, 0x00, };
const unsigned char sub_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0x70, 0x78, 0x00, 0x00, 0x30, 0x70, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x01, 0x00, 0xF8, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char onn_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0xF8, 0xFF, 0x07, 0x70, 0xF8, 0xFF, 0x07, 0xE0, 0x18, 0x00, 0x06, 0xC0, 0x18, 0x00, 0x06, 0x00, 0x18, 0x00, 0x06, 0x00, 0xF8, 0xFF, 0x07, 0x00, 0xF8, 0xFF, 0x07, 0x02, 0x18, 0x00, 0x06, 0x07, 0x18, 0x00, 0x06, 0x1E, 0x18, 0x00, 0x06, 0x38, 0x38, 0x00, 0x06, 0x70, 0xF8, 0xFF, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x8C, 0xE3, 0x18, 0x60, 0x0C, 0x63, 0x18, 0x60, 0x0C, 0x63, 0x18, 0x70, 0x0C, 0x63, 0x18, 0x30, 0x0C, 0x63, 0x18, 0x30, 0x0C, 0x63, 0x18, 0x18, 0x0C, 0x63, 0x18, 0x18, 0x0C, 0x63, 0x18, 0x1C, 0x0C, 0x63, 0x18, 0x8C, 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char doo_bmp[] PROGMEM = {0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0xF0, 0xFF, 0xFF, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0xB0, 0xFF, 0xFF, 0x7F, 0x30, 0xE7, 0xE0, 0x70, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0xE0, 0xFF, 0x00, 0x30, 0xE0, 0xFF, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0x03, 0x38, 0xFF, 0xFF, 0x07, 0x18, 0x30, 0x00, 0x03, 0x18, 0x60, 0x80, 0x01, 0x18, 0xE0, 0xC0, 0x01, 0x18, 0xC0, 0xE1, 0x00, 0x1C, 0x80, 0x7B, 0x00, 0x0C, 0x00, 0x1F, 0x00, 0x0E, 0x00, 0x3F, 0x00, 0x0E, 0xE0, 0xFB, 0x01, 0x06, 0xFE, 0xC0, 0x3F, 0xC6, 0x1F, 0x00, 0xFE, 0xC0, 0x01, 0x00, 0x60, };
const unsigned char deg_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0x04, 0x10, 0x3C, 0xC0, 0x07, 0x08, 0x18, 0x24, 0xF0, 0x1F, 0x10, 0x0C, 0x42, 0x38, 0x18, 0x30, 0x04, 0x42, 0x18, 0x30, 0x20, 0x06, 0x24, 0x0C, 0x70, 0x60, 0x06, 0x18, 0x0C, 0x00, 0x40, 0x02, 0x00, 0x0C, 0x00, 0x40, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x06, 0x00, 0xC0, 0x02, 0x00, 0x06, 0x00, 0xC0, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x0C, 0x00, 0x40, 0x06, 0x00, 0x0C, 0x60, 0x40, 0x04, 0x00, 0x1C, 0x30, 0x60, 0x0C, 0x00, 0x38, 0x38, 0x20, 0x08, 0x00, 0xF0, 0x1F, 0x30, 0x18, 0x00, 0xC0, 0x0F, 0x18, 0x30, 0x00, 0x00, 0x00, 0x08, 0x60, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char sit_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x38, 0xF8, 0xFF, 0x1F, 0x70, 0xF8, 0xFF, 0x1F, 0xE0, 0x18, 0x00, 0x18, 0xC0, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0xF8, 0xFF, 0x1F, 0x00, 0xF8, 0xFF, 0x1F, 0x06, 0x18, 0x00, 0x18, 0x1E, 0x18, 0x00, 0x18, 0x3C, 0x18, 0x00, 0x18, 0x70, 0x18, 0x00, 0x18, 0x20, 0xF8, 0xFF, 0x1F, 0x00, 0xF8, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x00, 0x0C, 0x63, 0x30, 0x60, 0x0C, 0x43, 0x30, 0x60, 0x18, 0x43, 0x18, 0x70, 0x18, 0x43, 0x18, 0x30, 0x30, 0x43, 0x0C, 0x30, 0x30, 0x43, 0x0C, 0x38, 0x30, 0x43, 0x06, 0x18, 0x00, 0x63, 0x00, 0x1C, 0x00, 0x63, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

void setup(){
// TFT液晶初期化
  tft.init();
  tft.setRotation(1);
  tft.setTextSize(1);
  tft.fillScreen(TFT_BLACK);
// SCD30初期化
  Wire.begin();
  if (airSensor.begin() == false)  {
      tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.drawString("SCD30 not detected", 0, 20, 4);
    while (1);
  }
  //The SCD30 has data ready every two seconds
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("SCD30 detected", 0, 20, 4);
delay(2000);
// SDカード初期化
spiSD.begin(14, 33, 13, 15); //SCK,MISO,MOSI,CS
// SDカードマウント確認
if (!SD.begin(15, spiSD)) {
  tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.drawString("Card Mount Failed", 0, 50, 4);
return;
}
else  {
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("Card Mount Successful", 0, 50, 4);
}
delay(2000);
// SDカードファイル書き込み
File dataFile = SD.open("/datalog.txt", FILE_WRITE);
dataFile.println("File written");
dataFile.close();
  tft.drawString("File written", 0, 80, 4);
delay(2000);
  // 画面クリア
  tft.fillScreen(TFT_BLACK);
}

void loop(){
  if (airSensor.dataAvailable())  {
// ----- SCD30センサが稼働している時の処理 -----
   tft.setTextColor(TFT_WHITE, TFT_BLACK);
// ----- 項目名を日本語で表示 -----
   // CO2濃度(ppm)
   tft.drawXBitmap(5, 10, ccc_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(40, 10, ooo_bmp, 32, 32, 0xFFFF); 
   tft.drawXBitmap(74, 18, sub_bmp, 32, 32, 0xFFFF); 
   tft.drawString("(ppm)", 8, 45, 4);
   // 温度(℃)
   tft.drawXBitmap(10, 92, onn_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 92, doo_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(24, 110, deg_bmp, 40, 40, 0xFFFF);
   // 湿度(%)
   tft.drawXBitmap(10, 170, sit_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 170, doo_bmp, 32, 32, 0xFFFF);
   tft.drawString("(%)", 24, 205, 4);
// ----- 液晶画面に測定値を表示 -----
   tft.setTextColor(TFT_YELLOW, TFT_BLACK);
   // CO2濃度
   int co2_tmp=airSensor.getCO2();
 if (co2_tmp <= 999 ) {
   tft.fillRect(265, 0, 55, 76, TFT_BLACK);  // 4桁から3桁に変わった時は4桁目の残像を消去
 }
 if (co2_tmp > 9999 ) {
   tft.fillRect(100, 0, 219, 76, TFT_BLACK); // 5桁表示領域の消去
   tft.drawFloat(co2_tmp, 0, 100, 20, 7);    // 5桁の場合はフォントサイズを変更
 } else {
   tft.drawFloat(co2_tmp, 0, 100, 0, 8);     // 通常は4桁表示
 }
    // 温度
    float prs_tmp = airSensor.getTemperature();
   tft.drawFloat(prs_tmp, 1, 100, 80, 8);
    // 湿度
    float hum_tmp = airSensor.getHumidity();
   tft.drawFloat(hum_tmp, 1, 100, 160, 8);
// ----- SDカードへの書き込み用データファイルの生成 ----- 
// 経過時間millis()を秒に変換
    String dataString = "";
    dataString += String(millis() / 1000); 
// 測定データ1:CO2濃度
    dataString += ",";  // カンマセパレータ
    if(!isnan(co2_tmp)){
      dataString += String(co2_tmp);
    }else{
      dataString += " ";
    }
// 測定データ2:温度
    dataString += ",";  // カンマセパレータ
    if(!isnan(prs_tmp)){
      dataString += String(prs_tmp);
    }else{
      dataString += " ";
    }
// 測定データ3:湿度
    dataString += ",";  // カンマセパレータ
    if(!isnan(hum_tmp)){
      dataString += String(hum_tmp);
    }else{
      dataString += " ";
    }
// ----- SDカードのdatalog.txtにdataStringを追加 ----- 
File dataFile = SD.open("/datalog.txt", FILE_APPEND);
dataFile.println(dataString);
dataFile.close();
}
// 測定間隔を調整
delay(2000);
}

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をコピーしました!
目次