Original keypad characters displayed on the 8051 LCD project, 4x4 keypad scanning, AT89C51 interface, Proteus LCD output

Interfacing of Keypad with 8051 Microcontroller in Proteus

2.5K Views
40
700
60
25
60
PCBWay

Hello friends! Today, we are going to interface a 4×4 keypad with an 8051 microcontroller in Proteus. We will read the pressed key through the AT89C51 and display its character on a 20×4 LCD, so we can see exactly what the program receives.

This project builds on our LCD interfacing tutorial. The display remains our output device, while the keypad becomes an input device. Together, they form the basic user interface for a calculator, menu, password entry system or small control panel.

I will explain the keypad matrix before discussing the program. We will then add debouncing, prevent one held button from producing repeated characters and keep typed text within the upper two LCD rows. The original simulation, images and download are preserved for reference.

Original keypad characters displayed on the 8051 LCD project, 4x4 keypad scanning, AT89C51 interface, Proteus LCD output
Figure: Original keypad characters displayed on the 8051 LCD project.

How a Matrix Keypad Works

A 4×4 keypad contains sixteen switches arranged at the intersections of four row wires and four column wires. Pressing a switch connects one row to one column. The microcontroller identifies that intersection and translates it into a character.

Without a matrix, sixteen separate switches might require sixteen input lines plus a shared connection. A four-row, four-column matrix uses eight signal lines. The saving comes at the cost of scanning: the program must examine the rows in sequence to discover which intersection is closed.

The keypad does not normally send the character '7' by itself. It closes a contact. Our software assigns the character 7 to a particular row and column. That means the printed key legends, connector order and lookup table must agree.

Microchip's matrix keypad application note discusses scanning and debouncing on AVR devices. The electrical principle also applies here, although the AT89C51 port configuration and program syntax are different.

The Key Layout Used in This Project

The pictured keypad has a calculator layout. Its first row contains 7, 8, 9 and division; the bottom row contains clear, zero, equals and addition. It is a sixteen-key keypad, not a twelve-key telephone keypad.

RowColumn 1Column 2Column 3Column 4
A789/
B456x
C123-
DC0=+

For example, pressing 1 closes row C and column 1 in this layout. It does not close the first row and first column. That distinction matters when diagnosing a keypad whose characters appear consistently shifted.

In this lesson, the clear key is represented by the letter C and is displayed like any other character. Its special role as a calculator clear command belongs to the later application. Separating key detection from application meaning makes the scanner reusable.

Components and Circuit Connections

We use an AT89C51, a 4×4 matrix keypad, a 20×4 character LCD, the oscillator and reset components, and the required power connections. The firmware is written for Keil C51, while Proteus provides the circuit simulation.

Components for the 8051 matrix keypad interface, 4x4 keypad scanning, AT89C51 interface, Proteus LCD output
Figure: Components for the 8051 matrix keypad interface.

The original circuit is shown below. Port 1 is dedicated to the keypad, Port 2 carries the LCD data bus, and two Port 3 pins control the LCD. Moving the LCD control signals away from Port 1 frees all eight keypad connections.

AT89C51 keypad and 20x4 LCD circuit with pin mapping, 4x4 keypad scanning, AT89C51 interface, Proteus LCD output
Figure: AT89C51 keypad and 20x4 LCD circuit with pin mapping.

SignalAT89C51 connection
Keypad rows A, B, C and DP1.0, P1.1, P1.2 and P1.3
Keypad columns 1, 2, 3 and 4P1.4, P1.5, P1.6 and P1.7
LCD D0 through D7P2.0 through P2.7
LCD RSP3.7
LCD EP3.6
LCD R/WGround
Processor clock11.0592 MHz in this example

Keep the LCD control-pin change in mind when reusing code from the preceding tutorial. That tutorial used P1.0 and P1.1 for RS and E. Leaving those declarations unchanged would make LCD writes interfere with keypad rows.

The historical reset-button drawing also needs the correction discussed in the LCD lesson: manual reset must assert the AT89C51's active-high RST input, rather than shorting it to ground. For physical hardware, provide appropriate decoupling, a valid oscillator network and adjustable LCD contrast.

Scanning One Row at a Time

We hold the column inputs high through the port's pull-up behavior and select one row by driving it low. If a key in that row is pressed, its column reads low. We then release that row and repeat the process for the next one.

On classic Port 1, writing a one releases the pin for input use with its internal pull-up behavior. Writing zero drives it low. The column bits therefore need ones in their output latches before they can be read as inputs. This is different from configuring a separate direction register on another microcontroller family.

The AT89C51 datasheet describes that port behavior. If you move the scanner to a device with push-pull outputs, use a suitable released or high-impedance state for inactive rows rather than blindly copying the same assumptions.

  1. Release all rows and columns.
  2. Drive row A low and allow the signals to settle.
  3. Read all four columns and record any closed intersections.
  4. Repeat for rows B, C and D.
  5. Release the port again before returning the scan result.

A complete scan gives us a position, not yet a confirmed key event. That distinction allows us to handle contact bounce and ambiguous multiple-key input before printing anything.

Why Debouncing Is Necessary

A physical switch can make and break contact several times during one press or release. A fast program may interpret those transitions as several separate actions. The idealized button in a simulation can hide this problem, so the firmware should not depend on perfectly clean contacts.

Our example requires the same scan result across several consecutive samples. It then reports one press and waits for a stable released state before allowing another press. Holding a key therefore produces one character, not a stream of repeated copies.

The sample count and loop interval together determine the debounce interval. Five equal samples separated by roughly five milliseconds span roughly twenty milliseconds from the first to the fifth sample. Display updates and software overhead can lengthen that time; this is an interactive teaching example, not a strict real-time sampling schedule.

What about several keys at once?

A diode-free matrix can have ambiguous current paths when several switches are pressed. The problem is often called ghosting. Software should not promise arbitrary multi-key recognition when the hardware cannot distinguish every combination.

This example accepts one key at a time. If a scan finds more than one closed intersection, it suppresses new events until all keys have been released and the release has passed debouncing. That is a deliberate input policy, not full multi-key rollover support.

Preparing the LCD Driver

Use the Timer0 delay and LCD functions from the preceding LCD article. Keep delay_ms, lcd_init, lcd_command, lcd_data, lcd_position and lcd_text, together with their supporting declarations and functions.

Replace its two LCD pin declarations with the following declarations, and remove its original main function. The application block below supplies the new main function. Keep only one definition of each function in the project.

sbit LCD_RS = P3^7;
sbit LCD_E = P3^6;

The driver is still configured for an eight-bit, write-only 20×4 interface at 11.0592 MHz. No keypad code should change Timer0 or Port 2 while that driver uses them. If you split the program into separate C files, provide function declarations in a header instead of duplicating the driver.

Keypad Scanner and Display Application

Add the following application code after the LCD driver in the same C file. It uses the driver's register header, no-operation intrinsic and delay functions. The raw scanner returns zero for no key, 1 through 16 for one intersection, and 0xFF for multiple detections.

unsigned char code row_pattern[4] = {0xFE, 0xFD, 0xFB, 0xF7};
char code key_map[16] = {
    '7', '8', '9', '/',
    '4', '5', '6', 'x',
    '1', '2', '3', '-',
    'C', '0', '=', '+'
};

unsigned char scan_keypad(void)
{
    unsigned char row, column, columns;
    unsigned char count = 0;
    unsigned char found = 0;

    for (row = 0; row < 4; row++) {
        P1 = row_pattern[row];
        _nop_(); _nop_(); _nop_(); _nop_();
        columns = (P1 >> 4) & 0x0F;
        for (column = 0; column < 4; column++) {
            if ((columns & (1u << column)) == 0) {
                count++;
                found = row * 4 + column + 1;
            }
        }
    }
    P1 = 0xFF;
    if (count > 1) return 0xFF;
    return found;
}

char keypad_event(void)
{
    static unsigned char candidate = 0;
    static unsigned char stable_samples = 0;
    static bit held = 0;
    unsigned char raw = scan_keypad();

    if (raw == 0xFF) held = 1;
    if (raw != candidate) {
        candidate = raw;
        stable_samples = 1;
    } else if (stable_samples < 5) {
        stable_samples++;
    }

    if (stable_samples < 5) return 0;
    if (candidate == 0) {
        held = 0;
    } else if (candidate != 0xFF && held == 0) {
        held = 1;
        return key_map[candidate - 1];
    }
    return 0;
}

void clear_input_rows(void)
{
    lcd_text(0, 0, "                    ");
    lcd_text(1, 0, "                    ");
}

void main(void)
{
    unsigned char position = 0;
    char key;

    P1 = 0xFF;
    lcd_init();
    lcd_text(2, 1, "www.TheEngineering");
    lcd_text(3, 4, "Projects.com");
    while (1) {
        key = keypad_event();
        if (key != 0) {
            if (position == 40) {
                clear_input_rows();
                position = 0;
            }
            lcd_position(position / 20, position % 20);
            lcd_data(key);
            position++;
        }
        delay_ms(5);
    }
}

Understanding the raw scan result

The row patterns keep the upper four bits high for the column inputs and select exactly one lower bit as low. For example, 0xFE selects row A, while 0xFB selects row C. The scanner checks all rows before deciding whether the result represents a single key.

The lookup index is calculated from the row and column, with one added so zero remains available for “no key.” When a stable press is accepted, subtracting one converts it back into an array index from zero through fifteen.

The character returned for the numeric zero key is '0', which is different from the numerical zero used to mean no event. This prevents a valid zero keypress from disappearing because the application mistakes it for an empty result.

Understanding the debounce state

The candidate records the most recently observed raw result. A changed result starts a new stability count. Only an unchanged result seen enough times can become an accepted press or release.

The held flag prevents repeated events while the same key remains down. Pressing a second key before releasing the first does not automatically create a new character. After an ambiguous scan, release all keys before trying again.

Unlike the earlier blocking wait-for-release loops, the event function returns to the caller on every scan. The main program can therefore continue its normal loop while a key is held. Other blocking work, such as LCD writes, can still delay the next scan and should be considered when extending the application.

Keeping Input within the Intended LCD Area

The top two rows provide forty character positions. The application calculates the row as position / 20 and the column as position % 20. That explicitly moves from the first row to the second after twenty characters.

After forty accepted keys, the next key clears those two input rows and begins a new sequence. The website text on the lower rows remains intact. This avoids relying on the LCD controller's internal address progression, which does not correspond to a simple top-to-bottom screen order.

The screen is an observation tool in this lesson. It displays the keypad's mapped characters without performing arithmetic. Seeing 2+3= confirms the input sequence, but no result is calculated until we add calculator logic in the next project.

Running the Proteus Simulation

  1. Build the combined LCD driver and keypad application in Keil C51.
  2. Enable HEX-file generation and load the resulting file into the AT89C51.
  3. Set the processor clock to 11.0592 MHz.
  4. Confirm the Port 1 row/column mapping and the LCD's P3.7/P3.6 control connections.
  5. Run the simulation and wait for the initial LCD setup.
  6. Press and release individual keys, checking the displayed character after each press.

Initial LCD screen in the original keypad simulation, 4x4 keypad scanning, AT89C51 interface, Proteus LCD output
Figure: Initial LCD screen in the original keypad simulation.

The original startup screen places the website text near the bottom of the display. The refreshed example keeps that visual idea while reserving the first two rows for keypad input.

Keypad entries shown on the original 20x4 LCD, 4x4 keypad scanning, AT89C51 interface, Proteus LCD output
Figure: Keypad entries shown on the original 20x4 LCD.

The historical result shows characters appearing as buttons are used. Check every one of the sixteen positions in your own circuit, including the four operator keys and the clear/equals keys. Testing only the digits can leave a row-order or column-order mistake unnoticed.

A Practical Keypad Test Sequence

Start with 7, 8, 9 and division, moving across the first row. Then test the next three rows in order. This produces a predictable pattern and makes a reversed row or column obvious.

Hold one key for a second. The revised application should append one character, not continually repeat it. Release it, wait briefly, and press it again; a second character should now appear. This checks that both press acceptance and release rearming work.

Next, press two keys together, release them and press a single key. The multiple-key state should not be accepted as an ordinary single-key event. Finally, enter more than forty characters to exercise the explicit display rollover behavior.

These are suggested checks for your project, not claims that a newly built physical keypad has already been tested. Real switch bounce and cable capacitance may require different timing from the idealized Proteus model.

Download the Original Project

The original archive remains available below. It contains the earlier project material, whose scanner uses delays and blocking release waits. Preserve it as a reference, and build the revised application separately when comparing the two approaches.

Download Proteus Simulation with Code

Do not mix the old main function with the new one or leave two versions of the LCD driver in the build. Duplicate definitions are a project-organization problem, not a reason to alter the keypad wiring.

Troubleshooting Keypad Input

SymptomLikely area to inspect
No key is detectedColumn latches must be released high; confirm common wiring and program execution
Every key in one row is wrongRow connection order and lookup-table order
A whole column is missingColumn net, connector or port-bit assignment
A held key repeatsDebounce and release-state handling
LCD changes disturb the keypadOld P1 LCD control declarations may still be present
Input overwrites the footerExplicit cursor positioning and forty-character limit

For a physical keypad with an unfamiliar connector, identify row and column connections with a continuity check while the keypad is unpowered. Connector order is not guaranteed to match the visual order of keys or the Proteus component's pin labels.

If the circuit works with short wiring but fails with a long cable, investigate signal settling, interference and pull-up strength. Increasing debounce time cannot fix every electrical problem, especially a consistently incorrect logic level.

Choosing the right debounce behavior

A longer acceptance interval rejects more brief disturbances but also makes the interface feel slower. A very short interval can react quickly while accepting contact bounce as a fresh action. Choose the interval using the actual switches and the fastest deliberate keypress the application should recognize.

Release filtering is equally important. If a brief release bounce immediately rearms the scanner, the same physical press can generate another event when contact closes again. Our stability check therefore applies to the no-key state as well as to a pressed key.

When adding lengthy calculations or display animations, measure the resulting scan interval again. A sample-count filter assumes that scans happen often enough; five samples spread over a second do not provide the same user experience as five samples over a few tens of milliseconds.

Project Review

The matrix approach gives us sixteen inputs using eight microcontroller lines. Separating scanning, debouncing, character mapping and display handling makes each part easier to understand and reuse. It also provides a clean foundation for the calculator project that follows.

The example intentionally supports one key at a time and uses a simple main-loop schedule. A more demanding interface may need diode isolation, a stricter periodic scan, interrupt-driven timing or a dedicated keypad controller. Add those features when the application's actual requirements justify them.

Frequently Asked Questions

Why do the column bits need to be written high?

On the classic AT89C51 Port 1, that releases the pins for input use with their internal pull-up behavior. A zero in the latch would actively hold the pin low.

Does pressing C reset the microcontroller?

No. It is just a mapped key character in this demonstration. A later application can assign it a clear function without electrically resetting the processor.

Can I replace the keypad with a 4×3 model?

Yes, but change the column count, wiring and key map. The sixteen-position lookup table above does not describe a twelve-key keypad.

Why is a key shown only once when held?

The held flag implements one event per press. Automatic repeat would require a separate intentional repeat delay and repeat-rate policy.

Can I send the keys to a serial terminal instead?

Yes. Pass accepted key events to the UART application rather than raw scan results. Check timer ownership and baud-rate settings before combining the two projects.


Comments

1

Join the conversation