electronicayciencia / EasyMCP2221

The most complete Python library to interface with MCP2221(A).
https://easymcp2221.readthedocs.io/
MIT License
15 stars 3 forks source link

Long time between i2c calls #7

Closed pe1obw closed 5 months ago

pe1obw commented 5 months ago

I connected an MCP2221A board to an i2c slave (implemented in FPGA, using clock stretching). Then I made a simple test program:

mcp = EasyMCP2221.Device()
addr = 0xB0 // 2
mcp.I2C_write(addr, [0x00, 0x00], kind='nonstop', timeout_ms=1000)
result = mcp.I2C_read(addr, 8, kind='restart', timeout_ms=1000)

The code works, but slowwww... There's a long time between the i2c start, the write and the read. See oscilloscope diagram. First you see the start (halfway the first division), then after ~440 ms the write is done, then after 330 ms the read and stop are executed. My PC runs Windows 11 and Python 3.12. On the same PC, the PyMCP2221A driver, does not show this delay. However, this driver has other issues (it hangs the bus when reading > 40 bytes) and I like the API of EasyMCP2221 more 😄.

On another Windows 11 PC, this all runs without the weird delays!

Any idea? Thanks in advance!

SDS2104X Plus_PNG_4

electronicayciencia commented 5 months ago

Hello.

I have no clue. You said that in another similar PC this delay does not happen? It may be some driver that interferes with USB or some weird USB controller.

You can try to activate the trace messages in order to debug the issue. See the end of this section: https://easymcp2221.readthedocs.io/en/latest/api_reference.html#low-level-and-debug (Device.debug_messages and Device.trace_packets).

It might be related to your FPGA or clock stretching. To discard some things, do you experiment the same delay with another scripts? Try some of the ones in https://github.com/electronicayciencia/EasyMCP2221/tree/master/examples. Let's say read/write an EEPROM (well known I2C, no clock stretching) or DAC output (continuous commands, easy to see the timing issues in the scope).

Some of the EasyMCP2221 routines retries multiple times. Maybe your USB port is unreliable and it takes several attempts to get the data.

73

pe1obw commented 5 months ago

Thanks for answering! I activated the logging but see no errors or unexpected things. 'Internal status' is 55.

It might be related to your FPGA or clock stretching

It's also slow without any slave attached, as shown in the oscilloscope diagram there is quite a long delay between the 'start' condition and the first address byte. The example program I2C_scan.py from your site takes minutes to complete (with or without slave attached).

or DAC output (continuous commands, easy to see the timing issues in the scope).

Good idea! The triangle has a period time of 6.5 seconds 😄! So, also the DAC commands are very slow. Even the mcp = EasyMCP2221.Device() takes 0.77 seconds.

Some of the EasyMCP2221 routines retries multiple times.

I expected these to be visible in the trace messages. Also, some test code using the PyMCP2221A driver from https://pypi.org/project/PyMCP2221A/ uses the same underlying library and does not have the delays, while using the same USB device. I must say I don't see it as well, there's not so much code in the EasyMCP2221 driver that can do this, I think? Maybe a version conflict? pip freeze gives me

EasyMCP2221==1.7.1
hidapi==0.14.0
setuptools==69.2.0

(this is the same as on the computer where the software gave no delays).

Thanks anyway, and if you come up with something brilliant I'd be happy to try it, as the EasyMCP2221 driver is the best I could find so far (I also tried pyFTDI - good, but no support for clock stretching).

pe1obw commented 5 months ago

I could not resist playing with it some more!

The gpio toggle rate with your gpio example code is ~4.6 Hz on my PC, i.e., a bit more than 100ms between each toggle. After some more experiments, I found that adding a short sleep between the calls to hidapi write() and read() helps.

                time.sleep(1e-3)  # this "fixes" very long delays...
                r = self.hidhandler.read(PACKET_SIZE, 50)

With the additional sleep, it toggles at ~250 Hz! On my PC, the 1ms is a 'magic' value, 0.8 ms is too short, then you see occasional long delays. Weird. An even better solution was to set the timeout parameter in the read() to 0, or remove the timeout parameter completely:

                r = self.hidhandler.read(PACKET_SIZE)

This works for me, also 250 Hz toggle rate and i2c functionality (read & write) seems to work ok. Any other timeout results in the delay! Do you think the timeout parameter can safely be removed?

electronicayciencia commented 5 months ago

Hello.

Thank you for further investigate the issue!

Do you think the timeout parameter can safely be removed?

I'm not sure about the side effects of removing that parameter . I'll try to find out. I'll at least include an option to allow the user to modify it.

electronicayciencia commented 5 months ago

I've released EasyMCP2221==1.7.2 without this parameter. Now the HID read should fail after the default 1000ms delay instead of 50ms. Please, can you confirm if that fixed the issue?

pe1obw commented 5 months ago

Great!

I can confirm that the long delays, observed with v1.7.1, are gone with v1.7.2 🥳! Speed is as expected now, with only the expected USB delays (a few ms) between the transfers.

Also, my other PC, which did not show the delays with v1.7.1 works ok with v1.7.2 (no regression).

Thanks a lot for fixing this!

electronicayciencia commented 5 months ago

Hi again.

I've read this part of your README:

The MCP2221A does support clock stretching, but it can leave the i2c bus in a 'hang' state when a transfer is terminated unexpectedly. I could reproduce this by terminating the program while reading the flash contents, after that SDA stayed low and the interface needed to be reset. This problem might be fixed in the future...

I think you mean: https://easymcp2221.readthedocs.io/en/latest/api_reference.html#EasyMCP2221.exceptions.LowSDAError

You can try this if you have one spare i/o pin: Just connect it to the SCL line (with a resistor) and manually cycle it multiple times until the slave finishes the transfer and releases SDA. Then proceed as usual.

pe1obw commented 5 months ago

Hi,

Great, thanks for pointing me to this hint! Yes, this is the issue, and the proposed workaround will for sure work. Indeed, pulsing SCL 9 times should be enough to free the bus in any case.

I'll try it! I'll put a diode between GPIO and SCL, I think, to avoid ever driving the bus high (e.g., when the MCP2221 has not been configured yet or is misconfigured).