ESP32-DevKitCとILI9341を搭載した2.8インチTFT液晶モジュールをSPI接続(液晶とタッチパネルはVSPI、SDカードはHSPI)、CO2濃度センサSCD30をI2C接続しました。
TFT_eSPIライブラリをインクルード(User_Setup.hの編集)して、グラフィック型の液晶に日本語をtft.drawXBitmap() で描画するスケッチ作成時のメモです。240×320の解像度があるので文字の描画も精細です。
モジュールの組立て、結線図
昨年作ったCO2濃度・温湿度計のディスプレイを16文字x2桁のキャラクタ型LCDからグラフィック型のILI9341を搭載した240×320の2.8インチTFT液晶モジュールに置き換えて、TFT_eSPIライブラリ対応のスケッチ修正を行います。
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です。
2.8インチ液晶モジュールはSPI接続、初期設定ファイル User_Setup.h の編集
2.8インチ液晶モジュールには、ドライバーICとしてILI9341を使った240×320の液晶モジュールに加えて、XPT2046を使ったタッチパネル、SDカードスロットが実装されています。利用するにあたり必要となるモジュール間の結線とライブラリの初期設定(User_Setup.hの編集)を下記ページに纏めました。
結線図
ESP32-DevKitCと液晶モジュールは4線式SPIで接続(液晶とタッチパネルはVSPI、SDカードはHSPI)。TFT_eSPIライブラリをインクルード(User_Setup.hの編集)して、グラフィック型の液晶表示とタッチパネル制御を行います。SCD30センサモジュールはI2C接続です。
スケッチの作成と動作確認
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_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濃度が急上昇しています。
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)
https://sensirion.com/media/documents/D7CEEF4A/6165372F/Sensirion_CO2_Sensors_SCD30_Interface_Description.pdf
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.