From 7349ed8c1dee403773ea4d8e792ebfafe4f437d1 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 19 Aug 2026 12:40:44 +0000 Subject: [PATCH 1/7] Probe the address the board detection is looking for The helper that decides which board this is did not speak I2C. It made no start condition, clocked every address bit twice over twenty pulses, and read the ninth bit a millisecond after the clock had gone back down, when any device that acknowledged had long released the line. What came back was the pull-up on the pins, not an answer from the address, and the caller read that value as the device being present. Every board on the branch has pull-ups on its internal bus, so the first condition matched on all of them. AtomS3RExt is checked first and Atom VoiceS3R, which carries an ES8311 where the other carries a BMI270, was reported as AtomS3RExt. Send the address over the software I2C port instead, which drives the lines open drain, keeps away from the peripheral, and reports the acknowledge it actually receives. The return value now means what the five call sites always read it as. A stop is offered first, because a device left mid transfer needs one to let go and one board in this same file already records a device that needed exactly that; it is made by driving low and releasing, never by driving high, so a device holding a line is not fought over. Pins that carry no pulled up bus are left alone, the same test the display autodetection uses on pins that may not be I2C at all. A single retry covers a device that is slower to answer than the old test, which never waited for an answer in the first place. The name says what it does now: the same name in M5GFX belongs to a different function with a different contract. --- src/M5Unified.cpp | 141 +++++++++++++++++++++++++++++----------------- src/M5Unified.hpp | 8 ++- 2 files changed, 95 insertions(+), 54 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index c447497..2e218bf 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1368,65 +1368,100 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { static constexpr gpio_num_t CoreInk_BUTTON_PWR_PIN = GPIO_NUM_27; #endif - bool M5Unified::_detect_i2c_device(uint8_t sda, uint8_t scl, uint8_t addr, const uint8_t* cmd_list) + bool M5Unified::_probe_i2c_addr(uint8_t sda, uint8_t scl, uint8_t addr) { - uint32_t result = 0; #if defined(M5UNIFIED_PC_BUILD) - return result; + return false; #else - m5gfx::gpio::pin_backup_t pin_backup[] = {scl, sda}; + /// アドレスの存在確認はソフトウェア I2C ポートで行う (オープンドレイン駆動で + /// ACK 競合が起きず、ハードウェアのペリフェラルにも触れない)。 + /// ポートは M5GFX の autodetect probe と同じ -1 を使う。負ポートは 2 本しかなく、 + /// probe は使い終わりに release するので逐次実行なら共有できる。2 本目を + /// ライブラリ側で占有せず、利用者のソフト I2C バス用に空けておく。 + static constexpr int_fast16_t probe_i2c_port = -1; + + m5gfx::gpio::pin_backup_t pin_backup[] = { scl, sda }; + + // デバイスの電源投入直後の安定待ち。バスは解放した状態で待つ + // (SCL を Low に駆動したまま待つと、バスタイムアウトを持つデバイスに対して不正)。 + m5gfx::pinMode(scl, m5gfx::pin_mode_t::input_pullup); + m5gfx::pinMode(sda, m5gfx::pin_mode_t::input_pullup); + m5gfx::delay(50); + + // 前回の通信の途中で止まっているデバイスに STOP を見せて論理状態を戻す。 + // START だけで戻らないデバイスが実在する (同ファイルの StampS3/Capsule 判別に + // 「STOP を出さないと正しく動作しないデバイスがあった (UnitHEART MAX30100)」の記録がある)。 + // 線を Low へ駆動するか解放するかの 2 状態しか使わない (High を駆動しない) ので、 + // デバイスが線を握っていてもパッド同士の衝突にはならない。 { - if(cmd_list == nullptr) + auto line_lo = [](uint8_t pin) + { m5gfx::gpio_lo(pin); m5gfx::pinMode(pin, m5gfx::pin_mode_t::output); }; + auto line_hi = [](uint8_t pin) + { m5gfx::pinMode(pin, m5gfx::pin_mode_t::input_pullup); }; + for (int i = 0; i < 8; ++i) { - uint8_t cmd_low[] = { - m5gfx::gpio::command_write_low, scl, - m5gfx::gpio::command_mode_output, scl, // SCL - m5gfx::gpio::command_write_low, sda, - m5gfx::gpio::command_mode_output, sda, // SDA - m5gfx::gpio::command_end, - }; - m5gfx::gpio::command(cmd_low); + line_lo(scl); m5gfx::delayMicroseconds(5); + line_lo(sda); m5gfx::delayMicroseconds(5); + line_hi(scl); m5gfx::delayMicroseconds(5); + line_hi(sda); m5gfx::delayMicroseconds(5); // SCL High 中の SDA Low->High = STOP } - else m5gfx::gpio::command(cmd_list); - - delay(50); // 延时 50ms,保证设备上电稳定 - - for (uint8_t i2caddr : (const uint8_t[]){static_cast(addr << 1)}) { //detect address - delay(2); // 小延时 - bool nack = true; - // I2C START - m5gfx::gpio_lo(sda); // SDA LOW = START - for (int cycle = 0; cycle < 20; ++cycle) { - // SCL toggle - m5gfx::gpio_hi(scl); - delay(1); - m5gfx::gpio_lo(scl); - delay(1); - - if (cycle & 1) { - if (cycle == 17) { - nack = m5gfx::gpio_in(sda); // 读 ACK - } - } else { - if (i2caddr & 0x80) { - m5gfx::gpio_hi(sda); - } else { - m5gfx::gpio_lo(sda); - } - i2caddr <<= 1; - if (cycle >= 16) { - m5gfx::pinMode(sda, (cycle == 16) ? m5gfx::pin_mode_t::input : m5gfx::pin_mode_t::output); - } - } - } - m5gfx::gpio_hi(sda); // SDA HIGH = STOP - result = result << 1 | nack; + } + + // 指定ピンが「外部プルアップの載った I2C バス」かどうかを先に確かめる。 + // ここは I2C ピンとは限らない場所を駆動する機種判別なので、判定を外すと + // 機種の読みが変わる。判定方式は M5GFX の autodetect probe と同一のものを使う + // (実機での実績がある形から動かさない。変えるなら該当機種すべてで再検証が要る)。 + // + // 4 回の read のうち、後半 2 回は入力プルダウンにしても High になること + // (= 外部プルアップが内部プルダウンに勝つこと) を確認するもの。 + // 弱いプルアップ (内部プルダウンと同程度の抵抗値) では通らないが、 + // 「強いプルアップがある = I2C バスである」を要求するのがこの判定の趣旨。 + const uint8_t cmd_bus_check_list[] = { + m5gfx::gpio::command_write_low , scl, + m5gfx::gpio::command_read , scl, // low チェック + m5gfx::gpio::command_write_low , sda, + m5gfx::gpio::command_read , sda, // low チェック + m5gfx::gpio::command_mode_input_pulldown, scl, + m5gfx::gpio::command_delay_usec , 10, + m5gfx::gpio::command_read , scl, // 外部プルアップがあるなら High + m5gfx::gpio::command_mode_input_pullup , scl, + m5gfx::gpio::command_mode_input_pulldown, sda, + m5gfx::gpio::command_delay_usec , 10, + m5gfx::gpio::command_read , sda, // 外部プルアップがあるなら High + m5gfx::gpio::command_mode_input_pullup , sda, + m5gfx::gpio::command_end + }; + m5gfx::pinMode(scl, m5gfx::pin_mode_t::output); + m5gfx::pinMode(sda, m5gfx::pin_mode_t::output); + if (m5gfx::gpio::command(cmd_bus_check_list) != 0x03) + { + for (auto& backup : pin_backup) { backup.restore(); } + return false; + } + + bool hit = false; + if (m5gfx::i2c::init(probe_i2c_port, sda, scl).has_value()) + { + // クロックが上がらないバス、前の通信の途中でデバイスがデータ線を握っている + // バスは、いずれも beginTransaction 側が検出して復旧または中断する。 + // NACK の場合も beginTransaction 自体は成功を返し (内部で STOP を出して + // エラーをラッチする)、そのエラーは endTransaction が報告する。 + // したがって両方の成功をもって「ACK が返った」と判定する。 + // 旧実装はプルアップの有無しか見ておらず、デバイスが応答するかは確かめて + // いなかった。厳密に ACK を要求するようになったぶん、起動が遅い個体を + // 取りこぼす余地ができるので、間を空けて一度だけ試し直す。 + for (int attempt = 0; attempt < 2 && !hit; ++attempt) + { + if (attempt) { m5gfx::delay(20); } + hit = m5gfx::i2c::beginTransaction(probe_i2c_port, addr, 100000, false).has_value() + && m5gfx::i2c::endTransaction(probe_i2c_port).has_value(); } + m5gfx::i2c::release(probe_i2c_port); } for (auto& backup : pin_backup) { backup.restore(); } - return result; + return hit; #endif } @@ -1656,7 +1691,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { if (board == board_t::board_unknown) { /// PowerHub ? - if (_detect_i2c_device(45, 48, 0x50)) { + if (_probe_i2c_addr(45, 48, 0x50)) { board = board_t::board_M5PowerHub; } } @@ -1665,16 +1700,16 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case 1: // EFUSE_PKG_VERSION_ESP32S3PICO: // LGA56 if (board == board_t::board_unknown) { /// AtomS3RExt / AtomS3RCam have a BMI270 on the internal I2C bus. - if (_detect_i2c_device(45, 0, 0x68) - || _detect_i2c_device(45, 0, 0x69)) { + if (_probe_i2c_addr(45, 0, 0x68) + || _probe_i2c_addr(45, 0, 0x69)) { board = board_t::board_M5AtomS3RExt; } /// Stamp-S3Bat ? - else if (_detect_i2c_device(48, 47, 0x6E)) { + else if (_probe_i2c_addr(48, 47, 0x6E)) { board = board_t::board_M5StampS3Bat; } /// AtomEchoS3R ? - else if(_detect_i2c_device(45, 0, 0x18)) { + else if(_probe_i2c_addr(45, 0, 0x18)) { board = board_t::board_M5AtomVoiceS3R; } /// StampS3Mini has no other onboard device that can identify it. diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp index 7e94124..8327b91 100644 --- a/src/M5Unified.hpp +++ b/src/M5Unified.hpp @@ -666,7 +666,13 @@ namespace m5 board_t _check_boardtype(board_t); void _setup_i2c(board_t); void _setup_led(board_t); - bool _detect_i2c_device(uint8_t sda, uint8_t scl, uint8_t addr, const uint8_t* cmd_list=nullptr); + /// 指定ピンのバス上に、指定した 7bit アドレスのデバイスが居るかを調べる。 + /// (M5GFX にも同名だった _probe_i2c_addr があるが、あちらは複数アドレスを + /// ビット列で返す別物。取り違えを避けるため名前を分けている) + /// @return true = ACK が返った (デバイスが存在する)。 + /// false は「ACK を確認できなかった」であり、不在のほかバスが + /// 成立していない場合・probe 用ポートの初期化に失敗した場合を含む。 + bool _probe_i2c_addr(uint8_t sda, uint8_t scl, uint8_t addr); static void _setup_pinmap(board_t); static bool _speaker_enabled_cb_core2(void* args, bool enabled); From 9c97ef098d40617704bf0eed01e05c6fa12c0857 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 19 Aug 2026 21:28:53 +0000 Subject: [PATCH 2/7] Take the internal bus first, and ask the sensor that is always there The three boards that share the internal bus were separated by a probe on another pair of pins in between: BMI270, then the Stamp-S3Bat address on 48 and 47, then the ES8311. Atom VoiceS3R drives its speaker out of GPIO48, so a board fully identified by its own bus reached for the I2S data line before it got there. Take the internal bus first and only step off it when nothing there answered. Within that bus the BMI270 is asked first. The AtomS3R-Ext and the AtomS3RCam are the same board, and on the Ext the camera end is a small breadboard the owner is free to wire up, so an address that is not the BMI270 can appear on the internal bus of a board that is one. The sensor that is always fitted decides, and something added later cannot take the identity away. Measured on hardware: Atom VoiceS3R answers at 0x18 alone, AtomS3RExt and both AtomS3RCam variants at 0x68 alone, and StampS3Mini has a pull-up on one line of the pair and nothing on the other. --- src/M5Unified.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 2e218bf..6e4b64e 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1699,19 +1699,27 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case 1: // EFUSE_PKG_VERSION_ESP32S3PICO: // LGA56 if (board == board_t::board_unknown) { + /// 内部 I2C バス (SDA45/SCL0) に載るデバイスで先に決める。他ピンの probe を + /// 間に挟まないので、ここで確定する機種は 48/47 に一切触れずに済む + /// (AtomVoiceS3R では GPIO48 が I2S の DOUT)。 + /// /// AtomS3RExt / AtomS3RCam have a BMI270 on the internal I2C bus. + /// この 2 機種は基板が共通で、カメラ部がユーザーの扱えるブレッドボードに + /// なっているため、内部バスにユーザーのデバイスが載りうる。オンボードで + /// 必ず存在する BMI270 を先に見て、後から載ったアドレスに identity を + /// 奪われないようにする。 if (_probe_i2c_addr(45, 0, 0x68) || _probe_i2c_addr(45, 0, 0x69)) { board = board_t::board_M5AtomS3RExt; } - /// Stamp-S3Bat ? + /// AtomVoiceS3R ? + else if (_probe_i2c_addr(45, 0, 0x18)) { + board = board_t::board_M5AtomVoiceS3R; + } + /// Stamp-S3Bat ? (内部バスに何も居なかったときだけ別のピンを触る) else if (_probe_i2c_addr(48, 47, 0x6E)) { board = board_t::board_M5StampS3Bat; } - /// AtomEchoS3R ? - else if(_probe_i2c_addr(45, 0, 0x18)) { - board = board_t::board_M5AtomVoiceS3R; - } /// StampS3Mini has no other onboard device that can identify it. else { board = board_t::board_M5StampS3Mini; From 9709e5f394c86c492ae80ebdbf375a45c4306fb0 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 19 Aug 2026 21:29:05 +0000 Subject: [PATCH 3/7] Confirm the bus before driving it, and keep the probe slot apart The probe held the pins low nine times over before it had established that they carry an I2C bus at all: the stop preamble ran first, and only then came the test for a pull-up. These pins are how the board is identified, so until that test passes they may be anything. Run the test first and offer the stop only once the pins have answered for themselves. Take the second software I2C slot rather than the one the display autodetection uses. The slots carry no ownership - opening one takes over whatever settings were there - so two libraries sharing a slot rests on nothing but the order they happen to run in. Drop the retry. Nothing between the two attempts changes the state of the device being asked: no reset, no power, no clock. It doubled the time spent on every address that is not answering, which the boards with nothing on their internal bus pay three times over. --- src/M5Unified.cpp | 57 +++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 6e4b64e..53bc11f 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1375,10 +1375,10 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { #else /// アドレスの存在確認はソフトウェア I2C ポートで行う (オープンドレイン駆動で /// ACK 競合が起きず、ハードウェアのペリフェラルにも触れない)。 - /// ポートは M5GFX の autodetect probe と同じ -1 を使う。負ポートは 2 本しかなく、 - /// probe は使い終わりに release するので逐次実行なら共有できる。2 本目を - /// ライブラリ側で占有せず、利用者のソフト I2C バス用に空けておく。 - static constexpr int_fast16_t probe_i2c_port = -1; + /// ポートは M5GFX の autodetect probe (-1) と分ける。ソフト I2C のスロットは + /// 所有権を持たず、init が既存の設定を黙って奪う作りなので、ライブラリ同士で + /// 同じスロットを共有しない。 + static constexpr int_fast16_t probe_i2c_port = -2; m5gfx::gpio::pin_backup_t pin_backup[] = { scl, sda }; @@ -1388,25 +1388,6 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { m5gfx::pinMode(sda, m5gfx::pin_mode_t::input_pullup); m5gfx::delay(50); - // 前回の通信の途中で止まっているデバイスに STOP を見せて論理状態を戻す。 - // START だけで戻らないデバイスが実在する (同ファイルの StampS3/Capsule 判別に - // 「STOP を出さないと正しく動作しないデバイスがあった (UnitHEART MAX30100)」の記録がある)。 - // 線を Low へ駆動するか解放するかの 2 状態しか使わない (High を駆動しない) ので、 - // デバイスが線を握っていてもパッド同士の衝突にはならない。 - { - auto line_lo = [](uint8_t pin) - { m5gfx::gpio_lo(pin); m5gfx::pinMode(pin, m5gfx::pin_mode_t::output); }; - auto line_hi = [](uint8_t pin) - { m5gfx::pinMode(pin, m5gfx::pin_mode_t::input_pullup); }; - for (int i = 0; i < 8; ++i) - { - line_lo(scl); m5gfx::delayMicroseconds(5); - line_lo(sda); m5gfx::delayMicroseconds(5); - line_hi(scl); m5gfx::delayMicroseconds(5); - line_hi(sda); m5gfx::delayMicroseconds(5); // SCL High 中の SDA Low->High = STOP - } - } - // 指定ピンが「外部プルアップの載った I2C バス」かどうかを先に確かめる。 // ここは I2C ピンとは限らない場所を駆動する機種判別なので、判定を外すと // 機種の読みが変わる。判定方式は M5GFX の autodetect probe と同一のものを使う @@ -1439,6 +1420,25 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { return false; } + // 前回の通信の途中で止まっているデバイスに STOP を見せて論理状態を戻す。 + // START だけで戻らないデバイスが実在する (同ファイルの StampS3/Capsule 判別に + // 「STOP を出さないと正しく動作しないデバイスがあった (UnitHEART MAX30100)」の記録がある)。 + // 線を Low へ駆動するか解放するかの 2 状態しか使わない (High を駆動しない) ので、 + // デバイスが線を握っていてもパッド同士の衝突にはならない。 + { + auto line_lo = [](uint8_t pin) + { m5gfx::gpio_lo(pin); m5gfx::pinMode(pin, m5gfx::pin_mode_t::output); }; + auto line_hi = [](uint8_t pin) + { m5gfx::pinMode(pin, m5gfx::pin_mode_t::input_pullup); }; + for (int i = 0; i < 8; ++i) + { + line_lo(scl); m5gfx::delayMicroseconds(5); + line_lo(sda); m5gfx::delayMicroseconds(5); + line_hi(scl); m5gfx::delayMicroseconds(5); + line_hi(sda); m5gfx::delayMicroseconds(5); // SCL High 中の SDA Low->High = STOP + } + } + bool hit = false; if (m5gfx::i2c::init(probe_i2c_port, sda, scl).has_value()) { @@ -1447,15 +1447,8 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { // NACK の場合も beginTransaction 自体は成功を返し (内部で STOP を出して // エラーをラッチする)、そのエラーは endTransaction が報告する。 // したがって両方の成功をもって「ACK が返った」と判定する。 - // 旧実装はプルアップの有無しか見ておらず、デバイスが応答するかは確かめて - // いなかった。厳密に ACK を要求するようになったぶん、起動が遅い個体を - // 取りこぼす余地ができるので、間を空けて一度だけ試し直す。 - for (int attempt = 0; attempt < 2 && !hit; ++attempt) - { - if (attempt) { m5gfx::delay(20); } - hit = m5gfx::i2c::beginTransaction(probe_i2c_port, addr, 100000, false).has_value() - && m5gfx::i2c::endTransaction(probe_i2c_port).has_value(); - } + hit = m5gfx::i2c::beginTransaction(probe_i2c_port, addr, 100000, false).has_value() + && m5gfx::i2c::endTransaction(probe_i2c_port).has_value(); m5gfx::i2c::release(probe_i2c_port); } for (auto& backup : pin_backup) { From 87e604abcd23c80764c31c9f8071a40ab4a94af2 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 19 Aug 2026 21:29:15 +0000 Subject: [PATCH 4/7] Require the M5GFX that has the software I2C the probe needs library.json and idf_component.yml already ask for it; the Arduino manifest asked for M5GFX with no version at all. An older one has no software I2C behind the negative port numbers, and the port number reaches the hardware port table as an index instead. --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index bdc5630..ef6547e 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Display url=https://github.com/m5stack/M5Unified.git architectures=esp32 includes=M5Unified.h -depends=M5GFX +depends=M5GFX (>=0.2.27) From 0ce7b470de91d44e8989b5688fc8d0c2e10d9b28 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 19 Aug 2026 22:08:05 +0000 Subject: [PATCH 5/7] Free a data line that is still held before giving up on the bus Checking for the pull-up before offering a stop condition left the one bus that most needs the stop unable to get it. A device interrupted mid read holds the data line down, and a software reset does not take its power away, so the line is still held when the board is identified again. The check reads that as no bus and returns before the stop is ever made. The signature is specific enough to act on: the clock comes back high against the internal pull-down while the data line stays low. That is a held bus, not a pin without a pull-up, where neither line comes back. Offer the stop in that case and look again; every other answer returns as it did. --- src/M5Unified.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 53bc11f..6c67003 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1412,9 +1412,20 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { m5gfx::gpio::command_mode_input_pullup , sda, m5gfx::gpio::command_end }; - m5gfx::pinMode(scl, m5gfx::pin_mode_t::output); - m5gfx::pinMode(sda, m5gfx::pin_mode_t::output); - if (m5gfx::gpio::command(cmd_bus_check_list) != 0x03) + auto bus_check = [&](void) -> uint32_t + { + m5gfx::pinMode(scl, m5gfx::pin_mode_t::output); + m5gfx::pinMode(sda, m5gfx::pin_mode_t::output); + return m5gfx::gpio::command(cmd_bus_check_list); + }; + + // 0x02 は「SCL は外部プルアップで戻るのに SDA だけ Low のまま」。前回の通信の + // 途中で止まったデバイスがデータ線を握っている典型で (ソフトリセットでは + // デバイスの電源が切れないため実際に起きる)、プルアップの無いただの Low ピンとは + // このシグネチャで区別できる。この場合だけは STOP を見せて握りを解かせ、 + // もう一度確かめる。それ以外の不一致は I2C バスではないとみなして即座に帰る。 + uint32_t check = bus_check(); + if (check != 0x03 && check != 0x02) { for (auto& backup : pin_backup) { backup.restore(); } return false; @@ -1439,6 +1450,12 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } } + if (check != 0x03 && bus_check() != 0x03) + { // 握りが解けなかった。ここから先は probe しても意味がない。 + for (auto& backup : pin_backup) { backup.restore(); } + return false; + } + bool hit = false; if (m5gfx::i2c::init(probe_i2c_port, sda, scl).has_value()) { From 110fe5190ecbc91f831b9c05a586bea4ca5f1347 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 19 Aug 2026 22:08:17 +0000 Subject: [PATCH 6/7] Wait for the devices to power up once, not once per address Every probe waited 50ms before it touched the pins. The old probe always answered on the first address, so that wait was paid once; now that an address which is not there says so, it is paid again for every address that misses - four times over on a board that answers nothing on its internal bus. The wait is about how long ago the board was powered, not about the address being asked, so it belongs at the entrance to the board check. Measured on an AtomS3RCam: one probe went from 49.8ms to 0.3ms, and the 50ms is now spent once for the whole identification. --- src/M5Unified.cpp | 23 ++++++++++++++++++++--- src/M5Unified.hpp | 3 +++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 6c67003..36e275d 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1368,6 +1368,22 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { static constexpr gpio_num_t CoreInk_BUTTON_PWR_PIN = GPIO_NUM_27; #endif + /// probe を始める前に一度だけ、デバイスの電源が安定するのを待つ。 + /// 待ちが要るのは「電源投入から間もない」ことであってアドレスごとの事情ではないので、 + /// 判定の入口で一度払う。旧実装は最初の probe が常に真を返して連鎖が止まっていたため + /// 結果的に 1 回しか待っていなかった。それを意図として書き直したもの。 + void M5Unified::_wait_i2c_device_power(void) + { +#if !defined(M5UNIFIED_PC_BUILD) + static bool waited = false; + if (!waited) + { + waited = true; + m5gfx::delay(50); + } +#endif + } + bool M5Unified::_probe_i2c_addr(uint8_t sda, uint8_t scl, uint8_t addr) { #if defined(M5UNIFIED_PC_BUILD) @@ -1380,13 +1396,14 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { /// 同じスロットを共有しない。 static constexpr int_fast16_t probe_i2c_port = -2; + _wait_i2c_device_power(); + m5gfx::gpio::pin_backup_t pin_backup[] = { scl, sda }; - // デバイスの電源投入直後の安定待ち。バスは解放した状態で待つ - // (SCL を Low に駆動したまま待つと、バスタイムアウトを持つデバイスに対して不正)。 + // ここでは待たない。電源安定待ちは判定の入口で一度だけ行う (_wait_i2c_device_power)。 + // アドレスごとに待つと、判定が空振りするたびに数十 ms が起動時間へ積み上がる。 m5gfx::pinMode(scl, m5gfx::pin_mode_t::input_pullup); m5gfx::pinMode(sda, m5gfx::pin_mode_t::input_pullup); - m5gfx::delay(50); // 指定ピンが「外部プルアップの載った I2C バス」かどうかを先に確かめる。 // ここは I2C ピンとは限らない場所を駆動する機種判別なので、判定を外すと diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp index 8327b91..53ebda9 100644 --- a/src/M5Unified.hpp +++ b/src/M5Unified.hpp @@ -666,6 +666,9 @@ namespace m5 board_t _check_boardtype(board_t); void _setup_i2c(board_t); void _setup_led(board_t); + /// probe を始める前に一度だけ、デバイスの電源が安定するのを待つ。 + static void _wait_i2c_device_power(void); + /// 指定ピンのバス上に、指定した 7bit アドレスのデバイスが居るかを調べる。 /// (M5GFX にも同名だった _probe_i2c_addr があるが、あちらは複数アドレスを /// ビット列で返す別物。取り違えを避けるため名前を分けている) From 3dd1440149672df4ed5aebb8577a03e1c29e7695 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 19 Aug 2026 22:35:18 +0000 Subject: [PATCH 7/7] Park the lines low before the pins become outputs The bus test set both pins to output while their latches were still high from the pull-up mode before it, so each pin drove a push-pull high for the moment before the test pulled it low. On pins that may not be an I2C bus at all - which is the whole point of the test - that is a driven high into whatever is on the other side. Put the latch low first. The lines are still driven low by the test after that, but a high is never driven at all. --- src/M5Unified.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 36e275d..eda7f43 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1431,8 +1431,11 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { }; auto bus_check = [&](void) -> uint32_t { - m5gfx::pinMode(scl, m5gfx::pin_mode_t::output); - m5gfx::pinMode(sda, m5gfx::pin_mode_t::output); + // ラッチを Low にしてから出力へ切り替える。直前は input_pullup (ラッチ High) + // なので、先に出力にすると一瞬 push-pull で High を駆動してしまう。 + // ここは相手が何か分からないピンなので、High は一度も駆動しない。 + m5gfx::gpio_lo(scl); m5gfx::pinMode(scl, m5gfx::pin_mode_t::output); + m5gfx::gpio_lo(sda); m5gfx::pinMode(sda, m5gfx::pin_mode_t::output); return m5gfx::gpio::command(cmd_bus_check_list); };