1

I am currently trying to establish two-way communications between an Arduino Leonardo and a Pi model B using the I2C bus, as slave and master respectively. Spookily, the Pi can successfully send commands to the Arduino, but fails when requesting data from the Arduino. Spcifically, smbus2 throws OSError: [Errno 121] Remote I/O error whenever I call the moisture_read() function. The included water() function has the intended outcome -- the Arduino reacts and alters its pins (more or less) correctly.

Arduino Code:

#include <Wire.h>

#define SOILPWR 11
#define SOILSIG A0

#define MOTORPWR 5
#define MOTORDTEC A1

#define BUFSIZE 16

#define CHANNEL 0x03

int readMoisture(byte power, word dlay){
    analogWrite(SOILPWR,power);
    delay(dlay);
    int moisture = analogRead(A0);
    analogWrite(SOILPWR,0);
    return moisture;
}

void motorPulse(byte power, word dlay){
    // Motor is active low
    analogWrite(MOTORPWR,255-power);
    delay(dlay);
    analogWrite(MOTORPWR,255);
    delay(1000);
}

void setup() {
    // put your setup code here, to run once:
    pinMode(SOILSIG, INPUT);
    pinMode(SOILPWR, OUTPUT);
    pinMode(MOTORPWR, OUTPUT);
    pinMode(MOTORDTEC, INPUT);

    digitalWrite(MOTORPWR, HIGH);
    Wire.onReceive(receiveEvent);
    Wire.onRequest(requestEvent);
    Wire.begin(CHANNEL);
    #ifdef DEBUG
    Serial.begin(9600);
    #endif
}

// BYTE STRUCTURE:
// ['S'][speed][time_LSB][time_HSB]

void receiveEvent(int nbytes){
    byte msg[nbytes];
    for (int i=0;i<nbytes;i++){
        msg[i]=Wire.read();
    }

    #ifdef DEBUG
    Serial.print("Message:");
    for (int i=0;i<nbytes;i++){
        Serial.print(msg[i]);
        Serial.print('\n');
    }
    Serial.println();
    #endif

    byte b=msg[0];
    if (nbytes >= 4 && b == 'S'){
        // S for [S]oak
        // look I don't know why it's like this
        byte fast = msg[1];
        word t = msg[2] + (word) msg[3] << 8;

        #ifdef DEBUG
        Serial.print("Time: ");
        Serial.println(t);
        #endif

        // Limit watering to 1 minute
        t = t < 60000 ? t : 60000;

        motorPulse(fast,t);

    } else {
        #ifdef DEBUG
        Serial.println("FAILED");
        #endif
    }

}

byte sendbuf[BUFSIZE];

// BYTE STRUCTURE
// [soil_LSB][soil_MSB][0][0]

void requestEvent(){
    int moisture = readMoisture(255, 200);
    sendbuf[0] = moisture&0x0f;
    sendbuf[1] = moisture&0xf0;
    for (int i=2; i<BUFSIZE;i++){
        sendbuf[i]=0x00;
    }
    Wire.write(sendbuf, 4);
}

void loop() {
    delay(100);
}

Pi Code:

import smbus2
import struct
import time

CHANNEL = 0x03
# Bus number is 1
bus = smbus2.SMBus(1)

def water(speed, time):
    req_str  = struct.pack('>B',speed)
    req_str += struct.pack('<H',1024*time)
    bus.write_i2c_block_data(CHANNEL, ord('S'), req_str)

def moisture_read():
    # read a block of 4 bytes (offset 0)
    res = bus.read_i2c_block_data(CHANNEL, 0, 4)
    return struct.unpack('<HBB',res)
  • SMBus is not I2C, similar but not the same. I'd try just reading four bytes rather than what SMBus does of write register (restart) read four bytes. – joan Jul 10 at 14:55
  • That throws the same error. All of read_byte_data, read_byte and read_i2c_block_data fail with the same (rather unhelpful) message. – catalogue_number Jul 10 at 22:12
  • Okay. I suggest you experiment with i2cget/i2cput (I think they are in i2c-tools). That'll be quicker than editing/running a Python program. If that doesn't help try my pigs. sudo pigpiod pigs i2co 1 x 0 where x is the Arduino address and then use pigs i2crd 0 4 – joan Jul 11 at 7:36
1

For what it's worth, my solution was to work around the problem entirely. I read rumors on other questions that the smbus read_i2c_block_data doesn't actually work, and so I rewrote the code such that the Arduino only ever sends one byte at a time (imitating the behaviour of a register on a basic I2C chip).

The final code can be found at https://github.com/Spuriosity1/basilbot.

The basic idea was:

  • Store an array of bytes byte buf[16] that contains all relevant data and an index byte idx
  • RPI writes a command to set idx (with range protection for safety)
  • RPI requests from slave, which sends buf[idx].

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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