Repository navigation
ESP32: I2C コントローラが bus busy のまま固まると、以降の転送がすべて失敗する #362
Description
ESP32: I2C コントローラが bus busy のまま固まると、以降の転送がすべて失敗する
症状
M5Stack Core (ESP32) の Ex_I2C (port 0) に Unit UltraSonic I2C (RCWL9620) を繋ぎ、次の手順で読み取ると、read が毎回失敗します。100 kHz でも 50 kHz でも同じです。
0x01を write する (測定開始)- 150 ms 待つ
- 3 バイト read する
- 同じ配線で Arduino Wire (ESP-IDF の legacy driver) を使うと、毎回成功します。
- NanoC6 (ESP32-C6) / NanoH2 (ESP32-H2) の Ex_I2C では起きません。
原因
測定待ちの間、ESP32 側は何も送っていないのに、I2C0 が bus_busy=1 と TIME_OUT のまま固まります。
| タイミング | bus_busy | int_raw |
|---|---|---|
| write が正常に終わった直後 | 0 | 0x1803 |
| 150 ms 待った後 | 1 | 0x1903 (TIME_OUT が立つ) |
beginTransaction() は、busy の解除を 128 µs 待つだけで先に進みます。
https://github.com/m5stack/M5GFX/blob/0.2.33/src/lgfx/v1/platforms/esp32/common.inl#L2280-L2290
ESP32 には FSM reset bit がないので、ここでは復旧できません (fsm_rst は ESP32 では入りません)。
https://github.com/m5stack/M5GFX/blob/0.2.33/src/lgfx/v1/platforms/esp32/common.inl#L2305-L2311
そのため、固まった状態のままアドレスフェーズに進み、最初の i2c_wait で失敗します。
ESP-IDF のドライバーは、コマンドを始める前に bus busy (または前回 TIMEOUT) であれば i2c_hw_fsm_reset() で復旧します。Wire で読めるのはこのためです。
https://github.com/espressif/esp-idf/blob/v4.4.7/components/driver/i2c.c#L1451-L1454
develop (6f81929) でも同じコードです。
修正案
save_reg() の直後で、まだ busy なら i2c_stop() でバスとコントローラを復旧します。
i2c_context[i2c_port].save_reg(dev);
+ // Still busy although no transfer of ours is running: a slave glitch can leave the
+ // controller waiting for a STOP forever (ESP32 has no FSM reset bit). Recover the bus
+ // and the controller as the ESP-IDF driver does before it starts a command.
+ if (getBusBusy(dev))
+ {
+ ESP_LOGV("LGFX", "i2c::beginTransaction : bus busy, recover");
+ i2c_stop(i2c_port);
+ }
+save_reg() の 後 に置くのは、転送の最後の load_reg() で、ESP-IDF ドライバーと共有しているレジスタ (タイミング / filter / int_ena) を元の値に戻すためです。save_reg() の前で復旧すると、リセット後の値が保存され、ESP-IDF ドライバー側の設定がその値のまま残ります。
lgfx のロックは ESP-IDF ドライバーと共有していないので、2 つのドライバーを同時に使うのは今も安全ではありません。順番に使う場合、128 µs 待っても busy であれば、どちらの転送も動いていません (ESP-IDF ドライバーはコマンドを終えてから return します)。なので、この復旧が止めるのは、空いているのに固まったコントローラだけです。
確認結果
M5GFX 0.2.33
環境: M5GFX 0.2.33、M5Unified 0.2.26、Core は Arduino-ESP32 2.0.17 / ESP-IDF 4.4.7、NanoH2 は Arduino-ESP32 3.3.12 / ESP-IDF 5.5.5。どちらも 100 kHz で、M5Unified の Ex_I2C を使っています。
| M5GFX | ボード | 結果 |
|---|---|---|
| 0.2.33 | Core + RCWL9620 | 最初の読み取りから失敗する (再現) |
| 0.2.33 + 修正案 | Core + RCWL9620 | 単発 20/20、150 ms 周期で読み取り失敗 0 回 (3 回実行) |
| 0.2.33 + 修正案 | NanoH2 + RCWL9620 | 失敗なし (修正前と同じ) |
| (比較) Arduino Wire | Core + RCWL9620 | 単発 20/20、150 ms 周期で失敗 0 回 |
修正案の差分は上と同じで、0.2.33 では common.inl L2290 の save_reg(dev); の直後に入ります。
詳細 (M5GFX a85d1dc)
環境: M5GFX a85d1dc (develop, 0.2.32 相当) にこの差分を当てたもの、Arduino-ESP32 2.0.17 / ESP-IDF 4.4.7、Core 100 kHz
Core + RCWL9620 (単発 20 回 + 150 ms 周期を 30 区間)
| 単発 | 周期 (中央値 / 最大) | |
|---|---|---|
| 修正前 | すべて失敗 | - |
| 修正後 | 20/20 | 150 / 150 ms |
| Arduino Wire | 20/20 | 150 / 150 ms |
- RCWL9620 では、測定のたびに busy と TIME_OUT が同時に出ます (52 回中 52 回)。
- 復旧 (
i2c_stop()) にかかる時間は平均 25〜29 µs、最大 59 µs です。 - 復旧直後の転送は一度も失敗していません。
回帰の確認
| ボード / デバイス | 修正前 | 修正後 |
|---|---|---|
| Core 内蔵 I2C (0x10 / 0x68 / 0x75)、read 200 回 | 200/200、574 µs | 200/200、574 µs (復旧 0 回) |
| Core + Unit DAC2 (0x59)、write 200 回 | 200/200、416 µs | 200/200、416 µs (復旧 0 回) |
| NanoC6 + RCWL9620 | test 4/4 PASS | 20/20 (busy / TIME_OUT は出ない、復旧 0 回) |
| NanoH2 + RCWL9620 | test 4/4 PASS | 20/20、test 4/4 PASS (busy / TIME_OUT は出ない、復旧 0 回) |
NanoC6 / NanoH2 は Arduino-ESP32 3.3.12 / ESP-IDF 5.5.5 で確認しました。ESP32-S3 は試していません。
Activity
ainyan03 commentedon Oct 20, 2026
詳しい調査と修正案をありがとうございます。修正案のとおりの変更を
developにマージしました (#368)。次のリリースに含まれます。こちらでも M5Stack Gray + Unit UltraSonic I2C (PortA、100 kHz、Arduino-ESP32 2.0.17) で再現しました。150 ms 待った後は毎回
bus_busy=1・int_raw=0x1903のままで、修正前は 20 回中 0 回しか読めませんでした。修正後は、この固まった状態で転送が失敗することはなくなりました。内蔵 I2C (IMU の読み取り 40 回、release → begin 10 回) にも変化はありません。1 点だけ、手元の個体では修正後も一部の読み取りが 2 バイト目で失敗しました (
connection_lost、100 回中 68〜71 回成功。Wire では 93 回)。復旧を周辺リセットだけにしても同じ率だったので、今回の変更とは別の原因とみて、別途調べます。そちらの環境では 20/20 だったとのことなので、個体や配線の差かもしれません。可能であれば
developで確認していただけますか。問題がなければ、この issue はクローズしてよいと思います。ainyan03 commentedon Oct 20, 2026
前回のコメントで「別途調べる」とした、修正後も一部の読み取りが失敗する件について、ロジックアナライザで原因がわかったので共有します。
RCWL-9620 の仕様書には I2C クロックの上限やタイミングの記載がありません。以下は手元の 1 個体での実測に基づく情報です。
結論
観測した内容
失敗していたのは、前回書いた 2 バイト目ではなく 3 バイト目でした (訂正します)。
環境: M5Stack Gray、PortA (G21/G22)、Arduino-ESP32 2.0.17、ロジックアナライザ 50 MS/s。
同じセンサーで読み取りが時々失敗する場合は、I2C の周波数を 80 kHz 程度まで下げてみてください (M5Unified の
M5.Ex_I2Cなら、各関数のfreq引数に80000を渡します)。ESP32 I2C: decide TwoWire::end() in one place; recover a controller l…
ainyan03 commentedon Oct 20, 2026
お待たせしました。この問題の修正を含む M5GFX 0.2.34 がリリースされました。ライブラリマネージャーから 0.2.34 に更新して、お手元の環境でご確認いただけますか。
問題がなければ、この issue はクローズしていただいて構いません。
なお、前のコメントで補足した Unit UltraSonic I2C 側のタイミングによる失敗 (100 kHz で 1 割前後) は 0.2.34 でも変わりません。発生する場合は I2C の周波数を 80 kHz 程度まで下げてください。
GOB52 commentedon Oct 20, 2026
0.2.34 で確認しました。M5Stack Basic (Core) + Unit Ultrasonic I2C (PortA) で、次のとおり問題ありません。