Keypad input using interrupt (Rising Callback) with OLED
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-15 10:20 PM - edited ‎2025-05-04 1:50 PM
Good day
I am using a NUCLEO-STMG0B1RE board and i am trying to handle a 4x3 matrix keypad inputs using interrupts to switch menus on an OLED screen. The keypad inputs worked when I used continuous polling for keypad inputs. The OLED needs to continuously update (at least once per second) with data from ADC. This also worked when I used continuous polling, but the keypad inputs was a bit sluggish and I am scared it will slow my processor down for future use.
With the current code I am trying to do the following:
- when key pressed, corresponding column goes high
- interrupt triggered
- Rising callback sets KeypadtriggerDetected = 1 and stores the column
- KeypadtriggerDetected activates if statement in while(1) which pulls all the rows low, then sets them high one at a time.
- When the key pressed is identified from keymap, that key gets processed with updates the state the OLED menu should be in
- that state then gets passed to displayMenuOLED to display the corresponding menu
With the current code I have to spam keys to get a input.
I will add code for relevant sections as well as my main.c file
Private variables:
/* USER CODE BEGIN PV */
const char keymap[4][3] = { { '1', '2', '3' }, { '4', '5', '6' }, { '7', '8',
'9' }, { '*', '0', '#' } };
GPIO_TypeDef *row_ports[4] = { GPIOC, GPIOB, GPIOB, GPIOB };
uint16_t row_pins[4] = { GPIO_PIN_0, GPIO_PIN_2, GPIO_PIN_6, GPIO_PIN_15 };
GPIO_TypeDef *col_ports[3] = { GPIOA, GPIOA, GPIOC };
uint16_t col_pins[3] = { GPIO_PIN_12, GPIO_PIN_11, GPIO_PIN_1 };
volatile char last_key = '\0';
volatile int col = -1;
typedef enum {
PAGE_DEFAULT,
PAGE_1,
PAGE_2,
PAGE_3,
MENU_LV1,
MENU_LV1_LOAD,
MENU_LV1_UNITS,
MENU_LV2_COUNT,
MENU_LV2_ADD,
MENU_ADD_UNITS_INPUT
} AppState;
volatile uint32_t KeypadtriggerDetected = 0;
volatile uint32_t KeypadtriggerTick = 0;
static AppState currentState = PAGE_DEFAULT;
int desired_load_state = -1; // -1 = none, 0 = OFF, 1 = ON
static uint32_t last_adc_update = 0;
uint8_t force_oled_refresh = 0;
in main:
ssd1306_Init(); // Initialize the OLED display
displayMenuOLED(PAGE_DEFAULT); // default page on startup
// set row pins high (output)
for (int i = 0; i < 4; i++)
HAL_GPIO_WritePin(row_ports[i], row_pins[i], GPIO_PIN_SET);
int start = HAL_GetTick();
uint32_t debounceTime = 80;
in while(1):
// Handle keypad press (triggered by column interrupt)
if (KeypadtriggerDetected == 1) {
// Scan each row to identify which key is pressed
for (int row = 0; row < 4; row++) {
// Set all rows LOW before driving one HIGH
for (int i = 0; i < 4; i++) {
HAL_GPIO_WritePin(row_ports[i], row_pins[i], GPIO_PIN_RESET);
}
// Set the current row HIGH
HAL_GPIO_WritePin(row_ports[row], row_pins[row], GPIO_PIN_SET);
// Check if the column pin that triggered interrupt is still HIGH
if (col >= 0 && col < 3 && HAL_GPIO_ReadPin(col_ports[col], col_pins[col]) == GPIO_PIN_SET) {
// Key at (row, col) is being pressed
char key = keymap[row][col];
last_key = key; // Store the key for processing
break; // Exit once key is found
}
}
// Restore all rows to HIGH after scanning
for (int i = 0; i < 4; i++) {
HAL_GPIO_WritePin(row_ports[i], row_pins[i], GPIO_PIN_SET);
}
// Debounce and finalize the key detection
if (last_key != '\0' && (HAL_GetTick() - KeypadtriggerTick > debounceTime)) {
process_key_press(last_key);
force_oled_refresh = 1;
last_key = '\0';
KeypadtriggerDetected = 0;
col = -1; // Clear column index for next interrupt
}
// Check if ADC data is ready and if enough time (500 ms) has passed since last processing
if (adc_ready && HAL_GetTick() - last_adc_update >= 500) {
process_adc_buffer(); // Process ADC data: calculate voltage, current, power, energy, etc.
update_LED_D3_UnitsStatus(); // Update LED D3 based on remaining energy units
update_LED_D5_PowerAlarm(); // Update LED D5 based on power threshold (e.g. > 3800W)
// Only update the OLED display if user is on one of the real-time data screens
if (currentState == PAGE_DEFAULT || currentState == PAGE_1 ||
currentState == PAGE_2 || currentState == PAGE_3) {
displayMenuOLED(currentState); // Refresh OLED with updated values
}
last_adc_update = HAL_GetTick(); // Update timestamp for next sampling window
}
}
in EXTI_Rising_Callback:
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_4 || GPIO_Pin == B1_Pin) {
ButtontriggerDetected = 1;
ButtontriggerTick = HAL_GetTick();
return;
}
if (GPIO_Pin == GPIO_PIN_1 || GPIO_Pin == GPIO_PIN_11 || GPIO_Pin == GPIO_PIN_12) {
KeypadtriggerDetected = 1;
KeypadtriggerTick = HAL_GetTick();
// Set global col index (NOT a local variable)
if (GPIO_Pin == GPIO_PIN_12)
col = 0;
else if (GPIO_Pin == GPIO_PIN_11)
col = 1;
else if (GPIO_Pin == GPIO_PIN_1)
col = 2;
}
}
Thank you in advace.
Solved! Go to Solution.
- Labels:
-
STM32G0 Series
Accepted Solutions
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-16 5:12 AM
Don't do that. Scan the keys in timer interrupt service routine. Even with port change interrupts you must use the timer interrupt to desensitize and rearm the inputs, and if you use timer interrupt there is no need to use port change interrupts.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-16 5:12 AM
Don't do that. Scan the keys in timer interrupt service routine. Even with port change interrupts you must use the timer interrupt to desensitize and rearm the inputs, and if you use timer interrupt there is no need to use port change interrupts.
