When writing Arduino libraries that need quite a few IO pins, it can be difficult to decide if the library should use an IO expansion device such as the ubiquitous shift register, popular PCF8574 chip, or just use Arduino pins directly. This library is designed to handle all scenarios for the vast majority of cases.
The ubiquitous LiquidCrystal library; which almost all Arduino developers will have come across at some point,
has two versions (
LiquidCrystalI2C). If a similar abstraction method had been used, there would be
no need for two versions. I’m not saying anything bad about the library; more just pointing out an observation as I
use it a lot (and someone has to keep those versions working and reasonably in sync).
This library is hosted on our github account, it comes complete with a working example showing both modes of operation.
Git hub library page [https://github.com/davetcc/BasicIoAbstraction/].
To use the library, get the latest source as an zip and expand it into your Arduino libraries directory.
Latest news: I’ve forked the LiquidCrystal library and made it work with this abstraction, it took all of about an hour to get everything working. Internally, the library was very well structured and easy to modify. It’s available here.
The library has two additional examples, one for i2c and one for shift register use.
This library works by abstracting away the access to read and write data on pins, such that there is an implementation that works with Arduino pins direct, another for shift registers and yet another that uses an 8574 IO expander. Other chips could easily be added in the future and I’d happily accept pull requests or patches!
When using Arduino pins mode, there is very little overhead, it just calls directly out to Arduino underlying functions.
However, for serial operations, you really need to understand what the library is doing. As there is an overhead to
sending things over the wire, rather than sending each bit change one at a time, the library waits for a
and then sends a write command if needed followed by a read.
Although I’ve packaged this as a library, I assume that some people may want to copy the header and source into their own library directly, and although I’d prefer you just package the library with your code as another library, it’s fine to just package the class with your library. But in this case please do rename the classes and global functions to avoid conflicts and consider an attribution to me.
Many libraries could make use of this code, so I’ve put a very open and permissive license on it: Apache 2.0; which
allows commercial use. For the sake of example say we have a library called
SuperLib that needs 6 IO pins, we could
first off have a constructor that took the 6 pins needed, assuming the pins were direct to Arduino
SuperLib sf(PIN1, PIN2, PIN3, PIN4, PIN5, PIN6)
But then we could add an overloaded constructor, so that we could provide an alternative IO device such as a shift register or an 8574 IO expander:
SuperLib sf(PIN1, PIN2, PIN3, PIN4, PIN5, PIN6, ioUsing8574(0x20));
In all cases include the library
To create an instance for direct Arduino pins, simply:
BasicIoAbstraction* pins = ioUsingArduino();
To create an instance for an 8574 IO expander provide the i2c address and include the Wire library:
#include <Wire.h> BasicIoAbstraction* pins = ioFrom8754(0x20); // where 0x20 is the i2c address
To create an instance that uses serial shift registers for read / write we would need a shift register for read and a shift register for write. We share the clock and data between the two chips, but we have two enable pins, that enables change on the registers output. If you are only using read, or write, you can use the two later functions. Because we need two shift registers and the directions are fixed, 0-23 are the read ‘pins’ and 24 onwards are the output ‘pins’. You can still call pin mode, but it won’t do anything on this variant. For more information about shift registers see [https://playground.arduino.cc/Code/ShiftRegSN74HC165N] and [https://www.arduino.cc/en/Tutorial/ShiftOut].
BasicIoAbstraction* shiftReg = inputOutputFromShiftRegister(readClockPin, readDataPin, readLatchPin, readClockEnaPin, writeClockPin, writeDataPin, writeLatchPin)
BasicIoAbstraction* shiftReg = inputOnlyFromShiftRegister(readClkPin, readClkEnaPin, dataPin, latchPin);
BasicIoAbstraction* shiftReg = outputOnlyFromShiftRegister(writeClkPin, dataPin, latchPin);
To configure input and output pins use the library instead of the usual pinMode call. Use standard Arduino constants for
pins->pinDirection(0, INPUT); pins->pinDirection(6, OUTPUT);
In the main loop read and write values using the library. For the Arduino version, the pin number is used, for i2c 8574 it is the bit 0-7 of the port.
uint8_t switchValue = pins->readValue(0); pins->writeValue(6, switchValue); // this is absolutely needed on the serial versions, it makes it send the commands pins->runLoop();
Please take a look at the examples folder in the library, all the above cases are covered in detail.
Generally, for most cases where the amount of IO is limited, I believe this library is a good fit. If the library is constantly setting and testing bits thousands of times a second, then this library is probably not appropriate; but I’d argue that in that case neither is using an IO expander.
As far as I can tell, it uses no RAM beyond the object creation, the virtual method table usage is all in flash memory, so that should not be a problem for most devices. As far as I can tell, the shift register version is the heaviest on memory and probably worst on performance too. If you have a Mega chip then the i2c version will use hardware serial.
As the old saying goes, it’s better to work smarter than harder (although it’s often said at inappropriate times). Rather than polling for a switch transition, why not use the interrupt support available on the 8574 chip, and only read from it when needed.
If you are bit blasting at a very high frequency or using PWM, you’re better off using Arduino pins directly.
There are few main sources of overhead in this library. For most cases, these should not pose a problem: