-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
machine_i2c: Add I2C bus clear support. #13281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Code size report:
|
This commit adds API support for implementing the sending of an I2C bus clear sequence, and, implements said function in the soft I2C driver. Signed-off-by: Hugo Frisk <hugopaf@outlook.com>
Since the rp2040 hardware I2C block does not support sending a bus clear sequence this is accomplished by using the soft I2C implementation of bus clearing. Signed-off-by: Hugo Frisk <hugopaf@outlook.com>
4ef10b4
to
418653a
Compare
You could to that with the machine.Pin class without changing the I2C driver. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #13281 +/- ##
=======================================
Coverage 98.40% 98.40%
=======================================
Files 159 159
Lines 21088 21088
=======================================
Hits 20752 20752
Misses 336 336 ☔ View full report in Codecov by Sentry. |
That is true. But it would fragment the driver code, and, it'd be nice to have it standardized. Furthermore, many I2C hardware blocks (unfortunately not the rp2) have this function built in. So bit banging the bus clear on those adds unnecessary init and deinit delays. If the code size is the problem we could perhaps add a define flag? |
For the given PR I would see it more as a problem, that it's just added for SoftI2C and the RP2 port. After I2C was harmonized across all ports, port-specific changes should be avoided.
|
Thank you for the suggestion! That will have to do for now. Do you see any other problems with this solution? I.e. if one were to implement this feature on all ports would it be merged? |
I cannot tell whether such a change would be accepted. And it is quite some work to implement it for all ports. |
@PepperoniPingu Thanks for explaining the use case for this and providing this implementation. It's 9 clock cycles, not 10, isn't it? As per I2C specification NXP Semiconductors UM10204 section 3.1.16 Bus clear. The main concern for adding this in the driver is implementing it across all ports, and the code size increase. 100-200 bytes doesn't seem like a lot, but these increases can add up! If arbitrary clock sequences succeed in clearing the bus, is it possible to clear the bus by writing to an address you know isn't on the bus, and ignoring the result? Something like |
Hi! I ran in to the same question as you did with 9 vs 10 clock cycles. I tried with 9 cycles first but still had problems which was solved by changing it to 10. More cycles can't hurt so I went with it. But when I looked into this now I found this article which says 16 cycles are needed? Don't know what's up with that. A simple The address A perfect bus clear in my world would be to first clock scl 10 times, then send a start followed by a stop condition to reset the state machines. |
Which MCUs do you know of that have I2C clear built-in as a hardware feature? How do they implement it (how many clocks, do they send start/stop conditions)? |
Ouch, that's unfortunate! 😵 There is some reasoning behind choosing address 0x7F, though: Recall that I2C pins are all open drain (driver pulls low only), so a logical 1 bit in the address means the SDA pin is doing nothing while SCL transitions. Sending 0x7F as the address is the same (during the address phase) as pulsing the SCL pin by itself without touching SDA. (This is, presumably, is why this address is reserved in the spec.) There is one small difference between sending this fake I2C transfer (without a STOP) versus only clocking the SCL pin, which is that there is a single "START" transition of the SDA pin from high to low mid-clock. However, as I understand it, the problem that the bus clear is trying to resolve is that another device is already pulling the SDA pin down already - so I think it's reasonable to expect that first "START" transition won't appear on the bus at all, as SDA is already held low by the device at this time.
Does that mean that this works as an option for resolving the bus issue you were seeing, at least?
Would something along these lines emulate this?
It may seem unnecessary to be looking hard at these possible workarounds when you've supplied working bus reset code already. The reason is entirely to do with saving binary size if possible (and, to a lesser extent, having to implement bus clear across all the different MCU controllers.) |
Not quite sure. I though there were a bunch but couldn't really find any concrete examples now, sorry for misleading. The rp2040s datasheet says that it sends out 9 clock cycles and than a stop. Though as previously established this is not implemented.
It doesn't have to be that way. The peripheral could be stuck "sending" HIGH but when a new transaction clocks to the next bit in the output register the peripheral pulls down SDA. So the fault can be hidden until you start clocking data, or even a couple of clocks in.
I don't believe it's any better to split it up in to two commands. Works fine with one.
Yep, using timeouts, retries and these dummy I2C sends seems to have solved the issue. Thanks for the help! For future reference, here are some outtakes from my new I2C wrapper class:
|
This is an automated heads-up that we've just merged a Pull Request See #13763 A search suggests this PR might apply the STATIC macro to some C code. If it Although this is an automated message, feel free to @-reply to me directly if |
I have some software that is using I2C on the rp2040. Over a couple of months of continuous running there have been some failed transactions resulting in exceptions. Since these are rare, and, the i2c waveform is not too pretty when looked at with an oscilloscope, I presume my hardware implementation is at fault. Nevertheless, since this system is running 24/7 in a critical environment it is essential to be able to recover from these crashes. The proper way to do this is to send an I2C bus clear sequence (a.k.a. software reset) and retry. To do the bus clearing you simply send 10 negative pulses on SCL. This clocks out potential stuck data in the output registers of peripherals.
This PR implements:
i2c.clear_bus()