Using IoAbstraction you can write a sketch / program that uses Arduino pins, shift registers and IO expander devices at the same time, very much like you’d normally use Arduino pins. This library also provides simple interrupt handling that again is consistent across Arduino, mbed and IO expander ICs.
What do we mean by consistent, we mean that configuring a pin, adding an interrupt, reading from pins, and writing to pins is the same across Arduino, mbed, PCF8574, MCP32017 and shift registers. Even analog operations are standardized too. For most cases, such as adding switches, encoders and checking analog levels, it is device independent.
There are several sketches in the examples folder showing how to use most of the capabilities mentioned here. They cover Arduino pins, shift registers, IO expander devices, and MultiIo abstraction that allows many devices to be treated as a single large IO device.
You can look at BasicIoAbstraction in the reference docs.
There is also a fork of the LiquidCrystal library that works with this abstraction, and therefore can be used with pins, IO expanders or a shift register simply by changing the IoAbstraction it’s using. Also see This blog post about using IO Abstraction
The IoAbstraction interface deals with pin configuration, reading, writing and processing interrupts. Each implementation has its own way of handling these functions, but to the outside user they look the same, apart from creating an instance where there are clear differences. When you create an IoAbstraction instance you are returned an IoAbstractionRef. It is this reference that you pass to all the
ioDevice* functions below.
First let’s take a look at how to set the direction of a pin, here it is very similar to Arduino, apart from we use
ioDevicePinMode(ioExpander, pin, INPUT); ioDevicePinMode(ioExpander, pin, OUTPUT); ioDevicePinMode(ioExpander, pin, INPUT_PULLUP);
To register a raw interrupt handler for a pin again similar to Arduino we use
ioDeviceAttachInterrupt(..), see the TaskManagerIO section on events and interrupt handling for better strategies, where you can either marshal interrupts to task manager, or use events.
ioDeviceAttachInterrupt(ioDevice, pin, pinMode)
Where ioDevice is any IOAbstractionRef pin is the pin on the device to attach to pinMode one of CHANGE, RISING, FALLING.
Reading and writing from pins works slightly differently with the library, this is because the IO may well be going over a serial bus. Due to this inefficiency, the serial implementations use a buffer to reduce reads and writes; but Arduino and mbed wrappers deal with pins directly without buffering. However, even when writing for Arduino or mbed, include the synchronisation code becuase otherwise it may not work with another IO device.
It’s up to you how and when you call
ioDeviceSync(ioExpander); but using the sync is most optimal when you first write, then sync, then read. This is demonstrated below:
ioDeviceDigitalWrite(ioExpander, pinWrite, newValue); ioDeviceSync(ioExpander); value = ioDeviceDigitalRead(ioExpander, pinRead)
If you’re only doing one operation, you can use the shorthand read and write functions with an ‘S’ at the end, these automatically sync the device as appropriate. However, if you’re doing many operations over serial they are much less efficient.
ioDeviceDigitalWriteS(ioExpander, pinWrite, newValue); value = ioDeviceDigitalReadS(ioExpander, pinRead)
It is also possible to read from and write to ports, 8 bits at a time. For some tasks this can be much easier than writing one bit at a time. On I2C expanders and shift registers there’s no risk using ports whatsoever. However, with the Arduino pin abstraction, be careful to understand what other functions you may interfere with on that port. At the moment mbed does not support this option.
void ioDeviceDigitalWritePortS(IoAbstractionRef ioDev, uint8_t pinOnPort, uint8_t portVal); uint8_t ioDeviceDigitalReadPortS(IoAbstractionRef ioDev, uint8_t pinOnPort, uint8_t portVal)
Firstly you always need to include the main header:
Also, if you are using i2c IoExpander’s such as the PCF8574 or MCP23017 also include this header:
The simplest IoAbstraction type of all is for Arduino pins, it’s pretty much a pass through, that calls through to pinMode, digitalRead, digitalWrite etc.
IoAbstactionRef arduinoPins = internalDigitalIo();
On mbed we fully support creating IoAbstractionRefs for the inbuilt pins. We wrap the mbed DigitalIn, DigitalOut and InterruptIn support using our simple collection. We use the underlying
gpio_* functions to configure, read and write, while interrupts are handled using the
InterruptIn class. In a future version, we will expose the
gpio_t for mbed, and make it possible to configure gpio’s differently.
IoAbstactionRef mbedPins = internalDigitalIo();
For PCF8574, the i2c communication bus is used to read and write values. You will need to know the address of the i2c device on the bus. If you are not sure what address it’s on, use this i2c address scanner. Interrupts are supported but read the notes to understand the limitations:
Creating an instance:
IoAbstactionRef ioExpander = ioFrom8754(i2cAddress, optionalInterruptPin, optionalWireClass);
As above, the i2c communication bus is used to read and write values. You will need to know the address of the device, use this i2c address scanner if unsure. IO capabilities are almost identical to that of the Arduino, there are few limitations. However, I recommend if you are doing heavy writing and heavy interrupt based reading, then try to keep each on separate ports if possible.
Creating an instance
// create an IO device that has no interrupts IoAbstractionRef io23017 = ioFrom23017(address, optionalWirePtr); // create an IO device that uses one interrupt pin on the Arduino for both ports IoAbstractionRef io23017 = ioFrom23017(address, interruptMode, arduinoIntPin, optionalWirePtr); // create an IO device that uses two interrupt pins on the Arduino, one for each port IoAbstractionRef io23017 = ioFrom23017(address, interruptMode, intPinA, intPinB, optionalWirePtr);
address is the i2c address on which the device is addressable.
intPinA & intPinB are the interrupt pins on the board that you’ve connected the IOExpander interrupt pins to.
optionalWirePtr if you’re using Wire on Arduino, you can omit this, otherwise set it to a pointer to the i2c class you are using.
interruptMode IoAbstraction does most of the work to do with the
InterruptMode parameter. There is little work on your side, just choose the mode and library will do the rest.. Options are:
NOT_ENABLED - interrupts are not enabled, in this case you could use the simpler single argument version.
ACTIVE_LOW_OPEN - it is easiest, and the library will enable pull up resistors. More than one device can share this pin.
ACTIVE_HIGH_OPEN - not really useful.
ACTIVE_HIGH - interrupt line will go high when there is an interrupt.
ACTIVE_LOW - interrupt line will go low when there is an interrupt
Unlike other IO devices, shift registers have a known direction upfront. The implementation handles the well-known 74HC165 for input and 74HC595 for output. You can define if an IO abstraction should handle input, output or both. For shift-registers inputs are always on pins 0..32, and outputs are always from 32
SHIFT_REGISTER_OUTPUT_CUTOVER upward. For more information about shift registers see [https://playground.arduino.cc/Code/ShiftRegSN74HC165N] and [https://www.arduino.cc/en/Tutorial/ShiftOut]. Interrupts cannot be supported on shift registers.
You can theoretically chain together up to 4 input shift registers and up to 4 output shift registers. However, we’ve only ever tested chaining two registers.
For input from 74HC165 shift register:
IoAbstractionRef inputFrom74HC165ShiftRegister(readClkPin, dataPin, latchPin, numOfDevices = 1)
For input from other types of shift register:
IoAbstractionRef inputOnlyFromShiftRegister(readClockPin, dataPin, latchPin, numOfDevicesRead = 1);
For output only:
IoAbstractionRef outputOnlyFromShiftRegister(writeClockPin, writeDataPin, writeLatchPin, numOfDevicesWrite = 1);
For input and output:
IoAbstractionRef inputOutputFromShiftRegister(readClockPin, readDataPin, readLatchPin, numOfReadDevices, writeClockPin, writeDataPin, writeLatchPin, numOfWriteDevices);
To use more than one IoAbstraction at once in the same code, simply create a multi IO as below, and add as many IO expander devices as needed.
numberOfPinsForArduino - 1 as for Arduino pins. Following this will be each expander that you add. The type is
MultiIoAbstractionRef and is created as below. See the full MultiIoAbstraction guide
MultiIoAbstractionRef multiIo = multiIoExpander(100); // allocate pins 0..99 to arduino multiIoAddExpander(multiIo, ioFrom8574(0x20), 10); // then have an 8574 from 100..109 ... then use just like an other IoAbstraction
If you decide to create your IoAbstractions explicitly instead of using the provided functions, you’ll have an
object of type
BasicIoAbstraction, to get an
IoAbstractionRef we would:
IoAbstractionRef multiIoRef = &manuallyCreatedIo;
Either submit a patch on github, raise an issue on IoAbstraction, or get in touch using the contact form.
On some Arduino boards there’s more than one wire instance, most of the IoExpanders can optionally take a wire instance as the last parameter, for example such as
IoAbstractionRef io23017 = ioFrom23017(address, &Wire1);