Board detection: probe the address instead of the pull-up (#330) - #331
Merged
Conversation
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.
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.
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.
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.
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.
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.
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.
ainyan03
force-pushed
the
board_probe_i2c
branch
from
August 19, 2026 22:35
e9cfac3 to
3dd1440
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the Atom VoiceS3R being reported as an AtomS3R-Ext (#330).
Builds on the software I2C fix in M5GFX (m5stack/M5GFX#261), which is what the
probe here now speaks through, and wants the pin restore fix alongside it
(m5stack/M5GFX#262).
Cause
_detect_i2c_device()was not I2C. It made no start condition, clocked everyaddress bit twice across twenty pulses, and read the ninth bit a millisecond
after the clock had gone back down — long after any device that acknowledged
had released the line. On a bus with pull-ups the reading is high whether or
not the address is there, so what the function returned was the presence of a
pull-up on the pins, and the five call sites read that value as the device
being present.
Every board on this branch has pull-ups on its internal bus, so the first
condition matched on all of them. The chain checks the AtomS3R-Ext BMI270
first, so the Atom VoiceS3R — same package, same internal bus pins, an ES8311
where the other has a BMI270 — came out as an AtomS3R-Ext. That is why none of
its examples work: only board 145 gets the I2S pins and the amplifier callback
for its built-in audio.
The report's workaround of calling
M5.begin()a second time withfallback_boardcannot help:begin()returns immediately once the board isknown, and
fallback_boardonly applies when detection came back unknown.Change
Probe the address, over the software I2C port. Open drain, no peripheral
touched, and the acknowledge that actually comes back. The return value now
means what the call sites always read it as.
Pins that carry no pulled-up bus are left alone, using the same test the
display autodetection applies to pins that may not be I2C at all. The one bus
that gets a second chance is the one holding its data line down — the clock
comes back against the internal pull-down while the data line stays low — which
is a device interrupted mid transfer rather than a pin without a pull-up. That
one is offered a stop condition and looked at again; this file already records
a device that needed exactly that (
UnitHEART MAX30100, in the Capsule branch).The stop is made by driving low and releasing, never by driving high, and the
lines are parked low before the pins become outputs, so no high is ever driven
into whatever is on the other side.
Decide the S3PICO boards on their internal bus first. The three boards
sharing that bus were separated by a probe on another pin pair in between:
BMI270, then the Stamp-S3Bat address on 48 and 47, then the ES8311. The Atom
VoiceS3R drives its speaker out of GPIO48, so a board fully identified by its
own bus was reaching for the I2S data line before it got there. Within the 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 part that is always fitted decides.
Wait once, not once per address. Every probe waited 50ms before touching
the pins. The old one always answered on the first address, so that wait was
paid once; an address that now says it is not there would have made it four
times over on a board with nothing on its internal bus. The wait is about how
long ago the board was powered, so it belongs at the entrance to the check.
The function is renamed to
_probe_i2c_addr(); M5GFX has a function of the oldname with a different contract, and the two are easy to confuse.
Measured on hardware
Each board was read three ways before
M5.begin(): a plain bit-banged scan ofthe bus, the new probe, and then
M5.getBoard().0x18only (ES8311)0x68only (BMI270)0x68, camera0x21(GC0308)0x68, camera0x3C(OV3660)0x50on 45/48A full scan of the Atom VoiceS3R internal bus,
0x08through0x77, answersat
0x18and nowhere else — no inertial sensor on that bus on this unit. BothAtomS3RCam variants still reach the camera identification and come out as 144.
One probe went from 49.8ms to 0.31ms on an AtomS3RCam, with the 50ms now spent
once for the whole identification.
Boards outside this branch were run as well, to see that the pin restore fix
underneath does not disturb display autodetection: M5StopWatch (30, 468x468),
Core2 v1.1 (2, 320x240) and PaperS3 (19, 540x960) all come up and keep running.
Not verified on hardware
Stamp-S3Bat. No unit at hand. This branch changes what its detection
depends on: the old code accepted the pull-ups on GPIO 48/47 and never asked
the M5PM1 at
0x6Efor an answer, while this one requires the acknowledge. Adevice slow to come up could be missed where it used to be assumed.
Known, and deliberately left for later
falsefrom the probe means "no acknowledge", which covers the address notbeing there, the pins carrying no pulled-up bus, and the port failing to open.
The chain reads all of them as absent, so a bus that could not be probed ends
at the last
elserather than at unknown. Splitting the answer three ways isthe right shape, and is worth doing on its own rather than inside this fix.
The
internal_imu = falseline for the Atom VoiceS3R suggests the board maycarry an inertial sensor that the unit measured here does not have. The
detection order above is chosen so that either answer identifies the board
correctly.