Repository navigation
ESP32: pin_backup_t::backup() が入力選択フィールドをピン番号ではなく信号番号と比較している #348
Description
概要
ESP32 系の pin_backup_t::backup() は、ピンから入力信号が来ているかを判定しています。その判定で、入力選択レジスタのフィールドを信号番号と比べていますが、このフィールドに入っているのは GPIO 番号です。そのため _in_func_num が常に -1 のままになり、restore() で入力側の割り当て (GPIO.func_in_sel_cfg[...]) が復元されません。
該当箇所
M5GFX/src/lgfx/v1/platforms/esp32/common.inl
Lines 710 to 722 in a85d1dc
| size_t func_num = ((_gpio_func_out_reg >> GPIO_FUNC0_OUT_SEL_S) & GPIO_FUNC0_OUT_SEL_V); | |
| if (func_num < sizeof(GPIO.func_in_sel_cfg) / sizeof(GPIO.func_in_sel_cfg[0])) { | |
| #if defined ( GPIO_FUNC0_IN_SEL_CFG_REG ) | |
| _gpio_func_in_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC0_IN_SEL_CFG_REG + (func_num * 4)); | |
| bool hit = func_num == ((_gpio_func_in_reg >> GPIO_FUNC0_IN_SEL_S) & GPIO_FUNC0_IN_SEL_V); | |
| #else | |
| _gpio_func_in_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC1_IN_SEL_CFG_REG + ((func_num - 1) * 4)); | |
| bool hit = func_num == ((_gpio_func_in_reg >> GPIO_FUNC1_IN_SEL_S) & GPIO_FUNC1_IN_SEL_V); | |
| #endif | |
| if (hit) { | |
| _in_func_num = func_num; | |
| // ESP_LOGD("DEBUG","backup pin:%d : func_num:%d", pin_num, _in_func_num); |
size_t func_num = ((_gpio_func_out_reg >> GPIO_FUNC0_OUT_SEL_S) & GPIO_FUNC0_OUT_SEL_V);
if (func_num < sizeof(GPIO.func_in_sel_cfg) / sizeof(GPIO.func_in_sel_cfg[0])) {
#if defined ( GPIO_FUNC0_IN_SEL_CFG_REG )
_gpio_func_in_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC0_IN_SEL_CFG_REG + (func_num * 4));
bool hit = func_num == ((_gpio_func_in_reg >> GPIO_FUNC0_IN_SEL_S) & GPIO_FUNC0_IN_SEL_V);
#else
_gpio_func_in_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC1_IN_SEL_CFG_REG + ((func_num - 1) * 4));
bool hit = func_num == ((_gpio_func_in_reg >> GPIO_FUNC1_IN_SEL_S) & GPIO_FUNC1_IN_SEL_V);
#endif誤りの理由
func_numは、ピンのGPIO_FUNCn_OUT_SEL_CFG_REGから読んだペリフェラルの信号番号です。GPIO_FUNCm_IN_SEL_CFG_REGは信号番号ごとに並んだレジスタで、そのIN_SEL(func_sel/in_sel) フィールドは、その信号に接続されている GPIO (パッド) 番号です。TRM とレジスタ定義ヘッダにもそう書かれています (例: ESP32-P4 のgpio_struct.h: "s=0-56: connect GPIO[s] to this port. s=0x3F: set this port always high level.")。- そのため、この判定が真になるのは、信号番号とピン番号がたまたま一致したときだけです。
例: ESP32-S3 で I2C0 の SDA を GPIO2 にした場合、func_num は I2C0 SDA の信号番号です。func_in_sel_cfg[func_num] には GPIO 2 が入っています。func_num != 2 なので、_in_func_num は -1 のままです。
修正案
両方の分岐で、ピン番号と比べます。
bool hit = pin_num == ((_gpio_func_in_reg >> GPIO_FUNC0_IN_SEL_S) & GPIO_FUNC0_IN_SEL_V);
...
bool hit = pin_num == ((_gpio_func_in_reg >> GPIO_FUNC1_IN_SEL_S) & GPIO_FUNC1_IN_SEL_V);こうすると、入力と出力の信号番号が同じペリフェラル (I2C の SDA/SCL など) の入力が見つかるようになります。「出力の信号番号から入力を探す」という元の意図にも合います。
影響
M5GFX の通常の使い方では、ピンを借りている間に入力側の割り当てが変わることはほとんどありません。そのため、復元されなくても実害はたいていありません。
問題になるのは、pin_backup_t で退避したピンの入力側の割り当てが、復元までの間に変わった場合です (esp_rom_gpio_connect_in_signal などで変えた場合)。このとき restore() の後も、ペリフェラルの入力は元に戻りません。
経緯
pin_backup_t を LovyanGFX / M5GFX から移植している M5UnitUnified で見つけました。M5UnitUnified では _in_func_num の値を「そのピンが GPIO matrix を通っているか」の判定に使っているため、このバグで機能が動かず、表に出ました。M5UnitUnified 側は、上の修正案と同じ比較にして次のリリースで直します。
LovyanGFX にも同じ問題があります: lovyan03/LovyanGFX#963
Activity
[-]ESP32: pin_backup_t::backup() compares the input-select field with the signal number instead of the pin number[/-][+]ESP32: pin_backup_t::backup() が入力選択フィールドをピン番号ではなく信号番号と比較している[/+]GOB52 commentedon Oct 12, 2026
実機確認の結果
LovyanGFX で確認しました。M5GFX の
pin_backup_t::backup()も同じコードです (lovyan03/LovyanGFX#963 と同内容)。M5Stack Core (ESP32) と CoreS3 (ESP32-S3) で確かめました。比べたのは 2 つのビルドです。
28fd2f8a(develop)func_num ==をpin_num ==に変えたもの (違いはこの 2 行だけ)どちらも pioarduino (arduino-esp32 3.x) でビルドしました。
手順
Wire.begin()する。GPIO_FUNCn_OUT_SELの信号番号 (func_num) とfunc_in_sel_cfg[func_num]のin_selを読む。lgfx::gpio::pin_backup_tで SDA / SCL を退避する。esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, I2CEXT0_SDA/SCL_IN_IDX)で入力側の割り当てを書き換える。restore()した後、in_selを読み、内蔵デバイスに probe (endTransmission) する。結果
元のまま
修正版
元のまま
修正版
func_num/in_selfunc_num/in_selfunc_num == in_selpin_num == in_selrestore()後のin_sel(SDA / SCL)restore()後の probe (0 = ACK)(56 = 0x38 は、入力を常に High にする定数です。)
in_selに入っているのは信号番号ではなくピン番号であることがわかります。そのため今の判定func_num == in_selは成り立たず、_in_func_numは -1 のままです。試験に使ったスケッチ
ainyan03 commentedon Oct 13, 2026
詳しい報告と、実機での確認・再現スケッチをありがとうございます。ご指摘のとおりで、修正の PR を出しました: #349
ご提案の比較 (
pin_num ==) をもとに、次の 2 点を加えています。SIG_IN_SEL(GPIO matrix 経由のビット) が立っているときだけ一致とみなします。IO MUX 直結の入力ではIN_SELに古い値が残っていることがあり、それがたまたまピン番号と一致すると、そのピンのrestore()が別のピンへの割り当てを元に戻してしまうためです。restore()での入力選択の書き戻しを、IO_MUX・GPIO_PIN・出力選択を戻した後に移しました。これまで実行されていなかった経路で、借り手の設定が残ったパッドへ入力を繋ぎ直さないようにするためです。内部 I2C の SDA/SCL を退避し、入力を定数 High に付け替えてから
restore()する試験で、修正前はIN_SELが定数のまま、修正後は元のピン番号に戻ることを ESP32 / ESP32-S3 / ESP32-P4 の実機で確かめています。ainyan03 commentedon Oct 13, 2026
修正 (#349) は M5GFX 0.2.33 としてリリースされました。ライブラリを更新してお使いください。確認いただけたら、この Issue は閉じてよいと思います。