cancel
Showing results for 
Search instead for 
Did you mean: 

Keypad input using interrupt (Rising Callback) with OLED

blade_runner_2004
Associate

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;
View more


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 } }
View more


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.

1 ACCEPTED SOLUTION

Accepted Solutions
gbm
Principal

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.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice

View solution in original post

1 REPLY 1
gbm
Principal

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.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice