Here are the methods that a J5 IO-Plugin is expected to implement:
- i2cWrite(address, inBytes)
- i2cWrite(address, register, inBytes)
- i2cWriteReg(address, register, value)
- i2cRead(address, register, bytesToRead, handler)
- i2cRead(address, bytesToRead, handler)
- i2cReadOnce(address, register, bytesToRead, handler)
- i2cReadOnce(address, bytesToRead, handler)
Here are the functions available in WiringPi:
- int wiringPiI2CSetup(int devId);
- int wiringPiI2CRead(int fd);
- int wiringPiI2CWrite(int fd, int data);
- int wiringPiI2CWriteReg8(int fd, int reg, int data);
- int wiringPiI2CWriteReg16(int fd, int reg, int data);
- int wiringPiI2CReadReg8(int fd, int reg);
- int wiringPiI2CReadReg16(int fd, int reg);
WiringPi has functions to read or write a byte, read or write a byte register, and read or write a word register. Note that WiringPi doesn't have functions for reading or writing blocks of data, but most J5 methods are block methods.
The following table shows the direct mapping from J5 methods to WiringPi functions:
Johnny-Five | WiringPi |
---|---|
i2cWrite(address, inBytes) | no block write available |
i2cWrite(address, register, inBytes) | no block write to register available |
i2cWriteReg(address, register, value) | wiringPiI2CWriteReg8 |
i2cRead(address, register, bytesToRead, handler) | no block read available |
i2cRead(address, bytesToRead, handler) | no block read available |
i2cReadOnce(address, register, bytesToRead, handler) | no block read available |
i2cReadOnce(address, bytesToRead, handler) | no block read available |
Perhaps it's possible to implement the J5 block methods by calling WiringPi byte/word functions multiple times. If not, new functions could be implemented to perform the task.
The next question is "which thread does the code that reads and writes I2C data run in?" There are two possible answers here:
- In the same thread as the JavaScript code
- In one or more worker threads
The worker thread option is available because Raspi-io uses C++ addons.
In Galileo-IO the code that reads and writes I2C data runs in the JavaScript thread. This is because Galileo-IO uses mraa under the covers and mraa only has blocking synchronous calls on offer. AFAIK, this is something Rick isn't to happy about. On the other hand, this make things a lot more deterministic and a lot easier to reason about. For example, because only one thread is involved, only one piece of code can be accessing the I2C bus at any point in time.
In BeagleBone-IO the code that reads I2C data runs in worker threads. The code that writes I2C data runs in the JavaScript thread. BeagleBone-IO uses the i2c-bus package under the covers and i2c-bus offers both blocking synchronous calls and non-blocking asynchronous calls. This means that multiple threads can be accessing the I2C bus at the same point in time. Even deeper under the covers, i2c-bus uses the Linux ioctl call. Multiple threads will end up calling this function at practically the same time. However, Linux has things organized so that these calls will not be stopped half way through. Each call will run to completion before the next call runs, perhaps in another thread.
In an early version of the BeagleBone-IO implementation there was code similar to the following in the continuous i2cRead method:
BeagleBone.prototype.i2cRead = function(address, register, bytesToRead, handler) {
...
setTimeout(function read() {
...
this.i2cWrite(address, register);
_i2c.i2cRead(address, bytesToRead, data, function(err) {
...
setTimeout(read.bind(this), _i2cDelay);
}.bind(this));
}.bind(this), _i2cDelay);
return this;
};
In a test program, this continuous read method was being called for two devices at the same time.
The call to this.i2cWrite
is a blocking synchronous call and runs in
the JavaScript thread, the call to _i2c.i2cRead
results in a non-blocking worker
thread reading the data asynchronously.
Between the call to this.i2cWrite
and _i2c.i2cRead
, there were sometimes
context switches to worker threads and the I2C bus was accessed by the worker
threads. This access to the bus messed up the prerequisites for the later call
to _i2c.i2cRead
.
In the current BeagleBone-IO implementation the code in the continuous i2cRead is similar to this:
BeagleBone.prototype.i2cRead = function(address, register, bytesToRead, handler) {
...
setTimeout(function read() {
...
_i2c.readI2cBlock(address, register, bytesToRead, data, function(err) {
...
setTimeout(read.bind(this), _i2cDelay);
}.bind(this));
}.bind(this), _i2cDelay);
return this;
};
The two calls to this.i2cWrite
and _i2c.i2cRead
were replaced by a single
call to _i2c.readI2cBlock
and everything works as expected.