I2C Slave mode Clock stretching intermittant failure

I’m using the I2C interface to communicate with a Microchip PIC controller. The PIC is the master and the Photon is the slave. The master (PIC uController) is doing both writes to the Photon (handled with onReceive()) and reads (handled with onRequest()).

Here’s the problem: If I do a read request from the photon slave the photon sends the Photons Slave address, not the data it should be sending. I am requesting 3 bytes of data in the request and all three bytes sent by the Photon are the address of the photon.

Now for the interesting part If I do a Write from the master to the slave Photon before the read request everything works just fine. As long as I proceed each read request with a write first, then the Photon sends the data I am expecting.

I’ve looked at the situation on my oscilloscope and I see in the failed read that the Photon Slave is sending an ACK but not doing a clock stretch while it goes off to retrieve the data from the handler. However when the read is successful (i.e.- it was proceeded by a write first) then I see the clock stretch occur after the ACK as one would expect because it has to execute the handler before it can respond with the data.

Is there a higher level protocol that the Wire library is expecting? Is it assumed that a write from the master must occur before a read request? My Photon code is exactly like the example shown in the API Docs.

I doubt if the user community uses the Photon as a slave very often as most applications the Photon would be the master talking to peripherals whereas i’m using the photon as a slave. So maybe I’m the first to see this.
My problem seems very basic. It seems clock stretching is not enabled or initialized correctly since I see no evidence of the clock stretch on my scope when the failed read requests were made. But like I said above, as long as the read is proceeded by a write from the master the read will work and I see the clock stretch holding off the transfer while the handler retrieves the data to be sent.

Anybody else encounter this problem? I think it’s actually a bug in the Photon Firmware. I have pdf’s of the oscilloscope plots if anyone is interested.

1 Like

AFAIK the slave is never in the position to start a request - the slave is expected to stay silent until asked by the master.
The master either initiates a write to the slave or requests a response from it but never the other way round.

The fact that the slave sends its address can be understood as a way for the slave to seek attention of the master who in turn may or may not allow the slave to speak by actively asking.

The initial write of the master allows the salve to speak and at the same time denies the other slaves access to the bus. It also allows the master to select the register of interest from the slave he's speaking to.

My wording must not have been clear. I know the slave is always passive.

To be a clear as possible, in my design the PIC is the I2C Master and the Photon is the slave. I am passing data back and forth from the PIC to the Photon with I2C Writes (R/W bit of address byte cleared) and reading data from the Photon into the PIC with I2C Reads (R/W bit of address byte set). I use the terms ‘Reads’ and ‘Writes’ with respect to the Master talking to the slave.

It was only by accident that I found that preceding the Read with a Write first got a correct transfer of data. The writes always work but its the reads that fail unless I do a write first. In my application the Reads and writes are totally independent of each other. I’m just passing data back and forth. Most of the time I will be reading data from the Photon. Only occasionally doing a write from the PIC to the Photon.
The PIC is always the master and the Photon always the slave. As it stands right now, I will have to do a dummy write first in order to get the read to work. Kind of silly right?

In the Particle Doc’s the descriptions of the two handlers are straightforward. One for reads and one for writes only a little different depending on whether the Photon is master or slave. The Docs don’t describe a higher level API where, for example, maybe you have to send some buffer address pointers before you start issuing read requests. Its appears to be a very low level of an I2C API. I dont see anything in the docs that says the master has to write a command or something similar first before a read request is made.

Looking at the transfers on my O-scope shows the problem pretty clearly. When the read request succeeds, the Photon Slave holds the I2C clock low after the I2C hardware address byte is received and the Photon Slave sends its ACK. Clock stretching in action. After a couple of milliseconds the Photon slave releases the clock and the master takes over and clocks in data from the Photon slave to the PIC master. This makes sense because firmware had to execute the handler to get the data that is going to be sent back to the master. The handler has to do its work before the transfer can happen.

The Master Writes to the Slave always work and I dont see any clock stretching happening. This makes sense because most processors can keep up with the bytes coming in, buffer them up, and then do heavy lifting after the transfer is done. Thus the handler is a post process in the case of receiving data.

But when the reads fail its because the Slave Photon doesn’t do a clock stretch (i.e. - hold the clock low). It just sends its ack, never holding the clock low, and the master starts clocking data right away. It’s as though the Photon Slave hasn’t initialized something in the firmware to enable the clock stretch or the address match interrupt routine isn’t being called (i.e.- OnRequest() handler. The fact that I get the address of the slave instead of real data that the handler would have provided also makes sense because I2C is by definition half duplex communication and the data buffer is shared by both reads and writes. The address received by the Slave during the reception of the address byte will be in its data buffer until the Slave firmware overwrites it with data. It’s pretty clear that the Photon firmware is not responding to the Read request and calling it’s handler… … Unless the firmware is implicitly expecting a preceding Write from the master first. Nothing in the Docs decribes the need to write to the slave first. Is there something missing in the docs? Bug in the software? Something Else?

2 Likes

Nice catch on the clock stretch. Using the Photon as a slave, I see 0xa1 intermittently (my address is 0xa0 + 1 for read bit), depending on how much I try to do within onRequest. The more I try to do in there, the more 0xa1’s happen. Of course I don’t want to do anything in there, but have to, because Wire.write must happen within the handler. I suspected a clock stretch might help, but docs show clock stretch is enabled by default so I didn’t look further. I didn’t check on the scope like you did.

So yes, someone else has seen this problem. Like you I don’t see the problem after a master write.

OS v1.2.1.

The Particle devices (and most Arduino/Wiring flavours) use a 7bit address where the read/write bit gets shifted out.
So if your raw write address is 0xA0 and your read address 0xA1 then the address you provide in the Wire.begin() call would be 0x50.

Is this on a logic analyser or where?

Yes I can see it in the logic analyzer and through debug statements in my code. I narrowed it down to occurring on the first byte(s) immediately after the master wrote the read setup… except for the very first read setup, in which everything was fine (the first read setup always followed a master write).

However, I am pretty sure I figured out the problem.

The number of bytes being read by the master exceeded the 32 byte buffer. Logic analyzer showed that it was trying to read 236 bytes. I changed the buffer size in i2c_hal.h to 255:

#define I2C_BUFFER_LENGTH 255

Note that this is the highest number permitted (at least in OS v1.2.1).

I set up a test uint8_t buffer with 256 elements all set to zero to test. Then within my OnRequest handler I simply put: Wire.write(buf,255);

That got rid of all the a1’s. Now I have different problems. I thought that OnRequest would fire every time a byte is requested, but it only fires when a read setup is received. I guess that’s all it can do. For this reason though I have no idea how many bytes the master is going to read. This has been mentioned in the Arduino forums. The answer seems to be to write more than the master expects, if possible, and if the buffer limit of 255 is enough for all scenarios (it isn’t for me).

Thanks for your attention.
Doug

1 Like

Are you locked at 1.2.1 for a particular reason?
Later versions offer Wire.acquireWriteBuffer() for an up to 512 byte buffer.

For your unknown message length issue that probably comes from the typical use case where the sensor/slave actually “dictates” how much data it would have to offer and the master “should know” to request that amount.

However, over time with smart I2C slaves that assumption is obviously not valid anymore but standardisation and implementation has not caught up with that development :wink:

One way to work around that when you are in charge of both (master & slave) you could first inform the slave about the number of bytes the master will request via Wire.wirte() and then send back that amount on next request.

Hi thanks for your continued interest.

I saw that option to increase the buffer from the sketch in 1.5.0 (but I didn’t realize it also permitted a 512 limit - which still isn’t enough, by the way). I tried the 1.5.0 toolchain a few days ago but it would not run when I POSTed 12k data to the device. I am running a softAP based REST server, and I want to accept as much POST data as I can. 1.2.1 is giving me about 20k of room, but it seems 1.5.0 was choking on much less, so I went back to 1.2.1.

Regarding using the master to inform the slave, I don’t have any control over the master’s firmware. It is what it is. It thinks it is talking to EEPROM. It writes a 16 bit address, then proceeds to do a sequential read of X bytes. X can be pretty large.

Meanwhile I think I fixed my 255 txBuffer limit. I commented out the code under “case I2C_EVENT_SLAVE_BYTE_TRANSMITTED” in i2c_hal.c and had it call my callback_onRequest instead. Now my sketch is called on every byte transmission and I call I2C_SendData directly within the handler, completely bypassing txBuffer for slave transmits.

Beyond this I added a bool parameter to Wire.OnRequest, which i2c_hal.c sets to true for I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED and false for I2C_EVENT_SLAVE_BYTE_TRANSMITTED. So I can discern a read setup from a byte read within the handler. This change required a lot of busy work because the function signature is declared in a lot of places.

1 Like

Okay final answer:

  1. Bypass txBuffer by commenting out code for the case I2C_EVENT_SLAVE_BYTE_TRANSMITTED in i2c_hal.c. Replace this with a call to i2cMap[i2c]->callback_onRequest(). This calls the OnRequest handler in the user sketch on every byte read by the master. Within the sketch, call I2C_SendData (I2C1, value) to send a uint8_t value. If you don’t have data to send, call I2C_StretchClockCmd(I2C1, DISABLE). It will be re-enabled when the master hangs up.

  2. I had trouble because the master WRITE immediately before read setup did not have a stop condition before the read setup. This meant that OnReceive is not called (it is called on stop condition after a write). This leaves the written bytes in the rxBuffer and Wire.available() will return a negative number, and the bytes cannot be read from the buffer. The fix is to do in the I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED case what they are doing in the I2C_EVENT_SLAVE_STOP_DETECTED case. Namely:

i2cMap[i2c]->rxBufferLength = i2cMap[i2c]->rxBufferIndex;
i2cMap[i2c]->rxBufferIndex = 0;

This just sets up the rxBuffer so it can be read using Wire.Read within the OnRequest handler to pick up any bytes written by the master immediately before the read setup.

Seems to me 2) could be a bug fix. Not sure. I don’t see a downside to it.

PS the whole onRequest(bool) thing was unnecessary once I got the rxBuffer sorted out, so I removed that modification (fortunately).

Link for reference: