I am trying to understand a simple I2C driver for a HD44780 with soldered PCF8574 daughter board, I'm still relatively new to electronics engineering (read: 20 years out of date), but an old hand with c.

The I2C board eludes me, other than I know it's based on a PCF8574, from the markings on the IC. The supplier did not give me enough information to go on to identify it, but it's a very common board, you can pick up from most electronic hobby shops- shown below.

This guy

Anyway, I can successfully drive the device from a Pi, using the following source code, modified from elsewhere:

#include <wiringPi.h>
#include <wiringPiI2C.h>

#define LCD_BACKLIGHT   0x08  // On
// LCD_BACKLIGHT = 0x00  # Off

#define ENABLE  0b00000100 // Enable bit
int fd;

void lcd_toggle_enable(int bits)   {
  // Toggle enable pin on LCD display
  wiringPiI2CReadReg8(fd, (bits | ENABLE));
  wiringPiI2CReadReg8(fd, (bits & ~ENABLE));

void lcd_byte(int bits, int mode)   {

  //Send byte to data pins
  // bits = the data
  // mode = 1 for data, 0 for command
  int bits_high;
  int bits_low;
  // uses the two half byte writes to LCD
  bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT ;
  bits_low = mode | ((bits << 4) & 0xF0) | LCD_BACKLIGHT ;

  // High bits
  wiringPiI2CReadReg8(fd, bits_high);

  // Low bits
  wiringPiI2CReadReg8(fd, bits_low);

void message(const char* text) {
    lcd_byte(*text++, 1);

int main() {
  fd = wiringPiI2CSetup(0x27);
  lcd_byte(0x33, 0); // Boring init code.
  lcd_byte(0x32, 0);
  lcd_byte(0x06, 0);
  lcd_byte(0x0C, 0);
  lcd_byte(0x28, 0);
  lcd_byte(0x01, 0);


  message("Hello world"); // my function, works fine.

This actually does sort of make sense- and I have happily driven the display from an MCU using the D0-7 / RST / RW / E pins in both 8 bit and 4 bit mode, so the individual hex instructions are readily understandable, because it seems effectively the lcd_byte function takes the value of D0-7, and the state of the RS flag.

What strikes me as odd, but perhaps it's just a kludge that this device uses, is that EVERY call over I2C is to READ a register, but clearly has the effect of setting the D0-7, RST, RW and EN pins.

Can anyone explain this behaviour, and is there a good reference documentation that would explain the supported I2C commands in relation to driving this board? (e.g. backlight state is being sent in every byte, but again, without documentation all I am doing is reverse engineering the protocol for now).

And secondly- I had some luck briefly with the LCD powered at 5v, but had SDATA and SCLOCK connected to a 3.3v MCU, with a common ground. I was able to at least turn the backlight off but this suddenly isn't happening. Did I get lucky? Or should I be able to drive the I2C pin logic from a 3.3v MCU provided I have a shared GND and the LCD is 5v? (I'm aware that due to the VO pin, I would need to run the display at 5v to get usable contrast, without soldering extra bits to the HD44780).


1 Answer 1


The PCF8574 has no concept of registers, as it has a single data register.

The standard protocol of reading a register means first the register address is transmitted, and then the data is read back.

Therefore, when the I2C library is commanded to do a register read, the PCF8574 understands the transmitted register address as data write. Also, when the I2C library continues to read data, it actually just returns the data register contents.

So, why someone would use I2C register read function to write data? No idea. But it works.

Perhaps the code was written by someone that does not know the API has also a function to write a single data byte. Or maybe it did not work or exist when this code was written.

Frankly, this means that for each API call to read a register, there are four bytes transmitted on the I2C bus, which makes this extremely slow.

  • \$\begingroup\$ Brilliant! I would add- it's FASTER to issue a read to transmit a byte over I2C, than a write- since a write requires a register AND a value. This mattered back in the early days when I2C PCF8574 extenders were being designed as much as it does today (particularly when being used as a generic pin extender). While you are correct, LOGICALLY, they are making an error, in fact it makes sense that they are potentially optimizing bus availability, as long as almost all PCF8574 I2C boards treat read this way. But your explanation was a massive critical leap to my understanding, thanks! \$\endgroup\$
    – mseddon
    Jul 30, 2020 at 18:54
  • 1
    \$\begingroup\$ It's faster because an I2C read is a bit vector of address, followed by a bitvector of register. An I2C write has both of those, and a bitvector of VALUE. You cut the bus overhead by 3/2 by using reads instead of writes. A standard conforming implementation is wasting time sending a value that is not ever used. \$\endgroup\$
    – mseddon
    Jul 30, 2020 at 19:05
  • 1
    \$\begingroup\$ I don't suggest THIS code is fast. This is cargo culted crap I found on the internet. But I stand by the fact that communicating to an I2C PCF8574 using READ only, requires less bits than using a WRITE, since a write is 3 bytes payload, and a read is 2. If you are designing an I2C PCF8574 board, do you just conform to standards, or provide a trapdoor for faster throughput? \$\endgroup\$
    – mseddon
    Jul 30, 2020 at 19:09
  • 1
    \$\begingroup\$ No it does not. Unless you omit the actual read part, and include only writing the register part of the read. Which equals writing one byte of data. Writing a single byte of data is only 2 bytes transferred. Reading a register is four bytes transferred. \$\endgroup\$
    – Justme
    Jul 30, 2020 at 19:12
  • 1
    \$\begingroup\$ So as it was apparent from reading the source code for the WiringPi, the I2C/SMBUS functions that do a REGISTER read or write, they will first send the register address byte in a write transaction, and after that the data read or write transaction is performed. There are I2C/SMBUS pure read and write functions available, that do not transfer the register address byte first, and they just transfer the data. So wiringPiI2CWrite would be the correct function to write to a device like PCF8574 that has no registers, or directly using i2c_smbus_access() to write a block of bytes in one API call. \$\endgroup\$
    – Justme
    Jul 30, 2020 at 19:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.