Skip to content

Add Example Code for 4x4 Keypad with Raspberry Pi Pico #542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,4 @@ add_subdirectory(usb)
add_subdirectory(watchdog)
add_subdirectory(sha)
add_subdirectory(freertos)
add_subdirectory(keypad)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ App|Description
[hello_7segment](gpio/hello_7segment) | Use the GPIOs to drive a seven segment LED display.
[hello_gpio_irq](gpio/hello_gpio_irq) | Register an interrupt handler to run when a GPIO is toggled.
[dht_sensor](gpio/dht_sensor) | Use GPIO to bitbang the serial protocol for a DHT temperature/humidity sensor.
[keypad](keypad) | Use GPIO to get key pressed on a 4x4 Keypad.

See also: [blink](blink), blinking an LED attached to a GPIO.

Expand Down
21 changes: 21 additions & 0 deletions keypad/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
set(TARGET_NAME keypad)
add_executable(${TARGET_NAME}
keypad.c
)

# pull in common dependencies
target_link_libraries(keypad pico_stdlib)

if (PICO_CYW43_SUPPORTED)
target_link_libraries(${TARGET_NAME} pico_cyw43_arch_none)
endif()

# Enable printing via USB serial
pico_enable_stdio_usb(${TARGET_NAME} 1)
pico_enable_stdio_uart(${TARGET_NAME} 0)

# create map/bin/hex file etc.
pico_add_extra_outputs(${TARGET_NAME})

# add url via pico_set_program_url
example_auto_set_url(${TARGET_NAME})
96 changes: 96 additions & 0 deletions keypad/keypad.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#include <stdio.h>
#include "pico/stdlib.h"


#define KEYPAD_NUM_ROWS 4
#define KEYPAD_NUM_COLUMNS 4
#define NO_KEY_PRESSED '\0' // Just a character that unlikely be
// present in keyboards, so it can by used
// to determine if no key is pressed.
#define READ_MS_DELAY 10 // Delay between lectures to avoid noise


const uint8_t keypad_rows_pins[KEYPAD_NUM_ROWS] = {
10,
11,
12,
13,
};


const uint8_t keypad_columns_pins[KEYPAD_NUM_COLUMNS] = {
18, // GP18
19, // GP19
20, // GP20
21, // GP21
};


const char keypad_keys[KEYPAD_NUM_ROWS][KEYPAD_NUM_COLUMNS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'},
};


void pico_keypad_init(void){
// Initialize GPIOs
//Initialize row pins as GPIO_OUT
for (int i=0; i < KEYPAD_NUM_ROWS; i++){
uint8_t pin_number = keypad_rows_pins[i];
gpio_init(pin_number);
gpio_set_dir(pin_number, GPIO_OUT);
gpio_put(pin_number, 0); // Make sure GPIO_OUT is LOW
}
//Initialize column pins as GPIO_IN
for (int i=0; i < KEYPAD_NUM_COLUMNS; i++){
uint8_t pin_number = keypad_columns_pins[i];
gpio_init(pin_number);
gpio_set_dir(pin_number, GPIO_IN);
}
}


char get_keypad_value(void){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's worth trying to detect if multiple keys are being pressed (and signal that as an error condition), rather than just assuming that the first key detected is the only key pressed? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about the same but allow multiple keys pressed at the same time, but did not find an "simple" way of doing this ...

This is what I have in mind, let me know what you think about this.

Enhanced Keypad changes

  1. Define a Macro to Switch Between Simple and Enhanced Keypad Modes
    Define a macro that allows switching between the basic single key read mode and the enhanced mode, which supports multiple key presses
// Allow reading multiple keys at the same time.
#define ENHANCED_KEYPAD
  1. Use an #ifdef Directive to Define MAX_MULTI_KEYS and KeypadResult
    When ENHANCED_KEYPAD is defined, the code uses a structure to hold the result of the key press. The MAX_MULTI_KEYS defines the maximum number of keys that can be pressed at the same time, and KeypadResult is a structure that contains the count of pressed keys and an array of those keys.
#ifdef ENHANCED_KEYPAD
/*
 * When using the 'Enhanced Keypad' the get_keypad_result function does not
 * return a single character but a KeypadResult instead which includes the
 * number of pressed keys (Up to MAX_MULTI_KEYS) and a list of the pressed keys
 * (Up to MAX_MULTI_KEYS).
 *
 */
#define MAX_MULTI_KEYS            6  // Maximum number of pressed keys at the
                                     // same time.

typedef struct {
    int count;                   // Number of pressed keys
    char keys[MAX_MULTI_KEYS];   // Pressed Keys
} KeypadResult;
#endif  //ENHANCED_KEYPAD
  1. Use an #ifdef Directive to Choose Between the Simple and Enhanced Function Implementations
    Depending on whether the ENHANCED_KEYPAD macro is defined, the code will either use the simple function that returns a single pressed key or the enhanced function that returns a KeypadResult structure. The enhanced function iterates through the rows and columns of the keypad matrix, checking each key, and saves the number of keys pressed and their values in the KeypadResult structure.
#ifdef ENHANCED_KEYPAD
KeypadResult get_keypad_result(void) {
    KeypadResult result;        // Define the result structure.
    result.count = 0;           // Initialize count to 0.

    // Iterate over key and rows to identify which key(s) are pressed.
    for (int row=0; row < KEYPAD_NUM_ROWS; row++) {
        gpio_put(keypad_rows_pins[row], 1);
        for (int column=0; column < KEYPAD_NUM_COLUMNS; column++) {
            sleep_ms(READ_MS_DELAY);
            if(gpio_get(keypad_columns_pins[column])) {
                // If the column pin is HIGH, a key is pressed.
                // Save the key in the KeypadResult structure and increase
                // count.
                result.keys[result.count] = keypad_keys[row][column];
                result.count++;
            }
        }
        gpio_put(keypad_rows_pins[row], 0);
    }

    // Return the structure containing all pressed keys.
    return result;
}
#else
char get_keypad_result(void) {
    // Iterate over key and rows to identify which key is pressed.
    // When iterating rows, the GPIO_OUT associated to the row needs to be set
    // to HIGH, and then iterate the columns to determine the GPIO_IN.
    for (int row=0; row < KEYPAD_NUM_ROWS; row++) {
        gpio_put(keypad_rows_pins[row], 1);
        for (int column=0; column < KEYPAD_NUM_COLUMNS; column++) {
            sleep_ms(READ_MS_DELAY);
            if(gpio_get(keypad_columns_pins[column])) {
                // If the column pin is HIGH, a key is pressed.
                // Put the row pin to low and return the pressed key.
                gpio_put(keypad_rows_pins[row], 0);
                return keypad_keys[row][column];
            }
        }
        gpio_put(keypad_rows_pins[row], 0);
    }

    // Return a constant indicating no key was pressed
    return NO_KEY_PRESSED;
}
#endif  //ENHANCED_KEYPAD
  1. Modification to the main Method
    The main method is modified to handle the enhanced keypad mode. When ENHANCED_KEYPAD is defined, the program will print the list of keys pressed. If no keys are pressed, it will print a message indicating that. Otherwise, it will print the number of keys pressed and list the keys in a comma-separated format.
    while (true) {
        #ifdef ENHANCED_KEYPAD
        // Call the enhanced function to get the result structure
        // containing the number of keys pressed and the keys themselves.
        KeypadResult result = get_keypad_result();

        // Check if no keys are pressed.
        if (result.count == 0) {
            // Inform the user that no keys are pressed.
            printf("No key pressed\n");
        }
        else{
            // If one or more keys are pressed, print the number of pressed keys.
            printf("Bang!!! '%d' key(s) are pressed. Keys: ", result.count);

            // Iterate through the pressed keys and print them.
            for (int i = 0; i < result.count; i++) {
                printf("%c", result.keys[i]);

                //  If it's not the last key, print a comma separator.
                if (i != (result.count - 1)) {
                    printf(", ");
                } else {
                    // If it's the last key, move to the next line.
                    printf("\n");
                }
            }
        }
        #else
        // Call the simple function to get a single pressed key.
        char key = get_keypad_result();

        // Check if no key is pressed.
        if (key == NO_KEY_PRESSED) {
            // Inform the user that no keys are pressed.
            printf("No key pressed\n");
        }
        else{
            // If a key is pressed, print the key.
            printf("Key '%c' pressed\n", key);
        }
        #endif
        sleep_ms(500);

Summary of Changes:

  1. Enhanced Keypad Mode:

    • Introduced a new mode via the ENHANCED_KEYPAD macro, allowing the detection of multiple key presses simultaneously.
    • Defined MAX_MULTI_KEYS to specify the maximum number of keys that can be pressed at once.
    • Created a KeypadResult structure to store the count and list of pressed keys.
  2. Enhanced get_keypad_result Function:

    • Modified the function to return a KeypadResult structure when ENHANCED_KEYPAD is defined.
    • Iterated through the keypad matrix to detect multiple key presses and store them in the KeypadResult structure.
    • Implemented conditional compilation to maintain the original single key detection functionality when ENHANCED_KEYPAD is not defined.
  3. Updated main Method:

    • Modified the main method to handle the enhanced keypad mode:
      • If no keys are pressed, the program prints "No key pressed".
      • If multiple keys are pressed, the program prints the number of keys and lists them in a comma-separated format.
    • Ensured backward compatibility by retaining the original behavior for single key presses when ENHANCED_KEYPAD is not defined.
  4. Improved Code Documentation:

    • Added detailed comments throughout the code to explain the purpose of each change.
    • Documented the new structure and function behavior for both the simple and enhanced keypad modes.

For a raw diff of the changes, you can view this link.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let me know if you agree with this implementation. I have created the README.adoc but this is including the 'Enhanced Keypad`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that actually work? I thought that for these simple matrix keypads, if more than one key is pressed at a time you can't actually reliably detect which keys have been pressed? 🤷
That's why I suggested that if you detect that more than one key has been pressed, you return this as an error-condition.
(although I'm more of a software-person than a hardware-person, so I might be wrong?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this method is also working.

A keypad is a matrix of buttons arranged in rows and columns, typically used in devices like old phones or security systems. Each button on the keypad connects a unique row and column. When a key is pressed, it completes a circuit between a specific row and column, which can be detected by a microcontroller like the Raspberry Pi Pico.

To detect which key is pressed, the microcontroller sequentially sets each row to a high voltage (1) and checks the voltage on each column. If pressing a key connects the active row to a column, the microcontroller detects the column as high (1). By knowing which row and column are connected, the microcontroller can determine which key was pressed.

I went ahead and merged this enhanced code. I think it is a more complete example.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this method is also working.

I'm afraid I'm still not convinced, see e.g. https://arduino.stackexchange.com/questions/50869/4x4-matrix-keypad-multiple-inputs-cause-extra-readings

// Iterate over key and rows to identify which key is pressed.
// When iterating rows, the GPIO_OUT associted to the row needs to be set
// to HIGH, and then iterate the columns to determine the GPIO_IN.
for (int row=0; row < KEYPAD_NUM_ROWS; row++){
gpio_put(keypad_rows_pins[row], 1);
for (int column=0; column < KEYPAD_NUM_COLUMNS; column++){
sleep_ms(READ_MS_DELAY);
if(gpio_get(keypad_columns_pins[column])){
// If the pin is HIGH, this means this is a matching row and
// column, so we put the row pin to LOW and return the pressed
// key by using the bidimensional array keypad_keys.
gpio_put(keypad_rows_pins[row], 0);
return keypad_keys[row][column];
}
}
gpio_put(keypad_rows_pins[row], 0);
}
return NO_KEY_PRESSED;
}


int main() {
stdio_init_all();
pico_keypad_init();
while (true) {
char key = get_keypad_value();
if (key == NO_KEY_PRESSED){
printf("No key pressed\n");
}
else{
printf("Key '%c' pressed\n", key);
}
sleep_ms(500);
}
}