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

CO2濃度、気圧・温湿度&WiFi時計(1)
目次

準備したパーツ

ネット通販でパーツを集めてブレッドボード上で結線しました。RTC(DS3231など)は使っていないので、起動時と1日1回NTPサーバに接続して時刻合わせを実行します。

 #  パーツ  個数  備考
1Seeed Studio XIAO ESP32C31M-17454
2ILI9431搭載2.8インチ
SPI制御タッチパネル付TFT液晶MSP2807
1M-16265
3BME280 気圧、温湿度センサ モジュール1手持ち
4CO2センサモジュール(二酸化炭素+温度/湿度センサ)【SCD30】1SCD30
5プルアップ抵抗適量※必要に応じて
6ブレッドボード 6穴版 EIC-39011P-12366
7細ピンヘッダ 1×40(黒)1C-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コネクタからの給電で動作に支障は無いようです。

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

参考:
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-esp32は、Seeed Stu...

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

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

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

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

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」はWinsowsでは下記フォルダ配下にありました。

C:\Users\[ユーザー名]\Documents\Arduino\libraries\TFT_eSPI-master

液晶モジュールと接続したXIAO_ESP32C3のGPIO番号をUser_Setup.hの「// For ESP32 Dev board・・・」の行付近にある#defineを設定して、コメント(//)を外します。

左がUser_Setup.h のデフォルト。右側の黄色点線枠のGPIO番号を結線した環境に合わせて変更して、左端の//を削除して保存します。

User_Setup.h の編集
今回結線したXIAO_ESP32C3環境向けのUser_Setup.h (右側)

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

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