Using a matrix keypad could save the I/O port of a micro-controller. For instant a 4x4 matrix keypad yields different 16 key values. It requires only 8 pins of micro-controller or one I/O port only. We can program the keypad scanning from scratch using C language.The program just divides the 8-bit port into two nibbles, one for output scanning and one for input key press detecting.
A matrix keypad can be made from scratch by using tactile switches and breadboard soldering without hazardous chemical etching. However there are many types of matrix keypad that made from tactile switches or even membrane switches. They are available at very low cost.
![]() |
| 16 Key Membrane Switch Keypad 4X4 3X4 Matrix Keyboard For Arduino DIY Kit |
In a simple introductory example, I just make a keypad scanning routine that uses PortD. Whenever any key press is founded the key value will display on a single seven-segment connects to PortA.
- /*
- * 7-keyapd_4x4_7.c
- *
- * Created: 1/28/2026 7:29:49 PM
- * Author : Admin
- */
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- /*
- const char key_16[][4]={'1','2','3','A',
- '4','5','6','B',
- '7','8','9','C',
- '*','0','#','D'};
- */
- const char key_16[][4]={7,8,9,15,
- 4,5,6,14,
- 1,2,3,13,
- 10,0,11,12};
- const uint8_t cc_7[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
- 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
- volatile char keyScan(void){
- char data=0,temp,key;
- for(uint8_t i=0;i<4;i++){
- data=0xFF;
- data&=~(1<<i);
- PORTB=data;
- _delay_ms(5);
- data=PINB;
- 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=0xFF;
- _delay_ms(10);
- }
- return temp;
- }
- int main(void)
- {
- /* Replace with your application code */
- DDRB=0x0F;
- PINB=0x0F;
- DDRD=0xFF;
- char temp;
- while (1)
- {
- temp=keyScan();
- if(temp!=0xFF){
- PORTD=cc_7[temp];
- _delay_ms(250);
- }
- }
- }
I just tested this example in software simulator since I don't want wire additional seven-segment display.
![]() |
| Schematic and Simulation Program |
I will use the real hardware on next example.
However we can use other micro-controller chips or even additional interface chips.
- ATMega32 interfaces to keypad and display
- ATMega32 LCD and Keypad Interfacing Example
- AT89C52 Matrix Keypad Interfacing in C using Keil
- AT89C52 Character LCD and Keypad Interfacing Using C
- PIC16F887 TM1637 Display and Key Scan Example using XC8
- PIC16F887 KeyPad and Character LCD Example using XC8
- PIC16F887 PCF8574AP I2C 4x4 KeyPad using XC8
- PIC16F887 MCP23017 Key Pad and 7-Segment Display Example using XC8
- PIC16F887 MCP23017 LCD and Keypad Interfacing using XC8
- PIC16F887 SPI MCP23S17 Character LCD and KeyPad XC8 Example
Here I added the LCD driver to this example program since it's already placed on-board.
- /*
- * 7-keypad_4x4_lcd.c
- *
- * Created: 1/28/2026 6:20:48 PM
- * Author : Admin
- */
- #include <avr/io.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- #include "hd44780.c"
- const char key_16[4][4]={'1','2','3','A',
- '4','5','6','B',
- '7','8','9','C',
- '*','0','#','D'};
- /*
- const char key_16[4][4]={'7','8','9','/',
- '4','5','6','X',
- '1','2','3','-',
- '*','0','=','+'};
- */
- volatile char keyScan(void){
- char data=0,temp,key;
- for(uint8_t i=0;i<4;i++){
- data=0xFF;
- data&=~(1<<i);
- PORTB=data;
- _delay_ms(5);
- data=PINB;
- 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 */
- DDRB=0x0F;
- PINB=0x0F;
- lcd_init();
- lcd_text("ATMega644P LCD");
- lcd_xy(1,2);
- lcd_text("4x4 Key Pad");
- _delay_ms(10000);
- lcd_clear();
- 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_command(0x01);
- _delay_ms(5);
- line=1;
- }
- }
- }
- }
It requires the "hd44780.c" that created in previous post.
hd44780.c
- /*
- * hd44780.c
- *
- * Created: 1/28/2026 9:55:18 PM
- * Author: Admin
- */
- #include "hd44780.h"
- void lcd_command(char command){
- char temp;
- temp=command&0xF0;
- PORTC=temp|(1<<EN);
- _delay_us(d_time);
- PORTC=temp;
- _delay_us(d_time);
- temp=command<<4;
- PORTC=temp|(1<<EN);
- _delay_us(d_time);
- PORTC=temp;
- _delay_us(d_time);
- }
- void lcd_data(char data){
- char temp;
- temp=data&0xF0;
- PORTC=temp|(1<<EN)|(1<<RS);
- _delay_us(d_time);
- PORTC=temp|(1<<RS);
- _delay_us(d_time);
- temp=data<<4;
- PORTC=temp|(1<<EN)|(1<<RS);
- _delay_us(d_time);
- PORTC=temp|(1<<RS);
- _delay_us(d_time);
- }
- void lcd_init(void){
- DDRC=0xFF;
- lcd_command(0x33);
- _delay_us(100);
- lcd_command(0x32);
- _delay_us(100);
- lcd_command(0x28);
- _delay_us(100);
- lcd_command(0x0F);
- _delay_us(100);
- lcd_command(0x01);
- _delay_ms(5);
- lcd_command(0x06);
- _delay_us(100);
- }
- void lcd_xy(char x, char y){
- char addr[]={0x80,0xC0};
- lcd_command(addr[y-1]+x-1);
- }
- void lcd_text(char *text){
- while(*text) lcd_data(*text++);
- }
- void lcd_clear(void){
- lcd_command(0x01);
- _delay_ms(5);
- }
hd44780.h
- /*
- * hd44780.h
- *
- * Created: 1/28/2026 9:56:39 PM
- * Author: Admin
- */
- #ifndef HD44780_H_
- #define HD44780_H_
- #endif /* HD44780_H_ */
- #include <avr/io.h>
- #include <stdio.h>
- #include <util/delay.h>
- #define F_CPU 16000000UL
- #define RS 2
- #define EN 3
- const char d_time=50;
- extern void lcd_command(char command);
- extern void lcd_data(char data);
- extern void lcd_init(void);
- extern void lcd_xy(char x, char y);
- extern void lcd_text(char *text);
- extern void lcd_clear(void);
The key press counts is auto-increment and it will start a new line whenever it reaches 16 character counts.
In Proteus it work slower than in real hardware since I add a 500ms delay time.
However on my AVR Prototype Board (PCBWay Offer) it work fine.
This PCB was offered from PCBWay.
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 |

%20Pin%20Out.jpg)
















