Data passed between a Raspberry Pi, configured as an I2C slave device, is not reliably transmitting between an Arduino I2C bus master device. Data is consistently getting lost, and slave responses are received out of sequence. And the status flag bytes are getting reversed between PIGPIO and PIGPIOD.
Furthermore, I have not been able to determine from the documentation how the Raspberry Pi slave device is notified of a simple requestFrom() bus master request. There appears to be an unnecessary requirement to send a control message first from the master to slave.
The following code is used in a raspberry pi configured as an i2c slave device. As seen in the code, it can be compiled to be used either directly with PIGPIO or indirectly through the PIGPIOD using the macro switch PIGPIOD. This way, the code can be tested in both configurations.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <cstring>
#include <signal.h>
#ifdef PIGPIOD
#include <pigpiod_if2.h>
int pi_handle;
#else
#include <pigpio.h>
#endif
bool runloop = false;
bsc_xfer_t xfer;
uint8_t i2caddr = 0x15;
void reset_i2c()
{
xfer.control = (uint32_t)((uint32_t)i2caddr << 16) | (1<<7) /*BK*/;
memset(xfer.txBuf, 0, BSC_FIFO_SIZE);
xfer.txCnt = 0;
#ifdef PIGPIOD
bsc_xfer(pi_handle, &xfer);
#else
bscXfer(&xfer);
#endif
}
void setup()
{
printf("I2C RPI Slave Test Program\n");
#ifdef PIGPIOD
pi_handle = pigpio_start(0, 0);
if (pi_handle < 0) {
printf("WHOAAAAAA!\n");
}
#else
if (gpioInitialise()) {
printf("WHOAAAAAA!\n");
}
#endif
usleep(25000);
reset_i2c();
//for (int i = 0; i < 32; i++) {
// event_callback_ex(pi_handle, i, (evtCBFuncEx_t)event_handler, (void *)i);
//}
usleep(25000);
}
void loop()
{
int status;
uint8_t rx_ctl[10];
bool rx_flag, tx_flag;
runloop = true;
while (runloop) {
rx_flag = false;
tx_flag = false;
xfer.control = (uint32_t)((uint32_t)i2caddr << 16)
| (1<<9) /*RE*/ | (1<<8) /*TE*/ | (1<<2) /*I2C*/ | (1<<0) /*EN*/;
#ifdef PIGPIOD
status = bsc_xfer(pi_handle, &xfer);
#else
status = bscXfer(&xfer);
#endif
int rx = xfer.rxCnt;
int tx = xfer.txCnt;
//
// Receive control bytes
//
if ((status >= 0) && (rx > 0))
{
for (int i = 0; (i < rx) && (i < 10); i++) {
rx_ctl[i] = xfer.rxBuf[i];
}
rx_flag = true;
}
else if (status < 0)
{
//printf("Bad receive status.\n");
}
else if (rx <=0 )
{
//printf("No data received.\n");
}
//usleep (2500);
//usleep (1);
//
// Transmit slave data
//
if (rx_flag) {
// rx control determins response
xfer.txBuf[0] = (rx_ctl[0]==5)?(15):(35);
xfer.txBuf[1] = (rx_ctl[0]==5)?(16):(36);
xfer.txBuf[2] = (rx_ctl[0]==5)?(17):(37);
xfer.txBuf[3] = (rx_ctl[0]==5)?(18):(38);
xfer.txCnt = 4;
#ifdef PIGPIOD
status = bsc_xfer(pi_handle, &xfer);
#else
status = bscXfer(&xfer);
#endif
xfer.txCnt = 0;
tx_flag = true;
}
//
// Print results
//
if (rx_flag) {
printf("Received control data, status=0x%x:", status);
for (int i = 0; (i < rx) && (i < 10); i++) {
printf("b[%d]=%d", i, rx_ctl[i]);
if ((i < (rx-1)) && (i <= (10-1))) printf(", ");
}
printf("\n");
}
// SHORT PAUSE
usleep (25000);
}
printf("Done looping.\n");
}
void quit_handler( int sig )
{
runloop = false;
}
int
main(int argc, char **argv)
{
printf("Main startup\n");
// Responds to Ctrl-C
signal(SIGINT,quit_handler);
setup();
loop();
reset_i2c();
}
The following is the code on the Arduino MKR1010 CPU.
#include <Wire.h>
void setup(void)
{
Wire.begin();
//Wire.setClock(100000);
Serial.begin(115200);
}
void loop(void)
{
static bool resp = false;
// Transmit
Wire.beginTransmission(0x15);
for (uint8_t i = (resp)?(5):(25); i < ((resp)?(5):(25))+5; i++) {
Wire.write(i);
}
Wire.endTransmission();
// Receive
uint8_t rxbuf[10];
int rxi = 0;
Wire.requestFrom(0x15, 4);
while (Wire.available()) {
rxbuf[rxi++] = Wire.read();
}
// Report Transmission
Serial.print("Sent following bytes: ");
for (uint8_t i = (resp)?(5):(25); i < ((resp)?(5):(25))+5; i++) {
Serial.print(i); Serial.print("...");
}
Serial.println("Done.");
resp = !resp;
// Report data Received
Serial.print("Received data from slave...");
if (rxi == 0) {
Serial.print("No data waiting...");
} else {
for (int i = 0; (i < rxi) && (i < 10); i++) {
Serial.print("B=");Serial.print(rxbuf[i]);
if ((i < rxi) && (i < 10)) Serial.print(", ");
}
}
Serial.println("Done.");
delay(100);
}
The OS version on the Raspberry Pi slave is:
$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description: Raspbian GNU/Linux 11 (bullseye)
Release: 11
Codename: bullseye
The Arduino IDE version and Wire library on the MKR1010 is version 1.8.19 (Windows store 1.8.57.0).
The i2c bus is configured in /boot/config.txt with the following line:
dtparam=i2c_arm=on
And the loaded modles are:
$ cat /etc/modules
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
i2c-dev
$ lsmod | grep i2c
i2c_bcm2835 16384 0
i2c_dev 20480 0
The two devices are connected as follows:
- The MKR1010 SDA Pin 11 is connected to Raspberry Pi GPIO 18
- The MKR1010 SCL Pin 12 is connected to Raspberry Pi GPIO 19.
- 2k pullup resistors are used on each of these lines to the Vcc of the MKR1010, 3.2volts.
Arduino i2C master output when the slave is not active appears like this:
21:46:28.699 -> Sent following bytes: 5...6...7...8...9...Done.
21:46:28.699 -> Received data from slave...No data waiting...Done.
21:46:28.772 -> Sent following bytes: 25...26...27...28...29...Done.
21:46:28.772 -> Received data from slave...No data waiting...Done.
21:46:28.910 -> Sent following bytes: 5...6...7...8...9...Done.
21:46:28.910 -> Received data from slave...No data waiting...Done.
21:46:29.013 -> Sent following bytes: 25...26...27...28...29...Done.
21:46:29.013 -> Received data from slave...No data waiting...Done.
21:46:29.100 -> Sent following bytes: 5...6...7...8...9...Done.
21:46:29.100 -> Received data from slave...No data waiting...Done.
21:46:29.169 -> Sent following bytes: 25...26...27...28...29...Done.
21:46:29.169 -> Received data from slave...No data waiting...Done.
21:46:29.293 -> Sent following bytes: 5...6...7...8...9...Done.
21:46:29.293 -> Received data from slave...No data waiting...Done.
Alternating byte sequences of [5,6,7,8,9] and [25,26,27,28,29] are sent to the non-existent slave. There is no response as indicated.
Raspberry Pi slave output using the PIGPIO direct method (no daemon) appears as so:
Received control data, status=0x400c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x400c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x400c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x400c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x400c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x400c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x400c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x400c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x400c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x400c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x401c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x401c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x401c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x402c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x402c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x402c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x402c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x403c2:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x403c2:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x10406:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x40302:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x40302:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0x40406:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0x406:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
This shows the control data received by the slave. Sequences are received mostly in alternating order. However, there are a number of cases where some transmissions are dropped and we see the same sequence multiple times in a row.
Looking at the data from the bus master, we find:
22:35:27.652 -> Sent following bytes: 25...26...27...28...29...Done.
22:35:27.652 -> Received data from slave...B=15, B=16, B=17, B=18, Done.
22:35:27.744 -> Sent following bytes: 5...6...7...8...9...Done.
22:35:27.744 -> Received data from slave...B=15, B=16, B=17, B=18, Done.
22:35:27.866 -> Sent following bytes: 25...26...27...28...29...Done.
22:35:27.866 -> Received data from slave...B=35, B=36, B=37, B=38, Done.
22:35:27.981 -> Sent following bytes: 5...6...7...8...9...Done.
22:35:27.981 -> Received data from slave...B=15, B=16, B=17, B=18, Done.
22:35:28.062 -> Sent following bytes: 25...26...27...28...29...Done.
22:35:28.062 -> Received data from slave...B=35, B=36, B=37, B=38, Done.
22:35:28.177 -> Sent following bytes: 5...6...7...8...9...Done.
22:35:28.177 -> Received data from slave...No data waiting...Done.
22:35:28.273 -> Sent following bytes: 25...26...27...28...29...Done.
22:35:28.273 -> Received data from slave...B=15, B=16, B=17, B=18, Done.
22:35:28.390 -> Sent following bytes: 5...6...7...8...9...Done.
22:35:28.390 -> Received data from slave...No data waiting...Done.
22:35:28.462 -> Sent following bytes: 25...26...27...28...29...Done.
22:35:28.462 -> Received data from slave...No data waiting...Done.
The alternating control sequence data can be observed being sent. However responses should match control sequence [5,6,7,8,9] with response [15,16,17,18] and control sequence [25,26,27,28,29] with response [35,36,37,38,39]. These occasionally line up, but more often than not, they do not. And many cases of no response data when Wire.available() is called.
When using the daemon, pigpiod
, we find that the missed control bytes increase in frequency, sometimes with as many as three in a row:
Received control data, status=0xc2000400:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0xc2000400:b[0]=5, b[1]=6, b[2]=7, b[3]=8, b[4]=9
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Received control data, status=0xc2000400:b[0]=25, b[1]=26, b[2]=27, b[3]=28, b[4]=29
Note that the status bytes are mixed up between pigpio
direct and pigpiod
versions. It doesn't appear to be a simple byteswap case either. byte 0 in the pigpio
version is the first byte in pigpiod
version. Regardless, any attempt to decode the status would not be portable. The documentation does not seem to mention any differences in status byte between the direct and daemon versions. In fact, I'm not even confident the pigpiod
status is valid. Note the status from the direct calls seems much more precise. The data back from pigpiod
is almost always the same status.
Output from the Arduino bus master shows similar characteristics with both pigpio
and pigpiod
versions of the slave. Therefore, I will not repeat the output.
For the Raspberry Pi to be useful as a slave:
- The master
requestFrom()
must get a matching response from the slave. If the responses don't match, the data is not useful. - The control data cannot be lost, or responses again will not be useful.
- It would be useful to know how the Raspberry Pi slave is notified that a
requestFrom()
has been issued without any control data being sent.
Regarding 1 and 2 above, I tried various delays in various places. It would sometimes make the problem worse, but never better.
Regarding #3 above, I did try some experiments with the pigpiod event mechanism. Two problems, I only received events from 31. The bsc_xfer()
had to be called to generate the event. However, once the call was made, it seemed better to just process it, rather than call it again in the event handler. Further, no event seemed generated for the requestFrom() command without control data. Also the event seemed to occur with no received data either.
Any pointers on how to resolve these issues is appreciated. Particularly how to detect a control dataless requestFrom()
on the slave side. Thank you.