2

Folks, I cringe to bring this up as it has been discussed frequently, but I am at a loss.

Background: I have a Raspberry project with about 3000 lines of Python3 code and want to add a wireless Arduino sensor. I seem to have all the individual parts working - the Arduino sensor board with transmitter and Arduino receiver board hooked up to the Pi via I2C. Reading and writing individual bytes works in both directions, as do the radios using SPI. But when I try to get a full block of data I fall flat on my face.

Some details:

uname -a
Linux pietester 4.14.52-v7+ #1123 SMP Wed Jun 27 17:35:49 BST 2018 armv7l GNU/Linux

cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)"
NAME="Raspbian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"

What is working: The Arduino attached to the Pi has a callback: void sendData() { Wire.write("Hello from Arduino"); }

On the Pi side it appears the data is available on the wire:

    i2cdump -y 1 0X08 i
    0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
    00: 48 65 6c 6c 6f 20 66 72 6f 6d 20 41 72 64 75 69    Hello from Ardui
    10: 6e 6f ff ff ff ff ff ff ff ff ff ff ff ff ff ff    no..............
    20: 48 65 6c 6c 6f 20 66 72 6f 6d 20 41 72 64 75 69    Hello from Ardui
    30: 6e 6f ff ff ff ff ff ff ff ff ff ff ff ff ff ff    no..............

On the Pi side, a loop call to: def writeNumber(value):

    my_bus.write_byte(slave_address, value)

will blink an LED on the Arduino - the number of blinks is "value".

A callback obtains the number for blinking:

    void receiveData(int byteCount){ if ( Wire.available() ) { number = Wire.read(); } }

So at this point everything seems to be working fine, LEDs flash as expected, the radios work, voltages and connections seem fine. But now the bad news.

I need more than just one byte of data so I try:

    my_bus.read_i2c_block_data(slave_address, 0, 32) ###(addr, cmd, # of bytes)

and at run time get the "usual" error: OSError: [Errno 121] Remote I/O error.

Believe me, I have spent many hours searching for an answer. The best guess I have at this point is the second parameter - the "command" parameter - is somehow the bad guy.

So, if someone can point me to enlightenment, I will take that path. But please remember, I have 3000 lines of Python3 already written.

Here is the Pi code:

#!/usr/bin/python3
import time
import smbus as smbus
my_bus = smbus.SMBus(1)  # On RPi only bus 1 exists for our use
block_data = [32]
slave_address = 0x08

def writeNumber(value):
    my_bus.write_byte(slave_address, value)
    return

def readNumber():
#   number = my_bus.read_byte(slave_address) ### This works.
    block_data = my_bus.read_i2c_block_data(slave_address, 0, 32)# (addr, cmd, # of bytes)
    return number

while(1):
    writeNumber(3) #Blink the Arduino LED this many times. ### This works.
    time.sleep(1/100)
    print("from Read: ")
    print(readNumber())
    time.sleep(3)

Here is the Arduino code - with radio code removed for clarity:

#include <Wire.h>

#define SLAVE_ADDRESS 0x08
byte number = 0;

void flash_data(int loops) { 
  for (int i = 0; i < loops; i++) {...flashing code...}

void flash_good(int loops) { 
  for (int i = 0; i < loops; i++) {... flashing code ...}

void flash_bad( int loops ) { ... more flashing code...}

// callback for received data
void receiveData(int byteCount){
    if ( Wire.available() ) {
       number = Wire.read();
    }
}

void sendData() {
  Wire.write("Hello from Arduino");  
}

void setup() {
  Wire.begin(SLAVE_ADDRESS); // an address makes this unit a Slave
  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);  
}

void loop() {
  if ( number > 0 ) { // If the RPi is sending a value, flash it.
    flash_data(number);
    number = 0;
  } else {
    flash_good(1);
  }
  delay(10);
}

Many thanks to the person who comes up with the obvious fix that I completely overlooked!!!

3 Answers 3

1

It works for me (with slight and irrelevant mods to your posted code).

$ python3 ard.py
from Read: 
(72, [72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 65, 114, 100, 117, 105, 110, 111, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255])
from Read: 
(72, [72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 65, 114, 100, 117, 105, 110, 111, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255])
from Read: 
(72, [72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 65, 114, 100, 117, 105, 110, 111, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255])

I would draw the conclusion that you have a loose connection.

Pi Python

#!/usr/bin/python3
import time
import smbus as smbus
my_bus = smbus.SMBus(1)  # On RPi only bus 1 exists for our use
slave_address = 0x08

def writeNumber(value):
    my_bus.write_byte(slave_address, value)

def readNumber():
   number = my_bus.read_byte(slave_address) ### This works.
   block_data = my_bus.read_i2c_block_data(slave_address, 0, 32)
   return number, block_data

while(1):
    writeNumber(3) #Blink the Arduino LED this many times. ### This works.
    time.sleep(1/100)
    print("from Read: ")
    print(readNumber())
    time.sleep(3)

Arduino C

#include <Wire.h>

#define SLAVE_ADDRESS 0x08
byte number = 0;

void flash_data(int loops)
{ 
   for (int i = 0; i < loops; i++)
   {
      digitalWrite(LED_BUILTIN, HIGH);
      delay(50);
      digitalWrite(LED_BUILTIN, LOW);
      delay(50);
   }
}

void flash_good(int loops)
{ 
   for (int i = 0; i < loops; i++)
   {
      digitalWrite(LED_BUILTIN, HIGH);
      delay(20);
      digitalWrite(LED_BUILTIN, LOW);
      delay(20);
   }
}

void flash_bad( int loops ) {}

// callback for received data
void receiveData(int byteCount){
    if ( Wire.available() ) {
       number = Wire.read();
    }
}

void sendData() {
  Wire.write("Hello from Arduino");  
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Wire.begin(SLAVE_ADDRESS); // an address makes this unit a Slave
  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);  
}

void loop() {
  if ( number > 0 ) { // If the RPi is sending a value, flash it.
    flash_data(number);
    number = 0;
  } else {
    flash_good(1);
  }
  delay(10);
}
2
  • Hello Joan. Well, with only two wires - data and clock - I would ask how with a loose connection I can pass data between the RPi and Arduino one byte at a time, and yet not a block of data. And also, how could I get a working "IC2 dump" on the RPi side? Since you have working code would you mind sharing the included libraries and the block-read line of code? Thank you. Commented Nov 30, 2018 at 18:25
  • You need ground connected as well.
    – joan
    Commented Nov 30, 2018 at 18:54
1

To prove, or disprove Joan's assertion that there was a connection problem, I intentionally disconnected the I2C lines, alternating between the Data and Clock lines. Without the read_i2c_block_data() statement I would get the exact same "121" error. Reconnecting the lines, everything would work except the read_i2c_block_data() statement (same "121" error).

This was getting very frustrating so I played with every line of code in one form or another. When I got to time.sleep(1/100) I changed it to time.sleep(3), and BINGO, it worked. Data, lots of beautiful, correct data.

Played with the delay some more... It appears that the minimum delay is somewhere around 1/2 second. I also took out the writeNumber() statement that blinks the Arduino LED and this did not have any impact - still needed a half-second delay.

More experiments: Moved the initial time.sleep outside the while loop and now everything works even with a very short time.sleep(1/10) after the block read. In other words I can read data quickly as long as there is a pause prior to running the code.

But I am happy since my objective is to read a sensor just a few times per minute - time is no object.

Here is the final RPi code:

#!/usr/bin/python3
import time
import smbus as smbus
my_bus = smbus.SMBus(1)  # On RPi only bus 1 exists for our use
slave_address = 0x08

def writeNumber(value):
    my_bus.write_byte(slave_address, value)
    return

def readNumber():
    number = my_bus.read_byte(slave_address) ### This works.
    block_data = my_bus.read_i2c_block_data(slave_address, 0, 32)
    return number, block_data # number is not needed in real life.

time.sleep(1)
while(1):
    #writeNumber(2) #Blink the Arduino LED this many times. ### This works.
    print("from Read: ")
    print(readNumber())
    time.sleep(1/10) # Will be time.sleep(20) in real life when done.

My guess (and only a guess) is that since I am using a raw ATMega328p chip, running slowly on its internal clock (fuses set to tick over at a few MHz to save power), and the RPi is cranking at GHz+, there is a synchronization issue. And I am totally to blame!

But without Joan's admonishment that the code was in fact working, I probably would have given up. Thank you for poking me with a stick!

3
  • Not a loose connection, but a loose screw! :) :) Commented Nov 30, 2018 at 22:36
  • Interesting. In a few days (when you can) you should mark your answer as the correct one.
    – joan
    Commented Dec 1, 2018 at 8:47
  • You took the time to test and prove my code was working. I just took some random shots and got lucky. Marking your answer as the correct one unless you object. Commented Dec 1, 2018 at 17:21
0

As stated here, the OSError: [Errno 121] Remote I/O error might occasionally occur while interfacing a Rspberry Pi with an Arduino AVR microcontroller via the Raspberry Pi I2C hardware device 1, which is on pins 3 (GPIO2/SDA1) and 5 (GPIO3/SCL1), especially when multiple read/write operations are issued without delay.

It happens due to incompatibility between the Raspberry Pi hardware and the AVR microcontrollers (ATMEGA TWI / I2C slave mode requires clock stretching, which the I2C hardare device does not support).

One possible workaround (which might not be supported by all the Raspberry Pi models) is to slow down the I2C speed, e.g. with dtparam=i2c_arm_baudrate=10000 in /boot/config.txt. Such configuration, while improving bus robustness, might result in unacceptable I2C performance.

Another solution consists in selecting the Raspberry Pi Software I2C implementation, which bit-bangs the I2C protocol from the kernel. By default, the Software I2C uses smbus.SMBus(3) (e.g., dtoverlay=i2c-gpio,bus=3 in /boot/config.txt) and SDA will be on GPIO23, while SCL will be on GPIO24. They do not have the pullup resistors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.