Home
Got Linux ?

Blah blah blah... Mostly technical thoughts, rants and gibberish


Raspberry Pi, Arduino and I2C

[2021-11-25]

Lately, I had a lot of fun - and frustation - playing with the I2C bus/protocol between a Rapsberry Pi and an Arduino-like expansion hat - namely, the Sleepy Pi - as part as my Pi Station project.

There are numerous examples on the Internet on how to achieve that - starting by the simple and excellent examples of the Arduino Wire library - and as many reports of frustation revolving around the (sporadic) i2cget -> Error: Read failed error message.

The catch

Recently, that frustration nearly turned into suicidal will, following the latest (2011-10-30, 64-bit) update of the Raspberry Pi firmware - aka. RaspiOS - and the dreaded i2cget -> Error: Read failed error message becoming systematic.

TL;DR, we Raspberry Pi owners - especially those of the BCM283x-powered versions (1, 2 and 3) - are bound to experience frustration - even contemplating death - given:

The rules

In parallel, the Internet is full of - more or less patroning - comments about a few rules one must observe when doing I2C, interrupts in general and Arduino specifically:

The quirk

Even though one might has taught oneself all the basic things one ought to know, bam!, frustation hits with i2cget -> Error: Read failed.

It’s unclear - to me at least - what the default I2C Clock Divider might be. And for some reason, it seems the situation is made worse by the latest RaspiOS release.

Without detours, here is how I initially solved it:

More specifically, on the Arduino, and very shortly put:

  // INTERRUPT SERVICE ROUTINES

  // I2C Receive
  void isrI2cReceive(int iBytes) {
    yI2cOpCode = iBytes-- > 0 ? Wire.read() : I2C_OPCODE_NONE;
    if(yI2cOpCode != I2C_OPCODE_REQUEST) {
      for(int8_t i=0; i<I2C_BUFFER_RECEIVE_SIZE; i++) {
        pyI2cReceiveBuffer[i] = iBytes-- > 0 ? Wire.read() : I2C_VALUE_UNSET;
      }
    }
  }

  // I2C Request
  void isrI2cRequest() {
    if(yI2cRequestSize != 0) {
      Wire.write(pyI2cRequestBuffer, yI2cRequestSize);
    }
  }

  // ARDUINO MAIN SETUP/LOOP

  // Setup
  void setup() {
    Wire.begin(I2C_ADDRESS);
    Wire.onReceive(isrI2cReceive);
    Wire.onRequest(isrI2cRequest);
  }

  // Loop
  void loop() {
    switch(yI2cOpCode) {

      case I2C_OPCODE_REQUEST:
        // I2C is LSB first
        for(int8_t i=yMetricSize-1; i>=0; i--) {
          pyI2cRequestBuffer[i] = (uint8_t)(uiMetricValue >> 8*i);
        }
        yI2cRequestSize = yMetricSize;
        break;

      case I2C_OPCODE_RECEIVE:
        for(int8_t i=0; i<I2C_BUFFER_RECEIVE_SIZE; i++) {
          Serial.println(pyI2cReceiveBuffer[i]);
        }
        break;

      }
    }
    yI2cOpCode = I2C_OPCODE_NONE;
  }

And very lengthily put: sleepy-pi.ino

The end

My own feable attempt to explain the problem at hand, given how I managed to quirk my way around it, is:

Still, no euphoria…

So I went on to alternatively try that dtparam=i2c_arm_baudrate=<freq> parameter, starting with a very reasonable 100kHz (100000) value. No change. Frustation. Again.

PS: Remember we’re talking the Sleepy Pi expansion hat, a 3.3V-running GPIO-compatible 8MHz ATmega328P, with no lengthy wires, no missing pull-down/up resistances, no alien goo spat on it, etc.

Out of despair, I lowered that frequency to 10kHz… and… bingo! Each and every i2cget calls succeeding! Life! Hope!!! FUTURE!!!

dtparam=i2c_arm_baudrate=10000

But then, 10kHz… WTF?!?…

The don’t

Digging further, I face-palmed myself realizing that one may change the I2C Clock Frequency of the Arduino, using the Wire.setClock function.

But don’t follow me there (unless you wanna die)! After further reading on I2C Clock Stretching, the basics of I2C 2-wire signaling eventually imprinted my slow brain, in particular the fact that the Clock signal/wire (SCL) is controlled solely by the I2C Master and Wire.setClock does not apply to I2C Slaves (the exception being… Clock Stretching! which merely indicates to the Master that it should hold).

However, bear with me, should you ever want to Wire.setClock when using the Arduino as the I2C Master.

I stumbled on a few reports claiming the resulting frequency might be wrong given some non-standard setups (like modding the Arduino frequency). So I verified:

… enough …