In this post I use the TWI module of ATMega644P to interfaces with an MCP23017 GPIO expansion that able to control a character LCD and perform keypad scanning. However the SPI version MCP23S17 could do the same things like the TWI version MCP23017.
Using SPI the master MCU needs to four I/O pins unlike that TWI that use only two pins. However SPI offers a high speed data communication between master MCU and its slave devices.
MCP23S17 HD44780 LCD Driving
A single 8-bit port of this chip can control an HD44780 character LCD using LCD 4-bit data transfer mode similar to a direct control from an MCU output port.
| Schematic |
Source Code "main.c":
- /*
- * 12-spi_mcp23s17_1602_kb.c
- *
- * Created: 2/17/2026 8:50:48 PM
- * Author : Admin
- */
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- const char MCP23X17_W=0x40;
- const char MCP23X17_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
- };
- #define DDR_SPI DDRB
- #define PRT_SPI PORTB
- #define DD_MOSI 5
- #define DD_MISO 6
- #define DD_SCK 7
- #define DD_SS 4
- void SPI_MasterInit(void)
- {
- /* Set MOSI and SCK output, all others input */
- DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
- /* Enable SPI, Master, set clock rate fck/16 */
- SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
- }
- void SPI_MasterTransmit(char cData)
- {
- /* Start transmission */
- SPDR = cData;
- /* Wait for transmission complete */
- while(!(SPSR & (1<<SPIF)))
- ;
- }
- void SPI_SlaveInit(void)
- {
- /* Set MISO output, all others input */
- DDR_SPI = (1<<DD_MISO);
- /* Enable SPI */
- SPCR = (1<<SPE);
- }
- char SPI_SlaveReceive(void)
- {
- /* Wait for reception complete */
- while(!(SPSR & (1<<SPIF)))
- ;
- /* Return Data Register */
- return SPDR;
- }
- void mcp23s17_transmit(char address, char data){
- PRT_SPI&=~(1<<DD_SS);
- SPI_MasterTransmit(MCP23X17_W);
- SPI_MasterTransmit(address);
- SPI_MasterTransmit(data);
- PRT_SPI|=(1<<DD_SS);
- }
- char mcp23s17_receive(char address){
- PRT_SPI&=~(1<<DD_SS);
- SPI_MasterTransmit(MCP23X17_R);
- SPI_MasterTransmit(address);
- SPI_MasterTransmit(0x00);
- char data=SPI_SlaveReceive();
- PRT_SPI|=(1<<DD_SS);
- return data;
- }
- /*HD44780 LCD Section*/
- const char RS=0,RW=1,EN=2;
- char BLK_ON=0;
- const char LCD_PORT = OLATA;
- const char LCD_DIR = IODIRA;
- void lcd_command(uint8_t temp){
- uint8_t command,led;
- command=temp&0xF0;
- if(BLK_ON==1) led=0x08;
- else led=0;
- mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
- _delay_us(10);
- mcp23s17_transmit(LCD_PORT,command|led);
- _delay_us(25);
- command=temp<<4;
- mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
- _delay_us(10);
- mcp23s17_transmit(LCD_PORT,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;
- mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
- _delay_us(10);
- mcp23s17_transmit(LCD_PORT,data|led|(1<<RS));
- _delay_us(25);
- data=temp<<4;
- mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
- _delay_us(10);
- mcp23s17_transmit(LCD_PORT,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_line_1(void){
- lcd_command(0x80);
- }
- void lcd_line_2(void){
- lcd_command(0xC0);
- }
- 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;
- mcp23s17_transmit(LCD_DIR,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 */
- SPI_MasterInit();
- PRT_SPI|=(1<<DD_SS); //Set CS Pin High
- lcd_init();
- lcd_text("ATMega644P SPI");
- lcd_line_2();
- lcd_text("MCP23S17 GPIO");
- while (1)
- {
- }
- }
We can control a larger display for instance a 20x4 character LCD with additional display address modification.
MCP23S17 HD44780 LCD and Matrix Keypad
There is one I/O port left, the GPB. So I added a 4x4 matrix keypad to this port. The result of found key scanning will show on a 16x2 character LCD connects to GPA.
| MCP23S17 HD44780 LCD and Matrix Keypad |
Source Code "main.c":
- /*
- * lcd_keyboard.c
- *
- * Created: 2/17/2026 11:07:07 PM
- * Author : Admin
- */
- #include <stdio.h>
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- /*SPI Setting*/
- #define DDR_SPI DDRB
- #define PRT_SPI PORTB
- #define DD_MOSI 5
- #define DD_MISO 6
- #define DD_SCK 7
- #define DD_SS 4
- void SPI_MasterInit(void)
- {
- /* Set MOSI and SCK output, all others input */
- DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
- /* Enable SPI, Master, set clock rate fck/16 */
- SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
- }
- void SPI_MasterTransmit(char cData)
- {
- /* Start transmission */
- SPDR = cData;
- /* Wait for transmission complete */
- while(!(SPSR & (1<<SPIF)))
- ;
- }
- void SPI_SlaveInit(void)
- {
- /* Set MISO output, all others input */
- DDR_SPI = (1<<DD_MISO);
- /* Enable SPI */
- SPCR = (1<<SPE);
- }
- char SPI_SlaveReceive(void)
- {
- /* Wait for reception complete */
- while(!(SPSR & (1<<SPIF)))
- ;
- /* Return Data Register */
- return SPDR;
- }
- /*MCP23S17 Setting*/
- const char MCP23X17_W=0x40;
- const char MCP23X17_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 mcp23s17_transmit(char address, char data){
- PRT_SPI&=~(1<<DD_SS);
- SPI_MasterTransmit(MCP23X17_W);
- SPI_MasterTransmit(address);
- SPI_MasterTransmit(data);
- PRT_SPI|=(1<<DD_SS);
- }
- char mcp23s17_receive(char address){
- PRT_SPI&=~(1<<DD_SS);
- SPI_MasterTransmit(MCP23X17_R);
- SPI_MasterTransmit(address);
- SPI_MasterTransmit(0x00);
- char data=SPI_SlaveReceive();
- PRT_SPI|=(1<<DD_SS);
- return data;
- }
- /*HD44780 LCD Section*/
- const char RS=0,RW=1,EN=2;
- char BLK_ON=0;
- const char LCD_PORT = OLATA;
- const char LCD_DIR = IODIRA;
- void lcd_command(uint8_t temp){
- uint8_t command,led;
- command=temp&0xF0;
- if(BLK_ON==1) led=0x08;
- else led=0;
- mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
- mcp23s17_transmit(LCD_PORT,command|led);
- command=temp<<4;
- mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
- mcp23s17_transmit(LCD_PORT,command|led);
- }
- void lcd_data(uint8_t temp){
- uint8_t data,led;
- data=temp&0xF0;
- if(BLK_ON==1) led=0x08;
- else led=0;
- mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
- mcp23s17_transmit(LCD_PORT,data|led|(1<<RS));
- data=temp<<4;
- mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
- mcp23s17_transmit(LCD_PORT,data|led|(1<<RS));
- }
- void lcd_xy(uint8_t x, uint8_t y){
- uint8_t cursor[]={0x80,0xC0};
- lcd_command(cursor[y-1]+x-1);
- }
- void lcd_line_1(void){
- lcd_command(0x80);
- }
- void lcd_line_2(void){
- lcd_command(0xC0);
- }
- 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;
- mcp23s17_transmit(LCD_DIR,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);
- }
- const char key_16[4][4]={'1','2','3','A',
- '4','5','6','B',
- '7','8','9','C',
- '*','0','#','D'};
- char key_scan(void){
- char data=0,temp,key;
- for(char i=0;i<4;i++){
- data=0xFF;
- data&=~(1<<i);
- mcp23s17_transmit(OLATB,data);
- _delay_ms(5);
- data=mcp23s17_receive(GPIOB);
- 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 key_init(void){
- mcp23s17_transmit(IODIRB,0xF0);
- mcp23s17_transmit(GPPUB,0xF0);
- }
- int main(void)
- {
- /* Replace with your application code */
- SPI_MasterInit();
- PRT_SPI|=(1<<DD_SS);
- lcd_init();
- key_init();
- lcd_line_1(); lcd_text("ATMega644P SPI");
- lcd_line_2(); lcd_text("MCP23S17 LCD");
- char temp,charCount=0,newLine=0,line=1;
- _delay_ms(5000);
- lcd_clear();
- while (1)
- {
- temp=key_scan();
- 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;
- }
- }
- }
- }
For a full tutorial list of ATMega644P using C in Microchip Studio IDE please see this page.


