13

I am testing i2c communication between Pi and Arduino.

The doc says:

write_i2c_block_data(addr,cmd,vals)  Block Write transaction.    int addr,char cmd,long[]    None

I have this test:

On Pi:

import smbus
bus = smbus.SMBus(0)
bus.write_i2c_block_data(address, 48, [49, 50, 51] )

On Arduino:

void receiveData(int byteCount){
    Serial.print("byte count=");
    Serial.println(byteCount);

    while(Wire.available()) {
        number = Wire.read();
        Serial.print((char)number);
     }
}

On the Arduino I see this output:

byte count=4
0123

My question is: what is the use of the cmd parameter? I don't see a distinction on the Arduino of which byte represents what.
I guess I can deal with it as a see fit. Maybe I want to use the first 2 bytes as a command.

This page has not much information on the method: http://wiki.erazor-zone.de/wiki:linux:python:smbus:doc

4
  • You may want to define what the cmd parameter is... I had to do a fair bit of looking to figure out what you meant. I didn't find an answer though... It may only be used by specific chips like a GPIO expander or something...
    – Butters
    Jul 17, 2013 at 3:42
  • Ok, I added the link to the documentation (which is not much)
    – Gus Smith
    Jul 17, 2013 at 4:54
  • 6
    I have no time to make a full answer right now (I hope someone will) but in short - this is how I²C works. Master can just send it some bytes of data (after sending proper address) and there is no specification on what those bytes actually are (their meaning is defined per device). It just so happens that the first byte is often a command (or register) number. Also you always have to send at least one byte so unlike vals, cmd is mandatory. Jul 17, 2013 at 8:58
  • 1
    @KrzysztofAdamski That sounds like a pretty complete answer to me.
    – Butters
    Jul 17, 2013 at 13:37

4 Answers 4

11

I²C protocol is very simple. It does not really define data structures that are sent over the wire. The frame consist of a slave address (with direction bit indicating if master wants to read or write) and (in case of writing) some bytes of data. Since it doesn't make sense to initiate write with 0 bytes of data, first byte is mandatory.

This first byte is often used as a slave register address or command number but it doesn't have to. There may or may not be additional bytes after the first one. The higher level protocol defining what each byte means is device specific.

This may explain why there are two separate arguments - first one (cmd) is mandatory and second one (vals) is optional. While your example is in Python language, the API used here is actualy very close mapping of original C API where you can't easly create optional arguments.

2
  • This is a little longer explanation of what I wrote in the comment under the question. Jul 17, 2013 at 17:01
  • I'm glad you did! These kinds of simple, yet "aha!" explanations are really helpful some times, like today :-)
    – uhoh
    Jan 19, 2018 at 7:08
4

When you issue a block write/read from the Pi with:

bus.write_i2c_block_data(i2c_address, register, list of bytes)

Example:
bus.write_i2c_block_data(80, 0, [49, 50, 51])

or

bus.read_i2c_block_data(i2c_address, register, length)

Example:
bus.read_i2c_block_data(80, 0, 16)

Two things happen on the Arduino depending on read or write.

The register byte (e.g., 0 or any other byte like for instance 48) is the first byte written to the I2C bus from the Pi; it is always sent as a "write" request. This means that if the Pi is issuing a

bus.read_i2c_block_data(i2c_addr, register, length)

or

bus.write_i2c_block_data(i2c_addr, register, data)

it first writes register to the I2C bus, then terminates in case of write_i2c_block_data (with no return value), or reads in case of read_i2c_block_data, returning a list of bytes.

This is a useful feature because some I2C hardware requires initialization before a read can be made (e.g., the process can be differentiated according to the register value).

On the Arduino this means that:

First the,

Wire.onReceive(yourCallback)

function is called because register was written to the bus by the Pi. register will be the first byte available on the bus (and should be read through a Wire.read() inside yourCallback). If the Pi sent a write request, then the Arduino will stay in the Wire.onReceive callback until the function is complete. If the Pi sent a read request, the Arduino will complete the Wire.onReceive callback, then call the Wire.onRequest callback.

In principle, the Wire.onReceive callback should include one or more Wire.read() operations to read data from I2C (one byte if read_i2c_block_data is used, or possibly multiple bytes if write_i2c_block_data has a list of bytes in its third parameter); the Wire.onRequest callback should instead include a Wire.write() function, to write data to I2C.

You must ensure that the value placed in register does not cause unintended behavior in your system by appropriately handling its value.

1

I am writing to an I2C LCD, the Newhaven NHD‐0216K3Z‐FL‐GBW‐V3. Its spec sheet can be googled. In its case, when the command byte is 0xfe it means that the following byte is a command - there are about 20 of them. Clear, backlight, blink cursor and so on. If cmd is not 0xfe it is just some character to display.

0

Updating deltatango's answer with some corrections and additional information.

Using i2c_register instead of cmd.


When you issue a block write from the Raspberry Pi with:

bus.write_i2c_block_data(i2c_address, i2c_register, list of bytes)

example:

bus.write_i2c_block_data(80, 0, [49, 50, 51])

or a block read

bus.read_i2c_block_data(i2c_address, i2c_register, length)

example:

bus.read_i2c_block_data(80, 0, 16)

the following things happen on the Arduino, depending on the usage of the read or write functions.

The i2c_register byte (e.g., 0 or any other byte like for instance 48 in the above question) is the first byte written to the I2C bus from the Pi; it is always sent as a "write" request. This means that if the Pi is issuing a

bus.read_i2c_block_data(i2c_addr, i2c_register, length)

or

bus.write_i2c_block_data(i2c_addr, i2c_register, data)

it first writes i2c_register to the I2C bus, then terminates in case of write_i2c_block_data (with no return value), or reads in case of read_i2c_block_data, returning a list of integers.

This is a useful feature because some I2C hardware requires initialization or flow differentiation before performing the read operation (e.g., the Arduino callback process can vary according to the i2c_register value).

On the Arduino this means that:

First the

Wire.onReceive(yourCallback)

function is called because i2c_register was written to the bus by the Pi. i2c_register will be the first byte available on the bus (and should be read through a Wire.read() inside yourCallback). If the Pi sent a write request, then the Arduino will stay in the Wire.onReceive callback until the function is complete. If the Pi sent a read request, the Arduino will complete the Wire.onReceive callback, then it will call the Wire.onRequest callback.

In principle, the Wire.onReceive callback should include one or more Wire.read() operations to read data from I2C (one byte if read_i2c_block_data is used, or possibly multiple bytes if write_i2c_block_data has a list of bytes in its third parameter); the Wire.onRequest callback should include a Wire.write() function, to write data back to I2C.


Full working Python example on a Raspberry Pi (sudo apt-get install -y python3-smbus):

import struct
import smbus
import time

bus = smbus.SMBus(3)
address = 0x2a

# Example of write_i2c_block_data
print("Starting")
print("LED on (write, register 254).")
bus.write_i2c_block_data(address, 254, [])  # LED on

# Examples of read_i2c_block_data
print("Read the whole out_set structure (32 bytes, 8 values; read, register 0):")
i2c_register = 0
byte_list = bus.read_i2c_block_data(address, i2c_register, 2 * 4 * 4)  # a list of 4 float values + 4 long values, each consisting of 4 bytes
temp = []
for i in [byte_list[x:x+4] for x in range(0, int(len(byte_list)/2), 4)]:
    temp.append(struct.unpack('<f', bytes(i))[0])
value = []
for i in [byte_list[x:x+4] for x in range(int(len(byte_list)/2), len(byte_list), 4)]:
    value.append(struct.unpack('<L', bytes(i))[0])
print(temp, value)
# output: [0.0, 3.0, 6.0, 9.0] [32, 4, 1, 4]

print("Read the initial value of the counter after incrementing it (0+1=1; read, register 1):")
i2c_register = 1  # increment the counter and then read it
counter = bus.read_i2c_block_data(address, i2c_register, struct.calcsize("<L"))  # one unsigned long value (made of 4 bytes)
print(struct.unpack('<L', bytes(counter))[0])
# output: 1

print("Read the True value (read, register 4):")
i2c_register = 4  # default case in sendDataVector
bool = bus.read_i2c_block_data(address, i2c_register, struct.calcsize("?"))  # one Boolean value (1 byte)
print(struct.unpack('?', bytes(bool))[0])
# output: True

print("Read the False value with unused register 3 (default):")
i2c_register = 3  # default case in sendDataVector
bool = bus.read_i2c_block_data(address, i2c_register, struct.calcsize("?"))  # one Boolean value (1 byte)
print(struct.unpack('?', bytes(bool))[0])
# output: False

print("Read the 3.1415 float value (read, register 48):")
i2c_register = 48
float = bus.read_i2c_block_data(address, i2c_register, struct.calcsize("<f"))  # one float value (made of 4 bytes)
print(struct.unpack('<f', bytes(float))[0])
# output: 3.1414999961853027

print("Set counter to 1025 (write, register 6).")
bus.write_i2c_block_data(address, 6, list(struct.pack('<L', 1025)))

print("Read the counter without incrementing it (read, register 2).")
counter = bus.read_i2c_block_data(address, 2, 4)  # read counter
print(struct.unpack('<L', bytes(counter))[0])
# output: 1025

print("Increment the counter (read, register 1).")
bus.write_i2c_block_data(address, 1, [])  # increment counter

print("Read the counter without incrementing it (read, register 2).")
counter = bus.read_i2c_block_data(address, 2, 4)  # read counter
print(struct.unpack('<L', bytes(counter))[0])
# output: 1026

print("Read the counter after incrementing it (read, register 1).")
counter = bus.read_i2c_block_data(address, 1, 4)  # increment counter and read it
print(struct.unpack('<L', bytes(counter))[0])
# output: 1027

print("Read the counter without incrementing it (read, register 2).")
counter = bus.read_i2c_block_data(address, 2, 4)  # read counter
print(struct.unpack('<L', bytes(counter))[0])
# output: 1027

print("LED off (write, register 253).")
bus.write_i2c_block_data(address, 253, [])  # LED off

Full working example on Arduino:

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

typedef struct { // Example of byte structure
  float temp[4];
  unsigned long value[4];
} type_temp;

type_temp temp_set, out_set;

unsigned char i2c_register = 0;
unsigned long counter = 0;
float pi = (float) 3.1415;
bool true_value = true;

void receiveEvent(int byteCount) {
  i2c_register = Wire.read();

  if ((i2c_register == 6) && (byteCount == 5)) {
    unsigned long val = 0;
    val = ((unsigned long) Wire.read());
    val += ((unsigned long) Wire.read()) << 8;
    val += ((unsigned long) Wire.read()) << 16;
    val += ((unsigned long) Wire.read()) << 24;
    counter = val;
  }
  while(Wire.available()) {
    Wire.read();
  }
  switch (i2c_register) {
    case 1:
      counter ++;
    case 253:
      digitalWrite(LED_BUILTIN, LOW);
      break;
    case 254:
      digitalWrite(LED_BUILTIN, HIGH);
      break;
  }
} // of receiveEvent

void sendDataVector() {
  switch (i2c_register) {
    case 0:
      Wire.write((byte*) &out_set, sizeof(out_set));
      break;
    case 48:
      Wire.write((byte*) &pi, sizeof(pi));
      break;
    case 1:
      Wire.write((byte*) (&counter), sizeof(counter));
      break;
    case 2:
      Wire.write((byte*) (&counter), sizeof(counter));
      break;
    case 4:
      Wire.write((byte*) (&true_value), sizeof(true_value));
      break;
    default:
      bool false_value = false;
      Wire.write((byte*) (&false_value), sizeof(false_value));
      break;
  }
} // of sendDataVector

void compute_temp_set() {
  for (int i=0; i<4; i++) {
    temp_set.temp[i] = i*3;
  }
  temp_set.value[0] = sizeof(type_temp);
  temp_set.value[1] = sizeof(counter);
  temp_set.value[2] = sizeof(true_value);
  temp_set.value[3] = sizeof(pi);
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Wire.begin(SLAVE_ADDRESS); // Join I2C as a slave
  Wire.onRequest(sendDataVector);
  Wire.onReceive(receiveEvent);
}

void loop() {
  compute_temp_set();
  noInterrupts();
  memcpy( (void*)&out_set, (void*)&temp_set, sizeof(type_temp) );
  interrupts();
}

Output of the Python program:

Starting
LED on (write, register 254).
Read the whole out_set structure (32 bytes, 8 values; read, register 0):
[0.0, 3.0, 6.0, 9.0] [32, 4, 1, 4]
Read the initial value of the counter after incrementing it (0+1=1; read, register 1):
1
Read the True value (read, register 4):
True
Read the False value with unused register 3 (default):
False
Read the 3.1415 float value (read, register 48):
3.1414999961853027
Set counter to 1025 (write, register 6).
Read the counter without incrementing it (read, register 2).
1025
Increment the counter (read, register 1).
Read the counter without incrementing it (read, register 2).
1026
Read the counter after incrementing it (read, register 1).
1027
Read the counter without incrementing it (read, register 2).
1027
LED off (write, register 253).

Note on the following error

OSError: [Errno 121] Remote I/O error

This error might occasionally occur while using the smbus.SMBus(1) I2C hardare device 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 many peripheral devices, including 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, bit-banging 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 which are pins 16 and 18 on the GPIO header respectively. 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.