JohnZero - Port Expander - I2C - LCD on Playground

This post is for JohnZero but any help from others is very welcome. I'm trying to get the LCD/i2c/port expander working as posted at arduinowebsite /playground/Code/I2CPortExpanderAndLCDs but the sample code doesn't compile. I get the following error:

In function 'void setup()': error: 'WriteLCD' was not declared in this scope

I looked in the .cpp file and don't see a WriteLCD() function but there is a WriteLCDByte() function so I changed it to WriteLCDByte but that fails the same.

Is this old code? Am I missing something? I added the .h and .cpp files to the hardware library and it is finding them. Is there an easier solution? Any help is very welcome.


Sorry for the late reply, I don’t get PMs in email :frowning:

This is my latest version, from 2007, it might work or not, but I am sure it worked for me. Put it under arduino-XXXX/hardware/libraries/LCDI2C4bit.cpp

LCDI2C4Bit.cpp file:

#include "LCDI2C4Bit.h"
#include <Wire.h>

extern "C" {
  #include <stdio.h>  //not needed yet
  #include <string.h> //needed for strlen()
  #include <inttypes.h>
  #include "WConstants.h"  //all things wiring / arduino

//command bytes for LCD
#define CMD_CLR 0x01
#define CMD_RIGHT 0x1C
#define CMD_LEFT 0x18
#define CMD_HOME 0x02

//stuff the library user might call---------------------------------

//constructor.  num_lines must be 1 or 2, currently.

byte dataPlusMask = 0; // TODO!!!

LCDI2C4Bit::LCDI2C4Bit( int devI2CAddress, int num_lines, int lcdwidth) {
  myNumLines = num_lines;
  myWidth = lcdwidth;
  myAddress = devI2CAddress;

void SetMCPReg( byte deviceAddr, byte reg, byte val ) {

void SendToLCD( byte deviceAddr, byte data ) {
  data |= dataPlusMask;
  data ^= 0x80; // E
  data ^= 0x80; // E

void WriteLCDByte( byte deviceAddr, byte bdata ) {
  SendToLCD(deviceAddr,bdata >> 4);
  SendToLCD(deviceAddr,bdata & 0x0F);

void LCDI2C4Bit::init( void ) {
  dataPlusMask = 0; // initial: 0
  SetMCPReg(myAddress,0x05,0x0C); // set CONFREG (0x05) to 0
  SetMCPReg(myAddress,0x00,0x00); // set IOREG (0x00) to 0
  WriteLCDByte(myAddress,0x0C); // turn on, cursor off, no blinking
  WriteLCDByte(myAddress,0x01); // clear display

void LCDI2C4Bit::backLight( bool turnOn ) {
  dataPlusMask |= 0x40; // Lights mask
  if (!turnOn) dataPlusMask ^= 0x40;

void LCDI2C4Bit::print( int value ) {
  dataPlusMask |= 0x10; // RS
  dataPlusMask ^= 0x10; // RS

void LCDI2C4Bit::printIn( char value[] ) {
  for ( char *p = value; *p != 0; p++ ) 

void LCDI2C4Bit::clear() {

void LCDI2C4Bit::cursorTo(int line_num, int x) {
  int targetPos = x + line_num * myWidth;
  for ( int i = 0; i < targetPos; i++)

void LCDI2C4Bit::commandWrite( int command ) {
  // RS - leave low

LCDI2C4bit.h file:

#ifndef LCDI2C4Bit_h
#define LCDI2C4Bit_h

#include <inttypes.h>

// IMPORTANT! Wire. must have a begin() before calling init()

class LCDI2C4Bit {
  LCDI2C4Bit(int devI2CAddress, int num_lines, int lcdwidth);
  void commandWrite(int command);
  void init();
  void print(int value);
  void printIn(char value[]);
  void clear();
  void backLight( bool turnOn );

  void cursorTo(int line_num, int x);
  //void leftScroll(int chars, int delay_time);
  //end of non-core--------

  //4bit only, therefore ideally private but may be needed by user
  //void commandWriteNibble(int nibble);

  //void pulseEnablePin();
  //void pushNibble(int nibble);
  //void pushByte(int value);
  int myNumLines;
  int myWidth;
  int myAddress;


Example use:

#include <Wire.h>
#include <LCDI2C4Bit.h>

;Connect the following pins from MCP23008 to LCD
;P0 - D4
;P1 - D5
;P2 - D6
;P3 - D7
;P4 - RS
;P5 - RW (not used, set to 0 to ground for write)
;P6 - Bl (backlight switch)
;P7 - E1

int ADDR = 0xA7;

byte x = 0;
byte data = 1;
byte c;
int val;

LCDI2C4Bit lcd = LCDI2C4Bit(ADDR,4,20);

void setup()
  Wire.begin(); // join i2c bus (address optional for master)

void loop()
/*  lcd.backLight(true);
  val = analogRead(0);
  if (val != 1023) {
    Serial.print(", ");

Hi there all. I realise this thread is nearly two years old, but this LCDI2C4Bit is the only 16 x 2 LCD expander using MCP23008 I can find. There is a newer one using a TI PCF8574N I2C Bus expander IC but I wasn’t able to get that chip. So I have set up LCDI2C4Bit and it works almost a 100% with my Hitachi compatible LCD.
I just commented out the non-compiling lines you mentioned but then modified the code for my purposes. Here is some of my code (this is on a ATMEGA328, btw)

#include <Wire.h>
#include <LCDI2C4Bit.h>

int ADDR = B10100111; //0xA7

LCDI2C4Bit lcd = LCDI2C4Bit(ADDR,2,16);

void setup()

This prints an A on the left, a B in the center of the top line, and a C on the left and a D in the center of the second line. But look at .cursorTo addresses. 0,0 is the default top left hand position as always. 0,8 is the middle of the top line (row 0 col 8). But there doesn’t seem to be a row 1, and the row 2 column addresses are 8 larger than they ‘should’ be (eg 2,8 instead of 2,0).
these cursor positions were discovered by trial and error, by the way.
This is a shame because I need to put specific texts and variables in specific places for a digital display that I had working without an expander. I can change my row and column variables to suit as above, but it is unfortunate that either such a useful header file has some error (that I can’t see because I dont really understand the binary maths) …
OR I have done something wrong in the LCDI2C4Bit lcd = LCDI2C4Bit(ADDR,2,16); line. And it’s not the binary ADDR either as the same result happens with 0xA7.
So it works, but in a flakey way.
Despite the fact it is an old thread, it has been read more than a thousand times, and hopefully some-one has been able to repair the code or point out what I am doing wrong. The port expander is an incredibly useful tool - because without it otherwise the LCD uses 6 pins, not two, and that is almost half of the digital inputs. And the MCP23008 is a cheap chip - I had to buy a minimum order of 5 for about $9.
Any help or clues most welcome!

Well it just goes on and gets more fun! The LCDI2C4Bit library looks a bit like its writer said, very beta.
I go my previous problem fixed by hacking a bit out of another library. I changed the LCDI2C4Bit.cpp, replacing ‘…cursorTo’ with:

void LCDI2C4Bit::cursorTo(int line_num, int x) {
int targetPos;
targetPos = x + ((line_num-1) * myWidth);
if(targetPos > (myWidth-1)) {
commandWrite( 0xC0 + (targetPos-myWidth)); // 0xC0 = start of second line in 2x16 LCD
} else {
commandWrite( 0x80 + targetPos ); // 0x80 = start of first line in 2x16 LCD
Which worked (on 16 x 2). As did using :

char charray[8];
ltoa(millis(),charray,10); //for example
As i was having trouble displaying integers rather than chars using print(int).

The PCF8574 seems similar to the mcp23008. Can one use a mcp23008 with the LiquidCrystal_I2C library?

That is the question.


I can't help you with that library but I can help with some background information on the LCD addressing and cursor positioning. First - take a look at the [u]LCD Addressing[/u] link at This will show you how to select the correct address for the desired location on the display.

Then look at the Instruction Set in the HD44780 (LCD controller) datasheet and you will see that the command to set the cursor location is derived from the address of the location with the eighth bit set high.

Examples: First character on first line: Address: 0x00 --> 0000 0000 Command: 1000 0000 --> 0x80

First character on second line: Address: 0x40 --> 0100 0000 Command: 1100 0000 --> 0xC0


Thanks for that Don - Now I see why it was so difficult! Typically obscure manufacturer data combined with translation errors and typos. Instead of using trial and error I can create reliable code... And start work on the port expander program with a lot more certainty. cheers jeremiah