🔎

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