The HD44780 based character LCD can be controlled by a micro-controller, digital circuit, an I/O expansion chip or event by hands. In this example I use an output port of the MCP23017 to control a 16x2 LCD in one direction (write only) in 4-bit data transfer mode.
The LCD connect to GPIOB (output register OLATB). That's,
- LCD RS (GPB0)
- LCD R/W (GPB1)
- LCD EN(GPB2)
- D4:D7(GPB4:GPB7)
Source Code: "main.c"
- /*
- * 10-i2c_mcp23017_1602.c
- *
- * Created: 2/5/2026 8:03:45 PM
- * Author : Admin
- */
- #include<stdio.h>
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- //IOCON.BANK=0
- enum BANK0{
- IODIRA=0,IODIRB,IPOLA,IPOLB,GPINTENA,GPINTENB,DEFVALA,
- DEFVALB,INTCONA,INTCONB,IOCON1,IOCON2,GPPUA,GPPUB,
- INTFA,INTFB,INTCAPA,INTCAPB,GPIOA,GPIOB,OLATA,OLATB
- };
- const char RS=0,RW=1,EN=2;
- char BLK_ON=0;
- void lcd_command(uint8_t temp){
- uint8_t command,led;
- command=temp&0xF0;
- if(BLK_ON==1) led=0x08;
- else led=0;
- mcp23017_write(OLATB,command|led|(1<<EN));
- _delay_us(10);
- mcp23017_write(OLATB,command|led);
- _delay_us(25);
- command=temp<<4;
- mcp23017_write(OLATB,command|led|(1<<EN));
- _delay_us(10);
- mcp23017_write(OLATB,command|led);
- _delay_us(25);
- }
- void lcd_data(uint8_t temp){
- uint8_t data,led;
- data=temp&0xF0;
- if(BLK_ON==1) led=0x08;
- else led=0;
- mcp23017_write(OLATB,data|led|(1<<EN)|(1<<RS));
- _delay_us(10);
- mcp23017_write(OLATB,data|led|(1<<RS));
- _delay_us(25);
- data=temp<<4;
- mcp23017_write(OLATB,data|led|(1<<EN)|(1<<RS));
- _delay_us(10);
- mcp23017_write(OLATB,data|led|(1<<RS));
- _delay_us(25);
- }
- void lcd_xy(uint8_t x, uint8_t y){
- uint8_t cursor[]={0x80,0xC0};
- lcd_command(cursor[y-1]+x-1);
- }
- void lcd_text(uint8_t *text){
- while(*text) lcd_data(*text++);
- }
- void lcd_clear(void){
- lcd_command(0x01);
- _delay_ms(5);
- }
- void lcd_init(void){
- BLK_ON=1;
- mcp23017_write(IODIRB,0x00);
- lcd_command(0x33);
- _delay_us(10);
- lcd_command(0x32);
- _delay_us(10);
- lcd_command(0x28);
- _delay_us(10);
- lcd_command(0x0F);
- _delay_us(10);
- lcd_command(0x01);
- _delay_ms(5);
- lcd_command(0x06);
- _delay_us(10);
- }
- int main(void)
- {
- /* Replace with your application code */
- lcd_init();
- lcd_text("MCP23017 TWI");
- lcd_xy(1,2);
- lcd_text("MCP23017 LCD");
- _delay_ms(10000);
- lcd_clear();
- lcd_command(0x0C);
- lcd_text("Counter Value:");
- long counter=0;
- char msg[10];
- while (1)
- {
- sprintf(msg,"%ld",counter);
- lcd_xy(1,2); lcd_text(msg);
- counter++;
- _delay_ms(500);
- }
- }
Source Code: "mcp23017.c"
- /*
- * mcp23017.c
- *
- * Created: 2/5/2026 8:06:38 PM
- * Author: Admin
- */
- const char MCP23017_W=0x40;
- const char MCP23017_R=0x41;
- //IOCON.BANK=0
- /*
- enum BANK0{
- IODIRA=0,IODIRB,IPOLA,IPOLB,GPINTENA,GPINTENB,DEFVALA,
- DEFVALB,INTCONA,INTCONB,IOCON1,IOCON2,GPPUA,GPPUB,
- INTFA,INTFB,INTCAPA,INTCAPB,GPIOA,GPIOB,OLATA,OLATB
- };
- */
- void mcp23017_write(char address,char data){
- twi_start();
- /*Select the write address*/
- twi_write(MCP23017_W);
- /*Select a register address*/
- twi_write(address);
- /*Send configuration data*/
- twi_write(data);
- twi_stop();
- }
- unsigned char mcp23017_read(char address){
- /*Select a specific address*/
- twi_start();
- twi_write(MCP23017_W);
- twi_write(address);
- twi_stop();
- /*Read data from the given address*/
- twi_start();
- twi_write(MCP23017_R);
- unsigned char i2cData=twi_read(1);
- twi_stop();
- return i2cData;
- }
Source Code: "twi.c"
- /*
- * twi.c
- *
- * Created: 2/4/2026 10:22:02 AM
- * Author: Admin
- */
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- void twi_init(void){
- TWSR|=0x00; //Prescaler Selection Bit
- TWBR=0x0F; //Baud Rate Generator
- TWCR=(1<<TWEN); //Enable The TWI Module
- PORTC|=(1<<0);
- PORTC|=(1<<1);
- }
- void twi_start(void){
- TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- }
- void twi_write(unsigned char data){
- TWDR=data;
- TWCR=(1<<TWINT)|(1<<TWEN);
- while((TWCR&(1<<TWINT))==0);
- }
- unsigned char twi_read(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 twi_stop(){
- TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
- _delay_us(10);
- }
Schematic:
AVR Prototype Board:
| AVR Experiment Board |
For PIC micro-controller using PIC16F887 is a good choice.
This PCB was offered by PCBWay (pcbway.com).
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 |
There is one port left, GPIOA. So I added a 4x4 matrix keypad at GPIOA. The result of keypad scanning will show on the LCD.
Source Code:
- /*
- * 10-i2c_mcp23017_1602_kb.c
- *
- * Created: 2/5/2026 10:32:40 PM
- * Author : Admin
- */
- #include<stdio.h>
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- //IOCON.BANK=0
- enum BANK0{
- IODIRA=0,IODIRB,IPOLA,IPOLB,GPINTENA,GPINTENB,DEFVALA,
- DEFVALB,INTCONA,INTCONB,IOCON1,IOCON2,GPPUA,GPPUB,
- INTFA,INTFB,INTCAPA,INTCAPB,GPIOA,GPIOB,OLATA,OLATB
- };
- const char RS=0,RW=1,EN=2;
- char BLK_ON=0;
- const char key_16[4][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);
- mcp23017_write(OLATA,data);
- _delay_ms(5);
- data=mcp23017_read(GPIOA);
- 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;
- }
- void lcd_command(uint8_t temp){
- uint8_t command,led;
- command=temp&0xF0;
- if(BLK_ON==1) led=0x08;
- else led=0;
- mcp23017_write(OLATB,command|led|(1<<EN));
- _delay_us(10);
- mcp23017_write(OLATB,command|led);
- _delay_us(25);
- command=temp<<4;
- mcp23017_write(OLATB,command|led|(1<<EN));
- _delay_us(10);
- mcp23017_write(OLATB,command|led);
- _delay_us(25);
- }
- void lcd_data(uint8_t temp){
- uint8_t data,led;
- data=temp&0xF0;
- if(BLK_ON==1) led=0x08;
- else led=0;
- mcp23017_write(OLATB,data|led|(1<<EN)|(1<<RS));
- _delay_us(10);
- mcp23017_write(OLATB,data|led|(1<<RS));
- _delay_us(25);
- data=temp<<4;
- mcp23017_write(OLATB,data|led|(1<<EN)|(1<<RS));
- _delay_us(10);
- mcp23017_write(OLATB,data|led|(1<<RS));
- _delay_us(25);
- }
- void lcd_xy(uint8_t x, uint8_t y){
- uint8_t cursor[]={0x80,0xC0};
- lcd_command(cursor[y-1]+x-1);
- }
- void lcd_text(uint8_t *text){
- while(*text) lcd_data(*text++);
- }
- void lcd_clear(void){
- lcd_command(0x01);
- _delay_ms(5);
- }
- void lcd_init(void){
- BLK_ON=1;
- mcp23017_write(IODIRB,0x00);
- lcd_command(0x33);
- _delay_us(10);
- lcd_command(0x32);
- _delay_us(10);
- lcd_command(0x28);
- _delay_us(10);
- lcd_command(0x0F);
- _delay_us(10);
- lcd_command(0x01);
- _delay_ms(5);
- lcd_command(0x06);
- _delay_us(10);
- }
- int main(void)
- {
- /* Replace with your application code */
- lcd_init();
- lcd_text("MCP23017 TWI");
- lcd_xy(1,2);
- lcd_text("LCD and Key Pad");
- _delay_ms(10000);
- lcd_clear();
- //Key Pad Init
- mcp23017_write(IODIRA,0xF0);
- mcp23017_write(GPPUA,0xF0);
- char temp,charCount=0,newLine=0,line=1;
- while (1)
- {
- temp=keyScan();
- if(temp!=0){
- lcd_data(temp);
- charCount++;
- _delay_ms(500);
- }
- if(charCount>16){
- newLine=1;
- charCount=0;
- line+=1;
- }
- if(newLine){
- newLine=0;
- if(line==2) lcd_xy(1,2);
- else{
- lcd_xy(1,1);
- lcd_command(0x01);
- _delay_ms(5);
- line=1;
- }
- }
- }
- }
Program Simulation:
| Program Simulation |
AVR Prototype Board:


No comments:
Post a Comment