Powered by Invision Power Board

Forum Rules Forum Rules (Please read before posting)
  Reply to this topicStart new topicStart Poll

> Pcf8574 I2c Port Expander Lcd Driver Pic 18f2550
Posted: November 08, 2015 07:13 pm
Reply to this postQuote Post


Group: Members+
Posts: 5
Member No.: 39,224
Joined: July 05, 2014

I2C Bus LCD Driver PCF8574 for PIC 18F2550 MPLAB X XC8

Not long ago, I ordered a 20X4 LCD display off of Ebay. When it arrived, there was some kind of interface module included with the LCD. I needed to go back and find out what it was that had been sent to me. It was billed as an IIC/I2C/TWI/SPI expander interface that would allow communication with the LCD using only two pins on my processor instead of 7 pins for 4-bit mode or 11 pins for 8-bit mode. It was built around a PCF8574 IC described as an I2C bus port expander. It turns out that IIC, I2C, and TWI all mean the same thing. However, SPI and I2C are completely different and not at all compatible. On Ebay, almost all of these things are advertised as I2C and SPI. The truth is that most all of them will only do one or the other. The I2C units have a 4-line bus with ground, V+, clock and data. The SPI units will have a 6-line bus with ground, V+, clock, data in, data out and slave select. This module, built around the PCF8574, was only made to do I2C. I decided to go ahead and write an I2C driver for this module. This project is actually two projects in one, the I2C driver and the LCD over I2C driver.

The I2C Driver: i2c.h

The prototype for this project is the venerable 18F2550. This processor contains the Microchip’s Master Synchronous Serial Port (MSSP). This same circuit can be found on some PIC16s most PIC18s and all subsequent PICs. Therefore, code for the I2C on the18F2550 should run unchanged on most modern PIC chips. The MSSP manages all traffic on the I2c bus. No more bit-banging here. All the work of this driver is done by reading and writing to the MSSP registers. Consult the I2C Master section of the MSSP chapter in the PIC 18F2550 data sheet for more specifics.

Configuring this driver only requires two steps. First, set the TRIS bits = 1, digital in, on the processor specific pins used by the MSSP. Second, set SPADD for the CPU clock and the desired data rate. Remember that the CPU clock is not necessarily the same speed as the crystal oscillator. This base driver should be sufficient to communicate with any or all the devices on the bus.

The LCD on the PCF8574 Driver: i2c_lcd_PCF8574.h

This driver must be configured by setting the address of the PCF5874 in the i2c_expander_write() routine. One might assume incorrectly that the adapter comes pre configured to the PCF8574 base address of 40h. However, the jumpers A0, A1, and A2 are open creating a “1” state. The real address is the top of the range at 4Eh. Shorting the jumpers to ground creates a “0” state. Thus, the address may be changed to one of 8 even addresses between 40h and 4Eh.

The interface uses PCF8574 output P3 to control the state of the LCD backlight. There is also a jumper on the interface that can be used to source the backlight with a higher voltage or control the LED brightness with a potentiometer. The driver contains a pre-defined variable, i2c_lcd_bl that is initially set to 1. This variable may be set to 0 or 1 to turn the backlight off or on. The change will take effect the next time the LCD is written to or commanded.

Enjoy the code,

M Headroom

The i2c to LCD Interface
user posted image

The i2c to LCD Interface Schematic
user posted image

Project Schematic
user posted image

File i2c.h


//  File:   i2c.h

#ifndef i2c_H
#define i2c_H

void i2c_init(void);                //initialize I2C functions
void i2c_waitForIdle(void);    
void i2c_start(void);               //issue Start condition
void i2c_ReStart(void);            //issue ReStart condition
int  i2c_read(unsigned char ack);    //read char - x=0, don't ack - x=1, ack
unsigned char i2c_write(unsigned char i2cWriteData ); //write unsigned char - returns ACK

void i2c_init(){
   TRISBbits.RB0 = 1;      // set SCL, RB1 and SDA, RB0 on 18F2550 family as inputs
   TRISBbits.RB1 = 1;
   CKE=1;                  // use I2C levels      worked also with '0'
   SMP=0;                  // disable slew rate control  worked also with '0'
   SSPCON2 = 0x00;  
   SSPADD = 0x77;          // 100k at 48Mhz clock, SSPADD = ((CPU_Clock)/(4 * Baud))-1
                           // E.G.  ((48MHz/(4*100KHz))-1 = 119 = 77h
   SSPCON1 = 0x28;         // set I2C master mode
   SSPIF=0;                // clear SSPIF interrupt flag
   BCLIF=0;                // clear bus collision flag


void i2c_waitForIdle(){
   while (( SSPCON2 & 0x1F ) | RW ) {}; // wait for idle and not writing


void i2c_start(){
   SEN=1;              //Start Condition Enable Bit of SSPCON2


void i2c_ReStart(){
   RSEN=1;             //Repeated Start Condition Enable Bit of SSPCON2


void i2c_stop(){
   PEN=1;              //Stop Condition Enable Bit of SSPCON2


int i2c_read( unsigned char ack ) {
   unsigned char i2cReadData;
   RCEN=1;                 //Receive Enable Bit of SSPCON2
   i2cReadData = SSPBUF;
   if ( ack ) {ACKDT=0;}   //Acknowledge Data bit of SSPCON2
   ACKEN=1;               // send acknowledge sequence
                          //Acknowledge Bit of SSPCON2
   return( i2cReadData );


unsigned char i2c_write( unsigned char i2cWriteData ){
   SSPBUF = i2cWriteData;
   return ( ! ACKSTAT  ); // function returns '1' if transmission is acknowledged


#endif   //i2c_H

File i2c_lcd_PCF8574.h


//  File:   i2c_lcd_PCF8574.h

#ifndef I2C_LCD_PCF8574_H
#define I2C_LCD_PCF8574_H
#define R_S 0x01    //PCF8574 Pin 4 -> P0 -> LCD RS
#define R_W 0x02    //PCF8574 Pin 5 -> P1 -> LCD RW
#define En  0x04    //PCF8574 Pin 6 -> P2 -> LCD E
#define B_L 0x08    //PCF8574 Pin 7 -> P3 -> LCD BL (Back Light)
                   //PCF8574 Pin 9 -> P4 -> LCD D4 (Comm with LCD is 4 bit wide)
                   //PCF8574 Pin 10 -> P5 -> LCD D5
                   //PCF8574 Pin 11 -> P6 -> LCD D6
                   //PCF8574 Pin 12 -> P7 -> LCD D7
#include "i2c.h"
unsigned int i2c_lcd_bl = 1;
void i2c_expander_write(unsigned char);
void i2c_lcd_4bit_strobe_write(unsigned char, unsigned int, unsigned int, unsigned int );
void i2c_lcd_init(void);               // Intialize the LCD - call before anything else
void i2c_lcd_write(unsigned char);     // Write a byte to the LCD in 4 bit mode
void i2c_lcd_command(unsigned char);     // Write a byte to the LCD in 4 bit mode
void i2c_lcd_clear(void);
void i2c_lcd_goto(unsigned char);
void i2c_lcd_putch(unsigned char c);
void i2c_lcd_puts(const char * s);

void i2c_expander_write(unsigned char i2cdata){
   i2c_write(0x4e);        // PCF8574 i2c buss address    *** CONFIGURE THIS ***

void i2c_lcd_4bit_strobe_write(unsigned char _data, unsigned int rs, unsigned int rw, unsigned int bl ){
    unsigned char fourbitdata;
    unsigned int ctldata = 0;
    if (rs == 1) ctldata = ctldata | R_S;      // RW is always 0
    if (bl == 1) ctldata = ctldata | B_L;
    fourbitdata = (_data<<4) | ctldata | En;
    i2c_expander_write(fourbitdata); // En high
    __delay_us(1);  // enable pulse must be >450ns
    fourbitdata = (_data<<4) | ctldata & ~En;
    i2c_expander_write(fourbitdata); // En low
    __delay_us(50);  // commands need > 37us to settle


void i2c_lcd_init(void){
   __delay_ms(10); __delay_ms(10); __delay_ms(10); __delay_ms(10);   // Wait for power up
   i2c_lcd_4bit_strobe_write((0x03), 0, 0, 1);    // Setup 4-bit mode
   i2c_lcd_4bit_strobe_write((0x03), 0, 0, 1);
   i2c_lcd_4bit_strobe_write((0x03), 0, 0, 1);
   i2c_lcd_4bit_strobe_write((0x02), 0, 0, 1);
   __delay_us(150);                                // Now in 4-bit mode
   i2c_lcd_command(0x28);      // Set interface length
   i2c_lcd_command(0x0C);      // Display on, Cursor on, Cursor blink  **CURSOR OFF**
   i2c_lcd_command(0x01);      // Clear Screen
   i2c_lcd_command(0x06);      // Set entry mode

void i2c_lcd_write(unsigned char _data){
   i2c_lcd_4bit_strobe_write((_data >> 4), 1, 0, i2c_lcd_bl);
   i2c_lcd_4bit_strobe_write((_data & 0x0f), 1, 0, i2c_lcd_bl);

void i2c_lcd_command(unsigned char _data){
   i2c_lcd_4bit_strobe_write((_data >> 4), 0, 0, i2c_lcd_bl);
   i2c_lcd_4bit_strobe_write((_data & 0x0f), 0, 0, i2c_lcd_bl);

void i2c_lcd_clear(void) {

void i2c_lcd_goto(unsigned char pos) {
i2c_lcd_command(0x80 + pos);

void i2c_lcd_putch(unsigned char c) {
i2c_lcd_write( c );

void i2c_lcd_puts(const char * s) {
#endif /* I2C_LCD_PCF8574_H */

File LCD_Demo.c


//  File:   main.c

#define _XTAL_FREQ 48000000
#include <xc.h>
//  **** CPU configuration lines go here ****
#include "i2c.h"
#include "i2c_lcd_PCF8574.h"

int main(void)   {

   while (1){
   i2c_lcd_goto(0x00);  i2c_lcd_puts("   Wake up world!   ");
   i2c_lcd_goto(0x40);  i2c_lcd_puts("12345678901234567890");
   i2c_lcd_goto(0x14);  i2c_lcd_puts("ABCDEFGHIJKLMNOPQRST");
   i2c_lcd_goto(0x54);  i2c_lcd_puts("abcdefghijklmnopqrst");
   __delay_ms(10);__delay_ms(10);__delay_ms(10);__delay_ms(10);__delay_ms(10);  //Wait a second
   }        // end of "while 1"

   return 0;  }   //end of main

/*  Normal useage:
*  i2c_lcd_goto(0x00)    Move the cursor, home in this case
*  i2c_lcd_put("s")      Write one character, "s" in this case
*  i2c_lcd_puts("Hello") Write a string, "Hello" in this case
*  i2c_lcd_write(0x20)   Write numeric ASCII value, space in this case
*  i2c_lcd_command(0x01) Write a command to the LCD.  See LCD documentation
*  i2c_lcd_bl = 0;    // Will turn off backlight on next write.  Only 1 or 0 allowed.
*  i2c_lcd_bl = 1;    // Will turn on backlight on next write

This post has been edited by MHeadroom on November 26, 2015 06:29 pm
PMEmail Poster
Posted: November 09, 2015 11:19 pm
Reply to this postQuote Post

Forum Addict ++

Group: Spamminator Taskforce
Posts: 3,781
Member No.: 181
Joined: October 05, 2002

I just had a hair-pulling fest with those very modules,last week.

Be aware that they come in 2 'flavors'.
One version uses the 4 Low Bits for the LCD control,and the 4 High Bits for the 4-bit data.
The other version has the 4 Low Bits doing data,and the 4 High Bits doing control.

And then there's the PCF8574 versus PCF8574A Which has a different set of I2C addresses.

The place I bought mine from apparently has all of them in the same bin. I got some "A" type,and some "Low bits= data" and some "High bits = data" A real PITA,if you assume they're all the same.

"we need an e-kick-in-the-nuts button" -Colt45
PMUsers WebsiteYahoo
Posted: July 19, 2017 03:54 pm
Reply to this postQuote Post


Group: Members+
Posts: 1
Member No.: 41,032
Joined: July 19, 2017

This is a very good post, but I have something to say.

The jumper is connected to LCD16 pin, but LCD16 pin must be Ground. Emitter of the NPN transistor is connected to Vcc. There is something wrong. I think there is a mistake in the drawings.
PMEmail Poster
0 User(s) are reading this topic (0 Guests and 0 Anonymous Users)
0 Members:

Topic Options Reply to this topicStart new topicStart Poll


:: support us ::

ElectronicsSkin by DutchDork & The-Force