I am having an intermittent timeout error when using I2C between a Pi 4 running as the I2C controller ("master") and a Pi Pico 2 as the peripheral ("slave"). I have tried a number of things and I am curious if anyone else has had this same problem before.
The Pico is on a custom PCB controlling a number of other peripherals on that PCB (stepper drivers, sound module, etc) over various other protocols, including the I2C0 bus on the Pico. The Pi communicates with the Pico over I2C, and this is done using I2C1 on the Pico. I have 4.7k pullup resistors on the I2C1 line and I'm using high-end switched mode DC/DC converters (TRACO) to power the Pico and the other devices. The Pi and the PCB use separate power supplies but share a ground.
I am using Python on the Pi and the Arduino HAL on the Pico (version 6.0.0 of the earlephilhower library).
The gist of the problem is that while I can use the Pi to write to the Pico with no problem (that is, commanding the Pico to do things, such as turn on a motor, play a sound, etc), when I try to read values from the Pico I can do so for a while, but eventually run into a 110 timeout error on the Pi. This requires the Pico to be reset before the Pi can either write or read to the Pico again.
The following is the relevant code for starting I2C1 on the Pico:
Serial.println("Starting I2C peripheral on I2C1");
Wire1.setSDA(D6);
Wire1.setSCL(D7);
Wire1.setClock(10000);
Wire1.begin(0x08); // I2C "slave" address
Serial.println("Setting I2C callbacks");
Wire1.onReceive(recv); // Called when master sends data
Wire1.onRequest(req); // Called when master requests data
The request and receive functions are relatively simple.
void recv(int len) {
int i;
// Just stuff the sent bytes into a global the main routine can pick up and use
for (i = 0; i < len; i++) {
buff[i] = Wire1.read();
}
buff[i] = 0;
}
buff is basically a buffer that holds a command and relevant other bytes for the command (how long to run a motor, how fast to spin it, etc).
On the Pi I am using smbus, the standard I2C channel (channel 1), standard GPIO pins for I2C, and I have lowered the bus speed to 10000. The Python code I use to write to the Pico is of the form:
bus.write_i2c_block_data(PICO_I2C_ADDRESS, STATE_LED_DIMMER, [dutyCycle[0]])
I can write as much as I want to the Pico from the Pi and no timeouts occur.
The issue comes with reading data from the Pico. On the Pi side, I use code of the form to request data from the Pico:
result = bus.read_i2c_block_data(0x08, 0x63, SIZEOF_UNSIGNED_LONG + SIZEOF_UNSIGNED_LONG)
In the Pico, code that is like the following responds to the request:
void req() {
if (i2cSendState == 0x63) {
int writtenBytes = Wire1.write(i2cBytesFullBuffer, 8);
}
}
On the Pi side I can request data at a rate of 1Hz, and after about a minute or so of doing this (the timing is seemingly random) I get an Errno 110 Timeout on the Pi. On the side of the Pico my watchdog timer does not timeout. It's as if the I2C1 bus is left in some kind of undefined state.
I have tried the following to no avail:
- Slowed the I2C bus to 10000Hz.
- Implemented a watchdog timer on the Pico
- Checked the return value of writtenBytes on the onRequest() callback and tried to reboot if it's not equal to 8 (the Pico doesn't reboot)
- Increased the priority of the Python script on the Pi using nice--timeout still occurs
Has anyone else experienced this issue before? Do you have suggestions of what else to look at?
Thanks in advance for any suggestions.