Cannot send data via i2c to master raspberry pi

Hi guys,
I'm struggling with an issue since 2 weeks and I cannot find any solution. I'd like to make a raspberry pi 3 and an arduino pro mini (3.3V) talking via i2c. I connected them directly since the raspberry pi 3 already has pull up resistors. Previous to this, I was able to make the raspberry pi 3 and an HTU21D sensor via i2c talking successfully, so I'm excluding hardware issues on raspberry pi, I'm guessing something is wrong with the arduino, and since changing the arduino module didn't fix the issue I have a software issue and cannot find what can be.
the arduino code (only the i2c part):

#include <Wire.h>

#define slaveAddress 0x08

byte  command = 0;

byte  txTable[4];                               //for sending data over I2C
byte  rxTable[4];                               //for sending data over I2C
bool  newDataAvailable;

void setup()  
{  
  Serial.begin(9600);
  
  newDataAvailable = false;  

  Wire.begin(slaveAddress);         //I2C slave address
  Wire.onReceive(i2cReceive);       //register handler onReceive
  Wire.onRequest(i2cTransmit);      //register handler onRequest
 
}//setup

void i2cReceive(int byteCount) {
  if (byteCount == 0) return;
  
  //the first byte is the command  
  command = Wire.read();
  if (command < 0x80) 
  {
    //it is a receiving data command, we have to handle the next bytes
    i2cHandleRx(command);
  }
}

byte i2cHandleRx(byte command) {
  // The I2C Master has sent data, store them in receiving buffer
  byte result = 0;
  
  switch (command) {
    case 0x0A:   
      
      if (Wire.available() == 4) 
      { 
        for (int i = 3; i >= 0; i--)
          rxTable[3-i] = Wire.read();
        
        result = 4;
      }
      break;     
  }
  if (result != 0)
    newDataAvailable = true;

  return result;
}

void i2cTransmit() {
  //called by event
  byte numBytes = 0;
   
  switch (command) {
    case 0x90: 
      numBytes = 4;
      break;
  }
  if (numBytes > 0) {
    Wire.write((byte*)txTable, numBytes);
  }
}

Data are stored in the txByte array in the loop method. I can successfully send data from raspberry pi to arduino, but I cannot read data from it. Using some print to the monitor I can see that the arduino is reacting to the master requests. On raspberry pi I'm using python-smbus, so the command:

import smbus
bus = smbus.SMBus(1)
data = [0,50,100,100]
bus.write_i2c_block_data(0x08, 0x0A, data)

is working. But the command:

import smbus
bus = smbus.SMBus(1)
data = bus.read_i2c_block_data(0x08, 0x90, 4)

raises the error:

Traceback (most recent call last):

  • File "", line 1, in *
    IOError: [Errno 5] Input/output error

I'm wondering what I'm doing wrong on data transmission.

Does anyone has an idea?

Thank you.

Regards.

You forgot to declare all global variables that are modified in interrupt context "volatile". Without that declaration the compiler may optimize the complete variable away. So although the command byte is received in the write request, the code in the following read request will not see it.

Good point!
So I added "volatile" to the data buffers, but no luck :frowning:
Just to be sure, even if I don't think is the cause, I disabled the PULL-UP resistors as described here.

data = bus.read_i2c_block_data(0x08, 0x90, 4)

Don't use a length var for read_i2c_block_data, as odds are you're receiving more data over the bus than you're telling the hardware to expect and that causes misbehavior. Just use the address and command. The return from the call will be a 32-byte buffer, the maximum amount of data that can be sent across an I2C bus in a single exchange, so the next step afterward is to parse the buffer - I2C considers a 0xFF to be a null or not-a-character, so as long as you're sending strings (which should always end with a 0x00) and never send a 0xFF as a character, you can simply read the buffer into a string until you hit a 0xFF.

Here's how I read I2C data on a master Odroid C2 (DietPi, Python 2.7.9) from a slave Arduino Mega acting as an external watchdog:

def read_from_slave(address, command, i2cbus):
    data = ""
    result = ""
    try:
        # Read an entire 32-byte data block from the slave.
        data = i2cbus.read_i2c_block_data(address, command)

        # Since the data block can contain any character but a 255 means no
        # string character, we'll convert the series of bytes into a Python-
        # compatible string, char-by-char.
        index = 0
        while (data[index] != 255):
            result += chr(data[index])
            index += 1
        return result
    except:
        pass

The slave is configured to send literally anything and everything it's going to send via I2C, regardless of its actual type, as a string. This is because it's super-easy to "stringify" other (especially numeric) types like floats thanks to commands like dtostrf(), and it's super-easy to cast or convert types in Python. Basically the Arduino sends a number as a series of ASCII codes representing the digits of a number (including sign and decimal point) and this code converts those back to ASCII text. If the slave sent a number as ASCII-encoded digits/sign/decimal, just doing a float() cast on the returned string gives you back a workable numeric value.

Using my read function, "data = bus.read_i2c_block_data(0x08, 0x90, 4)" would become "data = read_from_slave(0x08, 0x90, bus)".

Please note that this function should NOT be used to read I2C devices that send in a clearly specified format. For best results, write handlers for them that are specific to their datastream formats. It's intended to work as a generic slave-read function for slaves that send string data, and works nicely with Arduino slaves since you can control their datastream formats and thus can "stringify" everything being sent out.

Hi WebMaka,
thank you for your explanation, I see your point, unfortunately avoiding the numbers of bytes didn't fix the issue but first, via python command, I get a different error:

>>> data = i2c.read_i2c_block_data(0x08,0x90)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 110] Connection timed out

it is now a connection time out and then if I run the command again

>>> data = i2c.read_i2c_block_data(0x08,0x90)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 5] Input/output error

the same error I was getting before.

Regarding the data, I'm transmitting unsigned int and byte values, of course managing low and high bytes for int, the logic is working because the data transmission is working from raspberry pi to arduino, the data are correctly received from the Arduino.

Don't use a length var for read_i2c_block_data, as odds are you're receiving more data over the bus than you're telling the hardware to expect and that causes misbehavior

Excuse me but this is wrong. The master defines how many bytes are transfered as it's providing the clock signal. There is no option in the I2C standard to let the slave tell the master how many bytes it will transmit (except making a first request to get just the byte count of the next request).

I2C considers a 0xFF to be a null or not-a-character, so as long as you're sending strings (which should always end with a 0x00) and never send a 0xFF as a character, you can simply read the buffer into a string until you hit a 0xFF.

Again this is not true. 0xFF is not a null character but it's simply what the master get if the slave is not sending anything at all. I2C is not a good transport interface for strings anyway.

So I added "volatile" to the data buffers, but no luck

I saw more problems with the "command" variable. Did you change that too?

@OP: Change your code to always send the 4 bytes and not only if the correct command was sent. Tell us if this helps.

Hi Pylon,
yes i modified the variables declaration as:

byte  volatile  command = 0;

byte  volatile  txTable[4];                               //for sending data over I2C
byte  volatile  rxTable[4];                               //for sending data over I2C
bool  volatile  newDataAvailable;

ad some news here! Removing the check on command I get data on raspberry pi. Do you know why? But I get always [0,255,255,255] :confused: but it is a step forward.

Other news.
Changing the code from:

void i2cTransmit() {
  //called by event
  byte numBytes = 0;
   
  switch (command) {
    case 0x90: 
      numBytes = 4;
      break;
  }
  if (numBytes > 0) {
    Wire.write((byte*)txTable, numBytes);
  }
}

to:

void i2cTransmit() {
  //called by event
  byte numBytes = 1;
  Serial.println("Handle the command request from Master.");

  /*
  switch (command) {
    case 0x90: //requested HSB
      Serial.println("Master requested HSB.");
      numBytes = 4;
      break;
  }
  */
  if (numBytes > 0) {
    Serial.print("Send num bytes: ");
    Serial.println(numBytes);
    //Wire.write((byte*)txTable, numBytes);
    //Wire.write(txTable,4);
    Wire.write(txTable[0]);
    Wire.write(txTable[1]);
    Wire.write(txTable[2]);
    Wire.write(txTable[3]);
  }

is working!
It remains only the why I can't use the "command" variable.

    Serial.print("Send num bytes: ");
    Serial.println(numBytes);

Never use the Serial object in interrupt context (both I2C handling routines are called in interrupt context)! It may freeze because it's waiting endlessly for the UART interrupt to be called.

mortommy:
Hi Pylon,
yes i modified the variables declaration as:

byte  volatile  command = 0;

byte  volatile  txTable[4];                              //for sending data over I2C
byte  volatile  rxTable[4];                              //for sending data over I2C
bool  volatile  newDataAvailable;




ad some news here! Removing the check on command I get data on raspberry pi. Do you know why? But I get always [0,255,255,255] :confused: but it is a step forward.

Interesting that the compiler accepted that code, usually the volatile keyword has to be in front of the type declaration. I don't know what the compiler makes with your version. Try to set the volatile keyword in front of the type.

It is working! I reviewed the code using in the correct way the "volatile" keyword and avoiding the use of serial object in the interrupt methods. So the final code if can be useful for others:

#include <Wire.h>

#define slaveAddress 0x08
#define INTERUPT_PIN 4

volatile  byte  command = 0;

volatile  byte  txTable[4];                               //for sending data over I2C
volatile  byte  rxTable[4];                               //for sending data over I2C
volatile  bool  newDataAvailable;

void setup()  
{
   Serial.begin(9600);
  
   Serial.println("Setup...");

   newDataAvailable = false;

   Wire.begin(slaveAddress);         //I2C slave address
  
  //Wire activates the internal pull-up resistors, let's deactivate them
  #ifndef cbi
    #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
  #endif

  #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
    // deactivate internal pull-ups for twi
    // as per note from atmega8 manual pg167
    cbi(PORTC, 4);
    cbi(PORTC, 5);
  #else
    // deactivate internal pull-ups for twi
    // as per note from atmega128 manual pg204
    cbi(PORTD, 0);
    cbi(PORTD, 1);
  #endif  
  
  Wire.onReceive(i2cReceive);       //register handler onReceive
  Wire.onRequest(i2cTransmit);      //register handler onRequest

  Serial.println("...end.");
}//setup

void i2cReceive(int byteCount) {
  if (byteCount == 0) 
    return;
    
  // Commands in range 0x000-0x7F (0-127) are writes TO this slave, and expects nothing in return.
  // Commands in range 0x80-0xFF (128-255) are reads, requesting data FROM this device
  command = Wire.read();
  if (command < 0x80) {
     i2cHandleRx(command);
  }
}

void i2cTransmit() {
  //called by event
  byte numBytes = 0;

  switch (command) {
    case 0x90: 
      numBytes = 4;
      break;
  }
  
  if (numBytes = 4) {   
    Wire.write((byte*)txTable, numBytes);
  }
  
  //put down the interupt signal
  digitalWrite(INTERUPT_PIN, LOW);
}

byte i2cHandleRx(byte command) {
  // The I2C Master has sent data, store them in receiving buffer
  byte result = 0;
  
  switch (command) {
    case 0x0A:
      if (Wire.available() == 4)
      { 
        for (int i = 3; i >= 0; i--)
          rxTable[3-i] = Wire.read();
        
        result = 4;
      }
      break;     
  }
  if (result != 0)
    newDataAvailable = true; //let's say we have new data available and leave the receiving process

  return result;
}

In the code are missing some parts, like where I store or retrieve data from buffers or where I handle the start of transmission to the master.

Thank you very much for your help! :slight_smile:

Did you use same code for raspberry pi as above?

I will add my notes for those that try to use a Raspberry Pi with bare metal I2C slave.

I have been sorting out how to use the ATmega328pb second I2C1 port as a slave for a Raspberry Pi while the first port I2C0 does that for other boards.

For the Raspberry Pi I2C port, I set it up as follows

sudo apt-get install i2c-tools python3-smbus
sudo usermod -a -G i2c my_user_name
# logout for the change to take
i2cdetect 1
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-1.
I will probe address range 0x03-0x77.
Continue? [Y/n] Y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- 2a -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

The 328pb I2C1 port is connected to the Raspberry Pi, but the data echo I got back was [0, 0xFF].

The bare metal should have returned [0, 0xFC] so what has gone wrong? Well, nothing. I sent the bare metal [0, 0x03] and the 328pb inverted the bits of the second byte for the echo that way I could see it had done something. The SMBus block read function is a second I2C transaction and it runs the twi received event a second time after the block write transaction (and before running the transmit event). Since I want to echo the data from the first transaction, I needed to preserve that old data from the first I2C transaction when (or before) the event for receiving the second transaction data occurs. Finally when the second transaction causes the transmit event I can pass the old data from the first receiving event.

So to sum it up, there seem to be two receive events and one transmit event when the Raspberry Pi does a write_i2c_block and a read_i2c_block.