Seed Studio XIAO ESP32C3(以下、XIAO ESP32C3)とI2C接続したSCD30センサとBME280センサで測定して、SPI接続したマイクロSDカードに年月日、時刻、CO2濃度、気圧、気温、湿度を記録します。
モニタ用としてI2C接続したLCDモジュールAQM1602Y(16文字、2行)の上段に月日、曜日、時刻を、下段にCO2濃度、気圧、気温を表示しています。
準備したパーツ
XIAO ESP32C3は小型モジュールのためピン数が限られますが、独立したI2C、SPIピンがあるので、これらの接続インターフェースを持ったセンサモジュールを使って小型化できます。
リアルタイムクロック(RTC)モジュールは使っていないので、電源起動(スケッチ実行)時にWiFi経由でNTPサーバに接続してXIAO ESP32C3の内蔵時計をJSTに時刻合わせします。加えて、1日1回、NTPサーバに接続して時刻合わせすることで精度を維持します。
ネット通販(秋月電子通商、マルツオンライン など)でパーツを集めてブレッドボード上で結線しました。
#5~9はLCDモジュールの実装用パーツです。
# | パーツ | 個数 | 備考 |
1 | Seed Studio XIAO ESP32C3 | 1 | 【M-17454】 |
2 | CO2センサモジュール(二酸化炭素+温度/湿度センサ)【SCD30】 | 1 | 【SCD30】 |
3 | BME280 気圧、温湿度センサ モジュール ※今回使ったモジュールはI2C接続、電圧レギュレータとI2C電圧レベル変換回路を内蔵 | 1 | 手持ち |
4 | マイクロSDカードスロットDIP化キット | 1 | 【K-05488】 |
5 | LCDモジュール 16X2行 白色バックライト付 白文字 黒背景 AQM1602Y-NLW-FBW | 1 | 【P-12486】 |
6 | 積層セラミックコンデンサー 0.1μF 50V X7R 2.54mm(10個入) ※利用するのは1個 | 1 | 【P-13582】 |
7 | 1μF 50V Y5V 5mm(10個入) ※利用するのは2個 | 積層セラミックコンデンサー 1 | 【P-03093】 |
8 | 抵抗 220Ω ※バックライトLED電流制限抵抗 | 1 | 手持ち |
9 | ※LCDモジュールとパーツをはんだ付けして実装する時に利用 | 両面ユニバーサル基板適量 | 手持ち |
10 | 細ピンヘッダ 1×40(黒) ※購入したモジュールにピンヘッダが付属しない時に利用 | 1 | 【C-06631】 |
11 | SPI及びI2Cプルアップ抵抗 ※必要に応じて、今回SPI用に10kΩを4個利用 | 適量 | 手持ち |
12 | ブレッドボード 6穴版 EIC-3901 | 1 | 【P-12366】 |
13 | ジャンパーワイヤ オス-オス 10cmセット | 適量 | 【C-05371】 |
14 | マイクロSDカード ※FAT32でフォーマット ※64GB以上のマイクロSDカードは32GB以下でパーテションを切るか、32GB以上のFAT32フォーマットができるRaspberry Pi Imager等のツールを利用 | 1 | 手持ち |
結線図と組立て
LCDモジュールAQM1602Y、SCD30センサ、BME280センサはI2C接続、マイクロSDカードスロットはSPI接続です。
XIAO ESP32C3のI2CやSPIのピンレイアウトは下記サイトを参照して結線しました。
Getting Started with Seeed Studio XIAO ESP32C3
https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/
Pinout diagram
マイクロSDカードスロットをSPI接続
マイクロSDカードスロットDIP化キットとの接続には、XIAO ESP32C3のSPIピン(SCK、MOSI 、MISO)と任意設定できるSS (Slave Select)ピンにはGPIO2を使っています。
スケッチでは if (!SD.begin(2)) { }でGPIO2をSSピンとして指定します。
マイクロSDカードスロットDIP化キットの端子番号とXIAO ESP32C3のピン番号との対比表です。端子番号1、8、9、10は未使用です。
DIP化キット 端子番号 | DIP化キット 記号 | XIAO ESP32C3 ピン番号 | SPI Interfaces | XIAO ESP32C3
2 | CD/DAT3 | GPIO2 | SS (Slave Select) |
3 | CMD | GPIO10 | MOSI (Master Out Slave In) |
4 | VDD | 3V3 | |
5 | CLK | GPIO8 | SCK |
6 | VSS | GND | |
7 | DAT0 | GPIO9 | MISO (Master In Slave Out) |
参考:
・SD規格の概要 | SD Association (sdcard.org)
・SD スピードクラス | SD Association (sdcard.org)
・マイクロSDカードスロットDIP化キット | akizukidenshi.com
・Pinout diagram | wiki.seeedstudio.com
結線図
今回使ったBME280モジュールは、3.3V系/5V系 共用で使えるように、モジュール基板裏面に電源電圧Vccのレギュレータ(LDO:Low DropOut)と I2Cの電圧レベル変換回路を実装しています。この電圧レベル変換回路のI2C(SDA、SCL)にはプルアップ抵抗が内蔵されていたので、 外部に I2Cのプルアップ抵抗は実装していません。デジタルオシロスコープで見たXIAO ESP32C3のSCLとSDA信号は信号の立ち上がりが少しなまっているものの比較的良好な波形でした。
マイクロSDカードスロットのプルアップはMISOラインのみで良いのかもしれませんが、通信線4本を10kΩでプルアップしています。
LCDモジュールAQM1602Yの組立
下記の記事に、
・LCDモジュールAQM1602Yからのピン引出し加工、ユニバーサル基板でパーツを一体化
・曜日をカタカナや漢字(キャラクタパターンをCGRAM登録)で表記するスケッチ
について纏めています。
開発ツール arduino-esp32のインストールとライブラリの追加
ESP32の開発ツール arduino-esp32を準備してスケッチを作っていきます。
ESP32開発ツール arduino-esp32のインストール
XIAO ESP32C3サイトにarduino-esp32のインストール手順の記載があります。
Getting Started with Seeed Studio XIAO ESP32C3
https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/#software-setup
+ Software setup
当サイトにもESP32-DevKitCを例として、arduino-esp32 をArduino IDE 2.0.x版にインストールする手順メモを纏めています。
ボード(XIAO_ESP32C3を選択)とポート(当サイトのPCではCOM6)を切り替えることでXIAO ESP32C3でもそのまま使えます。
ライブラリのインクルード
先達の方々が開発されたライブラリをインクルードすることでスケッチ作成が容易になります。
LCDモジュール
LCDモジュールAQM1602Y用のライブラリには tomozh/arduino_ST7032 サイトの ST7032.h を利用させていただきました。ライブラリのzipファイル( arduino_ST7032-master.zip )をPCにダウンロードして保存(「Code」ボタンをクリックして「Download ZIP」)。Arduino IDEのメニューの「スケッチ」→「ライブラリをインクルード」→「.ZIP形式の ライブラリを インストール」で ダウンロードしたZIPファイルを追加します。
SCD30センサ
sparkfun/SparkFun_SCD30_Arduino_Library サイトのライブラリを利用させていただきました。 サイトの「Code」プルダウンから「SparkFun_SCD30_Arduino_Library-main .zip」をダウンロード。Arduino IDEのメニュー「スケッチ」–>「ライブラリをインクルード」–>「zip形式のライブラリをインクルード」でダウンロードしたzipファイルを指定します。
BME280センサ
BME280センサ(0x76)から測定データを取得するライブラリとして adafruit/Adafruit_BME280_Library と adafruit/Adafruit_Sensor を利用させていただきました。
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サーバから取得した時刻に一致しているかの判定に使っています。
スケッチ:マイクロSDカードにSCD30とBME280の測定データを記録(2023/01/25修正)
XIAO ESP32C3とI2C接続したSCD30センサとBME280センサで測定して、SPI接続したマイクロSDカードに年月日、時刻、CO2濃度、気圧、気温、湿度を記録します。モニタ用としてLCDモジュールAQM1602Y(16文字、2行)の上段に月日、曜日、時刻、下段にCO2濃度、気圧、気温を表示しています。
SDカードへの書き込み間隔は10秒、時計表示は1秒です。
下記スケッチの void loop(){ } は1秒間に数回ループさせ、必要に応じてif文で処理タイミングの分岐処理を行っています(delay(1000);だと処理によっては秒飛びが起きることがあったため)。
SDカードには10秒サイクルで測定データを書込みますが、同じ1秒間に多重書き込みが起こらないように書き込んだ時点の秒(d_sec_b)と現在の秒(d_sec)をif文で判別しています。
※ストーブを焚いている部屋の窓を全開にしたのでCO2濃度が1500ppm台から400ppm台に下がっています。
RTCモジュールは使っていないので、電源起動(スケッチ実行)時にWiFi経由でNTPサーバに接続してXIAO ESP32C3の内蔵時計をJSTに時刻合わせします。加えて、1日1回、NTPサーバに接続(下記スケッチでは12時30分0秒)して時刻合わせすることで精度を維持します。
最大1310720バイトのフラッシュメモリのうち、スケッチが743722バイト(56%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が39588バイト(12%)を使っていて、ローカル変数で288092バイト使うことができます。
XIAO_ESP32C3_AQM1602_scd30_bme280_sd-card.ino
※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <TimeLib.h> // https://github.com/PaulStoffregen/Time
#include <ST7032.h> // https://github.com/tomozh/arduino_ST7032
#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
#include <SPI.h>
#include <SD.h>
File dataFile;
ST7032 lcd;
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;
char d_mes[12] ;
char t_mes[12] ;
struct tm *tm;
//曜日(漢字キャラクタパターン)@2022/12/22追加
byte nichi[8] = { 0b11111, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b11111, 0b00000 }; //日
byte getsu[8] = { 0b01111, 0b01001, 0b01111, 0b01001, 0b01111, 0b01001, 0b10001, 0b00000 }; //月
byte kayou[8] = { 0b00100, 0b10101, 0b10101, 0b00100, 0b01010, 0b01010, 0b10001, 0b00000 }; //火
byte suiyo[8] = { 0b00100, 0b00101, 0b11110, 0b00110, 0b01101, 0b10100, 0b00100, 0b00000 }; //水
byte mokuy[8] = { 0b00100, 0b00100, 0b11111, 0b00100, 0b01110, 0b10101, 0b00100, 0b00000 }; //木
byte kinyo[8] = { 0b00100, 0b01010, 0b10001, 0b01110, 0b10101, 0b01110, 0b11111, 0b00000 }; //金
byte doyou[8] = { 0b00000, 0b00100, 0b00100, 0b01110, 0b00100, 0b00100, 0b11111, 0b00000 }; //土
int d_year ;
int d_mon ;
int d_mday ;
int d_hour ;
int d_min ;
int d_sec ;
int d_sec_b ;
int d_wday ;
void setup(){
Wire.begin(); // I2C初期化
lcd.begin(16, 2); // ディスプレイの文字数(16)と行数(2)
lcd.setContrast(40); // ディスプレイのコントラスト調整(0~63の範囲で調整)
// SDカードマウント確認
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Initializing SD");
delay(1000);
if (!SD.begin(2)) { // SS-pin 2
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("failed!");
while (1);
}
delay(1000);
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("done.");
delay(1000);
// SDカードファイル書き込み
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("File written...");
delay(1000);
dataFile = SD.open("/datalog.txt", FILE_WRITE);
dataFile.println("datalog.txt");
dataFile.close();
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("datalog.txt");
delay(1000);
//-------SCD30初期化
if (airSensor.begin() == false) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("SCD30 not detected");
while (1);
}
//The SCD30 has data ready every two seconds
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("SCD30 detected");
delay(2000);
// BME280初期化
status = bme.begin(0x76);
while (!status) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("BME280 failed");
}
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("BME280 connected");
delay(1000);
//---------内蔵時計のJST同期(起動時)--------
wifisyncjst();
//---------漢字_CGRAM登録@2022/12/22追加
lcd.createChar(0, nichi); //日
lcd.createChar(1, getsu); //月
lcd.createChar(2, kayou); //火
lcd.createChar(3, suiyo); //水
lcd.createChar(4, mokuy); //木
lcd.createChar(5, kinyo); //金
lcd.createChar(6, doyou); //土
}
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);
lcd.setCursor(0, 0);
lcdzeroSup(d_mon);
lcd.print("/");
lcdzeroSup(d_mday);
lcd.setCursor(6, 0);
// ----- 曜日(漢字表示)@2022/12/22追加
if (String(d_wday) == "0") {
lcd.write(0); //日
} else if (String(d_wday) == "1") {
lcd.write(1); //月
} else if (String(d_wday) == "2") {
lcd.write(2); //火
} else if (String(d_wday) == "3") {
lcd.write(3); //水
} else if (String(d_wday) == "4") {
lcd.write(4); //木
} else if (String(d_wday) == "5") {
lcd.write(5); //金
} else if (String(d_wday) == "6") {
lcd.write(6); //土
}
lcd.setCursor(8, 0);
lcdzeroSup(d_hour);
lcd.print(":");
lcdzeroSup(d_min);
lcd.print(":");
lcdzeroSup(d_sec);
// ----- センサからデータ取得、測定値を10秒毎に表示&SD書き込み -----
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()) {
co2_tmp=airSensor.getCO2();
}
// ----- BME280センサからデータ取得 -----
pressure=bme.readPressure() / 100.0F;
temp=bme.readTemperature();
humid=bme.readHumidity();
// -----液晶表示
lcd.setCursor(0, 1);
lcd.print(" "); //Co2が4桁→3桁に変わった時の残像消去
lcd.setCursor(0, 1);
lcd.print(String(co2_tmp,0));
lcd.setCursor(6, 1); //座標指定を追加@2023/01/25追加
lcd.print(" "); //気圧が4桁→3桁に変わった時の残像消去
lcd.setCursor(6, 1);
lcd.print(String(pressure,0));
lcd.setCursor(12, 1);
lcd.print(String(temp,1));
// -----SDカード書き込み
if( d_sec != d_sec_b ) { // 多重書き込み防止判定
sdcardwrite();
d_sec_b = d_sec; // 書き込んだ時点の秒を記憶
}
} // 10秒毎終了
//---------内蔵時計のJST同期(1日1回、12時30分0秒に実行)--------
if ((String(d_hour) == "12") && (String(d_min) == "30") && (String(d_sec) == "0")) {
wifisyncjst();
}
delay(100);
}
void sdcardwrite() {
// ----- SDカードへの書き込み用データファイルの生成 -----
// データ格納ファイル生成
String dataString = "";
// 内蔵時計の年月日と時分秒を記録
dataString += String(d_mes);
dataString += ","; // カンマセパレータ
dataString += String(t_mes);
// 測定データ1:CO2濃度
dataString += ","; // カンマセパレータ
if(!isnan(co2_tmp)){
dataString += String(co2_tmp,0);
}else{
dataString += " ";
}
// 測定データ2:BME280の気圧
dataString += ","; // カンマセパレータ
if(!isnan(pressure)){
dataString += String(pressure,0);
}else{
dataString += " ";
}
// 測定データ3:BME280の温度
dataString += ","; // カンマセパレータ
if(!isnan(temp)){
dataString += String(temp,1);
}else{
dataString += " ";
}
// 測定データ4:BME280の湿度
dataString += ","; // カンマセパレータ
if(!isnan(humid)){
dataString += String(humid,1);
}else{
dataString += " ";
}
// ----- SDカードのdatalog.txtにdataStringを上書き -----
File dataFile = SD.open("/datalog.txt", FILE_APPEND);
dataFile.println(dataString);
dataFile.close();
delay(100);
}
void wifisyncjst() {
//---------内蔵時計のJST同期--------
// WiFi接続
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("WiFi bigin");
}
delay(1000);
// WiFi接続の表示
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("WiFi connected");
delay(1000);
lcd.clear();
lcd.print(WiFi.localIP()); //LCD画面にIPアドレス表示
delay(2000);
// NTPサーバからJST取得
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("JST synchronized");
delay(1000);
// 内蔵時計の時刻がNTP時刻に合うまで待機
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) {
delay(1000);
}
//WiFi切断
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
lcd.clear();
}
void lcdzeroSup(int digit){
//---------月、日、時、分、秒が0~9の場合、1桁目を 空白 もしくは 0 に置換--------
if(digit < 10)
lcd.print(' '); // 現在「空白」
lcd.print(digit);
}
補足:SCD41センサを使ったスケッチ(2023/01/25修正)
CO2濃度センサをSCD41センサモジュール(FSNS-SCD41-X00)に替えたときのスケッチです。他のコードは同じす。
sparkfun/SparkFun_SCD4x_Arduino_Library サイトのSCD4xライブラリを利用させていただきました。 サイトの「Code」プルダウンから「SparkFun_SCD4x_Arduino_Library-main.zip」をダウンロードしてインクルードします。
XIAO_ESP32C3_AQM1602_scd41_bme280_sd-card.ino
※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <TimeLib.h> // https://github.com/PaulStoffregen/Time
#include <ST7032.h> // https://github.com/tomozh/arduino_ST7032
#include "SparkFun_SCD4x_Arduino_Library.h" // https://github.com/sparkfun/SparkFun_SCD4x_Arduino_Library
#include <Adafruit_Sensor.h> // https://github.com/adafruit/Adafruit_Sensor
#include <Adafruit_BME280.h> // https://github.com/adafruit/Adafruit_BME280_Library
#include <SPI.h>
#include <SD.h>
File dataFile;
ST7032 lcd;
SCD4x mySensor;
float co2_41x;
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;
char d_mes[12] ;
char t_mes[12] ;
struct tm *tm;
//曜日(漢字キャラクタパターン)@2022/12/22追加
byte nichi[8] = { 0b11111, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b11111, 0b00000 }; //日
byte getsu[8] = { 0b01111, 0b01001, 0b01111, 0b01001, 0b01111, 0b01001, 0b10001, 0b00000 }; //月
byte kayou[8] = { 0b00100, 0b10101, 0b10101, 0b00100, 0b01010, 0b01010, 0b10001, 0b00000 }; //火
byte suiyo[8] = { 0b00100, 0b00101, 0b11110, 0b00110, 0b01101, 0b10100, 0b00100, 0b00000 }; //水
byte mokuy[8] = { 0b00100, 0b00100, 0b11111, 0b00100, 0b01110, 0b10101, 0b00100, 0b00000 }; //木
byte kinyo[8] = { 0b00100, 0b01010, 0b10001, 0b01110, 0b10101, 0b01110, 0b11111, 0b00000 }; //金
byte doyou[8] = { 0b00000, 0b00100, 0b00100, 0b01110, 0b00100, 0b00100, 0b11111, 0b00000 }; //土
int d_year ;
int d_mon ;
int d_mday ;
int d_hour ;
int d_min ;
int d_sec ;
int d_sec_b ;
int d_wday ;
void setup(){
Wire.begin(); // I2C初期化
lcd.begin(16, 2); // ディスプレイの文字数(16)と行数(2)
lcd.setContrast(40); // ディスプレイのコントラスト調整(0~63の範囲で調整)
// SDカードマウント確認
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Initializing SD");
delay(1000);
if (!SD.begin(2)) { // SS-pin 2
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("failed!");
while (1);
}
delay(1000);
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("done.");
delay(1000);
// SDカードファイル書き込み
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("File written...");
delay(1000);
dataFile = SD.open("/datalog.txt", FILE_WRITE);
dataFile.println("datalog.txt");
dataFile.close();
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("datalog.txt");
delay(1000);
//-------SCD41初期化
if (mySensor.begin() == false) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("SCD41 not detected");
while (1);
}
//The SCD30 has data ready every two seconds
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("SCD41 detected");
delay(4000);
// BME280初期化
status = bme.begin(0x76);
while (!status) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("BME280 failed");
}
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("BME280 connected");
delay(1000);
//---------内蔵時計のJST同期(起動時)--------
wifisyncjst();
//---------漢字_CGRAM登録@2022/12/22追加
lcd.createChar(0, nichi); //日
lcd.createChar(1, getsu); //月
lcd.createChar(2, kayou); //火
lcd.createChar(3, suiyo); //水
lcd.createChar(4, mokuy); //木
lcd.createChar(5, kinyo); //金
lcd.createChar(6, doyou); //土
}
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);
lcd.setCursor(0, 0);
lcdzeroSup(d_mon);
lcd.print("/");
lcdzeroSup(d_mday);
lcd.setCursor(6, 0);
// ----- 曜日(漢字表示)@2022/12/22追加
if (String(d_wday) == "0") {
lcd.write(0); //日
} else if (String(d_wday) == "1") {
lcd.write(1); //月
} else if (String(d_wday) == "2") {
lcd.write(2); //火
} else if (String(d_wday) == "3") {
lcd.write(3); //水
} else if (String(d_wday) == "4") {
lcd.write(4); //木
} else if (String(d_wday) == "5") {
lcd.write(5); //金
} else if (String(d_wday) == "6") {
lcd.write(6); //土
}
lcd.setCursor(8, 0);
lcdzeroSup(d_hour);
lcd.print(":");
lcdzeroSup(d_min);
lcd.print(":");
lcdzeroSup(d_sec);
// ----- センサからデータ取得、測定値を10秒毎に表示&SD書き込み -----
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")) {
// ----- SCD41センサからデータ取得 -----
if (mySensor.readMeasurement()) {
co2_41x=mySensor.getCO2();
}
// ----- BME280センサからデータ取得 -----
pressure=bme.readPressure() / 100.0F;
temp=bme.readTemperature();
humid=bme.readHumidity();
// -----液晶表示
lcd.setCursor(0, 1);
lcd.print(" "); //Co2が4桁→3桁に変わった時の残像消去
lcd.setCursor(0, 1);
lcd.print(String(co2_41x,0));
lcd.setCursor(6, 1); //座標指定を追加@203/01/25追加
lcd.print(" "); //気圧が4桁→3桁に変わった時の残像消去
lcd.setCursor(6, 1);
lcd.print(String(pressure,0));
lcd.setCursor(12, 1);
lcd.print(String(temp,1));
// -----SDカード書き込み
if( d_sec != d_sec_b ) { // 2重書き込み防止判定
sdcardwrite();
d_sec_b = d_sec; // 書き込んだ時点の秒を記憶
}
} // 10秒毎終了
//---------内蔵時計のJST同期(1日1回、12時30分0秒に実行)--------
if ((String(d_hour) == "12") && (String(d_min) == "30") && (String(d_sec) == "0")) {
wifisyncjst();
}
delay(100);
}
void sdcardwrite() {
// ----- SDカードへの書き込み用データファイルの生成 -----
// データ格納ファイル生成
String dataString = "";
// 内蔵時計の年月日と時分秒を記録
dataString += String(d_mes);
dataString += ","; // カンマセパレータ
dataString += String(t_mes);
// 測定データ1:CO2濃度
dataString += ","; // カンマセパレータ
if(!isnan(co2_41x)){
dataString += String(co2_41x,0);
}else{
dataString += " ";
}
// 測定データ2:BME280の気圧
dataString += ","; // カンマセパレータ
if(!isnan(pressure)){
dataString += String(pressure,0);
}else{
dataString += " ";
}
// 測定データ3:BME280の温度
dataString += ","; // カンマセパレータ
if(!isnan(temp)){
dataString += String(temp,1);
}else{
dataString += " ";
}
// 測定データ4:BME280の湿度
dataString += ","; // カンマセパレータ
if(!isnan(humid)){
dataString += String(humid,1);
}else{
dataString += " ";
}
// ----- SDカードのdatalog.txtにdataStringを追加 -----
File dataFile = SD.open("/datalog.txt", FILE_APPEND);
dataFile.println(dataString);
dataFile.close();
delay(100);
}
void wifisyncjst() {
//---------内蔵時計のJST同期--------
// WiFi接続
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("WiFi bigin");
}
delay(1000);
// WiFi接続の表示
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("WiFi connected");
delay(1000);
lcd.clear();
lcd.print(WiFi.localIP()); //LCD画面にIPアドレス表示@2023/1/15追加
delay(2000);
// NTPサーバからJST取得
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("JST synchronized");
delay(1000);
// 内蔵時計の時刻がNTP時刻に合うまで待機
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) {
delay(1000);
}
//WiFi切断
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
lcd.clear();
}
void lcdzeroSup(int digit){
//---------月、日、時、分、秒が0~9の場合、1桁目を 空白 もしくは 0 に置換--------
if(digit < 10)
lcd.print(' '); // 現在「空白」
lcd.print(digit);
}