概要
前回はUIFlowを利用してかんたんに動作確認をしてみました。今回はArduinoを利用してのプログラミング方法をサンプルスケッチを確認していきたいと思います。
開発環境について
Arduinoでの開発環境でもいろいろな環境があり、差があるので注意して選択してください。
Arduiono IDE+M5Stack社のボードマネージャー
上記のURLをArduino IDEの追加ボードマネージャーに設定したときの開発環境となります。この環境の場合ボード一覧にM5StickS3がありますが、内部で利用しているArduino Coreのバージョンが古いです。最新のArduino Coreを利用したい場合には適していません。

まずはこの環境が公式環境なのですが、若干古いCoreの場合が多いので最新機能を利用したい場合には注意が必要です。
profiles:
m5stack_sticks3:
fqbn: m5stack:esp32:m5stack_sticks3
platforms:
- platform: m5stack:esp32 (3.2.5)
platform_index_url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
Arduono CLIの場合には上記のようなsketch.yamlでシンプルに利用可能です。
Arduiono IDE + Espressif社の純正ボードマネージャー
上記の一番標準的なボードマネージャーです。このボードマネージャーを利用するのが一番新しいArduino Coreを利用できるのですが、M5StickS3のボード定義が追加されていません。

M5StickS3の開発をする場合には「ESP32-S3 Dev Module」を選択して、M5Stack版のボードマネージャーと同じ設定にする必要があります。
profiles:
esp32s3:
fqbn: esp32:esp32:esp32s3:CDCOnBoot=cdc,FlashSize=8M,PartitionScheme=default_8MB,PSRAM=opi
platforms:
- platform: esp32:esp32 (3.3.6)
platform_index_url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
Arduono CLIの場合には上記のようなsketch.yamlで必要な設定を追加する必要があります。
USB CDC On Boot

USBポートをシリアルとして起動するかの設定になります。ESP32-S3 Dev ModuleはUSBポートが2つ搭載されており、通常はUSBシリアル変換チップと搭載したほうに接続するのでデフォルトはDisabledになっていますが、M5StickS3の場合にはUSBポートは1つのみなのでEnableに設定を推奨します。
この設定によりSerial.begin()したときのデフォルト出力先がUSBポートに変更になります。ハット側に内部のUARTポートがありますので、デフォルトのDisabledだとそちらにSerialが出力されています。ただしリセット端子が外部にでていないので、ハット側からの自動書き込みはできません。
Flashサイズ

デフォルトは4MBなので8MBに拡張します。小さいプログラムしか転送しない場合には4MBのままでも問題ありません。
Partition Scheme

こちらもデフォルトは4MBなので8MBに拡張します。小さいプログラムしか転送しない場合には4MBのままでも問題ありません。
PSRAM

PSRAMを利用する場合にはDisabledから「OPI PSRAM」に変更します。こちらがちょっと面倒なのですがESP32は有効か無効化の2択だったのですが、ESP32-S3は接続方法がQSPIとOPIがあります。たとえばM5Stack Core3はQSPI接続となっています。
ここの設定を間違えると正常に起動しないので注意が必要です。また、この設定があるためにPSRAM有効時に同じバイナリでM5Stack Core3とM5StickS3を動かすことはできません。
PlatformIO + PlatformIO純正ボードマネージャー
公式ドキュメント上ではサポートされていますが、espressif32@6.12.0は2024/05/22にリリースされたArduino Core 2.0.17向けのプラットフォームとなります。中身が古すぎるので個人的には利用をおすすめしません。
こちらもボードにM5StickS3がありませんのでesp32-s3-devkitc-1に変更点を設定している形となります。
[env:m5stack-sticks3]
platform = espressif32@6.12.0
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.partitions = default_8MB.csv
board_build.arduino.memory_type = qio_opi
build_flags =
-DESP32S3
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-DCORE_DEBUG_LEVEL=5
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DARDUINO_USB_MSC_ON_BOOT=0
-DARDUINO_USB_DFU_ON_BOOT=0
lib_deps =
M5Unified=https://github.com/m5stack/M5Unified
M5PM1=https://github.com/m5stack/M5PM1
PlatformIO + pioarduinoボードマネージャー
PlatformIO純正のボードマネージャーは最新バージョンをサポートしない宣言をして更新が止まっています。そこで有志のメンバーが保守をしている最新バージョンをサポートしたボードマネージャーになります。
上記のプロジェクトとなりますが、あくまで有志のメンバーが保守していますので公式的な立ち位置ではありません。
[env:m5stack-sticks3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.partitions = default_8MB.csv
board_build.arduino.memory_type = qio_opi
build_flags =
-DESP32S3
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-DCORE_DEBUG_LEVEL=5
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DARDUINO_USB_MSC_ON_BOOT=0
-DARDUINO_USB_DFU_ON_BOOT=0
lib_deps =
M5Unified=https://github.com/m5stack/M5Unified
M5PM1=https://github.com/m5stack/M5PM1
platformの場所にpioarduinoのURLを設定することでボードマネージャーを切り替えることができます。ただし、ボードマネージャーがArduino IDEのものとはかなり違うので、どの項目を変更しないといけないのかが非常にわかりにくいです。
Arduino Coreバージョンの確認方法
上記は私が管理しているページですが、各リリースバージョンの関係を整理してあります。

上記みたいな表があり、M5Stackのボードマネージャーは公式のものからバージョンが離れており、pioarduinoはほぼ同日に同一バージョンに追従しているのがわかります。
公式サンプル集
上記にArduiono IDEのセットアップ方法と、各デバイスについてのサンプルがあります。ここのコードを確認しながらボードの機能を把握していきたいと思います。
また、上記にArduino CLIを利用した場合の設定ファイル付きのサンプルコードを入れておきます。警告がでる場所について最低限の修正と、VSCODEでのフォーマットをかけてあります。
Battery
bool isCharging = M5.Power.isCharging();
int vol_per = M5.Power.getBatteryLevel();
int vol = M5.Power.getBatteryVoltage();
M5UnifiedのPowerクラスから電源の状態が取得可能なようです。
Button
M5.update();
if(M5.BtnA.isPressed() || M5.BtnB.isPressed())
{
こちらもM5Unifiedで対応してありますので、M5.update()でボタンの状態を更新して、M5.BtnAが正面のボタン、右側面のボタンがM5.BtnBになります。
Display
こちらもM5Unifiedで対応してありますので初期化さえすれば普通に利用可能です。
IMU
auto ImuData = M5.Imu.getImuData();
M5.Lcd.printf("IMU:\n\n");
M5.Lcd.printf(" ax:%6.2f\n ay:%6.2f\n az:%6.2f\r\n",
ImuData.accel.x,
ImuData.accel.y,
ImuData.accel.z);
M5.Lcd.println();
M5.Lcd.printf(" gx:%6.2f\n gy:%6.2f\n gz:%6.2f\r\n",
ImuData.gyro.x,
ImuData.gyro.y,
ImuData.gyro.z);
こちらも通常のIMUと同じ扱いで利用可能です。
IR NEC
IRの送受信はかなり注意が必要です。まず5Vの電源が必要になりますので有効化する必要があるのと、受信時には送信するリモコンを30センチ以上離す必要があるようです。
コード自体は汎用的なものではないのでこのコードを参考にしないほうがよいと思います。
MIC
MICもM5Unified標準の利用方法で利用可能です。こちらは少し使い方が難しそうなので後日詳細な確認をしてみたいと思います。
Speaker
MICもM5Unified標準の利用方法で利用可能です。サンプルはM5.Speaker.tone関数で音を鳴らしていますが、M5Unified標準のスピーカークラスのtoneはそこまで音質はよくない気がしますが、こちらも後日検証してみたいと思います。
Wakeup
スリープはESP32単体のスリープと、電源管理をしているPMIC経由のスリープがあり省電力やスリープ中の機能がかなり違ってきます。こちらも後日検証を行ってみたいと思います。
M5PM1(PMIC)
これまでM5StickCはAXP192などの電源管理を行うPMICを利用してきました。ただし、PMIC自体は在庫が不安定だったり、生産中止になったりと問題になりやすく前回のPlus2はPMICを搭載していない構成でした。
ただし、PMICを搭載していないと細かい制御ができないので、M5StickS3ではPY32という汎用的なMCUに自作の電源管理制御プログラムを入れてPMICとして利用しています。AXP192などに比べると電流管理などはできませんが、最低限の電圧監視とLDOなどの利用制御などがI2C経由で利用できるようになっています。
こちらの機能もPPK2などを利用して消費電力を計測しながら確認する必要があるので後日検証を行いたいと思います。
最低限の確認用サンプルコード
M5Stack社のサンプルコードはM5Unifiedの利用前提で、細かい処理で何をしているのかが少しわかりにくいので、最小限のサンプルコードでM5Stack社のコード相当のサンプルを作ってみました。
01_Button
/*
A: 1, B: 0
A: 1, B: 0
A: 0, B: 1
A: 0, B: 1
A: 1, B: 1
A: 0, B: 0
A: 0, B: 0
A: 1, B: 1
*/
#define BUTTON_A 11
#define BUTTON_B 12
void setup()
{
Serial.begin(115200);
pinMode(BUTTON_A, INPUT);
pinMode(BUTTON_B, INPUT);
}
void loop()
{
Serial.printf("A: %d, B: %d\n", digitalRead(BUTTON_A), digitalRead(BUTTON_B));
delay(500);
}
AがGPIO11、BがGPIO12になります。こちらは普通にGPIOでアクセス可能です。
02_I2C_SCAN
#include <Wire.h>
/*
Scanning for I2C devices ...
I2C device found at address 0x18
I2C device found at address 0x68
I2C device found at address 0x6E
*/
#define I2C_SDA_PIN GPIO_NUM_47 // M5StickS3 internal SDA Pin
#define I2C_SCL_PIN GPIO_NUM_48 // M5StickS3 internal SCL Pin
void setup()
{
Serial.begin(115200);
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
}
void loop()
{
byte error, address;
int nDevices = 0;
delay(5000);
Serial.println("Scanning for I2C devices ...");
for (address = 0x01; address < 0x7f; address++)
{
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0)
{
Serial.printf("I2C device found at address 0x%02X\n", address);
nDevices++;
}
else if (error != 2)
{
Serial.printf("Error %d at address 0x%02X\n", error, address);
}
}
if (nDevices == 0)
{
Serial.println("No I2C devices found");
}
}
内部のI2Cバスをスキャンしてみました。
| アドレス | |
|---|---|
| 0x18 | ES8311(オーディオ) |
| 0x68 | BMI270(IMU) |
| 0x6E | M5PM1(PMIC) |
上記のアドレスを発見しました。基本的な機能はM5Unified経由でも利用可能なのですが、細かい設定は直接アクセスする必要があります。今回は特にPMIC系についてはM5Unifiedを利用しないで初期化等を最低限の確認をしたいと思います。
03_PMIC
--- PM1 Information ---
Device ID : 0x50
Device Model : 0x20
HW Version : 0x05
SW Version : 0x4F
Power default : CHG=ON DCDC=ON LDO=ON 5V=OFF
Power CHG off : CHG=OFF DCDC=ON LDO=ON 5V=OFF
Power 5V on : CHG=OFF DCDC=ON LDO=ON 5V=ON
Power default : CHG=ON DCDC=ON LDO=ON 5V=OFF
Vref : 3340 mV (3.340 V)
VBAT : 4104 mV (4.104 V)
VIN : 5032 mV (5.032 V)
5V IN/OUT : 5012 mV (5.012 V)
Temp : 37
処理は長いのでタイトルのリンクから飛んでほしいですが、上記の情報をM5Stack社が提供しているPMIC用のライブラリから取得可能です。
特に外部5V出力を制御するpm1.setBoostEnable関数がGroveにユニットなどを接続するときには必須となります。これ以外にも電源ホールドの設定などがありますので、後日検証したいと思います。
04_IR_Receive
#include <M5PM1.h>
#include <IRrecv.h>
#include <IRutils.h>
#define I2C_SDA_PIN GPIO_NUM_47 // M5StickS3 internal SDA Pin
#define I2C_SCL_PIN GPIO_NUM_48 // M5StickS3 internal SCL Pin
M5PM1 pm1;
const uint16_t kRecvPin = 42;
const uint16_t kCaptureBufferSize = 1024;
const uint8_t kTimeout = 50;
IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);
void setup()
{
Serial.begin(115200);
delay(1000);
// IR Receiver needs 5V power & Speaker off
pm1.begin(&Wire, M5PM1_DEFAULT_ADDR, I2C_SDA_PIN, I2C_SCL_PIN, M5PM1_I2C_FREQ_100K);
pm1.setBoostEnable(true);
pm1.pinMode(3, OUTPUT);
pm1.digitalWrite(3, LOW);
pm1.dumpPinStatus();
irrecv.enableIRIn(true); // pullup enabled
}
void loop()
{
decode_results results;
if (irrecv.decode(&results))
{
Serial.print(resultToHumanReadableBasic(&results));
Serial.printf("Type=%s Bits=%d Addr=0x%04lX (%ld) Cmd=0x%02lX (%ld) Repeat=%d\n",
typeToString(results.decode_type).c_str(),
results.bits,
results.address, results.address,
results.command, results.command,
results.repeat);
Serial.println(resultToSourceCode(&results));
Serial.println();
}
}
今回はじめてIR受信がコントローラー本体に内蔵されました。しかしながら赤外線リモコンの信号を受信するのは若干不安定です。
利用するライブラリはIRremoteESP8266をおすすめします。長らくバージョンアップされておらず、最新版のArduino Coreでは動かない状況が続いたのですが、最近やっと対応してくれました。
ただし、動かすための前提として以下の4点に注意が必要です。
- pm1.setBoostEnable(true)で5V出力を有効化する
- pm1.digitalWrite(3, LOW)でPMICのGPIO3(スピーカー)を無効化する
- irrecv.enableIRIn(true)で受信時にプルアップする
- リモコンの送信機を30センチ以上離す
上記をすべて守らないと受信することができません。そして現状のところ受信が不安定ですので、受信率が悪いので使い方が難しいです。
05_IR_Receive_LCD
void setup()
{
auto cfg = M5.config();
cfg.internal_spk = false; // Disable speaker amp to avoid IR RX interference
M5.begin(cfg);
Serial.begin(115200);
// Display initialization
M5.Display.setRotation(3);
M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
M5.Display.clear();
M5.Display.println("StickS3 IR example");
// Enable external power output for IR receiver module
M5.Power.setExtOutput(true, m5::ext_none);
irrecv.enableIRIn(true); // pullup enabled
}
M5Unifiedを利用した場合の初期化例です。cfg.internal_spk = falseでPMICのGPIO3のスピーカーが無効化しています。M5.Power.setExtOutput(true)で5V出力を有効化しています。
シリアルに出力するより、USB接続をやめてバッテリー駆動をしたほうがノイズの影響は受けにくいかもしれません。
06_IR_Send
#include <M5PM1.h>
#include <IRsend.h>
#define I2C_SDA_PIN GPIO_NUM_47 // M5StickS3 internal SDA Pin
#define I2C_SCL_PIN GPIO_NUM_48 // M5StickS3 internal SCL Pin
M5PM1 pm1;
const uint16_t kIrLed = 46;
IRsend irsend(kIrLed);
void setup()
{
Serial.begin(115200);
pinMode(kIrLed, OUTPUT);
delay(1000);
// IR Send needs 5V power
pm1.begin(&Wire, M5PM1_DEFAULT_ADDR, I2C_SDA_PIN, I2C_SCL_PIN, M5PM1_I2C_FREQ_100K);
pm1.setBoostEnable(true);
}
void loop()
{
static uint16_t address = 0x0010;
static uint8_t command = 0x00;
address += 1;
command += 2;
uint64_t send = irsend.encodeNEC(address, command);
irsend.sendNEC(send);
Serial.printf("Sent NEC Address: 0x%04X Command: 0x%02X\n", address, command);
delay(1000);
}
IR送信は普通に利用可能です。5Vを有効化するのと、GPIO46はデフォルトがINPUTに設定されているピンのため、pinMode(kIrLed, OUTPUT)で出力モードに設定する必要があります。
IRremoteESP8266は出力用のピンを初期化してくれないみたいなのですが、利用するライブラリによっては初期化は不要だと思います。
まとめ
M5SticS3から個別のボード用ライブラリが提供されずに、すべてM5Unifiedを利用する構成になっています。構成的にはシンプルになったのですが、M5Unifiedはすべての機能をサポートしているわけではなく、各ボードで共通で利用できる機能のみをサポートする形となっております。
そのためM5Unifiedがサポートされていない細かい機能を利用しようとした瞬間にかなり面倒なことになるので注意が必要です。たとえばスリープ時にIMUを利用して復帰することができるのですがM5Unified経由では設定できないので、別途SparkFunのBMI270ライブラリとかをM5公式利用例では使っていました。
また、開発環境が複数あるのも結構困ったもので、M5Stack公式サポートのArduino IDE用のボードマネージャーと、設定例があるPlatformIO純正のプラットフォームの両方が古いバージョンのArudino Coreを利用しています。
バージョン差が少ないArduino IDE用のボードマネージャーでもM5Stack版は内部のESP-IDFが5.4系で、最新のArduino Coreだと5.5系と微妙に違います。そのためIR送信などで利用しているRMTのAPIで構造体の変数が増えていたりして、同一ソースでは警告がでる形となります。
次回以降で、公式サンプルがある機能以外の動作確認をしていきたいと思います。




コメント