Errno 110 Timeout when using I2C between Pi and Pico 2 using Arduino HAL

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.

Do you have a short delay between the I2C write and the read on the RPi?

I'm using a SAMD21 instead of a Pico as the slave.

      bus.write_i2c_block_data(13,1,[count,count + 1,count + 2])

      sleep(0.001)

      data = bus.read_i2c_block_data(13,0,4)


In this case, I don't need a delay. The read_i2c_block_data happens independently of any writes.

Basically the logic is:

  • I2C writing from the Pi requests that the Pico turn things on and off (high-power LED, steppers, sound board, etc)
  • I2C reading from the Pi requests that the Pico send light sensor readings (from light sensors connected to the Pico on I2C0; these light sensors all work properly and are muxed through an I2C mux)

So my Python control script on the Pi simply reads arguments from the command-line, parsed through argparse, and the control flow for reading data from the Pi only has reads in it.

Here's a fragment of the reading code in Python, even if it won't entirely make sense since it's from a larger project:

    elif args.command == "read_light_sensor_03":
        result = bus.read_i2c_block_data(0x08, 0x63, SIZEOF_UNSIGNED_LONG + SIZEOF_UNSIGNED_LONG)

        timestamp = result[0:4]
        timestamp = int.from_bytes(timestamp, "little")
       
        value = result[4:]
        value = int.from_bytes(value, "little")
        valueString = f"Light sensor 3: {value}; "

        s = f"Timestamp (ms): {timestamp}; {valueString}"

        print(s)

I should say that I also get the same timeout errors when using i2ctransmit from the command-line.

I'm reading two 4-byte values from the Pico (both unsigned longs). I should also say that this problem occurs when trying to read 8 bytes as well as when trying to read 32 bytes. I have not tried only reading a byte or two at a time to see if I still get the same timeouts.

My workaround for this, if I'm not able to get it to work using I2C, is likely to just have a separate USB serial channel between the Pi and the Pico just for reading the values from the Pico.

I'm reading 4 bytes at a time every second with no problems here.

How many devices are on the I2C bus?

I don't have a Pico to test, so maybe somebody else will help you test that.

Just one device on the I2C1 bus between the Pi and the Pico.

Thanks for your help so far!

Just one device on the I2C bus between the Pi and the Pico (that is, only the Pico).

Thanks for your help so far!

Just out of curiosity, what is the purpose of the Pico?

What is it doing that the RPi can't? My requirement was A/D converters.

Well, the design is already set and will be part of an art installation in a month, so major hardware things can't be changed.

The Pico controls:

  • A DC motor at 6V
  • Two steppers at 5V, along with photointerrupters indicating position, and enable pins for both stepper controllers
  • One stepper at 6V, along with an enable pin for the stepper controller
  • A high-powered LED at 12V using a Meanwell dimmer IC that requires PWM
  • A sound player using 5V that communicates over serial
  • 4 light sensors, which all use the same I2C address, necessitating an I2C mux in-between the sensors and the Pico

So there are four different voltages that needs to be dealt with here (including the 3.3V for the Pico). And the timing needed to control all of this is much easier on the Pico. The Pi will be running some extremely processor intensive tasks that necessitates the Pi.

So yeah, the microcontroller is being used for what its good for: managing lots of different peripherals :)

Maybe the Pico is a little too busy to respond to a request from the RPi before generating a timeout on the RPi.

Just a guess.

Responding to I2C messages is done in interrupts by default, so no, it's not too busy. Plus, when I'm doing the testing, the Pico isn't doing anything else other than its main loop and responding to the individual read requests from the Pi.

any particular reason to use I2C for communication?

simpler to plug the Pico USB in to a RPi4 USB port which creates a virtual serial port which can be access from C++, Python, etc

Since the circuit board is a visual part of the installation, we didn't want to have an extra cable going between the Pi and the Pico, and rather just use the I2C traces that are in the PCB I designed. And writing to the Pico to command it to control the other peripherals works just fine...it's the reading that times out. I'd really like to figure out why this is occurring, especially if it points to some kind of bug either on the Pi or the Pico in terms of their hardware libraries.

But given time pressures (the opening of the exhibition is soon), as mentioned earlier in the thread I will likely use a USB cable to to read the data from the Pico over serial.

I'll guess it's the Pico. It could be some other code is using interrupts or stopping them to do some processing.

My SAMD21 is still running my i2c test code without fail. I'm using a RPi Zero 2W using the latest OS release as the master.