I’m working on getting my project, Modulo working with the Photon and have run into an issue with I2C. It seems like after an I2C write fails, the bus locks up and won’t transmit again. Both SDA and SCL are held low rather than returning to idle (high) as they should. I created this trivial test case:
I have also bumped this up for the firmware team Erin, though most of them are asleep now.
I am extremely appreciative of your minimal, complete, verifiable example! We’ll take a look and get back to you as soon as is reasonable. And of course, if you happen to find out more, keep posting here. Thank you!
Awexome info! @modulo Love the logic analyzer usage
Just a quick guess here, do you not have an actual I2C Slave hooked up that would respond to address 8? I ask because if you did the slave should ACK instead of NAK. The I2C Master is waiting for a response… perhaps indefinitely. One thing that may come out of this if that’s the case, is being able to set an appropriate timeout for cases where devices just don’t respond for whatever reason.
Glad you liked the example. I’ve spent years fixing bugs in a multi-million line codebase, so I always appreciate a good bug report. I’m glad I can pay it forward.
That’s right, @BDub: There are no I2C slave devices attached in this example. This should be reproducible with nothing more than pullup resistors on SDA and SCL.
The master should not wait for a response since it already received a NAK condition. Since I2C timing is entirely driven by the master, the only the time the master should ever wait on the slave is when the slave “stretches the clock” by pulling SCL low.
Since this repeated "try again" scenario could also block forever, it seems that it would be a good idea to have a timeout value and/or max number of tries value that can be set by the user, but default to some reasonable values.
Per the IIC Specification, there are two ways to handle this, due to several reasons:
When SDA remains HIGH during this ninth clock pulse, this is defined as the Not
Acknowledge signal. The master can then generate either a STOP condition to abort the
transfer, or a repeated START condition to start a new transfer. There are five conditions
that lead to the generation of a NACK:
No receiver is present on the bus with the transmitted address so there is no device to
respond with an acknowledge.
The receiver is unable to receive or transmit because it is performing some real-time
function and is not ready to start communication with the master.
During the transfer, the receiver gets data or commands that it does not understand.
During the transfer, the receiver cannot receive any more data bytes.
A master-receiver must signal the end of the transfer to the slave transmitter.
Let me know if that sounds appropriate @modulo and I'll submit a firmware issue with the appropriate info
Sorry, I worded that poorly. I just meant that the loop function in this particular example program would run again. I don't think that the implementation of the Wire library should try again. The issue is that even though the example program immediately tries to transmit again, the bus state never changes.
Yes, that sound correct to me. I believe that whether it issues a "stop" or "repeated start" depends on the optional argument to Wire.endTransmission().
UBER fix: Minimum fix + API added for number of retries and/or timeout. Retries could always be set (or defaulted) to 0 if you wanted to manage this in user code.
I tried blinking the LED in the loop function and that appears to continue working, so no crash or infinite loop. The bus state just never changes.
Also good question on the Core behavior. I tried it and I get address+NAK over and over again as expected. Curiously though it holds SDA high for 100ms before the next iteration. Though that doesn't seem quite right, it hasn't caused any problems for me.
Probably another lame question, but:
If no slave is connected to the I2c , then there is really no technical problem?
But, if you had a second slave that was attached, does this mean the second slave will not get anymore commands ?
Could it be, that the photon is so smart, that once it determines that the slave is not available, it will not try to contact it again? Didn’t really think so, but?
Isn't the SDA held high by a pullup resistor, and the Core can only hold it low?
I believe the idle state of the SDA bus is high.
If that is the case, then the Core could be releasing the bus, to allow a slave to answer, or just going to idle mode?
Sorry, but I don’t understand, have you tested this with an actual slave on I2c, and it was prevented from operating by addressing another non existing slave?