Since this topic is very poorly covered and Sebastião's snippet
and helped me to solve this problem I want add a complete solution on how to setup a RaspberryPi right here (tested on a RPi 3 and Zero W)!
Setting up a working slave:
Preparations
Be sure to have commented out this line in your /boot/config.txt:
dtparam=i2c_arm=on
Dependencies
Next, install g++ and pigpio using this command:
sudo apt install g++ pigpio
Pins
As stated in the documentation, the pins are GPIO 18 (SDA) and 19 (SCL).
This site helps locating them on your RaspberryPi (layout should be the same for RaspberryPi 2, 3, Zero and Zero W)
This schematic from the site will help:
Software
Explanation
As already, said this solution is based on Sebastião's code snippet. I modified it with the help of joan's solution.
I also tried to make sense of the code using the documentation for the function bscXfer
.
In the source code, the data in the bsc_xfer_t
struct are used add or receive messages but those are only applied when bscXfer
is executed with the address to the struct (as joan pointed out in his solution).
The bsc_xfer_t.control integer has a fairly special role which states multiple things like slave I²C address, and various other states that are well documented in the documentation.
To understand this better, I copied the important parts of the documentation into the source code and changed some things or outsourced them into separate functions.
Source code
The address can be changed to whatever you want (as long as it is not above 127 (aka 7F(16) or 1111111(2)).
Since I'm not good at C++, you have to comment out, what you want you wan't to do. It is recommended to run the closeSlave
function upon finishing your testing.
Here the slaveTest.cpp file:
#include <pigpio.h>
#include <iostream>
using namespace std;
void runSlave();
void closeSlave();
int getControlBits(int, bool);
const int slaveAddress = 0x03; // <-- Your address of choice
bsc_xfer_t xfer; // Struct to control data flow
int main(){
// Chose one of those two lines (comment the other out):
runSlave();
//closeSlave();
return 0;
}
void runSlave() {
gpioInitialise();
cout << "Initialized GPIOs\n";
// Close old device (if any)
xfer.control = getControlBits(slaveAddress, false); // To avoid conflicts when restarting
bscXfer(&xfer);
// Set I2C slave Address to 0x0A
xfer.control = getControlBits(slaveAddress, true);
int status = bscXfer(&xfer); // Should now be visible in I2C-Scanners
if (status >= 0)
{
cout << "Opened slave\n";
xfer.rxCnt = 0;
while(1){
bscXfer(&xfer);
if(xfer.rxCnt > 0) {
cout << "Received " << xfer.rxCnt << " bytes: ";
for(int i = 0; i < xfer.rxCnt; i++)
cout << xfer.rxBuf[i];
cout << "\n";
}
//if (xfer.rxCnt > 0){
// cout << xfer.rxBuf;
//}
}
}else
cout << "Failed to open slave!!!\n";
}
void closeSlave() {
gpioInitialise();
cout << "Initialized GPIOs\n";
xfer.control = getControlBits(slaveAddress, false);
bscXfer(&xfer);
cout << "Closed slave.\n";
gpioTerminate();
cout << "Terminated GPIOs.\n";
}
int getControlBits(int address /* max 127 */, bool open) {
/*
Excerpt from http://abyz.me.uk/rpi/pigpio/cif.html#bscXfer regarding the control bits:
22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
a a a a a a a - - IT HC TF IR RE TE BK EC ES PL PH I2 SP EN
Bits 0-13 are copied unchanged to the BSC CR register. See pages 163-165 of the Broadcom
peripherals document for full details.
aaaaaaa defines the I2C slave address (only relevant in I2C mode)
IT invert transmit status flags
HC enable host control
TF enable test FIFO
IR invert receive status flags
RE enable receive
TE enable transmit
BK abort operation and clear FIFOs
EC send control register as first I2C byte
ES send status register as first I2C byte
PL set SPI polarity high
PH set SPI phase high
I2 enable I2C mode
SP enable SPI mode
EN enable BSC peripheral
*/
// Flags like this: 0b/*IT:*/0/*HC:*/0/*TF:*/0/*IR:*/0/*RE:*/0/*TE:*/0/*BK:*/0/*EC:*/0/*ES:*/0/*PL:*/0/*PH:*/0/*I2:*/0/*SP:*/0/*EN:*/0;
int flags;
if(open)
flags = /*RE:*/ (1 << 9) | /*TE:*/ (1 << 8) | /*I2:*/ (1 << 2) | /*EN:*/ (1 << 0);
else // Close/Abort
flags = /*BK:*/ (1 << 7) | /*I2:*/ (0 << 2) | /*EN:*/ (0 << 0);
return (address << 16 /*= to the start of significant bits*/) | flags;
}
Note that in some cases, you want the first byte to be the command byte rather than part of your general data.
EDIT: Also note, that while this works fine for testing purposes, @crasic pointed out (first comment), that there is a better (but also poorly documented) way of doing it with events rather than using an endless loop. That should be better when used with multiple applications.
Compile & Execute
You can compile this with
g++ slaveTest.cpp -lpthread -lpigpio -o slaveTest
and execute with
sudo ./slaveTest
Test with a master
To shortly test it a master, a popular option is to use smbus, which is far easier and can be found by simply searching with an search engine of your choice.
My chosen option in short:
- Look up the SDA, SCL pins (they differ as a master!)
- Run
sudo apt install python3 python3-smbus
- Copy the snippet below to as e.g. masterI2C.py onto your RPi
- Open a python shell with this snippet using
python3 -i masterI2C.py
- Run
sendData(0x03, 'Hello World of I2C!')
to send data
Master python snippet:
import smbus
bus = smbus.SMBus(1)
def sendData(slaveAddress, data):
intsOfData = list(map(ord, data))
bus.write_i2c_block_data(slaveAddress, intsOfData[0], intsOfData[1:])
Image of testing this:
I hope, that I could clarify this topic for other people.
(When experiencing sudden problems, restarting my slave raspberry pi usually helped me.)