ESR-2025/src/keypad.c

204 lines
6.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file keypad.c
* @brief Interrupt-driven 4×4 matrix keypad driver for MSP430FR2355.
*
* This driver scans a 4×4 keypad by:
* 1. Configuring rows as outputs (driven HIGH) and columns as inputs w/ pull-ups
* 2. Enabling falling-edge interrupts on each column line
* 3. On column interrupt:
* - Disable all column interrupts
* - Debounce
* - Pull each row LOW in turn to identify which row is active
* - Invoke user callback with ASCII key
* - Wait for key release and debounce
* - Clear flags and re-enable column interrupts
*
* @author
* @date 2025-07-02
*/
#include <msp430.h>
#include <driverlib.h>
#include "keypad.h"
#include "constants.h"
/** Number of rows in the keypad matrix */
#define ROWS 4U
/** Number of columns in the keypad matrix */
#define COLS 4U
/** Debounce delay (~20 ms @ 1 MHz SMCLK) */
#define DEBOUNCE_CYCLES 20000U
/** ASCII labels for each key at [row][col] */
static const char keys[ROWS][COLS] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' }
};
/** Row port and pin definitions */
static const uint8_t rowPort[ROWS] = { GPIO_PORT_P6, GPIO_PORT_P6, GPIO_PORT_P6, GPIO_PORT_P6 };
static const uint16_t rowPin [ROWS] = { GPIO_PIN0, GPIO_PIN1, GPIO_PIN2, GPIO_PIN3 };
/** Column port and pin definitions */
enum { COL0 = 0, COL1, COL2, COL3 };
static const uint8_t colPort[COLS] = { GPIO_PORT_P3, GPIO_PORT_P1, GPIO_PORT_P3, GPIO_PORT_P1 };
static const uint16_t colPin [COLS] = { GPIO_PIN0, GPIO_PIN4, GPIO_PIN2, GPIO_PIN1 };
/** User callback invoked on confirmed key press */
static KeypadCallback_t keyCallback = NULL;
/**
* @brief Scan rows to identify which one pulled the given column low.
* @param colIdx Index of column (03) that triggered interrupt
* @return ASCII character of pressed key, or 0 if none detected
*/
static char scan_row_for_column(uint8_t colIdx)
{
char result = 0;
unsigned int r;
/* Ensure all rows are driven HIGH before scanning */
for (r = 0; r < ROWS; r++) {
GPIO_setOutputHighOnPin(rowPort[r], rowPin[r]);
}
/* Drive each row LOW and check column input */
for (r = 0; r < ROWS; r++) {
GPIO_setOutputLowOnPin(rowPort[r], rowPin[r]);
__delay_cycles(DEBOUNCE_CYCLES);
if (GPIO_getInputPinValue(colPort[colIdx], colPin[colIdx]) == GPIO_INPUT_PIN_LOW) {
result = keys[r][colIdx];
break;
}
/* Restore row high for next iteration */
GPIO_setOutputHighOnPin(rowPort[r], rowPin[r]);
}
return result;
}
/**
* @brief Handle column interrupt: debounce, identify key, invoke callback,
* wait for release, then re-arm interrupts.
* @param colIdx Index of column that triggered the interrupt
*/
static void handle_column_interrupt(uint8_t colIdx)
{
unsigned int i;
/* Disable all column interrupts */
for (i = 0; i < COLS; i++) {
GPIO_disableInterrupt(colPort[i], colPin[i]);
}
/* Debounce delay */
__delay_cycles(DEBOUNCE_CYCLES);
/* Identify pressed key and invoke callback if set */
{
char key = scan_row_for_column(colIdx);
if (key && keyCallback) {
keyCallback(key);
}
}
/* Wait for key release */
while (GPIO_getInputPinValue(colPort[colIdx], colPin[colIdx]) == GPIO_INPUT_PIN_LOW) {
;
}
__delay_cycles(DEBOUNCE_CYCLES);
/* Clear interrupt flags and re-enable interrupts */
for (i = 0; i < COLS; i++) {
GPIO_clearInterrupt(colPort[i], colPin[i]);
GPIO_enableInterrupt(colPort[i], colPin[i]);
}
/* Reset all rows to LOW idle state */
for (i = 0; i < ROWS; i++) {
GPIO_setOutputLowOnPin(rowPort[i], rowPin[i]);
}
}
/*===========================================================================*/
/*=== Port ISR vectors =====================================================*/
/*===========================================================================*/
/**
* @brief Interrupt Service Routine for PORT2 (handles COL1 on P2.4)
*/
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
uint16_t mask = colPin[COL1] | colPin[COL3];
uint16_t status = GPIO_getInterruptStatus(GPIO_PORT_P1, mask);
if (status & colPin[COL1]) {
handle_column_interrupt(COL1);
__bic_SR_register_on_exit(LPM0_bits);
}
if (status & colPin[COL3]) {
handle_column_interrupt(COL3);
__bic_SR_register_on_exit(LPM0_bits);
}
GPIO_clearInterrupt(GPIO_PORT_P2, colPin[COL1]);
}
/**
* @brief Interrupt Service Routine for PORT3 (handles COL0, COL2, COL3)
*/
#pragma vector=PORT3_VECTOR
__interrupt void PORT3_ISR(void)
{
uint16_t mask = colPin[COL0] | colPin[COL2] | colPin[COL3];
uint16_t status = GPIO_getInterruptStatus(GPIO_PORT_P3, mask);
if (status & colPin[COL0]) {
handle_column_interrupt(COL0);
__bic_SR_register_on_exit(LPM0_bits);
}
if (status & colPin[COL2]) {
handle_column_interrupt(COL2);
__bic_SR_register_on_exit(LPM0_bits);
}
GPIO_clearInterrupt(GPIO_PORT_P3, mask);
}
/*===========================================================================*/
/*=== Public API ============================================================*/
/*===========================================================================*/
/**
* @brief Initialize keypad GPIOs and interrupts.
* @param cb Callback invoked with ASCII key on press
*/
void keypad_init(KeypadCallback_t cb)
{
unsigned int i;
keyCallback = cb;
/* Configure rows as outputs, idle LOW */
for (i = 0; i < ROWS; i++) {
GPIO_setAsOutputPin(rowPort[i], rowPin[i]);
GPIO_setOutputLowOnPin(rowPort[i], rowPin[i]);
}
/* Configure columns as inputs with pull-up resistors and falling-edge interrupts */
for (i = 0; i < COLS; i++) {
GPIO_setAsInputPinWithPullUpResistor(colPort[i], colPin[i]);
GPIO_clearInterrupt(colPort[i], colPin[i]);
GPIO_selectInterruptEdge(colPort[i], colPin[i], GPIO_HIGH_TO_LOW_TRANSITION);
GPIO_enableInterrupt(colPort[i], colPin[i]);
}
/* Enable global interrupts */
__enable_interrupt();
}