/** * @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 #include #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 (0–3) 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(); }