728x90

728x90

Tuesday, February 17, 2026

ATMega644P SPI and MCP23S17 LCD Matrix Keypad

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.

ATMega644P SPI and MCP23S17 LCD Martix Keypad
Schematic

 Source Code "main.c":

  1. /*
  2. * 12-spi_mcp23s17_1602_kb.c
  3. *
  4. * Created: 2/17/2026 8:50:48 PM
  5. * Author : Admin
  6. */

  7. #include <avr/io.h>
  8. #include <util/delay.h>
  9. #define F_CPU 16000000UL

  10. const char MCP23X17_W=0x40;
  11. const char MCP23X17_R=0x41;

  12. //IOCON.BANK=0
  13. enum BANK0{
  14. IODIRA=0,IODIRB,IPOLA,IPOLB,GPINTENA,GPINTENB,DEFVALA,
  15. DEFVALB,INTCONA,INTCONB,IOCON1,IOCON2,GPPUA,GPPUB,
  16. INTFA,INTFB,INTCAPA,INTCAPB,GPIOA,GPIOB,OLATA,OLATB
  17. };

  18. #define DDR_SPI DDRB
  19. #define PRT_SPI PORTB
  20. #define DD_MOSI 5
  21. #define DD_MISO 6
  22. #define DD_SCK 7
  23. #define DD_SS 4

  24. void SPI_MasterInit(void)
  25. {
  26. /* Set MOSI and SCK output, all others input */
  27. DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
  28. /* Enable SPI, Master, set clock rate fck/16 */
  29. SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
  30. }

  31. void SPI_MasterTransmit(char cData)
  32. {
  33. /* Start transmission */
  34. SPDR = cData;
  35. /* Wait for transmission complete */
  36. while(!(SPSR & (1<<SPIF)))
  37. ;
  38. }

  39. void SPI_SlaveInit(void)
  40. {
  41. /* Set MISO output, all others input */
  42. DDR_SPI = (1<<DD_MISO);
  43. /* Enable SPI */
  44. SPCR = (1<<SPE);
  45. }
  46. char SPI_SlaveReceive(void)
  47. {
  48. /* Wait for reception complete */
  49. while(!(SPSR & (1<<SPIF)))
  50. ;
  51. /* Return Data Register */
  52. return SPDR;
  53. }

  54. void mcp23s17_transmit(char address, char data){
  55. PRT_SPI&=~(1<<DD_SS);
  56. SPI_MasterTransmit(MCP23X17_W);
  57. SPI_MasterTransmit(address);
  58. SPI_MasterTransmit(data);
  59. PRT_SPI|=(1<<DD_SS);
  60. }

  61. char mcp23s17_receive(char address){
  62. PRT_SPI&=~(1<<DD_SS);
  63. SPI_MasterTransmit(MCP23X17_R);
  64. SPI_MasterTransmit(address);
  65. SPI_MasterTransmit(0x00);
  66. char data=SPI_SlaveReceive();
  67. PRT_SPI|=(1<<DD_SS);
  68. return data;
  69. }

  70. /*HD44780 LCD Section*/
  71. const char RS=0,RW=1,EN=2;
  72. char BLK_ON=0;

  73. const char LCD_PORT = OLATA;
  74. const char LCD_DIR = IODIRA;

  75. void lcd_command(uint8_t temp){
  76. uint8_t command,led;
  77. command=temp&0xF0;
  78. if(BLK_ON==1) led=0x08;
  79. else led=0;
  80. mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
  81. _delay_us(10);
  82. mcp23s17_transmit(LCD_PORT,command|led);
  83. _delay_us(25);
  84. command=temp<<4;
  85. mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
  86. _delay_us(10);
  87. mcp23s17_transmit(LCD_PORT,command|led);
  88. _delay_us(25);
  89. }

  90. void lcd_data(uint8_t temp){
  91. uint8_t data,led;
  92. data=temp&0xF0;
  93. if(BLK_ON==1) led=0x08;
  94. else led=0;
  95. mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
  96. _delay_us(10);
  97. mcp23s17_transmit(LCD_PORT,data|led|(1<<RS));
  98. _delay_us(25);
  99. data=temp<<4;
  100. mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
  101. _delay_us(10);
  102. mcp23s17_transmit(LCD_PORT,data|led|(1<<RS));
  103. _delay_us(25);
  104. }

  105. void lcd_xy(uint8_t x, uint8_t y){
  106. uint8_t cursor[]={0x80,0xC0};
  107. lcd_command(cursor[y-1]+x-1);
  108. }

  109. void lcd_line_1(void){
  110. lcd_command(0x80);
  111. }

  112. void lcd_line_2(void){
  113. lcd_command(0xC0);
  114. }

  115. void lcd_text(uint8_t *text){
  116. while(*text) lcd_data(*text++);
  117. }

  118. void lcd_clear(void){
  119. lcd_command(0x01);
  120. _delay_ms(5);
  121. }

  122. void lcd_init(void){
  123. BLK_ON=1;
  124. mcp23s17_transmit(LCD_DIR,0x00);
  125. lcd_command(0x33);
  126. _delay_us(10);
  127. lcd_command(0x32);
  128. _delay_us(10);
  129. lcd_command(0x28);
  130. _delay_us(10);
  131. lcd_command(0x0F);
  132. _delay_us(10);
  133. lcd_command(0x01);
  134. _delay_ms(5);
  135. lcd_command(0x06);
  136. _delay_us(10);
  137. }
  138. int main(void)
  139. {
  140. /* Replace with your application code */
  141. SPI_MasterInit();
  142. PRT_SPI|=(1<<DD_SS); //Set CS Pin High
  143. lcd_init();
  144. lcd_text("ATMega644P SPI");
  145. lcd_line_2();
  146. lcd_text("MCP23S17 GPIO");
  147. while (1)
  148. {
  149. }
  150. }




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.

ATMega644P SPI and MCP23S17 LCD Martix Keypad
MCP23S17 HD44780 LCD and Matrix Keypad

 Source Code "main.c":

  1. /*
  2. * lcd_keyboard.c
  3. *
  4. * Created: 2/17/2026 11:07:07 PM
  5. * Author : Admin
  6. */

  7. #include <stdio.h>
  8. #include <avr/io.h>
  9. #include <util/delay.h>
  10. #define F_CPU 16000000UL

  11. /*SPI Setting*/

  12. #define DDR_SPI DDRB
  13. #define PRT_SPI PORTB
  14. #define DD_MOSI 5
  15. #define DD_MISO 6
  16. #define DD_SCK 7
  17. #define DD_SS 4

  18. void SPI_MasterInit(void)
  19. {
  20. /* Set MOSI and SCK output, all others input */
  21. DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
  22. /* Enable SPI, Master, set clock rate fck/16 */
  23. SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
  24. }

  25. void SPI_MasterTransmit(char cData)
  26. {
  27. /* Start transmission */
  28. SPDR = cData;
  29. /* Wait for transmission complete */
  30. while(!(SPSR & (1<<SPIF)))
  31. ;
  32. }

  33. void SPI_SlaveInit(void)
  34. {
  35. /* Set MISO output, all others input */
  36. DDR_SPI = (1<<DD_MISO);
  37. /* Enable SPI */
  38. SPCR = (1<<SPE);
  39. }
  40. char SPI_SlaveReceive(void)
  41. {
  42. /* Wait for reception complete */
  43. while(!(SPSR & (1<<SPIF)))
  44. ;
  45. /* Return Data Register */
  46. return SPDR;
  47. }

  48. /*MCP23S17 Setting*/
  49. const char MCP23X17_W=0x40;
  50. const char MCP23X17_R=0x41;

  51. //IOCON.BANK=0
  52. enum BANK0{
  53. IODIRA=0,IODIRB,IPOLA,IPOLB,GPINTENA,GPINTENB,DEFVALA,
  54. DEFVALB,INTCONA,INTCONB,IOCON1,IOCON2,GPPUA,GPPUB,
  55. INTFA,INTFB,INTCAPA,INTCAPB,GPIOA,GPIOB,OLATA,OLATB
  56. };

  57. void mcp23s17_transmit(char address, char data){
  58. PRT_SPI&=~(1<<DD_SS);
  59. SPI_MasterTransmit(MCP23X17_W);
  60. SPI_MasterTransmit(address);
  61. SPI_MasterTransmit(data);
  62. PRT_SPI|=(1<<DD_SS);
  63. }

  64. char mcp23s17_receive(char address){
  65. PRT_SPI&=~(1<<DD_SS);
  66. SPI_MasterTransmit(MCP23X17_R);
  67. SPI_MasterTransmit(address);
  68. SPI_MasterTransmit(0x00);
  69. char data=SPI_SlaveReceive();
  70. PRT_SPI|=(1<<DD_SS);
  71. return data;
  72. }


  73. /*HD44780 LCD Section*/
  74. const char RS=0,RW=1,EN=2;
  75. char BLK_ON=0;

  76. const char LCD_PORT = OLATA;
  77. const char LCD_DIR = IODIRA;

  78. void lcd_command(uint8_t temp){
  79. uint8_t command,led;
  80. command=temp&0xF0;
  81. if(BLK_ON==1) led=0x08;
  82. else led=0;
  83. mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
  84. mcp23s17_transmit(LCD_PORT,command|led);
  85. command=temp<<4;
  86. mcp23s17_transmit(LCD_PORT,command|led|(1<<EN));
  87. mcp23s17_transmit(LCD_PORT,command|led);
  88. }

  89. void lcd_data(uint8_t temp){
  90. uint8_t data,led;
  91. data=temp&0xF0;
  92. if(BLK_ON==1) led=0x08;
  93. else led=0;
  94. mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
  95. mcp23s17_transmit(LCD_PORT,data|led|(1<<RS));
  96. data=temp<<4;
  97. mcp23s17_transmit(LCD_PORT,data|led|(1<<EN)|(1<<RS));
  98. mcp23s17_transmit(LCD_PORT,data|led|(1<<RS));
  99. }

  100. void lcd_xy(uint8_t x, uint8_t y){
  101. uint8_t cursor[]={0x80,0xC0};
  102. lcd_command(cursor[y-1]+x-1);
  103. }

  104. void lcd_line_1(void){
  105. lcd_command(0x80);
  106. }

  107. void lcd_line_2(void){
  108. lcd_command(0xC0);
  109. }

  110. void lcd_text(uint8_t *text){
  111. while(*text) lcd_data(*text++);
  112. }

  113. void lcd_clear(void){
  114. lcd_command(0x01);
  115. _delay_ms(5);
  116. }

  117. void lcd_init(void){
  118. BLK_ON=1;
  119. mcp23s17_transmit(LCD_DIR,0x00);
  120. lcd_command(0x33);
  121. _delay_us(10);
  122. lcd_command(0x32);
  123. _delay_us(10);
  124. lcd_command(0x28);
  125. _delay_us(10);
  126. lcd_command(0x0F);
  127. _delay_us(10);
  128. lcd_command(0x01);
  129. _delay_ms(5);
  130. lcd_command(0x06);
  131. _delay_us(10);
  132. }

  133. const char key_16[4][4]={'1','2','3','A',
  134. '4','5','6','B',
  135. '7','8','9','C',
  136. '*','0','#','D'};

  137. char key_scan(void){
  138. char data=0,temp,key;
  139. for(char i=0;i<4;i++){
  140. data=0xFF;
  141. data&=~(1<<i);
  142. mcp23s17_transmit(OLATB,data);
  143. _delay_ms(5);
  144. data=mcp23s17_receive(GPIOB);
  145. data&=0xF0;
  146. if((data&0x10)==0) {temp=key_16[i][0]; break;}
  147. else if((data&0x20)==0){temp=key_16[i][1]; break;}
  148. else if((data&0x40)==0){temp=key_16[i][2]; break;}
  149. else if((data&0x80)==0){temp=key_16[i][3]; break;}
  150. else temp=0;
  151. _delay_ms(10);
  152. }
  153. return temp;
  154. }

  155. void key_init(void){
  156. mcp23s17_transmit(IODIRB,0xF0);
  157. mcp23s17_transmit(GPPUB,0xF0);
  158. }

  159. int main(void)
  160. {
  161. /* Replace with your application code */
  162. SPI_MasterInit();
  163. PRT_SPI|=(1<<DD_SS);
  164. lcd_init();
  165. key_init();
  166. lcd_line_1(); lcd_text("ATMega644P SPI");
  167. lcd_line_2(); lcd_text("MCP23S17 LCD");
  168. char temp,charCount=0,newLine=0,line=1;
  169. _delay_ms(5000);
  170. lcd_clear();
  171. while (1)
  172. {
  173. temp=key_scan();
  174. if(temp!=0){
  175. lcd_data(temp);
  176. charCount++;
  177. _delay_ms(500);
  178. }
  179. if(charCount>16){
  180. newLine=1;
  181. charCount=0;
  182. line+=1;
  183. }
  184. if(newLine){
  185. newLine=0;
  186. if(line==2) lcd_xy(1,2);
  187. else{
  188. lcd_xy(1,1);
  189. lcd_command(0x01);
  190. _delay_ms(5);
  191. line=1;
  192. }
  193. }
  194. }
  195. }


For a full tutorial list of ATMega644P using C in Microchip Studio IDE please see this page.

 

 

 

ATMega644P SPI SN74HC595N TC1604A LCD

The SN74HC595N is an output expansion chip that could drive any devices, LED, relay or even writing data to LCD. A character LCD is easily controlled by this chip in write mode. A single chip SN74HC595N can control the HD44780 using its 4-bit data transfer mode.

ATMega644P SPI SN74HC595N TC1604A LCD 

 

 ATMega644P SPI SN74HC595N TC1604A LCD

In this example the ATMega644P master SPI send HD44780 LCD data and command to the LCD via an SN74HC595N shift registers chip.

ATMega644P SPI SN74HC595N TC1604A LCD
A finished Assembling. SN74HC595N is soldered on-board. A 16x4 LCD stays at the top.

 

ATMega644P SPI SN74HC595N TC1604A LCD
Soldering side

 

ATMega644P SPI SN74HC595N TC1604A LCD
Schematic Diagram

 The original post uses Arduino Uno to control this LCD module.

Source Code "main.c":

  1. /*
  2. * 12-spi_74hc595_1604.c
  3. *
  4. * Created: 2/17/2026 3:27:49 PM
  5. * Author : Admin
  6. */

  7. #include <stdio.h>
  8. #include <avr/io.h>
  9. #include <util/delay.h>
  10. #define F_CPU 16000000UL

  11. #define DDR_SPI DDRB
  12. #define PRT_SPI PORTB
  13. #define DD_MOSI 5
  14. #define DD_MISO 6
  15. #define DD_SCK 7
  16. #define DD_SS 4

  17. void SPI_MasterInit(void)
  18. {
  19. /* Set MOSI and SCK output, all others input */
  20. DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
  21. /* Enable SPI, Master, set clock rate fck/16
  22. data is sample at the falling edge of SCK*/
  23. SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
  24. }

  25. void SPI_MasterTransmit(char cData)
  26. {
  27. /* Start transmission */
  28. SPDR = cData;
  29. /* Wait for transmission complete */
  30. while(!(SPSR & (1<<SPIF)))
  31. ;
  32. }

  33. void SPI_SlaveInit(void)
  34. {
  35. /* Set MISO output, all others input */
  36. DDR_SPI = (1<<DD_MISO);
  37. /* Enable SPI */
  38. SPCR = (1<<SPE);
  39. }

  40. char SPI_SlaveReceive(void)
  41. {
  42. /* Wait for reception complete */
  43. while(!(SPSR & (1<<SPIF)))
  44. ;
  45. /* Return Data Register */
  46. return SPDR;
  47. }

  48. /*TWI LCD Driver*/
  49. #define RS 1
  50. #define RW 2
  51. #define EN 3
  52. #define BL 0


  53. char backLight=0;

  54. void lcd_delay(unsigned int counts){
  55. for (unsigned int i=0;i<counts;i++);
  56. }

  57. void spi_lcd_command(char command){
  58. char data;
  59. data=command&0xF0;
  60. SPI_MasterTransmit(data|(backLight<<BL)|(1<<EN));
  61. PRT_SPI&=~(1<<DD_SS);
  62. PRT_SPI|=(1<<DD_SS);
  63. SPI_MasterTransmit(data|(backLight<<BL));
  64. PRT_SPI&=~(1<<DD_SS);
  65. PRT_SPI|=(1<<DD_SS);
  66. data=command<<4;
  67. SPI_MasterTransmit(data|(backLight<<BL)|(1<<EN));
  68. PRT_SPI&=~(1<<DD_SS);
  69. lcd_delay(10);
  70. PRT_SPI|=(1<<DD_SS);
  71. SPI_MasterTransmit(data|(backLight<<BL));
  72. PRT_SPI&=~(1<<DD_SS);
  73. PRT_SPI|=(1<<DD_SS);
  74. }

  75. void spi_lcd_data(char command){
  76. char data;
  77. data=command&0xF0;
  78. SPI_MasterTransmit(data|(backLight<<BL)|(1<<EN)|(1<<RS));
  79. PRT_SPI&=~(1<<DD_SS);
  80. PRT_SPI|=(1<<DD_SS);
  81. SPI_MasterTransmit(data|(backLight<<BL)|(1<<RS));
  82. PRT_SPI&=~(1<<DD_SS);
  83. PRT_SPI|=(1<<DD_SS);
  84. lcd_delay(50);
  85. data=command<<4;
  86. SPI_MasterTransmit(data|(backLight<<BL)|(1<<EN)|(1<<RS));
  87. PRT_SPI&=~(1<<DD_SS);
  88. PRT_SPI|=(1<<DD_SS);
  89. SPI_MasterTransmit(data|(backLight<<BL)|(1<<RS));
  90. PRT_SPI&=~(1<<DD_SS);
  91. PRT_SPI|=(1<<DD_SS);
  92. }

  93. void spi_lcd_xy(int8_t x, int8_t y){
  94. //16x2
  95. //char addr[]={0x80,0xC0};
  96. //16x4
  97. char addr[]={0x80,0xC0,0x90,0xD0};
  98. spi_lcd_command(addr[y-1]+x-1);
  99. }

  100. void spi_lcd_line_1(void){
  101. spi_lcd_command(0x80);
  102. }

  103. void spi_lcd_line_2(void){
  104. spi_lcd_command(0xC0);
  105. }

  106. void spi_lcd_line_3(void){
  107. spi_lcd_command(0x90);
  108. }

  109. void spi_lcd_line_4(void){
  110. spi_lcd_command(0xD0);
  111. }

  112. void spi_lcd_cursor_off(void){
  113. spi_lcd_command(0x0C);
  114. }

  115. void spi_lcd_text(char *txt){
  116. while(*txt) spi_lcd_data(*txt++);
  117. }

  118. void i2c_lcdClear(void){
  119. spi_lcd_command(0x01);
  120. _delay_ms(10);
  121. }

  122. void spi_lcd_init(void){
  123. SPI_MasterTransmit(0);
  124. lcd_delay(500);
  125. spi_lcd_command(0x33);
  126. lcd_delay(10);
  127. spi_lcd_command(0x32);
  128. lcd_delay(10);
  129. spi_lcd_command(0x28);
  130. lcd_delay(10);
  131. spi_lcd_command(0x0F);
  132. lcd_delay(10);
  133. spi_lcd_command(0x01);
  134. _delay_ms(10);
  135. spi_lcd_command(0x06);
  136. lcd_delay(10);
  137. }

  138. int main(void)
  139. {
  140. /* Replace with your application code */
  141. _delay_ms(1000);
  142. SPI_MasterInit();
  143. PRT_SPI|=(1<<DD_SS); //Set CS Pin High
  144. spi_lcd_init();
  145. spi_lcd_line_1();
  146. spi_lcd_text("ATMega644P SPI");
  147. spi_lcd_line_2();
  148. spi_lcd_text("SN74HC595N LCD");
  149. spi_lcd_line_3();
  150. spi_lcd_text("TC1604A-01(R)");
  151. spi_lcd_line_4();
  152. spi_lcd_text("Example Using C");
  153. unsigned char data[7],msg[16];
  154. while (1)
  155. {
  156. }
  157. }





AVR Hardware Experiment:

ATMega644P SPI SN74HC595N TC1604A LCD
Tested ATMega644P

For a full tutorial list of ATMega644P using C in Microchip Studio IDE please see this page.


 

 

320x50

Search This Blog

tyro-728x90