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.
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...vals
,cmd
is mandatory.