Doubts on Wire library implementation

Hello everybody!

In order to avoid any unnecessary detour on the topic, I'm going straight to the point: the idea is to create a program that converts any of the most popular AVR Arduinos into a simple I/O expander (and keeping fuctions like analogRead and PWM output aka analogWrite) by using I2C as the controller interface.

I know (more or less) how I2C works and the way the master reads and writes to a slave; but I don't have clear how the Wire library handles the slave's events:

If the master, in order to request (read) data from a slave, has to specify the amount of bytes it expects to receive; then how the slave knows that if the onRequest event doesn't even have any parameter that tells it? What happens if the slave doesn't transmit the exact amount of bytes the master requests?
I make this question because I want to implement a muti-byte feature that sends the state of consecutive pins if the master requests multiple bytes on a digital/analog read. How the slave can know when to stop sending bytes? The event triggers once per request or once per byte?

Why onReceive does have a parameter and onRequest does not? It would be more useful if the slave acknowledges how many bytes the master requested, instead of how many the slave received.
I don't know what's really the deal, but I think it is redundant considering that available() does the same thing. Then... can I use the parameter's value instead of available() to determine how many bytes the slave received?

I'll appreciate any answer, since this doubt has me stuck :confused:

If the master, in order to request (read) data from a slave, has to specify the amount of bytes it expects to receive; then how the slave knows that if the onRequest event doesn't even have any parameter that tells it? What happens if the slave doesn't transmit the exact amount of bytes the master requests?

The slave knows because it's following a strict protocol. For example the master write a command byte and an address byte to the slave, the slave answers with exactly one byte of data. If the master sends three bytes the slave sends the amount of data the third byte tells it to do.

That's just an example, you can define any sequence just ensure the slave knows how much data to send.

The event triggers once per request or once per byte?

It triggers once per request.

Why onReceive does have a parameter and onRequest does not? It would be more useful if the slave acknowledges how many bytes the master requested, instead of how many the slave received.

The I2C protocol doesn't provide that information. It's possible to check for a stop condition and if no stop condition is reached just provide the next byte but that's not possible using the Wire library, you would have to write your own low level calls.

Think about most I2C devices that already exist. They're temperature sensors and memory chips. They don't have a general-purpose microcontroller on board. If the datasheet says "write a 1 to this address and you will get 3 bytes back" then that's what you get. Basically any request becomes two requests: first tell the slave what you want or how many, then request again to receive the bytes.

Look at an existing I2C port extender and just copy and extend that protocol to do what you want.

Lucario448:
What happens if the slave doesn't transmit the exact amount of bytes the master requests?

Then the master sends clock pulses and the data line is left high by all slaves, so all the "not transmitted" data gets turned into 0b11111111 bytes.

Worse is if the slave holds the data line low. Then the master locks up waiting for the slave to release the line.

pylon:
The I2C protocol doesn't provide that information. It's possible to check for a stop condition and if no stop condition is reached just provide the next byte

Oh yeah, the stop condition; how could I missed that?

pylon:
but that's not possible using the Wire library, you would have to write your own low level calls.

Well, then a lower level implementation it would be. Only for the slave, that is; the master can keep using the Wire library.

MorganS:
Think about most I2C devices that already exist. They're temperature sensors and memory chips. They don't have a general-purpose microcontroller on board. If the datasheet says "write a 1 to this address and you will get 3 bytes back" then that's what you get. Basically any request becomes two requests: first tell the slave what you want or how many, then request again to receive the bytes.

Look at an existing I2C port extender and just copy and extend that protocol to do what you want.

Correct. I already have in mind how to overcome the half-duplex nature of I2C; and it is by imitating that behavior.

On I2C, it's impossible to reply immediately after issuing a command (task); thus a master transmission is required to set beforehand:

  • How to read/write (digital or analog)
  • The way to do it (single pin or "bulk" aka "direct port manipulation")
  • A bit that overrides the first option if set (digital read/pin mode change)
  • A bit that flags if the "pin index" (couterpart of an address) will increment after each byte received/transmitted (consecutive pin manipulation)
  • An "ignore bit"

Second is the "pin index"; where 7 bits are used to set a pin number (A15 on an Arduino Mega equals 69), and the remaining one as the "ignore bit".
Ignoring a setting byte results in a dismiss of it and not making any change at all.

Any additional byte is taken for a write operation according to the above listed settings.

Yeah... more or less like the usual protocol of any I2C device.

Thank you very much guys! :slight_smile: For all the ideas.

When the Master reads data, the Master is in charge of the clock, the acknowledge and the stop. The Slave just gives bytes, but does not know how many.

In the requestEvent() function, you may fill the buffer with 32 bytes. Just fill the buffer to be sure. The Master can do a Wire.requestFrom() and request 1 up to 32 bytes. If the Master stops, the Slave does not use the remaining bytes in the buffer.

This makes sense if you have a array, just like the "registers" of a sensor. The Master could write the "register-address" (array index) and a requestFrom() starts reading from that address of the "registers".

To send commands to a "register" requires additional code, because most commands take time and should be handled in the loop() and not in the receiveEvent() function. You might need a boolean variable if a command is written in a special command byte of the array.

Many have tried to change the Wire library and even more have failed.
Have a look at this: GitHub - thexeno/HardWire-Arduino-Library: A spinoff of the Arduino Wire library, implements a fully controllable I2C slave.. I think that is exactly what you need if you don't use the "register" approach.

Koepel:
When the Master reads data, the Master is in charge of the clock, the acknowledge and the stop. The Slave just gives bytes, but does not know how many.

Then how can I implement the secuential (slave) read? Does the slave check for the stop bit?

Koepel:
In the requestEvent() function, you may fill the buffer with 32 bytes. Just fill the buffer to be sure. The Master can do a Wire.requestFrom() and request 1 up to 32 bytes. If the Master stops, the Slave does not use the remaining bytes in the buffer.

This makes sense if you have a array, just like the "registers" of a sensor. The Master could write the "register-address" (array index) and a requestFrom() starts reading from that address of the "registers".

I get your point: fill (up to full) the buffer anyways, and then only the amount requested will be sent. But in case of the incremental address register (secuential read), how can I know the actual amount of bytes used?

Koepel:
To send commands to a "register" requires additional code, because most commands take time and should be handled in the loop() and not in the receiveEvent() function. You might need a boolean variable if a command is written in a special command byte of the array.

I can do that too, but... isn't supposed that the receive event takes place after the master have sent all its bytes?

Koepel:
Have a look at this: GitHub - thexeno/HardWire-Arduino-Library: A spinoff of the Arduino Wire library, implements a fully controllable I2C slave.. I think that is exactly what you need if you don't use the "register" approach.

If for "register approach" you mean the secuential read (the reason of this topic), then why? I mean there is an event that triggers every requested byte (more or less what I need), then what's the problem? Mess up the timing?

The receiveEvent is an interrupt function, therefor it is better to do commands in the loop(), unless they (almost) don't take time.

When filling the buffer with 32 bytes in a requestEvent, the Slave still does not know how many bytes were read by the Master (the Master determines how many bytes will be transferred over the bus to the Master).
Therefor reading data with another Wire.requestFrom() from the Master will not continue at the next register address.
However, it is often implemented in a way that it does not matter.

Wire.beginTransmission(2);   // Hello slave address number 2
Wire.Write(10);   // set "register" address to 10 in the slave
Wire.endTransmission();  // transmit it to the Slave

Wire.requestFrom(2, 16);  // request 16 bytes

With the code above, the Slave could return data starting with buffer[10] and the next 32 bytes.
The next time the Master wants data, it should set the "register" address first.

I do not understand your last question. I think the HardWire-Arduino-Library might generate an interrupt for every byte, that would make it possible to count the number of bytes that the Master gets. But I have not looked into the HardWire-Arduino-Library that well.

Ohhh, now I understand your point.
Secuential read will be still possible, but not in the way I was thinking. I believed I could leave persistent the current "address" after the request, instead of going back to the previously set value.
In that case, persistence "post-request" is not possible since the slave can't assume that the master will always request the whole buffer.

Koepel:
I do not understand your last question. I think the HardWire-Arduino-Library might generate an interrupt for every byte, that would make it possible to count the number of bytes that the Master gets. But I have not looked into the HardWire-Arduino-Library that well.

I think it will, but my concern is about messing up the I2C timing because that handler let you send out a byte from anywhere outside of the buffer.
If the event triggers after the master became ready, then that's what I'm worried about. Since the master is who provides the clock signal, I don't think it will wait for the slave to actually be ready.
If that's true, then the handler will only have 10 us to spare at 100 KHz clock rate; or else the master receives incorrect values.

An Arduino as a Slave uses clock pulse stretching.

When the Slave needs more time, it keeps the SCL low.
That's why it is so hard to simulate a sensor with an Arduino, because no sensor with a hardware I2C inside the chip uses that. I think that most sensor don't even have the hardware to pull the SCL low.
That's also why some (software) I2C Masters can not deal with an Arduino as a Slave, when they forgot to implement the clock pulse stretching.

I don't know at what moment the Arduino Slave is capable to keep the SCL low. Perhaps at the end of every byte ? I really don't know.

You can see it with a logic analyzer.
With PulseView/sigrok and a cheap usb device from AliExpress, you can see the I2C signals for less than 10 dollars.
https://sigrok.org/wiki/Supported_hardware#Logic_analyzers
When you want something good, I suggest Saleae.

Well, looks like it's matter of code and test.

If clock pulse stretching is possible, then I can leave persistent the last accessed "address". The worst case will be doing analogRead, plus some microseconds spent on switches, global variables and ifs; so I hope it holds the line that long.

Might be possible, because I have understood that a data byte is only valid on a complete period of the clock pulse.

How the slave can know when to stop sending bytes?

If my understanding on the theory and technology of TWI Bus happens to be meaningful, the slave does not stop sending the data bytes; rather, it is the Master who decides whether to finish reading all the data bytes, as declared in the command Wire.requestFrom(deviceAddress, 5);, from the slave or to terminate further reading due to error condition.

I would like to validate the above proposition with an UNO-24C512 EEPROM setup (shown below and here) where the Master wishes to read 5-byte data from the slave starting at location 0x1234. Let us assume that the deviceAddress (aka slaveAddress) is 0b1010010 and the base address (0x1234) of the EEPROM has already been set in the Address Counter of the EEPROM.


The Master has executed the command:

Wire.requestFrom(0b1010010, 5);

I would like to believe that the above high level command can be broken into the following conceptual level to understand the events that are happening.

1. START condition on the TWI Bus

2. Slave address on TWI Bus with data direction mode from Slave to Master (Read Mode)
The slave accepts the address and pulls down the SDA line. This low-going pulse is known as 'generation of ACK signal by the slave' or SACK. The direction bit enables the output data buffer of the EEPROM.

The SACK signal triggers the TWI Logic of the Master to generate 8 clock pulses on the SCL line which are needed to read (shift-in) 8-bit data from the memory location (0x1234) pointed by the Address Counter. The 8-bit data automatically enters into the TWDR Register of the Master.

The Master has accepted the data byte. Now, it checks the 2nd argument of the Wire.requestFrom(0b1010010, 5); command and learns that another (in fact 4 more) data byte is to be read from the next memory location (0x1235) of the EEPROM. This requires advancing the Address Counter for which a clocking pulse is required. The Master creates this clocking pulse by pulling down the SDA line what is known as the 'generation of ACK signal by the Master who now acts as a receiver' or MACK.

The Master generates another set of 8 clock pulses on the SCL line to shift-in data from memory location 0x1235. As the new data will enter into the same TWDR register, the Master removes the previous data to another location of an array before advancing the Address Counter. The process continues this way.

When the 2nd argument of the Wire.requestFrom(0b1010010, 5); command becomes zero, the Master does not advance the Address Counter; it means that the Master does not generate the ACK signal which is to say in another way: the Master genertaes NACK (not acknowledge) signal.

After reading all the data bytes from the slave, the Master asserts STOP condition on the TWI Bus. The Bus is now available to other TWI devices for acquisition.

Got it.

The slave can't predict how many bytes the master request, but it can know when to stop (which is what I was looking for).
Then perhaps that library keeps triggering the onRequestData event until the master's NACK is received.

Now, what if the slave has to transmit a byte that is generated in runtime, instead of being previously buffered? It necessarly has to be buffered?
What if the slave hangs longer than the clock period to get the data?
Is "clock pulse stretching" a real thing?

Yes clock stretching is a thing but most I2C devices like thermometers don't do it, so the Arduino library doesn't do it either.

Usually I2C is so slow that the slave Arduino can do a lot of processing between clock pulses. If it does require a lot of work to provide what the master asked for then calculate it before the master asks.

A 100 kHz I2C-bus is slow for a 48 MHz Arduino Zero.
A 100 kHz I2C-bus is not that slow for a 16 MHz Arduino Uno.

100 kHz, and 9 clock pulses per byte plus a gap in between. That is a byte every 100 µs.
An interrupt is 5 µs ( https://gammon.com.au/interrupts ).
An analogRead() is about 112 µs.
A digitalWrite() is 5 µs.
A 16-bit integer calculation is slow when dividing: 14 µs.
A float division is 80 µs.
It is very easy to get to 100 µs and beyond.

MorganS:
Yes clock stretching is a thing but most I2C devices like thermometers don't do it, so the Arduino library doesn't do it either.

D'oh, then I have to buffer (or put on an "register" array) everything... right?

MorganS:
If it does require a lot of work to provide what the master asked for then calculate it before the master asks.

If I must...

Koepel:
A 100 kHz I2C-bus is not that slow for a 16 MHz Arduino Uno.

I know; the slave has only 10 µs (160 microcontroller's clock cycles) to spare before the next I2C clock pulse arrives.

Then maybe I really should put all readings in an array (processed in the loop); but in order to make the readings more "real-time", I have to speed up the ADC by setting the lowest prescaler.

Koepel:
It is very easy to get to 100 µs and beyond.

Wait... what's the time window it has without clock pulse stretching? 10 or 100?

By the way, if the slave shouldn't lag against the master's request, then doing a byte fetch from RAM, increment an 8-bit counter and a couple of ifs, is fine for a "data request interrupt"? I don't mean about the onRequest event, but one that triggers every requested byte.

I'm not familiar with the HardWire-Arduino-Library. So I don't know what kind of effects it has.

I was talking about bytes per second when receiving or sending data. To keep the clock pulse stretching minimal, there is 100 µs between the bytes.

For "real-time" high speed analog sampling, the I2C bus is not the best choice.
Is that your goal ? What kind of speed would you like to achieve ? For only one analog channel or for all digital and analog inputs ?

There are software tricks.
For example sending a command to the Slave to set it into some kind of analog-capture-mode. Then the Master could read two bytes of an analog channel, and nothing else. However, in that case the Master itself can not execute a lot of code, since the Wire.endTransmission() and the Wire.requestFrom() are waiting until the I2C bus transaction has completely finished. The Master is wasting all the time it takes to transfer data over the I2C bus.

Perhaps this is not the right direction for what you want to achieve.

Koepel:
To keep the clock pulse stretching minimal, there is 100 µs between the bytes.

If that's the case, then it is plenty for everything I have planned, except the analog read. Decreasing the prescaler will make it take less than that?

Koepel:
For "real-time" high speed analog sampling, the I2C bus is not the best choice.
Is that your goal ? What kind of speed would you like to achieve ? For only one analog channel or for all digital and analog inputs ?

  • I know; it isn't meant to be used as an analog sampler.
  • My goal is just to maintain the ADC capability on an Arduino as an I2C I/O expander.
  • Speed... since is not a sampler, then anything not lower than 1 KHz (don't take more than a millisecond) I guess.
  • Only one, if at most, 2 bytes are requested. In "analog mode", an even amount of bytes should be read; or else only the LSB of the last attempt is transmitted, and the "address counter" is not incremented after that attempt (it does only if both bytes are sent).

Koepel:
There are software tricks.
For example sending a command to the Slave to set it into some kind of analog-capture-mode. Then the Master could read two bytes of an analog channel, and nothing else.

It breaks my "secuential read" feature, so it won't work for me. However, there's a "hold address" bit in the "configuration byte"; if set, then the slave will behave exactly like you said: a repetitive requesting will only yield the reading of the same "address" (aka pin) over and over.

Koepel:
However, in that case the Master itself can not execute a lot of code, since the Wire.endTransmission() and the Wire.requestFrom() are waiting until the I2C bus transaction has completely finished. The Master is wasting all the time it takes to transfer data over the I2C bus.

Perhaps this is not the right direction for what you want to achieve.

It's fine since (again) is not intended as an analog sampler; we just saw I2C is not efficient for that matter anyways...

Only for reading a LDR or other analog sensor? That's no problem.
When using the normal Arduino Wire library then an analogRead() in the Wire.requestFrom() will cause a little clock pulse stretching, but that's no problem either.

I have a few Arduino Unos as a Slave with sensors. The Master is a Arduino Mega 2560 with Ethernet Shield running a webpage. When showing the data on a webpage, I request all the data from the Slaves during the build up of the webpage. The delay is hardly noticeable.

Koepel:
Only for reading a LDR or other analog sensor? That's no problem.

Of course, only for the simple stuff.

Koepel:
When using the normal Arduino Wire library then an analogRead() in the Wire.requestFrom() will cause a little clock pulse stretching, but that's no problem either.

Umm... requestFrom is a function for the master; nothing to worry about.

My concern is in the slave side; first for the requested bytes count (solved), and now asking if it is possible to transmit runtime-generated data (unbuffered or synchronic) without messing up the master's timing (after every ACK) or resulting in corrupted data.

Koepel:
When showing the data on a webpage, I request all the data from the Slaves during the build up of the webpage. The delay is hardly noticeable.

Delay isn't the problem either. Already said what actually is...

I ment a little clock pulse stretching in the requestEvent during a Wire.requestFrom() from the Master. I messed it up a little :confused: Some delay by clock pulse stretching is okay.

My Slaves have a number of sensors and a struct with data. The simulated "registers" (like the registers of a sensor) are that struct. The Master and the Slave know the struct.

You want to be more flexible than that.
If the Slave Arduino has nothing else to do, it could update the simulated "registers" in the loop() without delay. Put all the analog and digital inputs in those "registers".
Even when a pin is an OUTPUT, it is still allowed to call digitalRead() for that pin.

You could request all the data at once, since it fits in the maximum of 32 bytes of the Wire buffer.
Or you can do what I described: the Master should always send the "register address" first and then request some data.

I think you should stick with a normal solution and make that reliable.
You might add some tricks or optimize the timing later.

It is very easy to get into (a lot of) trouble with an Arduino as I2C Slave. My best advice is to stay far away from fancy trics.

Suppose the Master could tell the Slave: "You can be the Master for the next 2 seconds". Then the Slave can send data directly to the Master. That means another layer of software is needed to avoid that two Masters collide on the I2C bus. There are probably more of those trics that will result into a lot of trouble.

Suppose that you want to use the DHT or OneWire or NeoPixel library in the Slave. Those libraries use specific timing and turn off the interrupts. That will also mess with the I2C communication.