I2C using Pi smbus2 and Arduino Wire

Hello,

I’m working on a project where an Arduino will be sending data from approximately 50 sensors back to an RPi over I2C (RPi is master, Arduino is slave). I’ve had success using single byte read and write requests to pass single bytes of data back and forth. But I’m not having any success passing an array of bytes using smbus2.read_i2c_block_data. I continue to get the following error no matter what I try:

Traceback (most recent call last):
  File "/home/pi/sketchbook/I2Ctalk04/I2Ctalk04.py", line 36, in <module>
    readBunchOfData(4)
  File "/home/pi/sketchbook/I2Ctalk04/I2Ctalk04.py", line 25, in readBunchOfData
    myDataHere = readBlockData(numBytes)
  File "/home/pi/sketchbook/I2Ctalk04/I2Ctalk04.py", line 17, in readBlockData
    x = bus.read_i2c_block_data(address, 0, numBytes)
  File "/usr/local/lib/python3.4/dist-packages/smbus2/smbus2.py", line 391, in read_i2c_block_data
    ioctl(self.fd, I2C_SMBUS, msg)
OSError: [Errno 121] Remote I/O error

Line 17 is the " x = bus.read_i2c_block_data(address, 0, numBytes)" line. I’ve read that this is sometimes related to there being nothing on the other end of the i2c bus, but I can’t see where the problem is.

I’ve also noted that the Wire module appears to take an abbreviated approach. For example, there is no obvious implementation of the smbus2.read_i2c_block_data or write_i2c_block_data, or several other I2C function, nor does Wire implement the register/cmd byte. So it is not clear to me what I2C functions I can do with Wire other than read and write single bytes.

Any insight is appreciated. Many thanks.

Here is my code for the Pi:

from smbus2 import SMBus
import time

bus = SMBus(1)
address = 0x04

def writeNumber(value):
    bus.write_byte(address, value)
    return -1

def readNumber():
    number = bus.read_byte(address)
    return number

def readBlockData(numBytes):
    res3 = []
    x = bus.read_i2c_block_data(address, 0, numBytes)
    res3.extend(x)
    return res3

def readBunchOfData(numBytes):
    # Send number of bytes we are requesting
    writeNumber(numBytes)
    # Receive data
    myDataHere = readBlockData(numBytes)
    # Show what we've got
    print ("We received ", numBytes, " of data")
    for i in range(0, numBytes):
        print (myDataHere[i])
        

while True:
    # For now, hard-code 4 bytes of data
    print ()
    print ("We are requesting ", 4, " bytes of data")
    readBunchOfData(4)
    print ()

Here is the Arduino code:

#include <Wire.h>

#define SLAVE_ADDRESS 0x04
int number = 0;
int dataNo = 0;

void setup() {
  Serial.begin(9600);
  Wire.begin(SLAVE_ADDRESS);
  
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
  
  Serial.println("Ready!");
}

void loop() {
  doingSomething(10);
}

void doingSomething(int loops) {
  for (int i = 0; i<loops; i++) {
    Serial.print("I'm doing something else...");
    Serial.println(i);
    delay(50);
  }
}

void receiveData(int byteCount) {
  while(Wire.available()) {
    number = Wire.read();
    
    if (dataNo == 0) {
      // dataNo, so this is how many bytes to send - currently hard-coded at 4
      dataNo = number;
    }
    else {
      // We should not get here
      Serial.println("We got to hole 1");
    }  
  }
}

void sendData() {
  byte output[] = {0x01,0x02,0x03,0x04};  // This is just some sample data for testing
  Wire.write(output, 4);
  dataNo = 0;
}

Thanks.

The “read_i2c_block_data” method does not what you think it does. The second parameter is the cmd byte. This is sent as a write to the slave before the read is executed. The API you use on the Raspberry Pi is compatible with SMBus with is a standard that uses the I2C bus below but has additional rules.

That means before the read request is made two writes are executed. The first one sends a byte 0x04 to the Arduino, causing it to print to the serial interface during an interrupt service routine! You should never do this as the serial interface depends on interrupts and during an ISR all other interrupts are disabled. As you might have filled the serial buffer with other stuff already the Serial.println() call may block and freeze your Arduino without returning the requested bytes.

Hi, I have exactly the same problem as above. And I dont think it is just because of printing on Serial, because even with bare code like this i get the same error:

#include <Wire.h>
 
#define SLAVE_ADDRESS 0x0c

byte output[] = {0x01,0x02,0x03,0x04};
 
void setup() {
  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
}
 
void loop() {
  delay(10);
}
 
void receiveData(int byteCount) {
}
 
void sendData() {
  Wire.write(output, 4);
}

It seems like the arduino acts too slow and misses a clock and gets timed out. Because it sure gets one byte with the cmd inside that I did send and can read, after which should the arduino pull i2c data line down to acknowledge that it got something and is ready to write, but it seems like it never does, because the function sendData() is never called, that means RPi never sent the Read bit after.

I have not found a working example with more than byte_read(). The simple read_byte() function works because it immediately sends the Read bit without writing anything to arduino.

Any clue what might I be doing wrong?

Hi, I have exactly the same problem as above.

I'm not sure about that.

Post a wiring diagram of your setup. This might be a hardware problem. The default I2C frequency of a Raspberry Pi is 100kHz and the Arduino is definitely fast enough to answer in that speed with above code. So I guess the hardware is missing a level converter or anything like that.

Well, yes. I am indeed not using voltage converter. But from what I think I understand about the the I2C, the data line is by default HIGH, because of pullups on RPi side and the arduino slave just pulls it down when prompted.
But maybe the 3,3V is not enough voltage to be cosidered HIGH by arduino, but in that case i should not be able to read or write even byte by byte (which I am able to).

After I looked at my wiring, I may have found the problem. Because the wiring is just the TWO wires for I2C communication (pin 3,5 on RPi; pin A4, A5 on Arduino) and that is it. Arduino is powered through RPi USB port. And as I was thinking, both devices may not have common GROUND which may cause some issues i think.

Little bit off topic: I plan that in my project the RPi as a master, will control around 10 other microcontrolers, which will control around 26 servo motors and other sensors. Wouldn't it be better to use SPI instead of I2C? Yes, more wires, but faster communication and less problems because of physical lenght of the bus. Also I was thinking of using bare ATMEGA328 chips powered by 3,3V, comunicating through SPI. Does it seem like a better approach?

Unfortunately, I dont have logic converter at this time, so I will try the SPI next i think.

Well, yes. I am indeed not using voltage converter. But from what I think I understand about the the I2C, the data line is by default HIGH, because of pullups on RPi side and the arduino slave just pulls it down when prompted.
But maybe the 3,3V is not enough voltage to be cosidered HIGH by arduino, but in that case i should not be able to read or write even byte by byte (which I am able to).

Attach a scope to the SDA or SCL line and you will quickly see that the voltage level is not just 0V or 3V3. Depending on the power supply the Arduino has, a logic HIGH is recognized at about 3V. By using the Wire library you activate the internal pull-ups of the ATmega, so if you use weak pull-ups you even may destroy the pins of the Raspberry Pi (usually the current isn't high enough, so the probability is not very high).
I theory you're right but the actual bus does never show the ideal parameters that the theory is based on (google for I2C bus capacitance).

Yes, more wires, but faster communication and less problems because of physical lenght of the bus.

Wrong. Bus length is limited for both types of buses. Both are local buses, wire lengths of more than about half a meter are not recommended, especially not in a electrically noisy environment as you describe it.

I plan that in my project the RPi as a master, will control around 10 other microcontrolers

What distance does the complete bus have to cover?

Yes, I know about the capacitance problem, at least in therory. Thought I read somewhere that the SPI is not as much affected by it.
The thing about the physical lengt of the buss is, that I can only guess how long it will be and what capacitance will it have. My project is designing a bipedal robot, as you can probably tell by those 25 servos . Where there would be RPi controling 1-2 microcontrolers per limb and the robot will be around 50 cm high. So I can guess it will surely have more than 50 cm. More like 1 meter. Also I dont know how much cappacitance will the servos add (cant find it anywhere).

The speed I would like to get on the communication line is not really "that high". I think I would be ok with around 400 kbits/s maybe even less, which may make longer bus viable. But as I said, I have no practical experience with anything similar.

Also is my assumtion correct, that if I will use each microcontroler to control lets say 3 servo motors instead of 6, will it transfer to smoother more correct movement? Or can 1 arduino move 6 servos independently with same precision as say 3?

Still, I see the SPI as my best bet and need to hope that lowering the clock speed will be enough for the communication to work. Anyway thank you very much for the suggestions, they are really apriciated :slight_smile:

Yes, I know about the capacitance problem, at least in therory. Thought I read somewhere that the SPI is not as much affected by it.

It's a bit less of a problem because the bus is actively set and passively pulled high by resistors. But the principal problem still exists and the longer the bus cabling is the more electrical noise is caught by it. Because of the higher speed on the SPI this problem is worse than with the I2C.

Motors (servos are just controlled motors) tend to influence quite heavily so you might have bigger problems. In such environments transmission methods that use differential signal typically show much better reliability (one example: RS-485).

You can make tests in your environment. If you shield your cables you may get acceptable results.