198 lines
6.1 KiB
C
198 lines
6.1 KiB
C
/**
|
||
* @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_PIN1, GPIO_PIN2, GPIO_PIN3, GPIO_PIN4 };
|
||
|
||
/** Column port and pin definitions */
|
||
enum { COL0 = 0, COL1, COL2, COL3 };
|
||
static const uint8_t colPort[COLS] = { GPIO_PORT_P3, GPIO_PORT_P2, GPIO_PORT_P3, GPIO_PORT_P3 };
|
||
static const uint16_t colPin [COLS] = { GPIO_PIN7, GPIO_PIN4, GPIO_PIN3, GPIO_PIN2 };
|
||
|
||
/** 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 (0–3) that triggered interrupt
|
||
* @return ASCII character of pressed key, or 0 if none detected
|
||
*/
|
||
static char scanRowForColumn(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 handleColumnInterrupt(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 = scanRowForColumn(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=PORT2_VECTOR
|
||
__interrupt void PORT2_ISR(void)
|
||
{
|
||
uint16_t status = GPIO_getInterruptStatus(GPIO_PORT_P2, colPin[COL1]);
|
||
if (status & colPin[COL1]) {
|
||
handleColumnInterrupt(COL1);
|
||
GPIO_clearInterrupt(GPIO_PORT_P2, colPin[COL1]);
|
||
__bic_SR_register_on_exit(LPM0_bits);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @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]) {
|
||
handleColumnInterrupt(COL0);
|
||
__bic_SR_register_on_exit(LPM0_bits);
|
||
}
|
||
if (status & colPin[COL2]) {
|
||
handleColumnInterrupt(COL2);
|
||
__bic_SR_register_on_exit(LPM0_bits);
|
||
}
|
||
if (status & colPin[COL3]) {
|
||
handleColumnInterrupt(COL3);
|
||
__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 initKeypadInterrupts(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();
|
||
}
|