By dave | November 15, 2017

TL;DR: This library provides helps you write non trivial Arduino applications. It contains a very simple task management facility, IO abstraction (serial, 8754 i2c), interrupt management, switch de-bouncing and rotary encoder support. Currently, it is intended for use with Uno (MEGA328) and above. There are many examples packaged with it that cover most use cases.

Downloads and source for the IO Abstraction library are hosted on github, all you’ll have to do is download and unzip the file into your Documents/Arduino/libraries folder and rename the folder from basicIoAbstraction-mater to basicIoAbstraction.

Using TaskManager in your sketches

TaskManager is a very simple executor framework that allows work to be scheduled in the future. Instead of writing code using delays, one simply adds jobs to be done at some point in the future. In addition to this, interrupt handling is also supported, such that the interrupt is “marshalled” and handled like a very high priority task to be processed immediately.

When using this library you should never use any method that will delay for more than a few microseconds, failing to follow that guideline will cause problems with interrupt handling and timing.

Getting started with TaskManager

Firstly include it

#include <TaskManager.h>

Secondly, create a taskManager in the global scope at the top of the file - outside of setup(). There can be only one task manager within an application.

TaskManager taskManager;

Advanced: If you need more or less task slots, you can provide the number of slots too, it defaults to 6 and each slot uses about 8 bytes of memory:

TaskManager taskManager(numberOfSlots);

Scheduling things to be done

For every scheduled task that we create, we provide a call back function that will be called at that time. These are declared as below. You can also use C11’s shortened lambda syntax if you wish, see the examples in the taskManagement.ino sketch.

void onTimer() {
    // do something here

To schedule something once in the future - very similar to setTimeout in javascript:

uint8_t taskId = taskManager.scheduleOnce(millisFromNow, onTimer);

To schedule something over and over at a scheduled interval:

uint8_t taskId = taskManager.scheduleFixedRate(millisInterval, onTimer);

To cancel a Task

taskManager.cancelTask(uint8_t taskId);

Setting up loop()

In the Arduino loop method, just put one call to the task manager.

void loop() {

Interrupt handling

Interrupt handling is generally an advanced topic, but this library provides a very simple way to handle a few common interrupts. When you tell the library to handle an interrupt, the library registers the interrupt handler on your behalf, then when the condition is met the internal handler is triggered, it sets a flag to tell the library an interrupt has been raised. The task library treats this as the highest priority, and as soon as the current task is completed the interrupt code runs. This may not be real time enough for all uses, but will be fine for most cases.

You must provide a function that will be called back when the interrupt triggers. You can call any Arduino functions as normal during the callback, as you are not in the interrupt handler, the task library has ‘marshalled’ it.

void onInterrupt(uint8_t interruptNumber) {
    // do something with interrupt.
    // For pins 1, 2, 3, 5, 18 the number will be set. Otherwise 0xff

To register the interrupt callback, and add an interrupt on a pin whenever the pin changes:

taskManager.setInterruptCallback (onInterrupt);   // <--- always do this first
taskManager.addInterrupt(2, CHANGE);  

Using IO Abstraction in your sketches

BasicIoAbstraction makes it easy for both libraries and code to abstract away where the pins are located. You can treat both shift registers and i2c based IO expander devices very much like Arduino pins. This is exceptionally useful for library writers, who can then provide one library that works for most cases; but it is also useful in code, as it provides flexibility and simplicity. Also see This blog post about using IO Abstraction

There are also several sketches in the examples folder showing how to use this abstraction, one for Arduino pins, one for shift registers and one for i2c based IO using an expander device. In each case the circuit and code have been kept as simple as possible.

There is also a fork of the LiquidCrystal library that works with this abstraction, and therefore can be used with pins, i2c or a shift register without too much complexity.

Creating an Io Abstraction

To include it:

#include <BasicIoAbstraction.h>

To use it with an 8754 i2c expander:

With the IO expander version, 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.

IoAbstactionRef ioExpander = ioFrom8754(i2cAddress);

To use it with shift registers:

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 pins 0 onwards, and outputs are always 24 onwards. For more information about shift registers see [] and [].

For input only:

IoAbstractionRef ioExpander = inputOnlyFromShiftRegister(readClkPin, readClkEnaPin, dataPin, latchPin);

For output only:

IoAbstractionRef ioExpander = outputOnlyFromShiftRegister(writeClkPin, dataPin, latchPin);

For input and output:

IoAbstractionRef ioExpander = inputOutputFromShiftRegister(readClockPin, readDataPin, readLatchPin, readClockEnablePin, 
                                                           writeClockPin, writeDataPin, writeLatchPin);

To use with Arduino pins directly

You can use the IO abstraction even for direct use of Arduino pins, especially if you are considering changing to use a different type of IO later.

IoAbstactionRef arduinoPins = ioUsingArduino(); 

Want a different IO device?

Either submit a patch on github or get in touch via one of the means at the bottom of this page. Longer term I’m considering adding another abstraction for [] as I probably will have a need for it.

Setting pin direction, reading and writing.

Now that you’ve got an instance of the IoAbstraction, you need to be able to set up the pin directions, just like you would in a regular Arduino sketch:

ioDevicePinMode(ioExpander, pin, INPUT);
ioDevicePinMode(ioExpander, pin, OUTPUT);
ioDevicePinMode(ioExpander, pin, INPUT_PULLUP); // Arduino implementation only.

Reading from and writing to pins.

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 direct programs pins directly without buffering. However, even when writing for Arduino, include the synchronisation or it won’t work later when you use it 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, pinRead, newValue);
value = ioDeviceDigitalRead(ioExpander, pinWrite)

Using SwitchInput in your sketches

Switch input provides support for event based programming, similar to how web and UI applications are written. It supports switches and rotary encoders. It will de-bounces any switch presses to give greater accuracy and reduce the possibility of duplicate call backs. This library is designed for use with TaskManager, and it is trivial to set it up that way.

To create an instance of SwitchInput

// Outside of any function (at global scope)
SwitchInput switches;
TaskManager taskManager;

// inside setup
void setup() {
    // we default to using IO over arduino pins, see above to change to i2c or shift registers.
    switches.initialise(taskManager, ioUsingArduino());

To add a switch

To add a PULL_DOWN switch just call addSwitch as below.

void addSwitch(uint8_t pin, KeyCallbackFn callback, uint8_t repeat = NO_REPEAT);


  • pin : the chosen IO port that the switch is connected to.
  • callback : a callback function to receive the notification declared in the form of void functionName(bool held) { .. }
  • repeat: defaults to NO_REPEAT or provide a multiple of ten milliseconds up to 254 (2.54 seconds between events).

To initialise a rotary encoder

Rotary encoders need an interrupt in order to work properly, this is because they must be read extremely quickly after a change in order to get accurate readings. Using this library makes working with such encoders simple. Also, as encoders need an interrupt, pinA and pinB ALWAYS use arduino pins directly regardless of what IO the switches use.

First initialise the encoder within setup()

void initialiseEncoder(uint8_t pinA, uint8_t pinB, EncoderCallbackFn callback);


  • pinA is the A output of the encoder, this must be directly connected to a port that supports interrupts, usually 1 or 2.
  • pinB is the B output of the encoder, this can be directly connected to any available pin
  • callback is called whenever there is a change in the encoder value. The function is declared as void functionName(int change)

Register an interrupt handler with taskManager:

At global scope:

void encoderInterrupt(uint8_t pin) {
  // switch input provides a ready written function to handle rotary encoder interrupts.

Within setup add:

  taskManager.addInterrupt(encoderAPin, CHANGE);

To change the range of values for a rotary encoder

    void changeEncoderPrecision(uint16_t precision, uint16_t currentValue);


  • precision is the maximum value (0 based) the encoder can represent
  • currentValue is the current position of the encoder.
comments powered by Disqus
We use cookies to analyse traffic and to personalise content and adverts. Our social buttons may also use cookies.