delay I2C slave response - clock stretching

Hi all.

I'm writing the code for a I2C slave Arduino (Nano Every, if it matters). I'm using the Wire library.
I need to perform some slow operations before replying to the I2C master, and I need to use the clock stretch feature in order to keep the master waiting.
It seems to me the that when my onRequest() callback get called my code must reply, no matter what, but the response payload isn't ready yet.
Is there a way to set the clock stretch and delay the response to a later time? If Wire can't do this, is there a different library able to do the clock stretch?
Unfortunately I cannot modify the master device so I cannot move to a polling protocol.

Thanks

Give this link a try: Clock, Stretching, Arbitration - I2C Bus The key feature you will find is this: I2C devices can slow down communication by stretching SCL: During an SCL low phase, any I2C device on the bus may additionally hold down SCL to prevent it from rising again, enabling it to slow down the SCL clock rate or to stop I2C communication for a while. This is also referred to as clock synchronization.

Note: The I2C specification does not specify any timeout conditions for clock stretching, i.e. any device can hold down SCL as long as it likes. Doing this stops your code until the SCL is released.

That's exactly what I need. But the I2C dedicated pins are controlled by the Wire library, hence the Wire library (or any other viable I2C library for Arduino) should give me the clock stretching functionality.
What I want to do is:

  • set the clock-stretching the very moment the master device asks for data, namely holding down the SCL line;
  • perform all my calculations to build the response data payload, while the I2C master device waits;
  • release the SCL line and resume the I2C slave reply logic.
    All of this obviously without colliding with the Wire library inner workings.

Thanks anyway for you help.

I don't know how good the Nano Every with its ATMega4809 microcontroller is as a I2C Slave.

The Arduino Wire library supports clock pulse stretching as a Master and as a Slave it uses clock pulse stretching when running the onRequest handler. You can not influence that.

You should never, ever, put a delay in the onRequest or onReceive handler. Maybe you can get away with a few microseconds.
The SMBus (the cousin of the I2C bus) allows that a Slave can stretch a clock up to 25 ms. That is ridiculous. Don't even think about it.

The means that you have to reorganize your sketch and be sure that data is ready when the Master requests data.
In most situations the incoming data is processed and stored. For example in a struct.
When the Master does a request, it gets the current data of the struct, even if that is old data.

If you still want to find a way to delay the onRequest handler, than you are on your own. It might work once, but everything might fall apart if you add something else to your sketch.

Ok, now I know the Wire library isn't suitable for my needs. Thanks.

If you need to stretch the clock pulse (as a Slave), then the Wire library is exactly suitable for your needs.

Yes, you're right, but I didn't explain myself clearly, I'm sorry.
As far as I can tell, Wire requires me to send the response and complete the communication all inside the onRequest handler. This is the part the doesn't fit my needs, because the response payload availability depends on other activities running in the main loop.
What I'm looking for is just the asynchronous information telling me the master device is requesting data, implying the SCL line is held down, and then it's my responsibilty to send the response data as soon as possible, but in the main loop. There's no doubt this is feasable, but I need a I2C library designed this way.

clci:
There's no doubt this is feasable

It is really a bad idea. You should not stop the Master and stop interrupts for a long time. Everything will go wrong. Really.

clci:
but I need a I2C library designed this way.

No, you don't.
You have to find a way to have that data ready.

The most common solution is adding a 'interrupt' line to the I2C bus. Then the Slave can signal the Master that it has new data. Otherwise the Master can poll the Slave very often so check if there is new data.

You may not retrieve data in a slow way while being in the middle of a I2C bus session. That is not what the I2C bus for.

You should not stop the Master and stop interrupts for a long time

Generally speaking it is true. In this specific case, I already checked, it works fine; I have literally seconds before the master goes on timeout due to a long lasting clock-stretch.

You have to find a way to have that data ready.

I can't, because the response payload depends on what the master sends just right before requesting data.

The most common solution is adding a 'interrupt' line to the I2C bus

I'm not the hardware designer, unfortunately. If I could modify the master device I'd move to a much simpler polling protocol over I2C without even adding a new digital line.
I think it is feasable because I'm trying to use this Arduino to replace a custom board that works this way: master calls requesting data, hold down SCL, flag the pending request to the main loop, continue the regular loop operation with no interrupts blocked, resume the I2C response as soon as the main loop can respond. I get it isn't the most "classic" way a I2C slave operates, but it works fine and it isn't particularily complex. From the I2C master standpoint, it sees just a slightly long clock-stretch, but it is designed to handle it properly.
Thanks for your suggestion.

Okay, you are right, in this situation you have to keep the SCL low for a while (even if it is against the rules of the universe :wink: ).

When leaving the onRequest handler, the Wire library returns the data and releases the SCL pin.
It is possible to force the SCL low with another pin, but then the Wire library gets confused. I think the bus collision detection will trigger.
The Wire library can indeed not do what you want.

Changing the source of the Wire library is far too difficult. This is the low level I2C code for the mega-avr branch: twi.c.

There are other Wire libraries, even a few with Slave mode. But they are for the avr microcontrollers and not for the newer mega-avr microcontrollers. Those with Slave mode operate all in the same way as the Arduino Wire library, with the onRequest handler in a interrupt and also those are far too complex to change it.

Here are a few mentioned: Alternatives to Wire library for I2C.
This is my Overview of Arduino I2C libraries. As you can see the "SoftIIC" can do Slave mode in software (no interrupt), but it has restrictions. I took a look at the code, but I'm afraid I can not help to change it for your situation.
You might have more luck outside the Arduino family. For example with a ESP8266 board, or an other platform.

I can see no other option, then that you have all the information which could be requested ready.
Can you tell what kind of information you need to gather and how ?

I'm thinking about using a ESP32, it is the only other option at my hand right now, but it isn't 5V compatible and the hardware part is sadly my weak spot; but there's a chance I could even use MicroPython, it would be a BIG plus.

I'd like to do a shallow evaluation of writing the I2C slave code by myself, but I need an easy access to interrupts on raising and falling edges on both SCL and SDA lines. Is there any documentation about this for the Nano Every? I cannot find it.

You could use the Arduino attachInterrupt(), the source is in WInterrupts.cpp.

Thanks.

A I2C level shifter can be used to connect a ESP32 to a 5V I2C bus.
Both sides of the level shifter need their correct voltage to make it work. Connect 3.3V to "LV" and 5V to "HV".

It took many years to get the bugs out of the Wire library for the AVR microcontrollers (such as the Arduino Uno).
It will be a tough task to alter Slave code for your project.

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