tl,dr: Both libraries are not competing alternatives, but aimed at very different situations.
I had a closer look at the Adafruit_BusIO project. I think it is aimed at this situation:
- [Arduino as I2C/SPI controller]
<---> [I2C/SPI peripheral]
I2Cwrapper is aimed at this situation:
- [Arduino as I2C controller]
<---> [Arduino as I2C target] <---> [non I2C peripheral/target's own resources]
If I'd known about the BusIO project, I might have used it for the lower level communication, instead of implementing my own solution, which is built around the SimpleBuffer.h class, which is based on code snippets from Nick Gammon.
I2Cwrapper, however, is much more. BusIO is for the controller's side only. You'd still have to implement your own onRequest(), onReceive(), error handling, etc. if you'd want to use it to implement an I2C target.
I2Cwrapper comes with a complete firmware for the I2C-target, which already deals with everything but the functionality specific for some peripheral, which is implemented in modules. This makes implementing new peripherals easy, much of it is actually a relatively mindless routine.
As the simplest example, you could have a look at the PinI2C module which makes another Arduino's pins usable over I2C, similar to some I2C port extender chip, and mimicks the conventional Arduino pin control functions.
The PinI2C.h interface looks very similar to the native Arduino.h interface:
class PinI2C
{
public:
/*!
* @brief Constructor.
* @param w Wrapper object representing the target the pins are connected to.
*/
PinI2C(I2Cwrapper* w);
void pinMode(uint8_t, uint8_t);
void digitalWrite(uint8_t, uint8_t);
[...]
private:
I2Cwrapper* wrapper;
};
It's implementation in PinI2C.cpp, though, is quite different (these are slightly simplified snippets for demonstration purposes):
// examplary void function
void PinI2C::pinMode(uint8_t pin , uint8_t mode) {
wrapper->prepareCommand(pinPinModeCmd, myNum);
wrapper->buf.write(pin);
wrapper->buf.write(mode);
wrapper->sendCommand();
}
// examplary non void function
int PinI2C::digitalRead(uint8_t pin) {
wrapper->prepareCommand(pinDigitalReadCmd, myNum);
wrapper->buf.write(pin);
int16_t res = -1;
if (wrapper->sendCommand() and wrapper->readResult(pinDigitalReadResult)) {
wrapper->buf.read(res);
}
return res;
}
All this code does is wrapping function calls into commands and sending them to the target, and, for non-void functions, receiving their response and returning it.
The matching firmware module PinI2C_firmware.h does the unwrapping. It does so by injecting code into the firmware.ino framework at defined places, in this case into the central switch-clause within the processMessage() function:
#if MF_STAGE == MF_STAGE_processMessage
case pinPinModeCmd: {
if (i == 2) { // 2 uint8_t received?
uint8_t pin; bufferIn->read(pin);
uint8_t mode; bufferIn->read(mode);
pinMode(pin, mode);
}
}
break;
case pinDigitalReadCmd: {
if (i == 1) { // 1 uint8_t received?
uint8_t pin; bufferIn->read(pin);
bufferOut->write((int16_t)digitalRead(pin)); // int is not 2 bytes on all Arduinos (sigh)
}
}
break;
[...]
Modules can also inject code in other places like include section, declaration section, setup(), or main loop(), if they need to. PinI2C is very simple, so it does very little in other sections. This is all it injects into the setup() function:
#if MF_STAGE == MF_STAGE_setup
log("PinI2C module enabled.\n");
#endif
This is the controller addressing a target which runs the firmware with the PinI2C module enabled (from examples/Pin_control.ino):
[...]
I2Cwrapper wrapper(i2cAddress); // each target device is represented by a wrapper...
PinI2C pins(&wrapper); // ...that the pin interface needs to communicate with the controller
[...]
void setup()
{
if (!wrapper.ping()) {
halt("Target not found! Check connections and restart.");
}
wrapper.reset(); // reset the target device to its initial state
pins.pinMode(dPinIn, INPUT); // INPUT_PULLUP will also work
pins.pinMode(dPinOut, OUTPUT);
pins.pinMode(aPinIn, INPUT);
pins.pinMode(aPinOut, OUTPUT);
}
void loop()
{
pins.digitalWrite(dPinOut, pins.digitalRead(dPinIn));
pins.analogWrite(aPinOut, pins.analogRead(aPinIn)/4);
[...]
The three files above is all it takes for a new module which implements some new peripheral. The PinI2C and TM1638liteI2C modules took me each about 2h to write and test. There's documented templates for these three files to help adding a new module.
And you practically don't have to worry about any I2C stuff while doing so.