PlatformIOでM5Stackを使う

## デバイス
### ATOMS3
https://docs.m5stack.com/en/core/AtomS3
https://www.switch-science.com/products/8670
### CoreS3SE
https://docs.m5stack.com/en/core/M5CoreS3%20SE
https://www.switch-science.com/products/9690
### VL53L1X
https://shop.m5stack.com/products/time-of-flight-distance-unit-vl53l1x
https://www.switch-science.com/products/9427
## 環境
### VisualStudioCodeを設定
VisualStudioCodeをインストール
[表示][拡張機能]
* C/C++ v1.21.6
* C/C++ Extension Pack v1.3.0
* Japanese Language Pack for Visual Studio Code v1.93.2024091109
* PlatformIO IDE v3.3.3
* Arduino v0.6.0 廃止なので使用せず
* Command Palette (CTRL+SHIFT+P)
** Configure Display Language = 日本語(ja)

参考
https://elchika.com/article/6a153bc2-318f-44c7-871b-0ddbdce2ca42/
### PlatformIOでプロジェクト作成
```
[PlatformIO昆虫アイコン][QUICK ACCESS][PIO Home][Home]
[+ New Project]
Project Wizard
Name[プロジェクト名]
Board[M5Stack AtomS3]又は[M5Stack CoreS3]
Framework[Arduino]
Location[プロジェクトフォルダを置くフォルダ]
[Finish]
```
### M5Unifiedを追加
「BMI270 IMU 加速度角速度」に対応
```
[PlatformIO昆虫アイコン][QUICK ACCESS][PIO Home][Library]
[M5Unifiedで検索][M5Unified by M5Stack, lovyan03][Add to Project]
Add project dependency
Select a project[プロジェクト名][Add]
```
### M5Unit-ENVを追加
「BMP280 気圧温度センサ」に対応
```
[PlatformIO昆虫アイコン][QUICK ACCESS][PIO Home][Library]
[M5Unitで検索][M5Unit-ENV by M5Stack][Add to Project]
Add project dependency
Select a project[プロジェクト名][Add]
```
### platformio.ini
「VL53L1X 距離センサ」に対応
```
[env:m5stack-atoms3]
platform = espressif32
board = m5stack-atoms3
framework = arduino
lib_deps = m5stack/M5Unified@^0.1.16
	https://github.com/pololu/vl53l1x-arduino
```
## 実装
### UDP送受信
本家
https://www.arduino.cc/reference/en/libraries/wifi/
参考
https://note.com/khe00716/n/n65a479049078
### NTP時刻同期
### 実装例
```
#include <M5Unified.h>
#include <WiFi.h>
#include <time.h>
#include "image.h"
#include <VL53L1X.h>

#if ARDUINO_USB_MODE
#if ARDUINO_USB_CDC_ON_BOOT
//NOP
#else
#define Serial USBSerial
#endif
#endif

static const char* const ssid = "aterm-b3fa31-2s";
static const char* const password = "603f2aa16f0d5";
static VL53L1X sensor;
static bool l_M5AtomS3;
static bool l_M5StackCoreS3SE;
static int l_CursorMulY = 1;
static bool l_VL53L1X;
static bool l_BMM150;

static void setCursor(int32_t x, int32_t y) {
  M5.Lcd.setCursor(x, y * l_CursorMulY);
}

static bool ssidlist() {
  M5.Lcd.clearDisplay();
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("ssid scanning");
  WiFi.disconnect();
  int n = WiFi.scanNetworks();  // 6秒程度
  if (n == 0) {
    M5.Lcd.println("no networks found");
  } else {
    int i;
    for (i = 0; i < n; i++) {
      M5.Lcd.printf("%d:", i + 1);
      M5.Lcd.print(WiFi.SSID(i));
      M5.Lcd.printf("(%d)", WiFi.RSSI(i));
      M5.Lcd.println(WiFi.encryptionType(i) == WIFI_AUTH_OPEN ? " " : "*");
    }
  }
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  return 0 < n;
}

static bool ipaddress() {
  M5.Lcd.setCursor(0, 0);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  int trycnt = 10;
  M5.Lcd.print("Wifi");
  while (WiFi.status() != WL_CONNECTED) {
    if (--trycnt <= 0) {
      break;
    }
    M5.Lcd.print(".");
    delay(1000);
  }
  bool bOk = 0 < trycnt;
  if (bOk) {
    M5.Lcd.println(WiFi.localIP());
  } else {
    M5.Lcd.println("fail");
  }
  return bOk;
}

static void ntp() {
  const long gmtOffset_sec = 3600 * 9;
  const int daylightOffset_sec = 0;
  const char* const server1 = "ntp.jst.mfeed.ad.jp";
  configTime(gmtOffset_sec, daylightOffset_sec, server1);
}

static void initVL53L1X() {
  // https://www.switch-science.com/products/9427
  // https://github.com/pololu/vl53l1x-arduino
  sensor.setTimeout(500);
  if (!sensor.init()) {
    setCursor(0, 2 * 16);
    M5.Lcd.println("VL53L1X fail");
    return;
  }
  l_VL53L1X = true;
  // Use long distance mode and allow up to 50000 us (50 ms) for a measurement.
  // You can change these settings to adjust the performance of the sensor, but
  // the minimum timing budget is 20 ms for short distance mode and 33 ms for
  // medium and long distance modes. See the VL53L1X datasheet for more
  // information on range and timing limits.
  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(50000);
  // Start continuous readings at a rate of one measurement every 50 ms (the
  // inter-measurement period). This period should be at least as long as the
  // timing budget.
  sensor.startContinuous(50);
}

static void initBMM150() {
  // https://zenn.dev/tichise/articles/9b7fae21c2df6e
  if (true) {
    setCursor(0, 3 * 16);
    M5.Lcd.println("BMM150 fail");
    return;
  }
}

void setup() {
  Serial.begin(9600);
  delay(500);
  Serial.println("HelloWorld");
  M5.begin();
  l_M5AtomS3 = M5.getBoard() == m5::board_t::board_M5AtomS3;
  l_M5StackCoreS3SE = M5.getBoard() == m5::board_t::board_M5StackCoreS3SE;
  if (l_M5AtomS3) {
    l_CursorMulY = 2;
  }
  M5.Lcd.begin();
  ssidlist();
  ipaddress();
  delay(1000);
  if (l_M5AtomS3) {
    do {
      M5.update();
    } while (0 < M5.BtnA.isPressed());
  }
  if (l_M5StackCoreS3SE) {
    do {
      M5.update();
    } while (0 < M5.Touch.getCount());
  }
  M5.Lcd.clearDisplay();
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println(ssid);
  setCursor(0, 1 * 16);
  M5.Lcd.println(WiFi.localIP());
  ntp();
  Wire.begin();
  Wire.setClock(400000);  // use 400 kHz I2C
  initVL53L1X();
  initBMM150();
}

static void PutJpg(uint16_t x, uint16_t y, uint16_t number) {
  static const unsigned char* image_data[17] = {
    image0, image1, image2, image3, image4,
    image5, image6, image7, image8, image9,
    sun, mon, tue, wed, thu, fri, sat
  };
  static const uint32_t image_size[17] = {
    sizeof image0, sizeof image1, sizeof image2, sizeof image3, sizeof image4,
    sizeof image5, sizeof image6, sizeof image7, sizeof image8, sizeof image9,
    sizeof sun, sizeof mon, sizeof tue, sizeof wed, sizeof thu, sizeof fri, sizeof sat
  };
  M5.Lcd.drawJpg(image_data[number], image_size[number], x, y);
}

static void PutNum2(uint16_t x, uint16_t y, uint16_t x_offset, uint16_t number) {
  PutJpg(x, y, number / 10);
  PutJpg(x + x_offset, y, number % 10);
}

static void DateTime() {
  struct tm timeinfo;
  if (getLocalTime(&timeinfo)) {
    if (true) {
      char datetime[64];
      sprintf(datetime, "%04d-%02d-%02d %02d:%02d:%02d",
              timeinfo.tm_year + 1900,
              timeinfo.tm_mon + 1,
              timeinfo.tm_mday,
              timeinfo.tm_hour,
              timeinfo.tm_min,
              timeinfo.tm_sec);
      setCursor(0, 2 * 16);
      // M5.Lcd.println(datetime);
    }
    if (l_M5StackCoreS3SE) {
      const int y1 = 72;
      const int y2 = 156;
      PutNum2(0, y1, 52, timeinfo.tm_mon + 1);
      PutNum2(108, y1, 52, timeinfo.tm_mday);
      PutJpg(108 * 2, y1 + 27, timeinfo.tm_wday + 10);
      M5.Lcd.setCursor(0, 136);
      PutNum2(0, y2, 52, timeinfo.tm_hour);
      PutNum2(108, y2, 52, timeinfo.tm_min);
      PutNum2(108 * 2, y2, 52, timeinfo.tm_sec);
    }
  }
  if (true) {
    const int hourtrig = 4;
    const int mintrig = 0;
    const int sectrig = 0;
    static bool hour = false;
    static bool min = false;
    static bool sec = false;
    if (timeinfo.tm_hour == hourtrig) {
      if (!hour) {
        hour = true;
      }
    } else {
      hour = false;
    }
    if (timeinfo.tm_min == mintrig) {
      if (!min) {
        min = true;
      }
    } else {
      min = false;
    }
    if (timeinfo.tm_sec == sectrig) {
      if (!sec) {
        sec = true;
        ntp();
      }
    } else {
      sec = false;
    }
  }
}

static void VL53L1X() {
  const int array_size = 16;
  static int l_cnt = 0;
  static uint16_t l_data[array_size] = { 0 };
  uint16_t milli = sensor.readRangeContinuousMillimeters();
  if (0 < milli) {
    l_data[l_cnt++] = milli;
    l_cnt = l_cnt % array_size;
    long sum = 0;
    int i;
    for (i = 0; i < array_size; i++) {
      sum += l_data[i];
    }
    milli = sum / array_size;
  }
  char buf[32];
  sprintf(buf, "%4d %s", milli, sensor.timeoutOccurred() ? "TIMEOUT" : "mm");
  setCursor(0, 2 * 16);
  M5.Lcd.println(buf);
}

static void BMM150() {
  ;
}

void loop() {
  DateTime();
  if (l_VL53L1X) {
    VL53L1X();
  }
  if (l_BMM150) {
    BMM150();
  }
  delay(100);
}
```