M5Stackの多機種対応について
https://i.gyazo.com/490a3fc89b2ba9d5e588cbb09993b5d5.jpg
はじめに
M5Stackと仲間たちで、M5ScratchやM5bitLessの多機種対応のために色々と作業をしてきました。
その中で、経験したことを、備忘録としてまとめておきます。
機種の自動判定
Arduino IDEでは、ツール→ボードを選択すると自動的にARDUINO_*マクロが定義されます。
これを使って、#ifdefやdefined()などで場合わけが可能になります。
これらは、以下のようにboards.txtに、*.build.boardとして定義されています。
code:shell
% grep build.board ~/Library/Arduino15/packages/m5stack/hardware/esp32/2.0.2/boards.txt|grep M5
m5stack-core-esp32.build.board=M5Stack_Core_ESP32
m5stack-fire.build.board=M5STACK_FIRE
m5stick-c.build.board=M5Stick_C
m5stick-c-plus.build.board=M5Stick_C_PLUS
m5stack-atom.build.board=M5Stack_ATOM
m5stack-core2.build.board=M5STACK_Core2
m5stack-tough.build.board=M5STACK_Tough
m5stack-timer-cam.build.board=M5Stack-Timer-CAM
m5stack-unit-cam.build.board=M5Stack-Unit-CAM
m5stack-poe-cam.build.board=M5Stack-PoE-CAM
m5stack-paper.build.board=M5Stack_Paper
m5stack-coreink.build.board=M5Stack_CoreInk
% grep build.board ~/Library/Arduino15/packages/Seeeduino/hardware/samd/1.8.1/boards.txt|grep TERMINAL
seeed_wio_terminal.build.board=WIO_TERMINAL
この情報をまとめると、以下の表のようになります。
table:ボード選択で定義されるARDUINOマクロ
機器 ボード選択で定義されるマクロ #include 備考
M5Stack Basic, Gray ARDUINO_M5Stack_Core_ESP32 M5Stack.h
M5Stack Fire ARDUINO_M5STACK_FIRE M5Stack.h
M5StickC ARDUINO_M5Stick_C M5StickC.h
M5StickC plus ARDUINO_M5Stick_C_PLUS M5StickCPlus.h
ATOM Matrix ARDUINO_M5Stack_ATOM M5Atom.h
ATOM Lite ARDUINO_M5Stack_ATOM M5Atom.h ATOM Matrixと切り分けできない
M5Stack Core 2 ARDUINO_M5STACK_Core2 M5Core2.h
M5Stack Tough ARDUINO_M5STACK_Tough M5Tough.h
M5Paper ARDUINO_M5Stack_Paper M5EPD.h
M5Stack Unit CAM M5Stack-Unit-CAM M5Stack.h(?)
M5Stack PoE CAM M5Stack-PoE-CAM (?)
M5Stamp Pico STAMP_PICO (?)
M5Stamp C3 STAMP_C3 (?)
Wio Terminal ARDUINO_WIO_TERMINAL 参考情報
例えば、ヘッダファイルの読み込み部分では、以下のようなコードを書くことになります。
code:M5Scratch.ino
#if defined(ARDUINO_M5Stack_Core_ESP32) // M5Stackの場合
#include <M5Stack.h>
#elif defined(ARDUINO_M5STACK_Core2) // Core2の場合
#include <M5Core2.h>
#elif defined(ARDUINO_M5Stick_C) // M5StickCの場合
#include <M5StickC.h>
#elif defined(ARDUINO_M5Stick_C_PLUS) // M5StickC Plusの場合
#include <M5StickCPlus.h>
#elif defined(ARDUINO_M5Stack_ATOM) // ATOM Matrix/Liteの場合
#include <M5Atom.h>
#elif defined(ARDUINO_M5STACK_Tough) // Toughの場合
#include <M5Tough.h>
#endif
M5.begin()による初期化
M5.begin()による初期化も、機種によって異なる部分です。
詳しくはM5Stackシリーズ M5.begin()の互換性を読んでください。
経験上、以下のように初期化すると問題が起こらないと思います。
code:M5.begin.ino
#if defined(ARDUINO_M5Stack_ATOM)
M5.begin(true, false, true);
#elif defined(ARDUINO_M5STACK_TOUGH)
M5.begin(true, true, true, true);
#else
M5.begin();
#endif
画面描画まわりの対応
画面まわりのコードでは、LCDの横と縦のピクセル数を表すM5.Lcd.width()とM5.Lcd.height()を使って画面の大きさの違いを吸収可能です。
画面の向きをLandscapeやPortrateに変更する場合には、M5.Lcd.setRotation()を適切に設定します。
フォントのサイズの吸収に関しては、良い方法がわからないので、固定値で行っています。
機種ごとの差異を気にしないコードを書きたい場合は、LovyanGFXを利用することも検討してください。
LovyanGFXは、ESP32用のイケてるグラフィックスライブラリです。
以下のような特徴があります。
高速
高機能
たくさんのデバイスに対応: しかも、多くは自動認識
スケッチの例についての解説は、LovyanGFX入門 その11 スケッチ例解説が詳しいです。
キャラクタを動かす例は、サンプル例 → LovyanGFX → Sprite → MovingIconsをベースに使うのが一番良いと思います。
ボタンスイッチの対応
ボタンスイッチも機種により数が違ったりするので、対応に注意が必要です。
ボタンが3つの機種では、BtnA, BtnB, BtnCが、2つの機種ではBtnA, BtnBが、1つの機種ではBtnが使えます。
このため、以下のような場合分けを書くことになります。
code:button.ino
#if defined(ARDUINO_M5Stack_ATOM) // ATOMはボタン1つ
uint8_t btnA = M5.Btn.wasPressed();
#else // ATOM以外はボタンが2つ以上
uint8_t btnA = M5.BtnA.wasPressed();
uint8_t btnB = M5.BtnB.wasPressed();
#if defined(ARDUINO_M5Stack_Core_ESP32) // M5Stack Basicなどはボタンが3つ
uint8_t btnC = M5.BtnC.wasPressed();
#endif
IMUの対応
加速度やジャイロを取得できる6軸IMUは、(MPU6886を使っている機種では?)簡単にコードの共有ができます。
M5.IMU.Init()で初期化し、M5.IMU.getAccelData(&ax, &ay, &az)やM5.IMU.getGyroAdc(&gx, &gy, &gz)、M5.IMU.getTempData(&temp)で値を取得します。
code:6axis_IMU.ino
void setup() {
M5.IMU.Init();
}
void loop() {
M5.IMU.getAccelData(&ax, &ay, &az);
M5.IMU.getGyroAdc(&gx, &gy, &gz);
M5.IMU.getTempData(&temp);
}
9軸IMUであるMPU9250で地磁気も取得したい場合は、加速度やジャイロの取得も自分で行う必要があります。
大体、以下のようなコードになると思います。
code:IMU9250.ino
#include "utility/MPU9250.h"
MPU9250 Imu;
void setup() {
Imu.MPU9250SelfTest(Imu.SelfTest);
Imu.calibrateMPU9250(Imu.gyroBias, Imu.accelBias);
Imu.initMPU9250();
delay(500);
Imu.initAK8963(Imu.magCalibration);
}
void loop() {
if (Imu.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)
{
// get accel
Imu.readAccelData(Imu.accelCount); // Read the x/y/z adc values
Imu.getAres();
ax = (float)Imu.accelCount0 * Imu.aRes; // - accelBias0;
ay = (float)Imu.accelCount1 * Imu.aRes; // - accelBias1;
az = (float)Imu.accelCount2 * Imu.aRes; // - accelBias2;
// get gyro
Imu.readGyroData(Imu.gyroCount);
Imu.getGres();
gyroX = (float)Imu.gyroCount0 * Imu.gRes;
gyroY = (float)Imu.gyroCount1 * Imu.gRes;
gyroZ = (float)Imu.gyroCount2 * Imu.gRes;
// get magnetic
Imu.readMagData(Imu.magCount);
Imu.getMres();
mx = (float)Imu.magCount0 * Imu.mRes * Imu.magCalibration0;
my = (float)Imu.magCount1 * Imu.mRes * Imu.magCalibration1;
mz = (float)Imu.magCount2 * Imu.mRes * Imu.magCalibration2;
// get temp
Imu.tempCount = Imu.readTempData();
temp = ((float) Imu.tempCount) / 333.87 + 21.0;
Imu.updateTime();
}
}
おわりに
とりあえず、自分の備忘録としてまとめてみました。
おかしい点などがあると、ご指摘していただければうれしいです。
Enjoy happy M5Stack Life!!
#M5Stack #M5StickC #ATOMMatrix #ATOMLite #Core2 #Tough