HD44780 LCD over I2C (PCF8574) in PICSimLab

The parallel HD44780 LCD uses six or more pins. The I2C version of the display — an HD44780 with a PCF8574 I/O expander backpack — needs only two: SDA and SCL. PICSimLab models this directly with its LCD hd44780 I2C spare part, described in the documentation as a text display HD44780 connected to one PCF8574 I2C converter. This tutorial drives it from a PIC16F877A’s hardware I2C (MSSP).

What you need

  • PICSimLab 0.9.3 — see the download page.
  • MPLAB X IDE and the XC8 compiler.
  • Board: McLab2 or PICGenios, microcontroller PIC16F877A.
  • One LCD hd44780 I2C spare part (Outputs category). PICSimLab also lists a standalone IO PCF8574 expander part if you want to model the chip separately.
  • Helpful first: the parallel LCD tutorial — the HD44780 commands are identical, only the transport changes.

Wiring used in this tutorial (example)

  • SCL → RC3, SDA → RC4 (the PIC16F877A MSSP I2C pins)
  • PCF8574 address in the example code: 0x27 (write address 0x4E). This is the most common backpack address; if the part in your configuration uses a different address, change the define — the rest of the code is unaffected.

Steps

1. Generate the project and add the display

Create the MPLAB X project for the PIC16F877A. Open Modules → Spare parts, add Outputs → LCD hd44780 I2C and connect SCL and SDA in its Properties to RC3 and RC4.

2. Understand the PCF8574 pin mapping

The expander byte sent over I2C maps to the LCD like this in the standard backpack layout used by the example: bit 0 = RS, bit 1 = RW (kept 0, write only), bit 2 = EN, bit 3 = backlight (1 = on), bits 4–7 = data D4–D7. Each LCD nibble is written by setting the data bits and pulsing EN within the I2C bytes — no extra GPIO needed.

3. Initialise I2C, then the LCD

The example configures the MSSP as an I2C master at 100 kHz for a 20 MHz clock (SSPADD = 49), then performs the standard HD44780 4-bit initialisation through the backpack. After that, printing is the same lcd_puts() pattern as the parallel version.

4. Build, load and run

Build in MPLAB X, load the .hex and start the simulation. “I2C LCD OK” should appear on the first line, with the second line showing the backpack address the code used.

Example code (XC8)

// Example: HD44780 LCD via PCF8574 I2C backpack - PIC16F877A, XC8
// SCL=RC3, SDA=RC4. Backpack bits: P0=RS P1=RW P2=EN P3=BL P4-P7=D4-D7
#pragma config FOSC = HS
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config BOREN = ON
#pragma config LVP = OFF
#pragma config CPD = OFF
#pragma config WRT = OFF
#pragma config CP = OFF

#include <xc.h>
#define _XTAL_FREQ 20000000     // Example: match the board clock
#define LCD_ADDR 0x4E           // Example: PCF8574 at 0x27, write address

void i2c_init(void) {
    TRISCbits.TRISC3 = 1;       // SCL input (MSSP controls it)
    TRISCbits.TRISC4 = 1;       // SDA input
    SSPCON = 0x28;              // I2C master mode, module on
    SSPCON2 = 0x00;
    SSPSTAT = 0x00;
    SSPADD = 49;                // 100 kHz at 20 MHz Fosc
}

void i2c_start(void) { SSPCON2bits.SEN = 1; while (SSPCON2bits.SEN) { } }
void i2c_stop(void)  { SSPCON2bits.PEN = 1; while (SSPCON2bits.PEN) { } }

void i2c_write(unsigned char b) {
    SSPBUF = b;
    while (!PIR1bits.SSPIF) { }
    PIR1bits.SSPIF = 0;
}

static void lcd_expander(unsigned char data) {
    i2c_start();
    i2c_write(LCD_ADDR);
    i2c_write(data);
    i2c_stop();
}

// Send one nibble with EN pulse; rs = 0 command, 1 data; backlight on
static void lcd_nibble(unsigned char nibble, unsigned char rs) {
    unsigned char base = (unsigned char)((nibble & 0xF0) | 0x08 | rs);
    lcd_expander((unsigned char)(base | 0x04));  // EN high
    __delay_us(2);
    lcd_expander(base);                          // EN low
    __delay_us(100);
}

static void lcd_byte(unsigned char b, unsigned char rs) {
    lcd_nibble(b & 0xF0, rs);
    lcd_nibble((unsigned char)(b << 4), rs);
    __delay_ms(2);
}

void lcd_cmd(unsigned char c) { lcd_byte(c, 0); }
void lcd_putc(char ch)        { lcd_byte((unsigned char)ch, 1); }
void lcd_puts(const char *s)  { while (*s) lcd_putc(*s++); }

void lcd_init(void) {
    __delay_ms(50);
    lcd_nibble(0x30, 0); __delay_ms(5);
    lcd_nibble(0x30, 0); __delay_us(200);
    lcd_nibble(0x30, 0); __delay_us(200);
    lcd_nibble(0x20, 0);        // 4-bit mode
    lcd_cmd(0x28);              // 4-bit, 2 lines
    lcd_cmd(0x0C);              // display on
    lcd_cmd(0x06);              // entry mode
    lcd_cmd(0x01);              // clear
    __delay_ms(2);
}

void main(void) {
    i2c_init();
    lcd_init();
    lcd_puts("I2C LCD OK");
    lcd_cmd(0xC0);              // line 2
    lcd_puts("PCF8574 @0x27");
    while (1) { }
}

Try next

Troubleshooting

The display never responds. Address first: a backpack at 0x27 needs write address 0x4E; at 0x3F it is 0x7E. Then check SDA and SCL are not swapped in the part’s Properties — I2C fails silently when they are.

Characters appear but the backlight stays off or text is garbled. The backlight is bit 3 of every expander byte (kept set in the example). Garbled text with a working backlight points at the nibble/EN pulse code — compare it against the parallel tutorial’s driver, which shares the same HD44780 command set.

Similar Posts