Question about TWI and Wire.requestFrom() hanging.

I've just been doing some tests using the Two Wire Interface to communicate between two UNOs. Since I've never used TWI before I took advantage of the "slaveSender" and "masterReader" example sketches.

Everything worked as expected until I tried to build in some robustness to handle the slave device being unavailable. I ran into a problem with Wire.requestFrom() hanging indefinitely.

Here is the section of code of interest (running on the master device). This code runs once each time I press a push button BTW.

    if (Wire.requestFrom(8, 6)) {    // request 6 bytes from slave device #8
      for (int k=0; k<6; k++) {
        char c = Wire.read();
        Serial.print(c);
      }
    }
    else Serial.print("TWI slave not responding");

The slave device is programed to respond with "HELLO ", so everything works perfectly if the "slave" UNO board is up and running. I hit a push button and the masterReader requests the data, the slave sends the data, and "HELLO " gets printed.

If however I power off the slave board then the masterReader sketch hangs indefinitely at the Wire.requestFrom() statement when I press the button. Further, even after I restore power to the slave board it (the master) remains completely unresponsive. I can reset the slave but still no response. Only after reseting the master does everything come back to life.

BTW. I've done some debugging to trace the point where it hangs and I'm 100% certain it is at the Wire.requestFrom() statement and not anywhere else.

No other failures are causing any problems (apart from this one where the slave loses power). Here is a summary of the failures I've tested.

1. Hold reset on slave while requesting data: Prints "TWI slave not responding".

2. Disconnect only SCL and then request data: Prints "TWI slave not responding".

3. Disconnect only SDA and then request data: Prints "TWI slave not responding".

4. Disconnect both SCL and SDA then request data: Prints "TWI slave not responding".

5. Slave power off and then request data: Master hangs indefinitely and is not recoverable (after power is restored) by any combination of events other than a Master reset.

1 Like

until I tried to build in some robustness to handle the slave device being unavailable.

You planned to deal with a function that blocks until the request amount of information had been returned, when that will never happen because you unplugged the device, how, exactly?

PaulS:
You planned to deal with a function that blocks until the request amount of information had been returned, when that will never happen because you unplugged the device, how, exactly?

Yes, I realized that the blocking nature of this function is the problem. However I'm new to TWI so at this point I don't know what other alternatives are possible. :slight_smile:

From Nick Gammon's site here: Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino, it says that if an error occurs that Wire.requestFrom() returns zero. So the "if (Wire.requestFrom() )" method is recommended over use of Wire.available().

As I noted above, that code actually does work correctly (and prints "TWI slave not responding") for all of the other error conditions tested.

Update. I just added one more test.

  • Power on master before slave is powered on, and request data.

As expected there is no response. But this time everything is fine when I do power on the slave. It prints "TWI slave not responding" exactly once after power on, but then proceeds with normal operation.

So it's only when power on the slave is lost after communication has already been established (though not necessarily mid data transfer) that the master hangs completely and indefinitely (until it is reset).

Nick's site also says:

The Wire.requestFrom function does not return until either the requested data is fully available, or an error occurred.

Have you looked at the requestFrom() method to determine what errors it handles?

The requestFrom() method calls twi_readFrom() and waits for it to return. The ONLY error that twi_readFrom() handles is an attempt to read more data that will fit in the internal buffer. But, that can't happen because, if you request more bytes that can fit in TwoWire's (the class that Wire is an instance of) buffer, requestFrom() sets the number of bytes to read to match its internal buffer size, ignoring the number of bytes you wanted to read.

The I2C protocol was developed for two chips, soldered to the same printed circuit board, to communicate. The likelihood of a slave becoming disconnected just isn't possible. That the protocol, and its implementation, fails when extending to situations beyond what was intended (loose wiring, etc.) is hardly a problem with the protocol or its implementation.

Thanks Paul.

What I'm wondering is if this is an inherent problem with using TWI in this way, or if I could work around it by writing lower level functions (than those provided in the Wire Library).

The other simpler work arounds that I'm thinking of (if I decide that I really must eliminate this possibility) are.

  1. To run the power to the slave from the master, so that both boards are powered from the one master supply. This sounds like the easiest option.

  2. To connect the slave Vcc (through a resistor) to one of the master's digital ports. That way the master could test the slave has power before requesting anything.

stuart0:
2. To connect the slave Vcc (through a resistor) to one of the master's digital ports. That way the master could test the slave has power before requesting anything.

Ok, scrub that second idea it doesn't work. I just tested again, and even if the master makes no requests at all while the slave is without power, it still hangs indefinitely when power is restored. :frowning:

So any loss of power on the slave device (that occurs after initial communications have been established) results in the entire thing hanging indefinitely. This occurs even if no data is requested during the power out period.

This is bad!

You could add timeout parameters to the twi and Wire libraries, and check millis() in the while loops, or you could do the same thing I did over here: add a "mode" that makes the twi_readFrom non-blocking. Use a timeout in your main loop to call the new available() function and reset things if necessary.

This is a real mistake in the core; it's inconsistent (not like Serial), blocked (no available()) and uses duplicate buffers. Cosa gets it right.

Cheers,
/dev

/dev:
You could add timeout parameters to the twi and Wire libraries, and check millis() in the while loops

Yes thanks /dev, that's exactly the type of thing I was looking for. To be honest I was surprised that this option wasn't already included in the Wire library. I'm just reading through your links now.

BTW. I know that the following is not a good solution, but for reasons that I don't understand it seems to greatly alleviate (not fix) the problem.

If I pass the optional boolean parameter of "false" to Wire.requestFrom() then it stops the master hanging even after power to the slave is restored. It still hangs indefinitely while power to the slave is off, but now when slave power is restored it gives me a single "TWI slave not responding" and then resumes normal operation.

So now it only hangs while the slave is off, which is a big improvement. Previously it hung forever at that statement, even if the slave only lost power for one second and even if nothing was requested (even if no Wire operations at all occurred) during that time.

The following is the documentation of that boolean parameter.

As of Arduino 1.0.1, requestFrom() accepts a boolean argument changing its behavior for compatibility with certain I2C devices.

If true, requestFrom() sends a stop message after the request, releasing the I2C bus.

If false, requestFrom() sends a restart message after the request. The bus will not be released, which prevents another master device from requesting between messages. This allows one master device to send multiple requests while in control.

The default value is true.

1 Like

I hit a similar issue, except in my case, it would be an assembly error, so the I2C bus won't break during use, but may be totally broken. Having the Arduino hang in the Wire library is unpleasant and confusing.

My solution is to check that the SDA and SCL pins both read as a logic one before the Wire.begin() and the attempt to test individual I2C devices. Otherwise, mark all of the I2C devices as unavailable, and continue testing the rest of the hardware. If one of the sensors we use is put in backwards, it will pull down SDA. It is unlabeled and visually nearly symmetric.

1 Like

PaulS:
The I2C protocol was developed for two chips, soldered to the same printed circuit board, to communicate. The likelihood of a slave becoming disconnected just isn't possible. That the protocol, and its implementation, fails when extending to situations beyond what was intended (loose wiring, etc.) is hardly a problem with the protocol or its implementation.

This isn't a great assumption. In the real world, there are a number of scenarios where an I2C slave device will become unresponsive. One of the most common is during an ESD event; where the device is reset or knocked into an undefined state.

It is inherently poor design to place such hard dependencies on external devices as they are outside of your control. A blocking while loop with no way out other than through the perfect operation of a 3rd party device is doomed to unreliability.

I use these libraries daily in real products and the Wire library is the by far biggest pain point for a number of reasons:

  1. There isn't a practical way to override the I2C buffer size. I don't understand where the 32 byte max transaction size assumption came from, but the only way to deliver code that requires a larger buffer is to ship it with complicated instructions to increase the #defines in the underlying library source (which varies between MCU implementations). An overloaded Wire.begin() method that takes a pointer to a buffer and a new size would be amazing!

  2. Wire.requestFrom() puts your Arduino into an unrecoverable state when the slave device is unresponsive, and there is no way to circumvent this without modifying the underlying source or implementing a watchdog of some sort and resetting the whole part. As others have commented above, implementing an asynchronous mode would allow us to use our own timeouts to protect our products' from failing 3rd party devices or glitches, but an overloaded requestFrom() method that allows us to pass a timeout (in microseconds) would be phenomenal.

1 Like

Why are you reviving an old thread? It's coming up to its third birthday soon.

If you have a question, post a question as a new thread.

Yes, we know the Arduino Wire library is bad. Even the name is bad and has been admitted as a mistake by the creator. But it works well enough for "Arduino scale" projects that nobody wants to write a new one.

MorganS:
Why are you reviving an old thread? It's coming up to its third birthday soon.

If you have a question, post a question as a new thread.

Yes, we know the Arduino Wire library is bad. Even the name is bad and has been admitted as a mistake by the creator. But it works well enough for "Arduino scale" projects that nobody wants to write a new one.

Because this was never actually resolved? Because I'm used to forums like Stack Overflow where updates aren't shunned, but duplicate questions are? Because people troubleshooting this issue will still find this thread (as I did)?

The Wire library is only bad because no one has bothered to fix these relatively trivial flaws. It's actually very close to being a clean, easy-to-use library. The non-resizable I2C buffer isn't a show stopper, but the Wire.requestFrom locking up the processor is.

1 Like