Tuesday 18 December 2012

I2C port expander driving an LCD screen, click encoder and more

Adventures with the MCP23017 I2C port expander

Since working on the Panelolu I have been planning a project using the MCP23017 port expander. This chip gives 16 additional I/O ports which can be accessed using the I2C protocol, an easy to use bus communication protocol that is supported in the Arduino environment using the Wire library. My first exposure to I2C was during development of the Panelolu when I came across the Hobbytronics I2C LCD backpack, Adafruit also do a fancy 3 colour backlight LCD. I started this project in August but then put it on the back burner due to other commitments.

I re-invigorated the project recently as we get a lot of emails from people wanting to use the Panelolu with a Melzi or a Sanguinololu where pins on the expansion header are dedicated to other things. In both these cases the original Panelolu took too many pins. Added to this Nophead has announced he will be using Melzi on the Mendel90 which I feel is the best printer design out there right now.

Hardware setup.

The setup I have tested on uses a Sanguinololu but any similar reprap controller should work. The ISP header and 4 more pins are required:

Leaving 6 pins for other uses.

The circuit is setup as in the schematic

The Sanguinololu ISP and Expansion header pins are allocated as follows:
  • SCL and SDA are connected to pin 12 and 13 of the MCP23017
  • MOSI, MISO, SCK and A0 provide the SD card signals as they do in the Panelolu and SDSL.
  • A1 is the interrupt pin and I added an LED to this to help with debugging which is not illustrated in the schematic.
  • 5V and GND are distributed as required
from the MCP23017 the LCD display, Buzzer, Click Encoder and 3 LEDs are connected. There are 2 spare pins so further LEDS or something else could be connected.

Software Setup

I began testing in August with the library from Adafruit which does a great job of exposing the basic read and write functions of the expander. Recently Lincomatic updated the LiquidTWI2 library which extends the Adafruit library and I switched to that. It does not provide access to the interrupt functionality of the MCP23107 so I added some additional functions to the library:

//check registers
uint8_t LiquidTWI2::readRegister(uint8_t reg) {
  // read a register
  Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
  Wire.requestFrom(MCP23017_ADDRESS | _i2cAddr, 1);
  return wirerecv();

//set registers

void LiquidTWI2::setRegister(uint8_t reg, uint8_t value) {
    Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);

In addition I added a function to switch the buzzer pin rapidly at a certain frequency to make a sound

//cycle the buzzer pin for a certain duration (ms) and at a certain freq (hz)
void LiquidTWI2::buzz(long duration, uint8_t freq) {
  int currentRegister = 0;
  // read gpio register
  Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
  Wire.requestFrom(MCP23017_ADDRESS | _i2cAddr, 1);
  currentRegister = wirerecv();
  duration *=1000; //convert from ms to us
  int period = (1.0 / freq) * 1000000; //*1000000 as the delay is in us
  long elapsed_time = 0;
  while (elapsed_time < duration)
        Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
        wiresend(currentRegister |= M17_BIT_BZ);
        delayMicroseconds(period / 2);
        Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
        wiresend(currentRegister &= ~M17_BIT_BZ);
        elapsed_time += (period);

I need to get up and running on GitHub until I do so the T3P3 version of LiquidTWI2 is available here. just download the as a zip and extract into the libraries subdirectory of the arduino folder so it looks something like:


At this time I have only tested it with Arduino 0023.

A1 is not a hardware interrupt pin so the PinChangeInt library for the Arduino is required. I used version 2.19, just download it according to the instructions and put in the libraries subdirectory of the arduino folder.

Example Sketch

I adapted the HelloWorld_i2c sketch that comes with LiquidTWI2, the full sketch is in the example sub directory of the T3P3 version of LiquidTWI. In essence the program uses the LiquidTWI2 library to setup communication with the MCP23017, configure and display information on the LCD and set the buzzer and LED pins.

It also sets interrupts on the click encoder pins and switch which trigger an interrupt on A1 every time the click encoder is turned or switch is pressed.

(from setup() )
    lcd.setRegister(MCP23017_GPINTENA,0x07); //enable interrupts on 3 pins
  lcd.setRegister(MCP23017_DEFVALA,0x07); //set the default values as 1

  //set to compare with register values set to 1 with DEFVALB 
  lcd.setRegister(MCP23017_IOCONA,0x02); //enable active high for interrupt pin
  //read the interrupt capture register to reset it
  uint8_t reg = lcd.readRegister(MCP23017_INTCAPA);
  pinMode(interruptPin, INPUT); //set A1 (pin 30) as input

  //attach an interrupt using the pinChangeInt library to interruptPin
  PCintPort::attachInterrupt(interruptPin, &quickInt, RISING);


The state of the click encoder and switch pins are are captured at the interrupt point and read, determining which way the encoder is turned or if the switch was pressed.

  void handleInterrupt() {
    //reset the flag
    //get the interrupt status of the pins
    //this will clear the flag allowing further interrupts
    uint8_t test = intCap & 0b00000111;

    //only deal with the situation where the encoder is turned without the
    //click button held down
    if (test == 0b101) encoderPos += 1;
    if (test == 0b110) encoderPos -= 1;
    //mask out the encoder bits and look at the click button
    test &= 0b100;
    if (test == 0b000) clickButton = LOW;

    // while interrupts are still occurring clear the register (switch bouncing) 
    while(digitalRead(interruptPin) > 0)
      delay (10);

The program does not make use of the SD card part of the design - that is planned for later when this is integrated into Marlin firmware. Also further testing on the buzzer is required as I believe the frequency is fairly limited by the I2C protocol and associated processing.

The video below demonstrated the example in operation.

Now off to make it work with Marlin firmware.


  1. From the first time I saw the Panelolu display I have wanted one. But was not prepared to give up the heated bed and other options that RepRap Pro may go for, by over committing I/O pins.

    Today (before I saw this post) I got a 4 line display to work with the Philips PCF8574(A) as documented http://hmario.home.xs4all.nl/arduino/LiquidCrystal_I2C/

    The Microchip part looks cool. I have an 8 pin part on a PICkit I2C board and wasn't aware of the larger device. I have ordered some.

    The Marlin software I worked with was downloaded on the 28th November 2012.

    Compliments of the season.

    1. Hi Conseils

      Thanks for pointing out the PCF8574 chip, I have looked at the datasheet it appears functionally similar to the MCP23008 which is the 8 pin version of the MCP23017 I am using. Overall though the Microchip appears to have more functionality (or at least explain the functionality better) in the form of multiple registers to set input/output, interrupt logic etc.

      Marlin appears to have been recently re-organised to allow easier use of various different displays, when I get a chance I am going to look at the new changes and get the MCP23017 based solution working with it.

  2. The T3P3 Version of LiquidTWI2 is available here:
    but I recommend using Lincomatic's more uptodate version here:

  3. If I upload marlin firmware with comment out line #define panelolu2 and I am not plug in the panelolu2 LCD in printrboard then my printer is not connected with prontrface. please help me to solve this issue.

    1. Hi Vikas

      Please confirm the version of Marlin you are using




Comments are now moderated to reduce spam, please excuse the additional time this will entail.

Note: only a member of this blog may post a comment.