Go Down

Topic: Doubts on Wire library implementation (Read 2107 times) previous topic - next topic

Lucario448

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 :smiley-confuse:

pylon

Quote
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.

Quote
The event triggers once per request or once per byte?
It triggers once per request.

Quote
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.



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.

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.
"The problem is in the code you didn't post."

Lucario448

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?


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.


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! :)  For all the ideas.

Koepel

#4
Dec 07, 2017, 03:56 am Last Edit: Dec 07, 2017, 03:59 am by 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.

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: https://github.com/thexeno/HardWire-Arduino-Library. I think that is exactly what you need if you don't use the "register" approach.

Lucario448

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?

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?

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?

Have a look at this: https://github.com/thexeno/HardWire-Arduino-Library. 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?

Koepel

#6
Dec 11, 2017, 05:36 pm Last Edit: Dec 11, 2017, 05:41 pm by Koepel
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.

Code: [Select]

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.

Lucario448

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.



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.

Koepel

#8
Dec 11, 2017, 07:53 pm Last Edit: Dec 11, 2017, 08:02 pm by Koepel
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.

Lucario448

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.

GolamMostafa

#10
Dec 13, 2017, 04:44 am Last Edit: Dec 14, 2017, 01:52 am by GolamMostafa
Quote
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:
Code: [Select]
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.




Lucario448

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?

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.

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.
"The problem is in the code you didn't post."

Koepel

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.

Lucario448

#14
Dec 16, 2017, 05:23 pm Last Edit: Dec 16, 2017, 05:26 pm by Lucario448
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?

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...


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.


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.

Go Up