The PCF8574/PCF8574A series can interface to many parallel port I/O devices such as matrix keypad or even an HD44780 character LCD. Its 8-bit port can control the HD44780-based character LCD using the controller 4-bit data transfer mode without any auxiliary chip. Even the display is a one-line, two-line or 4-line display we can set its DD RAM via LCD command.
PCF8574T 16x2 Character LCD
In this introductory example I use a popular Arduino PCF8574T character LCD interface module with the ATMega644P TWI to control a 16x2 character LCD.
| A sample picture |
| Running Program on my ATMega644P Experiment Board |
The I2C default address of this module is 0x4E since the A2:A0 pins are pulled high. For the Arduino IDE its TWI address is 0x27. I draw and make my own DIY using the PCF8574AP. Its schematic is shown below.
![]() |
| Schematic Diagram |
However it works the same function except its I2C slave address. For more information about this DIY module please visit this blog.
Source Code: "main.c"
- /*
- * 10-i2c_pcf8574T_1602.c
- *
- * Created: 2/3/2026 11:05:19 AM
- * Author : Admin
- */
- #include <stdio.h>
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- /*TWI LCD Driver*/
- #define RS 0
- #define RW 1
- #define EN 2
- #define BL 3
- char backLight=0;
- void i2c_lcdCommand(uint8_t command){
- uint8_t data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- }
- void i2c_lcdData(uint8_t command){
- uint8_t data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- _delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- //_delay_us(50);
- }
- void i2c_lcdXY(int8_t x, int8_t y){
- int8_t addr[]={0x80,0xC0};
- i2c_lcdCommand(addr[y-1]+x-1);
- }
- void i2c_lcdText(int8_t *txt){
- while(*txt) i2c_lcdData(*txt++);
- }
- void i2c_lcdClear(void){
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- }
- void i2c_lcdInit(void){
- i2cInit();
- //_delay_us(10);
- pcf8574Write(0);
- //_delay_ms(10);
- i2c_lcdCommand(0x33);
- //_delay_us(10);
- i2c_lcdCommand(0x32);
- //_delay_us(10);
- i2c_lcdCommand(0x28);
- //_delay_us(10);
- i2c_lcdCommand(0x0F);
- //_delay_us(10);
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- i2c_lcdCommand(0x06);
- //_delay_us(10);
- }
- int main(void)
- {
- /* Replace with your application code */
- _delay_ms(1000);
- backLight=1;
- i2c_lcdInit();
- i2c_lcdText("ATMega644P I2C");
- i2c_lcdXY(1,2);
- i2c_lcdText(" PCF8574AP LCD");
- long counter=0;
- uint8_t msg[10];
- _delay_ms(10000);
- i2c_lcdClear();
- i2c_lcdCommand(0x0C);
- i2c_lcdText("Counter:");
- while (1)
- {
- sprintf(msg,"%u",counter);
- i2c_lcdXY(1,2);
- i2c_lcdText(msg);
- counter++;
- _delay_ms(500);
- }
- }
I putted the pcf8574.c in a a separated file that's needed to copy and add in project source folder. But it's not include in the main C source file.
The "pcf8574.c":
- /*
- * pcf8574.c
- *
- * Created: 2/3/2026 11:06:53 AM
- * Author: Admin
- */
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- //DIY PCF8574AP LCD Module (A2:A0=0)
- const char pcf8574_w=0x40;
- const char pcf8574_r=0x41;
- //PCF8574 (A2:A0=0)
- const char pcf8574a_w=0x70;
- const char pcf8574a_r=0x71;
- //DIY Arduino PCF8574T LCD Module
- const char pcf8574T_LCD_W=0x4E;
- const char pcf8574T_LCD_R=0x4F;
- void i2cInit(void){
- TWSR|=0x00; //Prescaler Selection Bit
- TWBR=0xF0; //Baud Rate Generator
- TWCR=(1<<TWEN); //Enable The TWI Module
- PORTC|=(1<<0);
- PORTC|=(1<<1);
- }
- void i2cStart(void){
- TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- }
- void i2cWrite(unsigned char data){
- TWDR=data;
- TWCR=(1<<TWINT)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- }
- unsigned char i2cRead(char ACK){
- if(ACK==0)
- TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWEA);
- else
- TWCR=(1<<TWINT)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- return TWDR;
- }
- void i2cStop(){
- TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
- _delay_us(10);
- }
- void pcf8574Write(char data){
- i2cStart();
- i2cWrite(pcf8574T_LCD_W);
- i2cWrite(data);
- i2cStop();
- }
- char pcf8574Read(void){
- i2cStart();
- i2cWrite(pcf8574T_LCD_R);
- char temp=i2cRead(1);
- i2cStop();
- return temp;
- }
Schematic:
| Schematic Diagram and Simulation |
| Simulating Program |
ATMega644 Experiment Board:
| ATMega644 Experiment Board |
| ATMega644 Experiment Board |
PCF8574T 16x4 LCD
Now I replace with a TC1604A-01(R) I posses that is a 16x4 character LCD. It almost identical to the previous one's.
| TC1604A-01(R) MODULE OUTLINE DRAWING |
Its display data RAM address is listed below.
Source Code "main.c":
- /*
- * 10-i2c_pcf8574T_1604.c
- *
- * Created: 2/3/2026 3:01:44 PM
- * Author : Admin
- */
- #include <stdio.h>
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- /*TWI LCD Driver*/
- #define RS 0
- #define RW 1
- #define EN 2
- #define BL 3
- char backLight=0;
- void i2c_lcdCommand(char command){
- char data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- }
- void i2c_lcdData(char command){
- char data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- _delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- //_delay_us(50);
- }
- void i2c_lcdXY(int8_t x, int8_t y){
- //16x2
- //char addr[]={0x80,0xC0};
- //16x4
- char addr[]={0x80,0xC0,0x90,0xD0};
- i2c_lcdCommand(addr[y-1]+x-1);
- }
- void i2c_lcdText(int8_t *txt){
- while(*txt) i2c_lcdData(*txt++);
- }
- void i2c_lcdClear(void){
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- }
- void i2c_lcdInit(void){
- i2cInit();
- //_delay_us(10);
- pcf8574Write(0);
- //_delay_ms(10);
- i2c_lcdCommand(0x33);
- //_delay_us(10);
- i2c_lcdCommand(0x32);
- //_delay_us(10);
- i2c_lcdCommand(0x28);
- //_delay_us(10);
- i2c_lcdCommand(0x0F);
- //_delay_us(10);
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- i2c_lcdCommand(0x06);
- //_delay_us(10);
- }
- int main(void)
- {
- /* Replace with your application code */
- _delay_ms(1000);
- backLight=1;
- i2c_lcdInit();
- i2c_lcdText("ATMega644P TWI");
- i2c_lcdXY(1,2);
- i2c_lcdText("PCF8574T LCD");
- i2c_lcdXY(1,3);
- i2c_lcdText("TC1604A-01(R)");
- i2c_lcdXY(1,4);
- i2c_lcdText("16x4 Lines LCD");
- while (1)
- {
- }
- }
Schematic:
| Schematic |
ATMega644 Prototype Board:
PCF8574T TC1604A-01(R) 16x4 LCD |
PCF8574T 20x4 LCD
A 20x4 character LCD is needed in any application that need to display a large amount of data. However a 4-line display is far more expensive than a conventional 16x2 LCD.
| A Sample Image of a PCF8574T 2004A LCD |
To display any text on this LCD correctly we need to modify the Display Data RAM (DD RAM) map. They are different depends on the manufacturer data sheet. Most of newer LCD(s) use modern controllers. However the conventional HD4478 LCD controller its DD RAM map for the 20x4 LCD is,
- Line 1: 0x80
- Line 2: 0xC0
- Line 3: 0x94
- Line 4: 0xD4
However I don't have this LCD in my lab. In Proteus there is a model for 20x4 LCD and PCF8574-based I2C LCD. The I2C 20x4 LCD allow us to set its frequency, I2C slave address, and line number addresses.
| Proteus Set the I2C Address to 0x4E |
Source Code:
- /*
- * 10-i2c_pcf8574T_2004A.c
- *
- * Created: 2/3/2026 6:47:50 PM
- * Author : Admin
- */
- #include <stdio.h>
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- /*TWI LCD Driver*/
- #define RS 0
- #define RW 1
- #define EN 2
- #define BL 3
- char backLight=0;
- void i2c_lcdCommand(char command){
- char data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- }
- void i2c_lcdData(char command){
- char data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- _delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- //_delay_us(50);
- }
- void i2c_lcdXY(int8_t x, int8_t y){
- //16x2
- //char addr[]={0x80,0xC0};
- //16x4
- //char addr[]={0x80,0xC0,0x90,0xD0};
- //20x4
- char addr[]={0x80,0xC0,0x94,0xD4};
- i2c_lcdCommand(addr[y-1]+x-1);
- }
- void i2c_lcdText(int8_t *txt){
- while(*txt) i2c_lcdData(*txt++);
- }
- void i2c_lcdClear(void){
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- }
- void i2c_lcdInit(void){
- i2cInit();
- //_delay_us(10);
- pcf8574Write(0);
- //_delay_ms(10);
- i2c_lcdCommand(0x33);
- //_delay_us(10);
- i2c_lcdCommand(0x32);
- //_delay_us(10);
- i2c_lcdCommand(0x28);
- //_delay_us(10);
- i2c_lcdCommand(0x0F);
- //_delay_us(10);
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- i2c_lcdCommand(0x06);
- //_delay_us(10);
- }
- int main(void)
- {
- /* Replace with your application code */
- _delay_ms(1000);
- backLight=1;
- i2c_lcdInit();
- i2c_lcdText("ATMega644P PCF8574T");
- i2c_lcdXY(1,2);
- i2c_lcdText("20x4 Character LCD");
- i2c_lcdXY(1,3);
- i2c_lcdText("AVR TWI Example");
- i2c_lcdXY(1,4);
- i2c_lcdText("Microchip Studio IDE");
- while (1)
- {
- }
- }
Schematic and Simulation:
TWI LCD and KeyPad
Fortunately I have these two chips, PCF8574T (LCD) and PCF8574AP(DIP). So I'm making an I2C LCD and I2C 4x4 matrix keypad that connects to a single TWI bus.
| Schematic and Simulation |
| KeyPad Scanning |
Source Code
The main.c:
- /*
- * i2c-lcd_keypad.c
- *
- * Created: 2/3/2026 8:23:40 PM
- * Author : Admin
- */
- #include <stdio.h>
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- /*TWI LCD Driver*/
- #define RS 0
- #define RW 1
- #define EN 2
- #define BL 3
- char backLight=0;
- void i2c_lcdCommand(uint8_t command){
- uint8_t data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL));
- //_delay_us(50);
- }
- void i2c_lcdData(uint8_t command){
- uint8_t data;
- data=command&0xF0;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- _delay_us(50);
- data=command<<4;
- pcf8574Write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
- //_delay_us(10);
- pcf8574Write(data|(backLight<<BL)|(1<<RS));
- //_delay_us(50);
- }
- void i2c_lcdXY(int8_t x, int8_t y){
- int8_t addr[]={0x80,0xC0};
- i2c_lcdCommand(addr[y-1]+x-1);
- }
- void i2c_lcdText(int8_t *txt){
- while(*txt) i2c_lcdData(*txt++);
- }
- void i2c_lcdClear(void){
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- }
- void i2c_lcdInit(void){
- i2cInit();
- //_delay_us(10);
- pcf8574Write(0);
- //_delay_ms(10);
- i2c_lcdCommand(0x33);
- //_delay_us(10);
- i2c_lcdCommand(0x32);
- //_delay_us(10);
- i2c_lcdCommand(0x28);
- //_delay_us(10);
- i2c_lcdCommand(0x0F);
- //_delay_us(10);
- i2c_lcdCommand(0x01);
- //_delay_ms(5);
- i2c_lcdCommand(0x06);
- //_delay_us(10);
- }
- char key_16[][4]={'1','2','3','A',
- '4','5','6','B',
- '7','8','9','C',
- '*','0','#','D'};
- char keyScan(void){
- char data=0,temp,key;
- for(char i=0;i<4;i++){
- data=0xFF;
- data&=~(1<<i);
- pcf8574AWrite(data);
- _delay_ms(5);
- data=pcf8574ARead();
- data&=0xF0;
- if((data&0x10)==0) {temp=key_16[i][0]; break;}
- else if((data&0x20)==0){temp=key_16[i][1]; break;}
- else if((data&0x40)==0){temp=key_16[i][2]; break;}
- else if((data&0x80)==0){temp=key_16[i][3]; break;}
- else temp=0;
- _delay_ms(10);
- }
- return temp;
- }
- int main(void)
- {
- /* Replace with your application code */
- _delay_ms(1000);
- backLight=1;
- i2c_lcdInit();
- i2c_lcdText("PCF8574T LCD");
- i2c_lcdXY(1,2);
- i2c_lcdText("PCF8574AP KeyPad");
- long counter=0;
- uint8_t msg[10];
- _delay_ms(10000);
- i2c_lcdClear();
- pcf8574AWrite(0x0F);
- char temp,charCount=0,newLine=0,line=1;
- while (1)
- {
- temp=keyScan();
- if(temp!=0){
- i2c_lcdData(temp);
- charCount++;
- _delay_ms(500);
- }
- if(charCount>16){
- newLine=1;
- charCount=0;
- line+=1;
- }
- if(newLine){
- newLine=0;
- if(line==2) i2c_lcdXY(1,2);
- else{
- i2c_lcdXY(1,1);
- i2c_lcdCommand(0x01);
- _delay_ms(5);
- line=1;
- }
- }
- }
- }
The pcf8574.c
- /*
- * pcf8574.c
- *
- * Created: 2/3/2026 11:06:53 AM
- * Author: Admin
- */
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- //DIY PCF8574AP LCD Module (A2:A0=0)
- const char pcf8574_w=0x40;
- const char pcf8574_r=0x41;
- //PCF8574 (A2:A0=0)
- const char pcf8574a_w=0x70;
- const char pcf8574a_r=0x71;
- //DIY Arduino PCF8574T LCD Module
- const char pcf8574T_LCD_W=0x4E;
- const char pcf8574T_LCD_R=0x4F;
- void i2cInit(void){
- TWSR|=0x00; //Prescaler Selection Bit
- TWBR=0xF0; //Baud Rate Generator
- TWCR=(1<<TWEN); //Enable The TWI Module
- PORTC|=(1<<0);
- PORTC|=(1<<1);
- }
- void i2cStart(void){
- TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- }
- void i2cWrite(unsigned char data){
- TWDR=data;
- TWCR=(1<<TWINT)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- }
- unsigned char i2cRead(char ACK){
- if(ACK==0)
- TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWEA);
- else
- TWCR=(1<<TWINT)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- return TWDR;
- }
- void i2cStop(){
- TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
- _delay_us(10);
- }
- // PCF8574
- void pcf8574Write(char data){
- i2cStart();
- i2cWrite(pcf8574T_LCD_W);
- i2cWrite(data);
- i2cStop();
- }
- char pcf8574Read(void){
- i2cStart();
- i2cWrite(pcf8574T_LCD_R);
- char temp=i2cRead(1);
- i2cStop();
- return temp;
- }
- //PCF8574A
- void pcf8574AWrite(char data){
- i2cStart();
- i2cWrite(pcf8574a_w);
- i2cWrite(data);
- i2cStop();
- }
- char pcf8574ARead(void){
- i2cStart();
- i2cWrite(pcf8574a_r);
- char temp=i2cRead(1);
- i2cStop();
- return temp;
- }
ATMega644P AVR Experiment Board
| ATMega644P PCF8574T LCD PCF8574AP 4x4 Matrix KeyPad |
| ATMega644P PCF8574T LCD PCF8574AP 4x4 Matrix KeyPad |
I have been using PCBWay for many years now. PCBWay fabricate PCBs at low cost, fast processing time for only 24 hours, and fast delivery time using any carrier options. This double side 10cmx10cm can be fabricate at only 5USD for 5 to 10pcs by PCBWay. It's a standard PCB with silk screen and solder mask.
![]() |
| 10 PCBs for only 5USD |
For different size of PCB we can instantly quote on PCBWay website using a zip PCB Gerber file without account.
![]() |
| PCBWay Instant Quote |



No comments:
Post a Comment