VSCodeとPlatformIOでM5Stack Core2開発

いつの間にかPlatformIOでM5Core2がサポートされていた（以前は他のボード設定を流用する必要があった）ので、設定方法をまとめる。
また、次のようなサンプルコードで動作を確認する。
・[サンプルコード1: 画面表示とバッテリー情報読み出し](#サンプルコード1画面表示とバッテリー情報読み出し)
・[サンプルコード2: Wifi接続とNTPによるRTCへの時刻設定](#サンプルコード2wifi接続とntpによるrtcへの時刻設定)
・[サンプルコード3: GPIOとADC](#サンプルコード3gpioとadc)
・[サンプルコード4: マイクロSDへのデータ書き込み](#サンプルコード4マイクロsdへのデータ書き込み)
動作確認はWindows 10とVSCodeを使用している。

参考資料
・[公式ページ（回路図等はここから）](https://docs.m5stack.com/#/en/core/core2)
・[Arduinoライブラリ（大体の使い方はM5Core2/examples/でわかる）](https://github.com/m5stack/M5Core2)
・[最初に書き込まれているFactory test image](https://github.com/m5stack/M5-ProductExampleCodes/tree/master/Core/M5Core2/Arduino/Core2_Factory_test)
・[MCUとして使用されているESP32-D0WDQ6-V3のデータシート](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)

# M5Core2用プロジェクトの作り方
## PlatformIOのインストールとプロジェクト生成
VSCodeの拡張機能からPlatformIOをインストールし、VSCodeを再起動する。
![Extension_ PlatformIO IDE - Visual Studio Code 2021_03_27 20_44_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/6e76a388-6fcd-8b13-16a0-40595f988029.png)
PIO HomeからProjects & Configurationを選択し、Create New Projectをクリック。
![PIO Home - Visual Studio Code 2021_03_27 20_54_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/a1f182e8-e9fc-c44d-bf3f-9bbaa84e046f.png)
Project Wizardが表示されるので、任意のプロジェクト名を設定し、BoardはM5Stack Core2を選択し、Finishi。
![PIO Home - Visual Studio Code 2021_03_27 20_55_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/3f4135c3-b5e5-c58a-c3e2-dfa861840fc6.png)
（なぜかプロジェクト生成に結構時間がかかった。）
![PIO Home - Visual Studio Code 2021_03_27 20_58_56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/993a0542-22f0-1233-7755-e0af224a4c14.png)
この画面のようにプロジェクトファイルが生成されればOK。後のサンプルではsrc/main.cppを編集する。
![platformio.ini - Core2Test1 - Visual Studio Code 2021_03_27 21_05_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/519351f3-d973-31a1-9380-1d32824df973.png)
## 必要なライブラリのインストール
次に、生成したプロジェクトに必要なライブラリを関連付ける。
PIO HomeからLibrariesでM5Core2を検索する。
![platformio.ini - Core2Test1 - Visual Studio Code 2021_03_27 21_06_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/c7c59d65-fedf-d91c-f4c6-846489bf92cb.png)
M5Core2を選択し、Add to Projectをクリック。
![platformio.ini - Core2Test1 - Visual Studio Code 2021_03_27 21_06_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/4d027d40-66f4-e2a2-88e3-3e3c8350489c.png)
２つめのドロップダウンリストから、先程生成したプロジェクトを選択し、Addをクリック。
![platformio.ini - Core2Test1 - Visual Studio Code 2021_03_27 21_06_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/69afc99b-37fb-eb44-53d0-b6e015c1a739.png)
もうひとつ、画面表示用ライブラリのLovyanGFXも追加する。手順はM5Core2ライブラリの追加と同じ。（画面表示をデフォルトのライブラリで操作する場合は不要。）
![PIO Home - Core2Test1 - Visual Studio Code 2021_03_27 21_09_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/419696b7-5afd-717e-b773-acbb179d8367.png)
ここまでの設定が正しく出来ていれば、最初に生成したプロジェクト内のplatformio.iniがこのように変化している。
![platformio.ini - Core2Test1 - Visual Studio Code 2021_03_27 21_11_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/b8f6e2d5-e73f-a56f-0d6e-a2bcc526124b.png)
# ビルドと実機への書き込み
## 準備
M5Stack公式からCP2104 Driverをダウンロードしてインストール（環境によってはなくても動くかも？）。
![Download _ m5stack-store - Google Chrome 2021_03_27 21_27_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/4b841c78-b242-bf18-6f21-000730faa7a3.png)
今回の環境は64bit版Windows 10なのでCP210xVCPInstaller_x64_v6.7.0.0.exeをインストールした。
![CP210x_VCP_Windows 2021_03_27 21_33_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/d0fd29a7-b04a-3ba4-1fcf-4c28692ecddf.png)
M5Core2をUSBケーブルでPCに接続、電源をONにする。
![R0000542_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/ded3ef19-99b5-3bc2-1355-2d085bb63767.jpeg)
## サンプルコード1（画面表示とバッテリー情報読み出し）
バッテリ電圧と電流値をライブラリで読み出し、LovyanGFXを使って画面に表示する。
充電中は常時表示、バッテリー動作中は約500ミリ秒で画面ONとOFFを繰り返す。

```cpp:main.cpp
#include <M5Core2.h>

#define LGFX_M5STACK_CORE2
#include <LovyanGFX.hpp>

static LGFX lcd;

void setup()
{
  M5.begin(true, true, true, true);

  lcd.init();
  lcd.setFont(&fonts::lgfxJapanGothic_28);
  lcd.setBrightness(128);
  lcd.setCursor(0, 128);
  lcd.println("はろーテスト壱");
}

void loop()
{
  M5.update();

  float batVol = M5.Axp.GetBatVoltage();
  float batCur = M5.Axp.GetBatCurrent();

  lcd.setCursor(0, 156);
  lcd.printf("Baterry: %1.3f\nCurrent: %.3f\n", batVol, batCur);

  if (batCur < 0) {
    delay(500);
    lcd.setBrightness(0);
    lcd.sleep();
    delay(500);
    lcd.wakeup();
    lcd.setBrightness(128);
  }
}
```
このコードをプロジェクトのsrc/main.cppに置き、ビルドしてみる。
PlatformIOのアイコンをクリックして、PROJECT TASKSからm5stack-core2→General→Build、ターミナルでSUCCESSが表示されることを確認。
![platformio.ini - Core2Test1 - Visual Studio Code 2021_03_27 21_50_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/f622b782-0d1e-827d-2a7d-674949ac093e.png)
今度はUpload（エラーが出る場合はM5Stack側の電源が入っていること、USBが接続されていることを確認）。
![main.cpp - Core2Test1 - Visual Studio Code 2021_03_27 21_57_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/ef3f92f5-0495-bb0b-a579-d89fca3e68dc.png)
書き込みが完了すると自動でリセットがかかり、この画面が表示される。
ひらがな、カタカナ、漢字も表示できるが、機種依存文字はバグる。
バッテリが正常なら電流値は正の値（充電電流？）になる。バッテリが抜けていると電流値は0。
![R0000545_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/769c33aa-ce48-eb15-8c4d-69ae5d2ac18b.jpeg)
USBケーブルを外すと電流値が負の値（消費電流）になる。
![R0000546_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/109be24a-1008-b56a-c823-f343d763e9f6.jpeg)

## サンプルコード2（Wifi接続とNTPによるRTCへの時刻設定）
M5Core2はRTCを持っているので、時刻を設定すれば電源OFF時やDeep Sleep時に時刻が保持される。
Wifi接続し、NTPを使って時刻同期する。
※こちらの記事を参考にしました。
https://qiita.com/kaoruka/items/b36c99fb124fea0d5d24

```cpp:main.cpp
#include <M5Core2.h>
#include <time.h>
#include <WiFi.h>

#define LGFX_M5STACK_CORE2
#include <LovyanGFX.hpp>

static LGFX lcd;

char ssid[] = "*****************";
char pass[] = "*****************";

const char* ntpServer = "ntp.jst.mfeed.ad.jp";
const long  gmtOffset_sec = 9 * 3600; // Offset: +9Hour (JST)
const int   daylightOffset_sec = 0;   // Summer time = no
static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};

RTC_DateTypeDef RTC_DateStruct;
RTC_TimeTypeDef RTC_TimeStruct;

void setNTP2RTC(){
    struct tm timeinfo;
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
    while (!getLocalTime(&timeinfo)) {
      delay(1000);
    }

    //Over write
    M5.Rtc.GetDate(&RTC_DateStruct);
    RTC_DateStruct.Year = timeinfo.tm_year + 1900;
    RTC_DateStruct.Month = timeinfo.tm_mon + 1;
    RTC_DateStruct.Date = timeinfo.tm_mday;
    RTC_DateStruct.WeekDay = timeinfo.tm_wday;
    M5.Rtc.SetDate(&RTC_DateStruct);

    //Over write
    M5.Rtc.GetTime(&RTC_TimeStruct);
    RTC_TimeStruct.Hours = timeinfo.tm_hour;
    RTC_TimeStruct.Minutes = timeinfo.tm_min;
    RTC_TimeStruct.Seconds = timeinfo.tm_sec;
    M5.Rtc.SetTime(&RTC_TimeStruct);
}

int wifiConnect() {
  int con_count = 0;
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    lcd.print(".");
    con_count++;
    if(con_count >= 20){
      break;
    }
  }
  if(con_count < 20){
    lcd.print("\nWiFi connected.");
  }else{
    lcd.print("\nWiFi did not connect.");
  }
  lcd.print("\nIP=");
  lcd.print(WiFi.localIP());
  return con_count;
}

void setup() {
  M5.begin(true, true, true, true);
  lcd.init();
  lcd.setFont(&fonts::lgfxJapanGothic_28);
  lcd.setBrightness(128);
  lcd.setCursor(0, 0);

  if (wifiConnect() < 20) {
    setNTP2RTC();
  }
}


void loop() {
  lcd.setCursor(0, 100);
  M5.Rtc.GetDate(&RTC_DateStruct);
  M5.Rtc.GetTime(&RTC_TimeStruct);
  lcd.printf("%04d.%02d.%02d %s\n", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date, wd[RTC_DateStruct.WeekDay]);
  lcd.printf("%02d:%02d:%02d", RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds);
  delay(500);
}
```
コード内のssid[]とpass[]には、接続したいWifiのSSIDとパスワードを設定すること。
5GHz帯の無線LANには接続できなかった。ESP32-D0WDQ6-V3のデータシートに以下の記載があるので2.4GHz帯でないとダメかもしれない。
• 802.11 b/g/n
• 802.11 n (2.4 GHz), up to 150 Mbps

Wifiに接続できるとNTPで時刻が同期され、以下の画面が表示される。
![R0000548_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/94ac6d3f-abc3-7542-088c-70088b282ed5.jpeg)

## サンプルコード3（GPIOとADC）
GPIOとADCはよくあるArduinoと同じ書き方で制御できる。
（GPIOの35と36はもともとADC用に設定されているが、他のGPIOをADCに設定する場合は追加で何か設定が要るかも知れない。未調査。）

```cpp:main.cpp
#include <M5Core2.h>

#define LGFX_M5STACK_CORE2
#include <LovyanGFX.hpp>

static LGFX lcd;

void setup() {
  M5.begin(true, true, true, true);
  lcd.init();
  lcd.setFont(&fonts::lgfxJapanGothic_28);
  lcd.setBrightness(128);
  lcd.setCursor(0, 0);

  //Initialize GPIO
  pinMode(19, OUTPUT);
  digitalWrite(19, LOW);
}


void loop() {
  uint16_t pin35ADC = analogRead(35);
  float pin35vol = (float)pin35ADC *  3.3 / 4096;
  uint16_t pin36ADC = analogRead(36);
  float pin36vol = (float)pin36ADC *  3.3 / 4096;

  lcd.setCursor(0, 100);
  lcd.printf("ADC35: %1.4f[V]\nADC36: %1.4f[V]\n", pin35vol, pin36vol);

  //Toggle GPIO
  digitalWrite(19, LOW);
  delay(400);
  digitalWrite(19, HIGH);
  delay(100);
}
```
動作画面。自作拡張ボードでADCに信号入力している。（拡張ボードの話は別記事で書くかも。）
![R0000549_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/59fa9e44-39a3-2c30-7db9-92c1394e5aed.jpeg)

## サンプルコード4（マイクロSDへのデータ書き込み）
マイクロSDカードは、公式には16GBまで対応している。実験した範囲では32GBも認識した。
マイクロSDカードはあらかじめFAT32でフォーマットしておく。

以下のサンプルは、
・setupでRTCから時刻を読み出し、YYYYMMDD_HHMMSS.csvという命名規則でファイル生成
・loopではRTCから定期的に時刻を読み出して上記のファイルに追記
という動作をしている。
※あらかじめサンプルコード2でRTCを設定しておく必要がある。

```cpp:main.cpp
#include <M5Core2.h>

#define LGFX_M5STACK_CORE2
#include <LovyanGFX.hpp>

static LGFX lcd;

RTC_TimeTypeDef RTCtime_Now;
RTC_DateTypeDef RTCDate_Now;
char fileStrbuff[64];
char timeStrbuff[64];

File file;

void setup()
{
  M5.begin(true, true, true, true);

  lcd.init();
  lcd.setFont(&fonts::lgfxJapanGothic_28);
  lcd.setBrightness(128);

  //SDカードが存在するかチェック
  if (!SD.begin()) {
    lcd.println("ERROR: SD CARD.");
    while (1);
  }

  //RTCで起動した時刻を取得し、ファイル名に使用する
  M5.Rtc.GetTime(&RTCtime_Now);
  M5.Rtc.GetDate(&RTCDate_Now);
  //ファイルは絶対パスで指定するので、/（ファイル名）、/が無いとエラーになる
  sprintf(fileStrbuff,"/%04d%02d%02d_%02d%02d%02d.csv",
        RTCDate_Now.Year,RTCDate_Now.Month,RTCDate_Now.Date,
        RTCtime_Now.Hours,RTCtime_Now.Minutes,RTCtime_Now.Seconds
        );

  //ファイル生成（チェック）
  file = SD.open(fileStrbuff, FILE_WRITE);
  if (!file) {
    lcd.println("ERROR: OPEN FILE.");
    while (1);    
  }
  file.close();
}

void loop()
{
  M5.update();

  //現在時刻取得
  M5.Rtc.GetTime(&RTCtime_Now);
  M5.Rtc.GetDate(&RTCDate_Now);
  sprintf(timeStrbuff,"%04d/%02d/%02d %02d:%02d:%02d",
        RTCDate_Now.Year,RTCDate_Now.Month,RTCDate_Now.Date,
        RTCtime_Now.Hours,RTCtime_Now.Minutes,RTCtime_Now.Seconds
        );

  //生成したファイルが存在することを確認し、タイムスタンプを書き込み
  //安全対策で毎回ファイルを開いて閉じているが、別途クローズ処理するならsetupでオープンしたまま書き込み続けて良い
  if (SD.exists(fileStrbuff)) {
    file = SD.open(fileStrbuff, FILE_APPEND);
    file.println(timeStrbuff);
    file.close();
  } else {
    //途中でSDが抜けたor認識エラー
    lcd.println("ERROR: FILE IS MISSING!");
    while (1);
  }

  lcd.setCursor(0, 100);
  lcd.println(timeStrbuff);

  delay(1000);
}
```
マイクロSDが正しく挿さっていれば、この画面のように時刻を表示し、同時にデータを書き込み続ける。
![R0000550_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/34f38f29-ae59-13cc-fcd3-dc0f4e47c9dd.jpeg)
一度電源を切ってSDカードを抜き、PCで確認するとこのようなファイルが生成されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263098/c7eea237-d067-0340-9df4-4e561828aca1.png)