Bug in the arduino I2C library?

So to put it simply without even needing to paste code- I have a Mega 2560 as slave and Uno as master.

The slave does a Wire.begin(SLAVE_ID); and set the onRequest() callback function.

Here's where I'm seeing an issue: The onRequest callback does NOTHING and returns.

then in the loop on the master I do a Wire.requestFrom(SLAVE_ID, BYTES)

And...

The requestFrom(SLAVE_ID, BYTES) ALWAYS returns the number of bytes as the same amount I request, and Wire.available() ALWAYS shows that exact number of bytes initially requested.

However all the bytes are 255 (-1) unless I purposely actually return the 4 bytes it should be getting.

This CAN'T be correct. I read that the requestFrom() function is blocking, but it's clearly not. Even tried setting a timeout and that did jack-nothing.

Also, it doesn't even look like the LEDs on the slave are working, so I'm pretty sure there is no communication going on. It doesn't "timeout" as it seems to return in 0 microseconds with garbage data.

How is this supposed to work? Clearly it's not blocking so that phrase should be removed from documentation is clearly at odds with what a blocking socket is.

Does anybody have some better documentation? All I find (including the arduino docs) are bad examples and even worst best practices (I'm not putting in a half second delay if I'm trying to read 8 different slaves).

Yup, you need to post your code. Occam's Razor says the bug is probably there. Use Code Tags.

Make it really easy for everyone, and post an MRE instead.

1 Like

Hi, welcome to the forum.

As soon as the Slave runs Wire.begin(SLAVE_ID); then you can run a I2C Scanner sketch on the Master to check the I2C bus.

In the I2C protocol, when the Master does a request, it does not know how many bytes the Slave actually returns. When the Slave acknowledges to its I2C address but does not return data, then the SDA stays high and the Master reads 0xFF. The Master will never know if those are valid bytes or not. The Wire.available() will return the same amount as requested, regardless if those are valid bytes or not.
That is how the I2C protocol works, that is not the Wire library doing something wrong.

The Wire.endTransmission() and Wire.requestFrom() wait until the I2C session has finished. So yes, they are blocking.

Can you tell why you want to use two Arduino boards ? Can you do your project with a single Arduino board ? What would you do if the Master-Slave communication is ten times harder than where you are at this moment ? What kind of data do you want to transfer ?
I made something on Github about the Wire library.

1 Like

Hi Thanks. I actually use 26 Arduinos, divided up to 8 Mega 2560 slaves per Uno master. The slaves all have up to 20 or 30 digital and analog switches and dials connected to the them, which is why I can't use one. I really don't want to plug in 22 arduinos over USB as an alternative. The 3 masters connect over a digi xbee mesh to a 4th that is the "master" receiver, which is connected to the PC via USB, which runs a program doing various things that forwards packets via UDP to another PC program. So this device can actually be placed in a separate room from the PC.

I'm not a noob, it works beautifully except there is no timeout or blocking going on in I2C when there is no data.

By definition, a blocking call will not return until it completes the "task" which in this case seems to be saying it got bytes but didn't. It should be the number of bytes or has failed. So I suppose arduino does not really know what it means and misusing the word? I have never seen a "blocking call" return instantly with bad data and a timeout that does nothing. I have put debug code in twi.cpp and passed it back to my code by reference to print out what it's doing. It should actually return something, or nothing- but wait until it is sure. It's not waiting, it's instantly returning. That's not how a blocking call works. I suppose it shouldn't even be a blocking call. Why did they implement it this way? It's counter intuitive and useless. Blocking does not mean disable interrupts for a microsecond and then return garbage, it should be blocking the code for a reason.

I guess the best way around this is to reply to the master with a control byte telling it I have no bytes? My first 2 bytes are a uint16_t that doesn't start until 10000, so I could just reply with a zero.

*So the above is now what I'm doing. I'm checking if the slave is there and responding by sending and receiving an ACK packet. I don't check that control again for 15 seconds if it doesn't respond. I throw out any packets that don't respond to the request with accurate data and then send it back another packet to let it know I received it, so it can safely remove that control change from it's buffer. The slave's buffer is a dictionary that only keeps the most recent control changes, and automatically deletes it from the buffer if the change is back to the last position it had successfully sent, which keeps memory usage small with no fear of overflowing the stack. This seems to be fairly quick and redundant. It is what it is- I was hoping for a more robust protocol, but it's a cheap embedded device, so a little more work upfront will pay off for the cost.

Thanks again.

About the I2C protocol:

I tried to explain that this is inherent to the I2C protocol. You are struggling with the I2C protocol itself.

When a Master requests data from a Slave, and the Slave acknowledges its I2C address, then the Slave must return data, ready or not. The Master can not tell how much of that data is valid. That is how the I2C protocol works.
If the Slave just gave up, then the Master reads 0xFF, but the Master does not know if those 0xFF are valid or not.

You need a time-machine to go back to 1979 and tell the designers of the I2C bus at Philips that they should do it in a different way.

About the Arduino Wire library:

In the Arduino Wire library, the Wire.requestFrom() is sometimes called "blocking" because it waits until the I2C session has completely finished. If you think about "blocking" when dealing with data, that's okay, but the low-level I2C protocol can not deal with that.

The Wire.requestFrom() or Wire.available() can not return the number of valid bytes. That is impossible, because that is not implemented in the I2C protocol.

You see ? It is not a bug, it is not caused by a implementation by Arduino. It is just how the I2C bus works.

If you want to verify that you actually received valid data, then send a checksum or hash along with the data so that the master can have some hope of detecting errors.

Yeah, I didn't realize it's just a low level protocol. I basically wrote a wrapper on top of it where the master sends a control packet, asks for data, and the slave responds if it has data or not, and sends the 4 byte data. If the slave does not respond with a valid reply, the master skips it until the next loop. So now I know exactly what should be received.

To be a bit more robust, I thought of doing a checksum and then a reply back from the master saying if it was valid or not, so the slave could attempt resending it next request. My "queue" would only be about 80 bytes ever, so I don't have to worry about overflowing anything. But it seems to work fine as is, so I don't know if I'll bother. It's mission critical info, that needs a to get there.

A few thoughts:

The Arduino Mega and Uno have a buffer size of 32 bytes.

The I2C bus is not fault tolerant, it is supposed to work 100% all the time.
When there would be a glitch that changes the SDA level of a data byte, then it is not known. A checksum would give some extra safety. However, if your I2C bus is not 100% reliable, then there is something seriously wrong.

The reliability depends also on the code in the Slave. Specifically the code of the onRequest and onReceive handlers and if libraries are used that turn off the interrupts for some time (DHT, NeoPixel, FastLED, OneWire, and so on).

Since the high-level of the I2C bus is made by pullup-resistors, it is a weak bus. It is not the best choice for a mission critical project.

Instead of sending JUST the needed data from the slave, send a packet, with start and end markers. If a packet received missing those markers, then discard it, and return an error to the application. If a packet is received which has those markers, strip off the markers, and return JUST the data to the application. A very simple change to both master and slave. If you want to make it even more robust, the also add a checksum or CRC byte, to validate the data itself.

@RayLivingston I don't know what you mean. The I2C bus has already packets of data, it has already a START and STOP. Even adding a checksum is trivial. I made this page for you: Adding a checksum (is almost useless).

Yes, but as pointed out, if the slave does not respond, the master will read all FFs, and be unable to determine whether that is valid data or NO data. Having the slave send additional framing characters makes it trivial for the master to detect bogus/null packets, and discard them. This is what the OP is asking for.

I see, then I misunderstood you.

For the I2C bus I never use readable ASCII data, and I think that I have never done a project with a variable number of bytes, and I almost always transfer a 'struct' with data. In the struct I put the information if the data is valid or with a timestamp. The Master can poll the Slave many times, until there is new data.

I added a 'interrupt' signal to the I2C bus in a project. A Slave can signal the Master that it has data. But then I had a few Slaves, and the Master has to poll every Slave to check which one issued the 'interrupt'. So it was almost as slow as continuously polling all the Slaves.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.