m5stackでセンサーデータをsdカードに保存してみた。

# 初めに
I2Cを使って取得可能ないくつかのセンサーデータをsdカードへ保存してみました。

# 環境
OS: windows 10.0
Arduino IDE：2.0.0
M5stack core2 
M5Stack用温湿度気圧センサユニット Ver.3（ENV Ⅲ）[link](https://www.switch-science.com/products/7254)
TVOC/eCO2 ガスセンサユニット(SGP30)[link](https://www.switch-science.com/products/6619)
M5Stack用環境光センサユニット（BH1750FVI-TR）[link](https://www.switch-science.com/products/7985)

#イメージ
M5stack core2から各種センサーに繋いでます。センサーごとにアドレスが違うので、別々に読み出すことができます。
![スクリーンショット 2022-10-30 17.14.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176158/0dab2b75-f266-56b1-427c-10174af53f07.png)

# 時間
一定時間置きに測定をする場合、ネット環境がなければ、RTCが必要ということが分かりました。RTCは、低消費電力なので別電源で時を刻むことができるため、ネットワークの環境がなくても時を刻んでくれます。そのため、RTCが実装されているM5stack core2を使うことにしました。

## 一定時間置きにデータをcsvに保存する。
RTCから時間情報を取得し、5分おききにデータ取得するようにしました。時間を５で割って、余りが0のときに、一度測定をして、4分後に測定するフラグを0にして5分おきに測定をできるようにしました。


```test.cc
bool  meas_csv = false;
bool  write_csv = false;

RTC_TimeTypeDef RTC_TimeStruct; // Time

  if (RTC_TimeStruct.Minutes % 5 == 4 ) meas_csv = true; 
  
  // get the csv per 5min
  if(RTC_TimeStruct.Minutes % 5 == 0){
     if (meas_csv == true){ 
      //write data per 5min.
      meas_csv = false;
      write_csv = true ;
  }
}
```

## 日付のファイル名で保存する。
SDカードに保存するときの形式に合わせて、RTCのデータを変換するところが苦労しました。
下記のようにsnprintfを使うと、SDカードに保存するための形式に合うようになりました。

```test.cc
char fname[26];
snprintf(fname, 26, "/sensor_data/%04d%02d%02d.csv", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date);

```

#プログラム
作成したプログラムは、下記のようになります。```file.print```は、取得したデータの形式が違い、合わせこむ方法が分からなかったので、べた書きしてデータを取得するようにしています。

```test.cc
#include <M5Core2.h>
#include <WiFi.h>
#include "time.h"
#include "M5_ENV.h"
#include <M5_DLight.h>
#include "Adafruit_SGP30.h"

// sprite 
TFT_eSprite sprite = TFT_eSprite(&M5.Lcd);
extern uint8_t m5_logo[];

// csv file
File file;

// set up sensor 
SHT3X sht30;
QMP6988 qmp6988;
M5_DLight sensor;
uint16_t lux;
Adafruit_SGP30 sgp;
long last_millis = 0;

// for WiFi
char ssid[] = "xxxxxxx"; // your SSID
char pass[] = "xxxxxxx"; // your Pass word

float tmp      = 0.0;
float hum      = 0.0;
float pressure = 0.0;
int   meas_btm = 1; 
int   con_count = 0;
int   timer_reset = 0;
bool  meas_csv = false;
bool  write_csv = false;
char info[40];

// for Time
const char* ntpServer = "ntp.jst.mfeed.ad.jp"; // NTP server
const long  gmtOffset_sec = 9 * 3600;          // Time offset 9hr
const int   daylightOffset_sec = 0;            // No summer time
RTC_DateTypeDef RTC_DateStruct; // Data
RTC_TimeTypeDef RTC_TimeStruct; // Time
struct tm timeinfo;
String dateStr;
String timeStr;

void setup() {
  M5.begin();
  M5.Lcd.setBrightness(10);
  M5.Lcd.setRotation(1); 
  M5.Lcd.setTextSize(2);
  M5.Lcd.fillScreen(BLACK);

  // connect wifi  
  if (timer_reset == 1){
    WiFi.begin(ssid, pass);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      M5.Lcd.print(".");
      con_count++;
      if(con_count >= 20){
        break;
      }
    }
    if(con_count < 20){
      M5.Lcd.print("\nWiFi connected.");
      setNTP2RTC();
    }else{
      M5.Lcd.print("\nWiFi did not connect.");
    }
    M5.Lcd.print("\nIP=");
    M5.Lcd.print(WiFi.localIP());  
    delay(2 * 1000);
  }

  // Output logo on the display.
  M5.Lcd.fillScreen(BLACK);    
  M5.Lcd.drawJpg(m5_logo,6374,60, 20);
  M5.update();// update button state  
  delay(2 * 1000);

  Wire.begin();               //  // Wire init, adding the I2C bus.
  qmp6988.init();
  if (!sgp.begin()) { 
    M5.Lcd.println("Sensor not found");
    while (1)
      ;
  }
  sensor.begin();
  sensor.setMode(CONTINUOUSLY_H_RESOLUTION_MODE);
  sprite.setColorDepth(8);
  sprite.setTextSize(2);
  sprite.fillScreen(BLACK);
  sprite.createSprite(M5.lcd.width(), M5.lcd.height());
  //make a directory
  if(!SD.exists("/sensor_data")){
    SD.mkdir("/sensor_data");
  }
}

void loop() {

  sprite.pushSprite( 0, 0);
  M5.update();// update button state

  if (RTC_TimeStruct.Minutes % 5 == 4 ) meas_csv = true; 
  
  // get the csv per 5min
  if(RTC_TimeStruct.Minutes % 5 == 0){
     if (meas_csv == true){ 
      //write data per 5min.
      meas_csv = false;
      write_csv = true ;

      M5.Rtc.GetDate(&RTC_DateStruct);
      M5.Rtc.GetTime(&RTC_TimeStruct);

      pressure = qmp6988.calcPressure()* 0.01;
      if (sht30.get() == 0) {  // Obtain the data of shT30.  
        tmp = sht30.cTemp;   // Store the temperature obtained from shT30.
        hum = sht30.humidity;  // Store the humidity obtained from the SHT30.
      } else {
        tmp = 0, hum = 0;
      }

      // for Touch
      TouchPoint_t pos= M5.Touch.getPressPoint();
      if(pos.y > 240){
        if(pos.x < 109)
          sprite.setTextColor(RED,BLACK);
        else if(pos.x > 218)
          sprite.setTextColor(BLUE,BLACK);
        else if(pos.x >= 109 && pos.x <= 218)
          sprite.setTextColor(GREEN,BLACK);
      }   
      lux = sensor.getLUX();
      if (!sgp.IAQmeasure()) {  //Commands the sensor to take a single eCO2/VOC 
        Serial.println("Measurement failed");
        return;
      }
      delay(1 * 1000);

      char fname[26];
      snprintf(fname, 26, "/sensor_data/%04d%02d%02d.csv", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date);

      // write data
      if(!SD.exists(fname)){
        file = SD.open(fname, FILE_APPEND);
        file.print("date,time,temperature,humidity,pressure,lux,TVOC,eCO2\n");
        file.close();
        delay(0.1 * 1000); 
      }
      file = SD.open(fname, FILE_APPEND);
      file.print(RTC_DateStruct.Month);
      file.print("/");
      file.print(RTC_DateStruct.Date);
      file.print(",");
      file.print(RTC_TimeStruct.Hours);
      file.print(":");
      if(RTC_TimeStruct.Minutes< 10) file.print("0");
      file.print(RTC_TimeStruct.Minutes);
      file.print(",");
      file.print(tmp);
      file.print(",");    
      file.print(hum);
      file.print(",");
      file.print(pressure);
      file.print(",");       
      file.print(lux);
      file.print(",");
      file.print(sgp.TVOC);
      file.print(",");       
      file.print(sgp.eCO2);
      file.print("\n");
      file.close();
    }
  }
}