element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Smart Security and Surveillance
  • Challenges & Projects
  • Design Challenges
  • Smart Security and Surveillance
  • More
  • Cancel
Smart Security and Surveillance
Forum Identity Protocol - Part 5 - Interfacing a Keypad
  • News
  • Projects
  • Forum
  • DC
  • Leaderboard
  • Files
  • Members
  • More
  • Cancel
  • New
Join Smart Security and Surveillance to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • Replies 4 replies
  • Subscribers 46 subscribers
  • Views 181 views
  • Users 0 members are here
  • security-challenge
  • keypad
  • design-challenge
  • MAX32630FTHR#
Related

Identity Protocol - Part 5 - Interfacing a Keypad

arvindsa
arvindsa 20 days ago

In Part 5, I'll interface an 4x4 Tactile Keypad without using an external library

Recap:

The idea is simple enough: stop making people swipe a card and type a PIN at every single door. Instead, the ID card (a MAX32630FTHR + ATECC508A in your pocket) unlocks once via PIN, then silently does challenge-response crypto over Bluetooth every time you walk up to a door. If the card gets yanked off you, the IMU detects the tug and it locks itself. No PIN, no entry. For more details check the Part 1 of the series

  • Identity Protocol Part 1 - Plan 
  • Identity Protocol - Part 2 - Django Server  
  •  Identity Protocol - Part 3 - Unboxing and Blinking with Maxim LPSDK   
  •  Identity Protocol - Part 4 - BLE using PAN1326B and BTstack  

For my ID Card device i need a way to enter the pin in order to unlock it, I initially thought of a membrane keyboard, but in my box of electronics treasure, I found an tactile keyboard which  i had bought before from Robu: https://robu.in/product/4x4-matrix-16-keyboard-keypad/  and an datasheet attached to it.

image

image

But when i tried with the code, it was not working well, the letter orientation came such that it seemed the rows and columns were transposed. Swapping the pins for Rows and Columns worked. Confusion set it, is my simple enough code wrong? Revelation set in when Adafruit's learning page showed the correct pin out here: https://learn.adafruit.com/matrix-keypad/pinouts . My Code is right, the datasheet was wrong. Here is the correct one

image
Photo Credits: Adafruit  (Do note my code uses C0~C3 instead of C1~C4) and similarly for the Rows

Working of Keypad

The working of keypad is an age old and proven mechanism. Each key is a switch, one side of the switch is wired along the column and the other side is wired along the row. When switch is pressed, it connects the respective column to the row. To detect the key pressed, we wire the 4 Row and 4 Columns pins to GPIO

image
Photo Credits: Adafruit

  1. All the Rows are made input without any pull-ups / pull downs (HiZ - High impedance). The Columns are made input with pull ups
  2. One Row (lets saw R1) is driven low and the 4 columns are read to see which one is low. The column corresponding to the key pressed will be low. 
  3. If no Column is found to be low, the next Row is made low after restoring the previous row to HiZ 
  4. If all rows are scanned and no columns are found low, then it means no key is pressed

When One key is found to be pressed, It is saved in key and last key. During the next round of scan if the same key is found to be still pressed, it is ignored. This way i have implemented a simple hold detection.  This happens until the the scan finds no key pressed, but last key is set, this signals a key release event, and last_key is set as none. a 10ms interval between scans should be sufficient for the de-bouncing delay. If you don't know what de-bouncing is check out https://www.picotech.com/library/articles/blog/what-is-switch-bounce-how-to-implement-debounce

In my implementation of this as a function - keypad_scan() it returns an integer such that the number starts from 0 and increases as keys go from top to bottom and left to right. This way i can quickly compare the pressed key to the last one in one statement, plus also is easier to debug in case a key / row / column is not working

1=0   2=1   3=2   A=3
4=4   5=5   6=6   B=7
7=8   8=9   9=10  C=11
*=12  0=13  #=14  D=15

Flow Chart of working

image

Keypad Wiring 

Signal Pin
KPD_ROW0 P5_0
KPD_ROW1 P5_1
KPD_ROW2 P5_2
KPD_ROW3 P5_3
KPD_COL0 P5_4
KPD_COL1 P5_6
KPD_COL2 P3_2
KPD_COL3 P3_3

The Results

image

This one is in a gif format. Below is the full video (there might be a video sync issue between the screen capture and the video of me pressing the button)

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Source Code


#include <stdio.h>
#include "mxc_errors.h"
#include "gpio.h"
#include "tmr_utils.h"
#include "uart.h"

/* ---- Board_Init override ------------------------------------------------- */
int Board_Init(void) { return E_NO_ERROR; }

/* ---- Keypad configuration ------------------------------------------------ */
#define KEYPAD_ROWS  4
#define KEYPAD_COLS  4

/* Row pins: P5_0, P5_1, P5_2, P5_3 (SPI header pins, no SPI peripheral on ID device) */
static const gpio_cfg_t row_cfg[KEYPAD_ROWS] = {
    { PORT_5, PIN_0, GPIO_FUNC_GPIO, GPIO_PAD_NORMAL },
    { PORT_5, PIN_1, GPIO_FUNC_GPIO, GPIO_PAD_NORMAL },
    { PORT_5, PIN_2, GPIO_FUNC_GPIO, GPIO_PAD_NORMAL },
    { PORT_5, PIN_3, GPIO_FUNC_GPIO, GPIO_PAD_NORMAL },
};

/* Column pins: P5_4, P5_6, P3_2, P3_3 -- input with internal pull-up */
static const gpio_cfg_t col_cfg[KEYPAD_COLS] = {
    { PORT_5, PIN_4, GPIO_FUNC_GPIO, GPIO_PAD_INPUT_PULLUP },
    { PORT_5, PIN_6, GPIO_FUNC_GPIO, GPIO_PAD_INPUT_PULLUP },
    { PORT_3, PIN_2, GPIO_FUNC_GPIO, GPIO_PAD_INPUT_PULLUP },
    { PORT_3, PIN_3, GPIO_FUNC_GPIO, GPIO_PAD_INPUT_PULLUP },
};

#define KEY_NONE  -1   /* no key detected this scan, cant use 0 */

/* Key label lookup: row-major, matches physical layout
 *   Row 0: 1 2 3 A
 *   Row 1: 4 5 6 B
 *   Row 2: 7 8 9 C
 *   Row 3: * 0 # D
 */
static const char key_map[KEYPAD_ROWS][KEYPAD_COLS] = {
    { '1', '2', '3', 'A' },
    { '4', '5', '6', 'B' },
    { '7', '8', '9', 'C' },
    { '*', '0', '#', 'D' },
};

/*
 * keypad_init -- configure all row pins as outputs (high), all col pins as
 * inputs with pull-ups.
 */
static void keypad_init(void)
{
    for (int r = 0; r < KEYPAD_ROWS; r++) {
        GPIO_Config(&row_cfg[r]);
        GPIO_OutSet(&row_cfg[r]);   /* idle high */
    }
    for (int c = 0; c < KEYPAD_COLS; c++) {
        GPIO_Config(&col_cfg[c]);
    }
}

/*
 * keypad_scan -- drive each row low in turn, read columns, restore row.
 * Returns encoded key index (row*4+col) or KEY_NONE.
 *
 * Rows not being scanned are set to input (high-Z) to avoid bus contention
 * when multiple keys are wired to the same column.
 */
static int keypad_scan(void)
{
    for (int r = 0; r < KEYPAD_ROWS; r++) {
        /* Set all rows to input (high-Z) */
        for (int i = 0; i < KEYPAD_ROWS; i++) {
            gpio_cfg_t hi_z = row_cfg[i];
            hi_z.func = GPIO_FUNC_GPIO;
            hi_z.pad  = GPIO_PAD_INPUT;
            GPIO_Config(&hi_z);
        }

        /* Drive the target row low (output) */
        gpio_cfg_t out = row_cfg[r];
        out.func = GPIO_FUNC_GPIO;
        out.pad  = GPIO_PAD_NORMAL;
        GPIO_Config(&out);
        GPIO_OutClr(&out);

        /* Small settling time */
        TMR_Delay(MXC_TMR0, USEC(10));

        /* Read columns */
        for (int c = 0; c < KEYPAD_COLS; c++) {
            if (GPIO_InGet(&col_cfg[c]) == 0) {
                GPIO_OutSet(&out);
                GPIO_Config(&row_cfg[r]);
                GPIO_OutSet(&row_cfg[r]);
                return r * KEYPAD_COLS + c;
            }
        }

        /* Restore row */
        GPIO_OutSet(&out);
        GPIO_Config(&row_cfg[r]);
        GPIO_OutSet(&row_cfg[r]);
    }

    return KEY_NONE;
}

/* ---- Debounce ------------------------------------------------------------ */
/*
 * Two consecutive identical scan results 10 ms apart constitute a confirmed
 * state.  Transitions are only reported on confirmation.
 */
#define DEBOUNCE_MS  10

static int debounce(void)
{
    int first = keypad_scan();
    TMR_Delay(MXC_TMR0, MSEC(DEBOUNCE_MS));
    int second = keypad_scan();
    return (first == second) ? first : KEY_NONE - 1;  /* -2 = unstable */
}

/* ---- Main ---------------------------------------------------------------- */
int main(void)
{
    /* Init UART1 MAP_A for printf (DAPLink serial, P2_0/P2_1, 115200 baud).
     * CONSOLE_UART=1 in Makefile routes _write() here. */
    {
        const uart_cfg_t uart_cfg = {
            .parity     = UART_PARITY_DISABLE,
            .size       = UART_DATA_SIZE_8_BITS,
            .extra_stop = 0,
            .cts        = 0,
            .rts        = 0,
            .baud       = 115200,
        };
        const sys_cfg_uart_t uart_sys_cfg = {
            .clk_scale = CLKMAN_SCALE_AUTO,
            .io_cfg    = IOMAN_UART(1, IOMAN_MAP_A, IOMAN_MAP_UNUSED, IOMAN_MAP_UNUSED, 1, 0, 0),
        };
        UART_Init(MXC_UART1, &uart_cfg, &uart_sys_cfg);
    }

    keypad_init();

    printf("INIT\r\n");

    int last_key = KEY_NONE;

    while (1) {
        int key = debounce();

        if (key >= 0 && key != last_key) {
            /* New confirmed press */
            int row = key / KEYPAD_COLS;
            int col = key % KEYPAD_COLS;
            printf("KEY %c (%d,%d)\r\n", key_map[row][col], row, col);
            last_key = key;

        } else if (key == KEY_NONE && last_key != KEY_NONE) {
            /* Key released (confirmed idle) */
            printf("UP\r\n");
            last_key = KEY_NONE;
        }
        /* key == -2 (unstable): ignore, loop again immediately */
    }
}

Code for this particular post is available at https://github.com/arvindsa/identity-protocol-e14-challenge/tree/main/firmware/tests/keypad-scan 
Code for entire project is available at https://github.com/arvindsa/identity-protocol-e14-challenge

Final Notes

The code worked brilliantly and now i can get on the next peripherals. I was supposed to do the Crypto signing work next, but i decided to do it on a weekend when i have a longer block of free time.  Also i decided to move away from inkscape to draw.io website at https://app.diagrams.net/ for my flow chart creation. Gets work done faster. and I stick to copy-pasting from Typora for the github style tables.

  • Sign in to reply
  • Cancel

Top Replies

  • arvindsa
    arvindsa 19 days ago in reply to saramic +1
    Actually, I write my draft first in typora and mermaid. Infact I purchased typora for that specific purpose, writing academic style documents is a breeze with typora. But as you said, control over visuals…
  • saramic
    saramic 19 days ago

    ahh nice - reminds me I have a keypad somewhere in my junk box that I should look at using in a project some time. For the diagrams, you could also use Mermaid - putting your image above into Claude AI it gave me something like

    ```mermaid
    graph TD
        START([START]) --> SCAN[Scan keypad twice<br/>10 ms apart]    
        SCAN --> MATCH{Scans match?<br/>Debouncing}
        MATCH -- No (unstable) --> SCAN
        MATCH -- Yes --> NEW_KEY{New key pressed?<br/>last_Key != key?}
        NEW_KEY -- No --> RELEASED{Key just released?}
        RELEASED -- No --> SCAN
        NEW_KEY -- Yes --> CALC["row = key / 4<br/>col = key % 4<br/>label = key_map[row][col]"]
        CALC --> REPORT_KEY["Report KEY label<br/>last_key = key"]
        REPORT_KEY --> SCAN
        RELEASED -- Yes --> REPORT_UP["Report KEY_UP<br/>last_key = KEY_NONE"]
        REPORT_UP --> SCAN
    ```

    The beauty is that things like Github just render the flow chart as does Typora - the flow chart is unfortunate not as pretty, and you have less control on how it is laid out - but it is code - which for me has always been the winner ¯\_(ツ)_/¯

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Cancel
  • saramic
    saramic 19 days ago in reply to saramic

    the gist does display it - but clearly not through the embedding function - the mermaid version of the flow chart would be something like
    image

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Cancel
  • arvindsa
    arvindsa 19 days ago in reply to saramic

    Actually, I write my draft first in typora and mermaid. Infact I purchased typora for that specific purpose, writing academic style documents is a breeze with typora. But as you said, control over visuals is near to impossible. So i used to extract it as svg, ask chatgpt to style it  use Inkscape for final touch up. But this process is lengthy and unpredictable. Thats why I shifted to making the flow chat on draw.io. at the end. Claude AI, i had used it for a very complicated free lancing project, a solid assistant but expensive. 

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • Cancel
  • DAB
    DAB 16 days ago

    Nice update.

    It has been a very long time since I used a keypad.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube