How to drive a HD44780 with I2C backpack with a STM32

I2C backpack advantages

The HD44780 is a de-facto standard controller for display. We have already use it providing a source code to use a 16×2 LCD with a STM32. In this article we will step over introducing an I2C backpack for that display. Of course we will explain how to edit old code in order to get it work with this new hardware configuration.

Even if it is still popular, this controller was made commercially available in the late eighties. At that time serial communications were not so widespread because their were costly and involved constraint about clock speed. Because of that, the HD44780 comes with a parallel bus and requires up to 16 lines to work (8 for data, 3 for handshake, 2 for supply, 1 for contrast and 2 for backlight). With fastest MCU it is possible to use the 4-bit mode and with a couple of welds and a potentiometer we can reduce to 9 the required lines.

Nowadays, serial communication are largely used and SIPO (Serial-in Parallel-Out) converters are very cheap. Using a SIPO, it is possible to drive a HD44780 with less wires accepting a small extra charge due to additional hardware. Today it is possible to buy our display with the SIPO already mounted with a small extra amount (about 3.90 USD instead of 2.80 USD considering a 16×2 LCD).

The most used solution is based on PCF8574A, an I2C I/O expander from NXP. This solution is available as a backpack ready to be  soldered on our display. The backpack reduce the number of wires to 4: 2 for supply and 2 for the I2C communication.

Backpack schematic

I2C backpack PCF8574AT
I2C backpack PCF8574AT

The backpack used in this tutorial is almost standard. It is a black PCB designed to be soldered on the backside of the LCD, indeed,as the display, it has a 1×16 PIN header connector.

Aside the 1×16 header connector, the backpack has another 1×4 header which PINs are:

  1. GND, which should be connected to ground;
  2. VDD, which should be connected to 5V;
  3. SDA, which should be connected to I2C serial data;
  4. SCL, which should be connected to I2C serial clock.

In order to understand how we have edited the old code, it is very important to take a look to the backpack schematic. We will note that:

  • The chip on the backpack is the root of the problem. We already said it is an I/O expander, hence it should be clear that it is somehow capable to read/write from its I/O PINs which are P0~P7.
  • The chip mounted on the backpack could be both PCF8574T or PCF8574AT.
  • The chip is connected to 3 control PINs (RS, RW, CS) and to 4 data PINs. This suggests it uses the display in 4-bit mode.
  • P3 manages the K pin and A is connected to VCC through the jumper. Hence, P3 works like a backlight enabler (backlight is enabled if P3 is HIGH).
  • The chip has also three additional PINs (A0, A1 and A2) connected to VCC through a 10k resistance. They could be also shortcircuited to GND (They are not by default).
HD44780 with I2C backpack schematic
The schematic of a HD44780 based LCD with a PCF8574AT I2C backpack schematic.

These notes lead us to ask self-questions:

  1. How it is possible to read write from the I/O expander?
  2. What are the differences between PCF8574T and PCF8574T?
  3. What is the purpose of the A0~A2 PINs?

PCF8574 documentation

To answer these questions and solve our problem we need to read the documentation. Fortunately, the PCF8574T and the PCF8574AT are described by the same document:

PCF8574 Datasheet

Reading that document, we can answer to our questions. Question 2 and question 3 are strictly related. With the I2C bus each slave device could be identified through a slave address (in this way it is possible to connect more slave devices on the same bus using only two wires).

The slave address is partially hardcoded and depends on chip version, the non-hardcoded part depends on A0~A2 PINs connection (See Fig.3). As example, since my backpack equips a PCF8574AT having A2, A1 and A0 connected to VCC my slave address is 0b0011 1111 or 0x3F.

PCF8574 slave address
PCF8574 slave address

Question 1 is even simpler: to write/read from the PCF8574 we need to write/read an 8 bit word from the I2C bus. From the datasheet, we can also understand (in the timing section) that we need to use a 100kHz clock and standard duty cycle.

As a separate note, the I2C Specification establishes specific clock speeds (100kHz, 400KHz and 1MHz) and specific duty cycles (Standard, Fast Mode and Fast Mode Plus). These specifications are easily achievable through the I2C configuration which in ChibiOS for an STM32 Nucleo-64 F401RE is:

So let’s now focus on code and edits required to make old example work with this new hardware.

Editing old code

Updating my old project I have made all the edits at once. I will try to explain what I have done step by step, in this way a reader who wants to use this article like a kind of exercise will be facilitated.

We have seen that somehow backpack doesn’t allow the backlight dimming and the 8-bit mode. So, before to start edit code, I have simplified the old library removing all that parts which could not be used with this new hardware configuration. I have removed all the code related to backlight dimming  (including the API) and all the code related to the 8-bit mode.

This step is simple to perform and easy to test since it doesn’t impact code functionality and could be hence tested using a classical HD44780 LCD connected in 4-bit mode.


Starting from driver, old structure was:

it will remain almost the same except for backlight type which now will become a boolean


Edits to the configuration structure require foresight. The old version of this structure (after removal of backlight dimming related fields )is:

Note that backlight was a uint32_t and now has been already updated to bool. While configurations (cursor, blinking, font, lines) should be left untouched, pinmap structure has no more sense.

Now our code don’t need to act on PAL. We need hence to replace pinmap structure with an I2C driver, its configuration and a slave address which strictly depends on hardware:

Edits to internally used functions

The old library was based on three functions which were not exported but internally used and their was:

  • hd44780IsBusy(), which reads the busy flag from the D7 PIN;
  • hd44780WriteRegister(), which writes a value in command/data register;
  • hd44780InitByInstructions(), which is executed once on driver start-up. It initializes the device.

This function has been removed in this new library to avoid complication. It has been replaced with a small sleep enough large to ensure the completion of internal operations.


This function has been completely rewritten replacing the logical write on PINs with I2C transmits. The old function was:

The new one is:

We should notice that the busy flag check has been replaced with a 2 milliseconds sleep. To send a whole 8-bit word we need for 4 transactions. This because the I/O expander can operate only a 4-bit mode communication and we need to send out each half-word twice changing the status of the E pin from high to low.

We have created a couple of macro in order to get the higher and lower part of the word


Even the initialization by instructions requires an edit since this function directly acts on LCD PIN. The oldest version was:

the new one is more compact even because it hasn’t to discriminate between 8-bit and 4-bit mode:

A simple demo

The main.c has been changed a little bit. Indeed we need to update the LCD configuration structure according to our edits and we can remove all the PWM related stuff.


The PINs are connected as follow:

  1. VCC, to 5V;
  2. GND, to Ground;
  3. SDA, to PB9;
  4. SCL, to PB8.

Demo explained

This demo is completely equivalent to the old one. It writes a string for each line performing a smooth shift effect.

Project download

The attached demo has been tested under ChibiOS 18.2.x. Note that you can find more recent version of this project int the Download Page. Note also that starting from the version 20 all the demos from PLAY Embedded will be distributed together with ChibiStudio.


Replies to How to drive a HD44780 with I2C backpack with a STM32

    • Ciao Gaetan,
      in an I2C communication, the slave address is a 7-bit value. Now, this value is left aligned which means this value should be left shifted by 1 and the less significant bit is used to indicate the read/write bit (0 write, 1 read).

      In my case, the address is 0b011 1111 (i.e. 0x3F). Left-shifted, this becomes 0b011 111x. In a write operation, it is 0b011 1110 while in a read 0b011 1111.

      The operation of left shifting and bit masking is performed internally by the API of ChibiOS thus we should remain stuck with the standard definition of the I2C slave address which, again, is a 7-bit value and in my case 0x3F.

      I hope this clarifies things.

Leave a Reply