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

ILI9341を搭載した240×320の2.8インチTFT液晶モジュールをSeeed Studio XIAO ESP32C3(以下、XIAO ESP32C3)とSPI接続して動作確認した際の作業メモです。

XIAO ESP32C3とI2C接続したSensirion社のCO2センサモジュールSCD30、BME280、WiFi接続で情報通信研究機構(NICT)のNTPサーバと時刻合わせして、2.8インチTFT液晶モジュールに日時、CO2濃度、気圧、気温、湿度を表示します。

本記事の動作不具合について
esp32 by Espressif Systemsのバージョン3.0.3 では、本サイトの手順とプログラムでは「Seeed Studio XIAO ESP32C3」と「ILI9341を搭載した240×320の2.8インチTFT液晶モジュール」の場合、Leaving… Hard resetting via RTS pin…の後も白画面のままでした。
本記事の執筆当時のesp32 by Espressif Systemsバージョン2.0.5 にすると正常動作を確認できました(Arduino IDEのバージョンは2.3.2)。
当面本記事については esp32 by Espressif Systemsバージョン2.0.5 でお試しください。

Seeed StudioのWiki よくあるご質問(FAQ):
デバイスが認識されない問題やアップロードに失敗する問題の解決

What to do when upload fails/the program runs abnormally/the device port is not found? | Seeed Studio Wiki
CO2濃度、気圧・温湿度&WiFi時計(1)
目次

準備したパーツ

ネット通販(秋月電子通商マルツオンライン など)でパーツを集めてブレッドボードで組み立てました。RTC(DS3231など)は使っていないので、起動時と1日1回NTPサーバに接続して時刻合わせを実行します。

 #  パーツ  個数  備考
1Seeed Studio XIAO ESP32C31【M-17454】
2ILI9431搭載2.8インチ
SPI制御タッチパネル付TFT液晶MSP2807
1【M-16265】
3BME280 気圧、温湿度センサ モジュール1手持ち
4CO2センサモジュールSCD30
(二酸化炭素+温度/湿度センサ)
1SCD30
5プルアップ抵抗適量※必要に応じて
6ブレッドボード 6穴版 EIC-39011【P-12366】
7細ピンヘッダ 1×40(黒)1【C-06631】
8ジャンパーワイヤ
オス-オス 10cmセット
適量【C-05371】

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

結線図

XIAO ESP32C3は、Espressif ESP32-C3 WiFi / Bluetoothデュアルモードチップをベースにした超小型のマイコンボードです。小型モジュールのためピン数が限られますが独立したI2C、SPIピンがあるので、これらの接続インターフェースを持ったセンサモジュールや液晶モジュールを利用できます。

SCD30センサモジュール

Sensirion社の 非分散型赤外線(NDIR)方式の SCD30センサモジュールを利用しました。測定範囲は400~10000ppmです。

このモジュールには基板内に同じSensirion社のSHT31湿度・温度センサを補正用として内蔵しています。

参考: https://sensirion.com/jp/products/product-catalog/SCD30/

SCD30 CO2 センサ モジュール基板
SCD30センサ モジュール基板。補正用SHT31センサは基板左上の切り込み部分

XIAO ESP32C3と2.8インチ液晶モジュールを4線式SPIで接続

2.8インチ液晶モジュールにはILI9341を使った240×320の液晶モジュールに加えて、XPT2046を使ったタッチパネル、SDカードスロットが実装されています。今回は表示用として240×320の液晶モジュールのみを利用しています。

電源電圧は3.3Vで使用するので、秋月電子通商サイトを参考に、J1のランドパターンをはんだ付けして三端子レギュレータ(U1)をバイパスしています。

基板裏面のJ1のランドパターン
基板裏面のJ1のランドパターン(黄色の点線枠)

XIAO ESP32C3と2.8インチ液晶モジュールはSPIで接続、この液晶モジュールのロジックレベルは3.3Vなので、同じ3.3V系のXIAO ESP32C3と直結できます。

J2コネクタ(タッチパネルを除く液晶への接続箇所)とXIAO ESP32C3のGPIOへのピンアサインです。

# 2.8インチTFT液晶の
シルク印刷
XIAO ESP32C3に
接続したGPIO番号
1VCC  ※共通、3.3V3V3
2GND  ※共通GND
3CSGPIO2
4RESETGPIO3
5DCGPIO4
6SDI(MOSI)GPIO10
7SCKGPIO8
8LED ※バックライト3V3
9SDO(MISO)GPIO9

結線図

動作確認できた現在の結線図です。通信線には短めの長さ10cmのジャンパーワイヤを使っています。

SCD30(最大:75mA、平均:19mA)と2.8インチTFT液晶(約90mA)は供給電流が比較的大きいモジュールです。ブレッドボードとジャンパーワイヤの接触が悪いと安定動作しない時がありました。

XIAO ESP32C3サイトの power-pins には「3V3 – This is the regulated output from the onboard regulator. You can draw 700mA」の記載があったので外部電源モジュールは使っていません。数日試しましたが、XIAO ESP32C3のUSB Type-Cコネクタからの給電で動作に支障は無いようです。

下記の回路図では、XIAO ESP32C3のピン番号はGPIO番号を記載しています。詳細なピン配置は「Getting Started with Seeed Studio XIAO ESP32C3(Pinout diagram、power-pins)」を参照ください。

動作確認できた現在の結線図
結線図

参考:
Getting Started with Seeed Studio XIAO ESP32C3(Pinout diagram、power-pins)
ILI9341搭載2.8インチSPI制御タッチパネル付TFT液晶(akizukidenshi.com)
2.8inch SPI Module ILI9341 SKU-MSP2807 – LCD wiki
  + Product Documentation
   + 2.8 inch SPI Module Schematic(回路図)

開発ツール arduino-esp32のインストール

ESP32の開発ツール arduino-esp32を準備してスケッチを作っていきます。XIAO ESP32C3サイトにarduino-esp32のインストール手順の記載があります。

Getting Started with Seeed Studio XIAO ESP32C3
+ Software setup

https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/#software-setup

当サイトでもESP32-DevKitCを例として、arduino-esp32 をArduino IDE 2.0.x版にインストールする手順メモを纏めました。

あわせて読みたい
ESP32-DevKitCの開発ツール arduino-esp32 のインストール手順 Arduino IDE上で動作する ESP32-DevKitC の開発ツール arduino-esp32(Arduino core for the ESP32)をインストールした際の手順メモです。 Arduino IDE 2.0.0で ESP32-...

ボード(XIAO_ESP32C3を選択)とポート(当サイトのPCではCOM6)を切り替えることでXIAO ESP32C3でもそのまま使えます。

Arduino IDEでボードとポートの選択
ボード:XIAO_ESP32C3を選択

本記事の動作不具合について
esp32 by Espressif Systemsのバージョン3.0.3 では、本サイトの手順とプログラムでは「Seeed Studio XIAO ESP32C3」と「ILI9341を搭載した240×320の2.8インチTFT液晶モジュール」の場合、Leaving… Hard resetting via RTS pin…の後も白画面のままでした。
本記事の執筆当時のesp32 by Espressif Systemsバージョン2.0.5 にすると正常動作を確認できました(Arduino IDEのバージョンは2.3.2)。
当面本記事については esp32 by Espressif Systemsバージョン2.0.5 でお試しください。

バージョン2.0.5へのダウングレードはバージョン3.0.3を削除(アンインストール)し、プルダウンから2.0.5を選んでインストール下さい。その後、Arduino IDEを再起動して下さい。

書き込み中にシリアルエラーが出た際は、Seeed Studio XIAO ESP32C3基板上のBボタンを押しながら電源を入れることで回避できることもあります。

Seeed StudioのWiki よくあるご質問(FAQ):
デバイスが認識されない問題やアップロードに失敗する問題の解決

What to do when upload fails/the program runs abnormally/the device port is not found? | Seeed Studio Wiki

また、書き込み後にLeaving… Hard resetting via RTS pin…の後も白画面のままになった場合、電源を入れ直すことで正常に液晶表示されることもありました。。

ライブラリのインクルード

先達の方々が開発されたライブラリをインクルードすることでスケッチ作成が容易になります。

TFT_eSPIライブラリのインクルード、User_Setup.hの編集

2.8インチ液晶モジュールを利用するにあたりTFT_eSPIライブラリのインクルードと初期設定と動作確認を行います。

TFT_eSPIライブラリのインクルード

GitHubサイトからTFT_eSPIライブラリのzipファイル( TFT_eSPI-master.zip )をPCにダウンロードして保存(「Code」ボタンをクリックして「Download ZIP」)。Arduino IDEのメニューの「スケッチ」→「ライブラリをインクルード」→「.ZIP形式の ライブラリを インストール」で ダウンロードしたZIPファイルを追加します。

Bodmer/TFT_eSPI

https://github.com/Bodmer/TFT_eSPI

User_Setup.hのGPIO番号を結線した環境に合わせて設定変更

Arduino IDEのメニューからTFT_eSPIライブラリのzipファイルをインクルードすると「User_Setup.h」はWindowsでは下記フォルダ配下にありました。

C:\Users\[ユーザー名]\Documents\Arduino\libraries\TFT_eSPI-master
または、ライブラリのアップデートを行った際などに下記のフォルダ名になっている場合もあります。
C:\Users\[ユーザー名]\Documents\Arduino\libraries\TFT_eSPI

「User_Setup.h」の200行目付近の「// For ESP32 Dev board・・・」の下にある#defineを今回の環境用にピン番号を変更します。
左側がデフォルトのUser_Setup.h 設定、右側の黄色点線枠が環境に合わせてGPIO番号を変更した箇所です。左端の // を削除して上書き保存します。

User_Setup.h の編集
左側は初期のUser_Setup.h 右側は今回結線したXIAO_ESP32C3環境向けのUser_Setup.h(200行目付近)

・ESP32-DevKitC と XIAO_ESP32C3 の2つの環境でArduino IDEを使う場合は、User_Setup.hファイルを別々に分けて保存しておき、利用時に入れ替えます。
・ライブラリのアップデートを実施した際にUser_Setup.hファイルが初期化される事象があったのでUser_Setup.hファイルは別途バックアップしています。

TFT_eSPIライブラリ付属のスケッチ例で動作確認

TFT_eSPIライブラリに付属しているスケッチ例を使って配線やUser_Setup.h設定に誤りがないか動作確認します。

Arduino IDEの上段メニューから、ファイル —> スケッチ例 —>「カスタムライブラリ」の TFT_eSPI —> 320×240 —> Free_Font_Demo を開きます。

このスケッチでは大小のフォントをデモ表示します。

Free_Font_Demo.inoで動作確認
Free_Font_Demoで動作確認

SCD30センサ

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

BME280センサ

BME280センサから測定データを取得するライブラリとして adafruit/Adafruit_BME280_Library と adafruit/Adafruit_Sensor を利用させていただきました。

Adafruit_I2CDevice.hが無いとのエラーメッセージがでた場合はadafruit/Adafruit_BusIO(Adafruit_BusIO-master.zip)をインクルードします。

TimeLib.h

Time.h、TimeLib.hライブラリは PaulStoffregen/Time を利用させていただきました。サイトの「Code」プルダウンから「Download ZIP」でPCに「Time-master.zip」をダウンロード。Arduino IDEのメニュー「スケッチ」–>「ライブラリをインクルード」–>「zip形式のライブラリをインクルード」で「Time-master.zip」を指定します。

esp_sntp.h

esp_sntp.h」は arduino-esp32同梱モジュールです。詳細は Official develooment framework for ESP32 (ESP-IDF) に記述があります。mulong.meサイトを参考に内蔵時計の時刻が NTPサーバから取得した時刻に一致しているかの判定に使っています。

espressif/esp-idf

https://github.com/espressif/esp-idf/blob/4a011f3/components/lwip/include/apps/esp_sntp.h

スケッチ:2.8インチTFT液晶に日時、CO2濃度、気圧、気温、湿度を表示、WiFi接続してJSTに時刻合わせ(2022/12/30 修正)

電源起動(スケッチ実行)時にWiFi接続して、情報通信研究機構(NICT)のNTPサーバに接続してXIAO ESP32C3の内蔵時計をJSTに時刻合わせします。加えて、1日1回(下記スケッチでは12時30分0秒)、NTPサーバに接続して時刻合わせを実行して精度を維持します。

時刻合わせ実行時には、WiFI接続、NTPサーバ接続、JST同期の進捗状況を表示しています。240×320の解像度があるので文字の描画も精細です。

スケッチ修正
2022/12/30 :旧コード12行目に記載していた未使用の「SPIClass spiSD(HSPI);」を削除しました。

xiao-esp32c3_2.8TFT-LCD_SCD30_BME280.ino
※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <SPI.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <TimeLib.h>    // https://github.com/PaulStoffregen/Time
#include <TFT_eSPI.h>   // https://github.com/Bodmer/TFT_eSPI
#include "SparkFun_SCD30_Arduino_Library.h"  // https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
#include <Adafruit_Sensor.h>   // https://github.com/adafruit/Adafruit_Sensor
#include <Adafruit_BME280.h>   // https://github.com/adafruit/Adafruit_BME280_Library

TFT_eSPI tft = TFT_eSPI();

SCD30 airSensor;
float co2_tmp;

Adafruit_BME280 bme;
bool status;
float pressure;
float temp;
float humid;

const char* ssid      = "your ssid";
const char* password  = "your password";
const char* ntpServer = "ntp.nict.jp";
const long  gmtOffset_sec = 32400;
const int   daylightOffset_sec = 0;

const char* weekStr[7] = {"(Sun)","(Mon)","(Tue)","(Wed)","(Thu)","(Fri)","(Sat)"};
char d_mes[12] ;
char t_mes[12] ;
struct tm *tm;
int d_year ;
int d_mon ;
int d_mday ;
int d_hour ;
int d_min ;
int d_sec ;
int d_wday ;

const unsigned char kii_bmp[] PROGMEM = {0x80, 0x01, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0x1F, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x8E, 0xFF, 0xFF, 0x03, 0x86, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x01, 0xFC, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x83, 0x01, 0xE0, 0xC0, 0x81, 0x01, 0xC0, 0xC3, 0x80, 0x01, 0x00, 0x67, 0x80, 0x01, 0x00, 0x7E, 0x80, 0x01, 0x00, 0x3C, 0x80, 0x01, 0x00, 0x7C, 0x80, 0x03, 0x00, 0xEF, 0x00, 0x43, 0x80, 0xC3, 0x01, 0x63, 0xE0, 0x81, 0x03, 0x63, 0xF8, 0x00, 0x07, 0x66, 0x3E, 0x00, 0x06, 0x7E, 0x0E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x18, };
const unsigned char atu_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x7F, 0xF0, 0xFF, 0xFF, 0x7F, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0xFE, 0xFF, 0x1F, 0x30, 0xFE, 0xFF, 0x1F, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x38, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0xCE, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char ccc_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, 0xC0, 0xFF, 0x00, 0x00, 0xE0, 0xFF, 0x00, 0x00, 0xF8, 0xC0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x78, 0xC0, 0x00, 0x00, 0xF0, 0xFF, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char ooo_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, 0xF0, 0x1F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x80, 0x07, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x01, 0x00, 0x03, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x03, 0x80, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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(void) {
// TFT液晶初期化
  tft.init();
  tft.setRotation(3);
  tft.setTextSize(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("Initializing TFT library", 0, 20, 4);
delay(1000);
//---------内蔵時計のJST同期(起動時)--------
  wifisyncjst();
// BME280初期化
status = bme.begin(0x76);
while (!status) {
  tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.drawString("BME280 connection failed", 0, 140, 4);
  }
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("BME280 connected", 0, 140, 4);
delay(1000);
// SCD30初期化
  Wire.begin();
  if (airSensor.begin() == false) {
      tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.drawString("SCD30 not detected", 0, 170, 4);
    while (1);
  }
//The SCD30 has data ready every two seconds
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("SCD30 detected", 0, 170, 4);
delay(2000);
// 画面クリア
   tft.fillScreen(TFT_BLACK);
   tft.setTextColor(TFT_WHITE, TFT_BLACK);
// ----- 項目名を日本語で表示 ----- 
// 気圧(hPa)
   tft.drawXBitmap(10, 0, kii_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 0, atu_bmp, 32, 32, 0xFFFF);
   tft.drawString("(hPa)", 250, 7, 4);
// CO2濃度(ppm)
   tft.drawXBitmap(5, 47, ccc_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(30, 47, ooo_bmp, 32, 32, 0xFFFF); 
   tft.drawString("2", 62, 60, 4);
   tft.drawString("(ppm)", 250, 53, 4);
// 温度(℃)
   tft.drawXBitmap(10, 95, onn_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 95, doo_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(250, 85, deg_bmp, 40, 40, 0xFFFF);
// 湿度(%)
   tft.drawXBitmap(10, 142, sit_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 142, doo_bmp, 32, 32, 0xFFFF);
   tft.drawString("(%)", 250, 146, 4);
// ----- 測定周期を10秒間隔にしたのでTFT画面への初期表示用 -----
// ----- SCD30センサが稼働している時の処理 -----
   if (airSensor.dataAvailable()) {
// ----- SCD30センサからデータ取得、測定値をTFT表示 ----- 
co2_tmp=airSensor.getCO2();
   tft.setTextColor(TFT_WHITE, TFT_BLACK);
   tft.fillRect(105, 47, 140, 45, TFT_BLACK);  // 残像消去
   tft.drawFloat(co2_tmp, 0, 105, 47, 6);
// ----- BME280センサからデータ取得、測定値をTFT表示 ----- 
pressure=bme.readPressure() / 100.0F;
temp=bme.readTemperature();
humid=bme.readHumidity();
   tft.setTextColor(TFT_YELLOW, TFT_BLACK);
   tft.fillRect(105, 0, 140, 45, TFT_BLACK);  // 残像消去
   tft.drawFloat(pressure, 0, 105, 0, 6); 
   tft.drawFloat(temp, 1, 105, 93, 6);
   tft.drawFloat(humid, 1, 105, 140, 6);
  }
}


void loop() {
//---------内蔵時計の表示--------
time_t t = time(NULL);
tm = localtime(&t);
d_year  = tm->tm_year+1900;
d_mon  = tm->tm_mon+1;
d_mday = tm->tm_mday;
d_hour = tm->tm_hour;
d_min  = tm->tm_min;
d_sec  = tm->tm_sec;
d_wday = tm->tm_wday;
sprintf(d_mes, "%04d/%02d/%02d", d_year, d_mon, d_mday);
sprintf(t_mes, "%02d:%02d:%02d", d_hour, d_min, d_sec);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
   tft.setCursor(70, 190, 4);
   tft.println(d_mes);
   tft.setCursor(200, 190, 4);
   tft.println(weekStr[d_wday]);  
   tft.setCursor(110, 215, 4);
   tft.println(t_mes); 
// -------- 10秒毎に測定してTFT表示 --------
   if((String(d_sec) == "0")  || (String(d_sec) == "10") || 
      (String(d_sec) == "20") || (String(d_sec) == "30") || 
      (String(d_sec) == "40") || (String(d_sec) == "50")) { 
// ----- SCD30センサが稼働している時の処理 -----
   if (airSensor.dataAvailable()) {
// ----- 液晶画面に測定値を表示 -----
   tft.setTextColor(TFT_WHITE, TFT_BLACK);
// ----- SCD30センサからデータ取得、測定値を表示 ----- 
co2_tmp=airSensor.getCO2();
   tft.fillRect(105, 47, 140, 45, TFT_BLACK);  // 残像消去
   tft.drawFloat(co2_tmp, 0, 105, 47, 6);
// ----- BME280センサからデータ取得、測定値を表示 ----- 
pressure=bme.readPressure() / 100.0F;
temp=bme.readTemperature();
humid=bme.readHumidity();
   tft.setTextColor(TFT_YELLOW, TFT_BLACK);
   tft.fillRect(105, 0, 140, 45, TFT_BLACK);  // 残像消去
   tft.drawFloat(pressure, 0, 105, 0, 6); 
   tft.drawFloat(temp, 1, 105, 93, 6);
   tft.drawFloat(humid, 1, 105, 140, 6);
    } //dataAvailable_SCD30ここまで
  } //10秒毎ここまで
//---------内蔵時計のJST同期(1日1回、12時30分0秒に実行)--------
if ((String(d_hour) == "12") && (String(d_min) == "30") && (String(d_sec) == "0")) {
     wifisyncjst();
     tft.fillRect(105, 210, 205, 25, TFT_BLACK);  // 残像消去
  }
delay(200);
}


void wifisyncjst() {
//---------内蔵時計のJST同期--------
// WiFi接続
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
  tft.drawString("WiFi bigin", 110, 215, 4);
  }
delay(1000);
// WiFi接続の表示
  tft.drawString("WiFi connected", 110, 215, 4);
delay(1000);
// NTPサーバからJST取得
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  tft.drawString("JST synchronized", 110, 215, 4);
delay(1000);
// 内蔵時計の時刻がNTP時刻に合うまで待機
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) {
delay(1000);
  }
//WiFi切断
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}

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