AtomS3RのIMUとI2Cの関係の謎に迫る・・
発端
AtomS3Rの公式情報では、IMUは内部I2C(G0/G45)に接続されていることになっている。
G38/G39は背面ソケットの外部用、G1/G2はGroveコネクタ。I2Cバスが3系統あるように見える。
本当にそうなのか? M5Unifiedを使って実機で検証してみた。

検証1:G38/G39でI2Cスキャンしたら何が見える?
internal_imu = falseにしてIMUのM5Unified管理を切り、In_I2C(G38/G39)をスキャンしてみた。
In(G38/G39): 0x41 0x68
0x68はIMUのアドレス。G0/G45にいるはずのIMUが、G38/G39で見えた。
検証2:AtomS3Rだけの話?
同じコードをAtomS3に書き込んだ。
In(G38/G39): 0x41 0x68
IMU:OK ax=0.22
S3でも同じ。IMUはG38/G39にいる。
検証3:2バス同時に使える?
In_I2C(G38/G39)とEx_I2C(G1/G2)を同時にスキャン。IMUも有効にした状態。
In(G38/G39): 0x41 0x68
Ex(G1/G2): 0x25
IMU:OK ax=0.22
全部同時に動いた。S3でもS3Rでも同じ結果。
検証4:G38/G39をGPIOにしたらIMUは?
G38/G39をpinModeでGPIO出力にして、LED点滅させながらIMUを読んでみた。
LED38/39: ON
IMU:FAIL
IMU機能停止。S3でもS3Rでも同じ。
I2CスキャンとGPIOを同じピンで同時にやったら、両方ダメになった。共倒れ。
検証中にハマったこと
WireとIn_I2C/Ex_I2Cの混同

ArduinoのWire/Wire1は自分でピンを指定する。M5UnifiedのIn_I2C/Ex_I2Cはデバイスごとにライブラリ側がピンを決める。
「Wire1だからI2C_NUM_1でG0/G45だろう」という思い込みが生まれやすいが、実際はM5Unifiedが内部で再マッピングしている。M5Unified使うならWireの知識は忘れた方が混乱しないかもしれない。
ポート番号は明示、ピンはgetで取得
最終的にたどり着いた書き方。
M5.In_I2C.begin(I2C_NUM_1, M5.In_I2C.getSDA(), M5.In_I2C.getSCL());
M5.Ex_I2C.begin(I2C_NUM_0, M5.Ex_I2C.getSDA(), M5.Ex_I2C.getSCL());
ポート番号を明示するのは、コードを読んだときにどっちがNUM_0でどっちがNUM_1かわかるように。ピンをgetにすることで機種が変わっても楽ができるようにしてみた。
わかったこと
M5Unifiedで使うと、S3もS3Rもこうなってた。
| バス | M5Unified名 | ポート | ピン |
|---|---|---|---|
| 内部 | M5.In_I2C | I2C_NUM_1 | G38/G39 |
| 外部 | M5.Ex_I2C | I2C_NUM_0 | G1/G2(Grove) |
- IMUはIn_I2C(G38/G39)に同居。アドレスが違えば外部デバイスと共存できた
- 2バス同時にスキャンが通り、IMUも同時に動く
- G38/G39をGPIOにしたらIMU機能停止。I2CとGPIOの共存はできなかった
- S3とS3Rで挙動に差はなかった
わからなかったこと
G0/G45が何なのか、わからなかった。
IMUはG38/G39にいる。G0/G45をスキャンする手段がM5Unifiedにはない(In_I2CのgetSDA()/getSCL()は38/39を返す)。LP5562(RGB LED、0x30)がG0/G45にいるのかもしれないし、いないのかもしれない。
M5Unifiedを使わずにESP-IDFやWireで直接叩いたら、また違う結果が出るかもしれない。今回の検証はあくまでM5Unified経由の話。
検証コード
defineを切り替えるだけで、I2Cスキャン・IMU・GPIOの各組み合わせをテストできる。
#include <M5Unified.h>
// ============================================================
// I2C構成 define
// ============================================================
#define USE_IMU 1
#define USE_EX_I2C 1
#define USE_IN_AS_EXT 1
// ============ テスト機能 ============
#define TEST_LED38 0
#define TEST_LED12 0
#define LED_PERIOD_MS 1000
static unsigned long lastLedToggleTime = 0;
static bool led38_state = false;
static bool led12_state = false;
// ============ I2Cスキャン共通関数 ============
void scan_i2c_bus(m5::I2C_Class& bus, const char* label) {
M5.Display.println(label);
bool found = false;
for (uint8_t addr = 8; addr < 127; addr++) {
if (bus.scanID(addr, 100000)) {
M5.Display.printf(" 0x%02X", addr);
found = true;
}
}
if (!found) M5.Display.print(" None");
M5.Display.println("");
}
// ============ IMUテスト ============
void test_imu() {
#if USE_IMU
if (M5.Imu.update()) {
auto data = M5.Imu.getImuData();
M5.Display.printf("IMU:OK ax=%.2f\n", data.accel.x);
} else {
M5.Display.println("IMU:FAIL");
}
#endif
}
// ============ LEDテスト ============
void test_led() {
#if TEST_LED38 || TEST_LED12
unsigned long currentTime = millis();
if (currentTime - lastLedToggleTime >= LED_PERIOD_MS) {
lastLedToggleTime = currentTime;
#if TEST_LED38
led38_state = !led38_state;
digitalWrite(38, led38_state);
digitalWrite(39, led38_state);
#endif
#if TEST_LED12
led12_state = !led12_state;
digitalWrite(1, led12_state);
digitalWrite(2, led12_state);
#endif
}
#endif
}
// ============================================================
void setup() {
auto cfg = M5.config();
#if !USE_IMU
cfg.internal_imu = false;
#endif
M5.begin(cfg);
M5.Display.setTextSize(2);
M5.Display.setTextColor(WHITE, BLACK);
M5.Display.fillScreen(BLACK);
#if USE_IN_AS_EXT
bool ok = M5.In_I2C.begin(I2C_NUM_1, M5.In_I2C.getSDA(), M5.In_I2C.getSCL());
M5.Display.printf("In(G%d/%d)\n", M5.In_I2C.getSDA(), M5.In_I2C.getSCL());
M5.Display.println(ok ? " OK" : " FAIL");
#endif
#if USE_EX_I2C
bool exOk = M5.Ex_I2C.begin(I2C_NUM_0, M5.Ex_I2C.getSDA(), M5.Ex_I2C.getSCL());
M5.Display.printf("Ex(G%d/%d)\n", M5.Ex_I2C.getSDA(), M5.Ex_I2C.getSCL());
M5.Display.println(exOk ? " OK" : " FAIL");
#endif
#if USE_IMU
M5.Display.printf("IMU(G%d/%d)\n", M5.In_I2C.getSDA(), M5.In_I2C.getSCL());
#endif
#if TEST_LED38 && !USE_IN_AS_EXT
pinMode(38, OUTPUT);
pinMode(39, OUTPUT);
#endif
#if TEST_LED12 && !USE_EX_I2C
pinMode(1, OUTPUT);
pinMode(2, OUTPUT);
#endif
delay(5000);
}
void loop() {
M5.update();
M5.Display.fillScreen(BLACK);
M5.Display.setCursor(0, 0);
#if USE_EX_I2C
scan_i2c_bus(M5.Ex_I2C, "Ex(G1/2)");
#endif
#if USE_IN_AS_EXT
scan_i2c_bus(M5.In_I2C, "In(G38/39)");
#endif
test_imu();
test_led();
#if TEST_LED38
M5.Display.println("LED38/39:");
M5.Display.println(led38_state ? " ON" : " OFF");
#endif
#if TEST_LED12
M5.Display.println("LED1/2:");
M5.Display.println(led12_state ? " ON" : " OFF");
#endif
M5.Display.display();
delay(300);
}
検証環境:M5Unified 0.2.x / PlatformIO / AtomS3 + AtomS3R
Discussion