Wednesday, 19 August 2015

I2C Port Expander

For all of my projects I have used the standard digital output pins when connecting to the Armdroid's 8-bit parallel interface.  But, what if you want to free up some of these pins in order to use external sensors, controls, or other devices...

The easiest way of getting more inputs and outputs is to use an "i/o port expander".  This is a device that allows you to control a number of ports using data sent to the device.  A port expander takes the data and controls the appropriate I/O pins.  This allows lots of sensors and devices, including the ability to control multiple Armdroids using only a few pins on the Arduino board.

The device I chose was the PCF8574A which has eight general purpose input/output pins controlled using I2C (pronounced I-squared-C).  I2C is a serial communications protocol allowing ICs to swap data on the same two-wire bus - Serial Data Line (SDA) and Serial Clock Line (SCL).

There are other devices available supporting 16 ports (eg. MCP23017), or SPI buses (eg. MCP23S17) and are similar in usage.  Although SPI (10Mhz) is faster than I2C (1.7MHz), for our purpose interfacing the Armdroid, it doesn't really make much difference


PCF8574A pinouts
The expanded I/O port are Pins 4-7 and 9-12 to be connected to the Armdroid interface D1-D8

Three address pins A0, A1, A3 determine the chips ID and must be wired to either +5v or GND.

Pin 13 (INT) used for interrupt output - leave disconnected for now

SDA - SDA pin on Arduino
SCL - SCL pin on Arduino
Vcc - +5v on Arduino
GND - GND on Arduino plus Armdroid interface GND

The 8574A can be assigned an address in the range 0x38-0x3F, and you can change this address by changing the connections of the pins A0, A1, and A2 as shown in the following table:

+5V GND GND 0x39
GND +5V GND 0x3A
+5V +5V GND 0x3B
GND GND +5V 0x3C
+5V GND +5V 0x3D
+5V +5V GND 0x3E
+5V +5V +5V 0x3F

You can use up to eight of these PCF8574A chips on the same IC2 bus.  Texas Instruments also manufacture the PCF8574 (without the A), essentially the same device with addresses ranging from 0x20-0x27.

You might find this i2c_scanner_sketch useful to verify your I2C communication is working if you have any problems.

Circuit Diagram

Circuit diagram for 8-bit Armdroid interface with PCF8574A

PCF8574A / Armdroid 1 connections:

P0 (pin 4) - D2 (J1/pin 4)
P1 (pin 5) - D1 (J1/pin 3)
P2 (pin 6) - D4 (J1/pin 6)
P3 (pin 7) - D3 (J1/pin 5)
P4 (pin 9) - D6 (J1/pin 8)
P5 (pin 10) - D5 (J1/pin 7)
P6 (pin 11) - D8 (J1/pin 10)
P7 (pin 12) - D7 (J1/pin 9)

J1/pin 1 (+5v) - not connected
J1/pin 2 (Gnd) - Common Gnd (Arduino & PCF8574A)

The address of the PCF8574A is hard-coded to 0x38 by grounding A0, A1, and A2.  I would recommend starting with these settings until you have something working.


Surprisingly, the code changes to support the port expander in the Armdroid Library are minimal thanks to C++ inheritance, and the Arduino 'Wire' library.

When designing the Armdroid library, flexibility was foremost, and it was always my intention to allow the library to be easily adapted for use in different projects.

To make this possible, core functionality was implemented in an abstract base class which can be derived into specialized classes.  This base class doesn't actually have any knowledge how control the hardware, rather, its up to the derived classes to implement that.  Another potential use in future might include supporting optimized versions for different Arduino boards.

In our case, we're creating a new class ArmdroidPortExp inheriting from ArmBase to represent our port expander variant:

 class ArmdroidPortExp : public ArmBase {  
  void begin(uint8_t address = 0x38);  
  void armdroid_write(uint8_t output);  
  uint8_t i2c_addr;  

This implementation adds a new member function ArmdroidPortExp::begin(uint8_t address) responsible for setting up the I2C hardware, and stores the supplied address of the port expander.  We also take the opportunity to initialize the Armdroid interface at this point.

The virtual function ArmdroidPortEx::armdroid_write(uint8_t output) simply writes a byte to the port expander using the address supplied earlier.  This is all done using the 'Wire' library that conveniently abstracts us from low-level I2C protocol/transmission details:

 void ArmdroidPortExp::begin(uint8_t address)  
  i2c_addr = address;  
 void ArmdroidPortExp::armdroid_write(uint8_t output)  

The implementation really is that simple !

Finally, here's a snippet of code that instantiates and uses the newly derived class...

 #include <Wire.h>  
 #include "Armdroid.h"  
 #include "ArmdroidPortExp.h"  
 // port expander address:  
 const int i2c_addr = 0x38;  
 // initialize Armdroid library:  
 ArmdroidPortExp myArm;  
 void setup()  
 void loop()  

I've added this code as an extension to the Armdroid Library as a separate package:

It wasn't possible to add this code to the library without introducing a dependency on the Wire library due to an 'oddity' with the Arduino IDE build mechanism.  If you intend to make use of these extensions, you will need to first install the Armdroid-Arduino-Library, then follow the instructions to install Armdroid-Arduino-PortExp

Included with this library is a modified version of the AsyncDemo previously presented with the Asynchronous library enhancements.

Photographs of my test circuit for reference:

See Also

PCF8574A data sheet:
I2C tutorial:
Arduino wire library:

Standard Armdroid / Arduino wiring: