Seed Studio XIAO ESP32C3とSDカードスロットをSPI接続して環境センサの測定データをマイクロSDカードに記録

Seed Studio XIAO ESP32C3(以下、XIAO ESP32C3)とI2C接続したSCD30センサとBME280センサで測定して、SPI接続したマイクロSDカードに年月日、時刻、CO2濃度、気圧、気温、湿度を記録します。

モニタ用としてI2C接続したLCDモジュールAQM1602Y(16文字、2行)の上段に月日、曜日、時刻を、下段にCO2濃度、気圧、気温を表示しています。

XIAO ESP32C3とSDカードソケットをSPI接続
XIAO ESP32C3とマイクロSDカードスロットをSPI接続
目次

準備したパーツ

XIAO ESP32C3は小型モジュールのためピン数が限られますが、独立したI2C、SPIピンがあるので、これらの接続インターフェースを持ったセンサモジュールを使って小型化できます。

リアルタイムクロック(RTCモジュールは使っていないので、電源起動(スケッチ実行)時にWiFi経由でNTPサーバに接続してXIAO ESP32C3の内蔵時計をJSTに時刻合わせします。加えて、1日1回、NTPサーバに接続して時刻合わせすることで精度を維持します。

ネット通販でパーツを集めてブレッドボード上で結線しました。
#5~9はLCDモジュールの実装用パーツです。

 パーツ 個数 備考
1Seed Studio XIAO ESP32C31M-17454
2CO2センサモジュール(二酸化炭素+温度/湿度センサ)【SCD30】1SCD30
3BME280 気圧、温湿度センサ モジュール
※今回使ったモジュールはI2C接続、電圧レギュレータとI2C電圧レベル変換回路を内蔵
1手持ち
4マイクロSDカードスロットDIP化キット1K-05488
5LCDモジュール 16X2行
白色バックライト付 白文字 黒背景
AQM1602Y-NLW-FBW
1P-12486
6積層セラミックコンデンサー
0.1μF 50V X7R 2.54mm(10個入)

※利用するのは1個
1P-13582
7積層セラミックコンデンサー
1μF 50V Y5V 5mm(10個入)
※利用するのは2個
1P-03093
8抵抗 220Ω
※バックライトLED電流制限抵抗
1手持ち
9両面ユニバーサル基板
※LCDモジュールとパーツをはんだ付けして実装する時に利用
適量手持ち
10細ピンヘッダ 1×40(黒)
※購入したモジュールにピンヘッダが付属しない時に利用
1C-06631
11SPI及びI2Cプルアップ抵抗
※必要に応じて、今回SPI用に10kΩを4個利用
適量手持ち
12ブレッドボード 6穴版 EIC-39011P-12366
13ジャンパーワイヤ
オス-オス 10cmセット
適量C-05371
14マイクロSDカード
※FAT32でフォーマット
※64GB以上のマイクロSDカードは32GB以下でパーテションを切るか、32GB以上のFAT32フォーマットができるRaspberry Pi Imager等のツールを利用
1手持ち

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

結線図と組立て

LCDモジュールAQM1602Y、SCD30センサ、BME280センサはI2C接続、マイクロSDカードスロットはSPI接続です。

XIAO ESP32C3のI2CやSPIのピンレイアウトは下記サイトを参照して結線しました。

Getting Started with Seeed Studio XIAO ESP32C3
Pinout diagram

https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/

マイクロ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
ピン番号
 XIAO ESP32C3
SPI Interfaces
2CD/DAT3GPIO2SS (Slave Select)
3CMDGPIO10MOSI (Master Out Slave In)
4VDD3V3 
5CLKGPIO8SCK
6VSSGND 
7DAT0GPIO9MISO (Master In Slave Out)

参考:
SD規格の概要 | SD Association (sdcard.org)
SD スピードクラス | SD Association (sdcard.org)
マイクロSDカードスロットDIP化キット | akizukidenshi.com
microSDカードスロットモジュール取扱説明書 | 秋月電子通商
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登録)で表記するスケッチ
について纏めています。

あわせて読みたい
Seeed Studio XIAO ESP32C3と小型LCDモジュールAQM1602YとBME280を使った気圧・温湿度&WiFi時計 Seed Studio XIAO ESP32C3(以下、XIAO ESP32C3)とI2C接続したBME280、WiFi接続で情報通信研究機構(NICT)のNTPサーバと時刻合わせして、LCDモジュール AQM1602Y-NLW-...

開発ツール arduino-esp32のインストールとライブラリの追加

ESP32の開発ツール 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版にインストールする手順メモを纏めています。

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

Arduino IDEでボードとポートの選択
ボード:XIAO_ESP32C3を選択
あわせて読みたい
ESP32-DevKitCの開発ツール arduino-esp32 のインストール手順 Arduino IDE上で動作する ESP32-DevKitC の開発ツール arduino-esp32(Arduino core for the ESP32)をインストールした際の手順メモです。 arduino-esp32は、Seeed Stu...

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

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

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 を利用させていただきました。

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サーバから取得した時刻に一致しているかの判定に使っています。

スケッチ:マイクロ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文で判別しています。

測定データをマイクロSDカードに記録
マイクロSDカードに記録した測定データ(datalog.txt)
※ストーブを焚いている部屋の窓を全開にしたのでCO2濃度が1500ppm台から400ppm台に下がっています。
XIAO ESP32C3とSDカードソケットをSPI接続
XIAO ESP32C3とSDカードソケットをSPI接続

RTCモジュールは使っていないので、電源起動(スケッチ実行)時にWiFi経由でNTPサーバに接続してXIAO ESP32C3の内蔵時計をJSTに時刻合わせします。加えて、1日1回、NTPサーバに接続(下記スケッチでは12時30分0秒)して時刻合わせすることで精度を維持します。

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

スケッチ修正
2023/01/25:気圧が4桁→3桁に変わった時の残像消去で座標指定が漏れていたので175行に追加

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);  //座標指定を追加@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 ) {  // 多重書き込み防止判定
     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とSDカードソケットをSPI接続
SCD41センサモジュールに交換

 

スケッチ修正
2023/01/25:気圧が4桁→3桁に変わった時の残像消去で座標指定が漏れていたので175行に追加

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);
}
よかったらシェアしてね!
  • URLをコピーしました!
目次