Open haydenroche5 opened 8 months ago
@igrr Can you offer any advice here?
My apologies, I wouldn't normally @ you directly, but we are trying to make a decision about the future of a product. It would be nice to hear from someone on the Espressif side before we make any decisions.
I have already have a solution and it's now in reviewing internally. If you are urgent. I can provide a patch for you late this day. @haydenroche5
I have already have a solution and it's now in reviewing internally. If you are urgent. I can provide a patch for you late this day. @haydenroche5
Sure I'd be interested in the patch.
i2c_stretch_patch.txt Try is if it's meet your requirement or not. Or any suggestion.
Thanks @mythbuster5! I haven't had a chance to try the patch out yet, but just looking at the diff, it looks promising.
I noticed a potential problem in the patch, which is that in test_i2c_stretch.c, the stretch callback, test_i2c_stretch_callback
, calls functions not in IRAM:
i2c_slave_get_tx_empty_size
i2c_slave_write_one_shot
So this could cause a stall if these functions are in PSRAM, I think.
Am running into the same problem. Thanks for posting the code changes. I'm using the S2, but it too lists clock stretching as being supported. But I think I can port your changes to the S2 HAL.
However, in the S2, the clock stretch reason function returns void:
Unsure why that is. I'm going to at least try to interact with the hardware as specified...
Without clock stretching it's not a viable slave device.
i2c_ll_slave_clear_stretch(hal->dev);
Brand new to this chip, but does this clear the stretch? If so, you have to put in the bytes before you do this. Isn't it better to clear the stretch interrupt? Or am I misreading what this does?
EDIT: OK, I think this is already being done by the ISR. But yeah, I don't see why you clear the stretch condition here.
OK, now I see, that's what's in the code already. I think this is a mistake, at least for the TX buffer empty clock stretch. The correct way to clear this is to put in bytes to send.
I think I get the picture now of what needs to happen. If I can get this to work, I'll post back.
@schveiguy For what it's worth, I ended up giving up on trying to get the driver to work for our use case. I pulled in the Arduino ESP32 I2C slave driver code, and immediately recognized that the code addressed several of the bugs/problems in the IDF driver. The support for clock stretching is much better, too. It would be great to get the IDF driver on par with the Arduino one, but if time is of the essence, I recommend checking out the Arduino driver.
I think it depends on how the data to transmit/receive is populated. Since I think in IDF, you don't directly write to the hardware buffer, you write to a queue, which is then pulled into the buffer on a separate task, then you are going to miss the window.
Between the ACK of the read command and the clock for the first data byte, it's only one clock cycle. You have to stretch the clock until that hardware buffer is filled. I used to work on a device (STMicro 8-bit) that basically interrupted until you put in the next byte to send, and that is when it knew to stop stretching the clock.
TBH, the hardware spec is very complicated, but I think this might work. Going to try it now.
It is good to know there is another option that might work though, thanks for pointing it out @haydenroche5!
Ugh it just gets worse and worse -- so let's say I receive data on the slave. I pass in a buffer to fill, and a length. But on the callback it doesn't tell me how much data the master actually sent! it just gives me a buffer pointer.
I'm looking at the example on the espressif web site, and it creates a variable for the received data length but never uses it.
No serious person is using this driver for anything, is my assertion.
EDIT: Yep: #13954
So, I ended up writing my own custom driver. The driver in IDF is completely incorrect. In addition to the above mentioned issues:
It looks like the driver was written by someone who doesn't understand how i2c slave events happen -- you need to handle events as the master addresses you, not with calls from outside the ISR.
My new driver is very simple:
And that's it. All hardware and driver buffers are cleared after the callback for completion. If you want the data outside the ISR, it's on you to store it for later processing.
Works correctly, will be building the rest of my code on top of it. I don't know if I can contribute it back (is a project for a client), but of course, the entire slave API would have to change.
But if anyone has questions about how it works, happy to answer. The hardware spec is very cryptic and not a lot of examples that match interrupts to the protocol. It also has some unexpected things -- like there is no interrupt when the device is addressed (unless it's a slave read), and you get an interrupt for a stop condition even if your device wasn't addressed. I basically had to do a lot of trial and error.
Hi Schveiguy I read this thead carefully and I face the same problems. I had a running I2C-Slave code on ESP-IDF V5.0 with an ESP32-C3. Then I updated the code using the new I2C slave driver from ESP-IDF v5.3. Is there any chance to share your custom, updated driver? So far I was not able to fix it myself. Problems:
Thank you for your hints.
Is there any chance to share your custom, updated driver? So far I was not able to fix it myself.
I can ask if it's OK to share. This is part of a proprietary project, not under my control.
The stock esp-idf i2c slave driver is not usable, the design of it is fundamentally flawed (neither the 5.3 version or the previous design). If you want to use the device as a slave, you will need to write your own driver or find a driver code that is properly designed. There is mention up above that an arduino driver is available that might help, but I haven't looked at it.
That would be great if you could ask. I agree with you that the I2C Slave always had some problems with ESP-IDF. I have used the I2C Slave with previous esp-idf versions with some hacks that it worked well but there have always been some cases where errors or NACK have not been handled correctly. There are several problems. I will look into the Arduino I2C slave driver in order to see if I can use it on my esp-idf project. I hope the driver will be fixed on later version of esp-idf. I already lost too much time on that part of the software.
Thanks for these posts, apparently it isn't me. I've been wasting two days on this.
I hope one day it'll work, since this is a stopper for me to use the esp32 in this project.
I got the OK to post the driver. I need to make sure it has appropriate copyright (will be Apache licensed), and make sure there's nothing specific to this project in there. It is very specific to the esp32s2 chip I'm using (Because the HAL functions for clock stretching are not correct). But you can probably rewrite it to your chip. I'll try to get it up this weekend.
That's great. Thank you so much for your support. Does your updated driver only works correctly with clock stretching enabled?
Correct. A slave driver won’t work without stretching unless you have all the data to send already in the fifo before the master addresses you.
sorry I didn’t get the driver up this past weekend. I unfortunately am on a trip and my work laptop has the code. I’ll see if I can get it. But it may be next week before I can post it.
I tried this one this weekend and works for my application: https://github.com/leeebo/esp_i2c_slave
Its a modified version of the arduino one.
Thanks again for your efforts. I have the following problem: The I2C master (which is not under my control) does not support clock stretching. So it just starts reading. The I2C Master continuosly "polls" with "Master Read" Commands and if the I2C Slave (ESP32-C3) wants something, it can send an ACK on the Master Read Request, otherwise a NACK. What happens now is that the ESP32-C3 slave also sends an ACK and returns a random byte when the txbuffer is empty. I was unable to avoid this. Earlier I tried to delete and re-init the complete I2C driver after each command. But it also caused problems as the tx buffer can only be filled when the I2C driver is already initiated. I am quite sure that the clock stretching method would solve the problem but in my case it might not work. Is there any other possibility to temporatily disable the I2C-Slave driver to force the return of a NACK until the buffer is filled and ready to be clocked out?
Here a I2C log image what happens after a Master read on Address 0x61. The ESP32-C3 immediately sends an ACK and returns the just received address byte (0x61 read = 0xC3):
I think this is an error in the ESP32-C3 I2C Slave as I would expect that a NACK is returned when the buffer was not filled.
Sorry for the delay, I did not get a chance to post the code while on the trip, I will post it soon.
The I2C master (which is not under my control) does not support clock stretching.
@VectorDominik I don't think this is possible. I2C is open drain with pullup signal, which means that any device on the bus can pull down the clock. It's impossible for the master to run the clock signal when someone else is pulling on it.
Your trace looks like the clock is being pulled down for the clock stretch (that long low period in the clock), but then it releases before the data is ready to be transmitted. This is consistent with the current released driver -- it clears the clock stretch before filling the FIFO with data (see here). You need to clear the the clock stretch only after you have data to send. As soon as the clock is released, the master will start driving the clock, expecting the slave is transmitting data.
This is always how the master/slave negotiation works! It's inherent in I2C.
I tried this one this weekend and works for my application
Yes, that driver has the key difference that it only clears the clock stretching after data is written. It's more complex than mine, mine does everything from the ISR, and it's on you to build a queue for the messages how you want to process them, or handle them fully in the interrupt. But it is similar in how it handles the clock stretching (good validation that I found the right mechanism). However, it is also clearing the interrupt masks before handling, so you will get repeat "ghost" interrupts.
Just published my driver code: https://github.com/schveiguy/espi2cslave/
Please focus any questions about it in that repo.
Thank you very much. I will give it a try this week. Very appreciated that you shared your driver code!
@igrr Are you guys working on fix for the ESP-IDF any time soon?
Regardless, this is becoming a useful reference guide, and may help influence or guide the official implementation.
Is your feature request related to a problem?
There's a known problem with the I2C slave driver where it responds to master reads with garbage if the slave's TX FIFO is empty:
This is easily reproducible with the following application running on an ESP32-S3:
If I then connect another MCU to the I2C bus as the master and have it read 2 bytes from the slave, the I2C slave responds with garbage:
In this case, the bytes are
0x2F 0xFF
, but they can be anything. It's not predictable (i.e. the response is garbage). For the protocol my team has built on top of I2C, this is not acceptable. For our case, if there's nothing available to read, the slave needs to respond with 2 0x00 bytes. Until recently, there was no way to do this with the IDF.Describe the solution you'd like.
The user should be able to respond to this scenario in a predictable way.
Describe alternatives you've considered.
Now, there is a way, but it still requires modifying the IDF code itself. The slave driver now supports clock stretching and its associated interrupts. Perplexingly, this isn't supported for the ESP32-S3, even though there's a whole section about it in the technical reference manual. So the first thing I had to do was alter soc_caps.h:
Now, the technical reference manual also states that there's an interrupt that should fire when the slave needs to respond to a master's read request, but the TX FIFO is empty. Quoting section 27.4.3:
That's exactly what I was looking for. However, if you install a clock stretching callback using
i2c_slave_register_event_callbacks
, the clock stretching reason register NEVER indicates this as the reason, even when the TX FIFO is in fact empty. The only reason you'll ever see in this case isI2C_SLAVE_STRETCH_CAUSE_ADDRESS_MATCH
. There's never aI2C_SLAVE_STRETCH_CAUSE_TX_EMPTY
.However, we can work with this. Again quoting that section from the manual:
When the R/W bit is 1, that means the master is doing a read. So we can use the clock stretching interrupt to detect when the master is reading, check if the TX FIFO is empty, and, if it is, stuff in 0 bytes, as required by our protocol. I hoped this would be achievable using the clock stretching callback, but it's not. I need access to
i2c_ll_get_txfifo_len
to check if the FIFO is empty andi2c_ll_write_txfifo
to push 0s into the FIFO if it is. Neither of these are accessible via application code. So I had to modifys_i2c_handle_clock_stretch
directly, like this:Now, the response to a master read when there's nothing to send is
0x00 0x00
, as required by our protocol:Additional context.
I don't know how you all want to handle this, but this is clearly something users have wanted over the years. My team's products all have to act as I2C slaves, and our SKUs that use ESP32 have been hamstrung by this deficiency in the IDF for a long time now. I would just ask that you consider making what I've done above possible in application/user code, without requiring any modifications to the IDF itself. Our application frequently reads and writes to external PSRAM, so we are subject to long delays where the I2C slave TX FIFO will inevitably become empty, as the code that feeds this FIFO is also in PSRAM.