Simone: a game of visual memory and speed
stm32f4_keyboard.c File Reference

Portable functions to interact with the keyboard FSM library. All portable functions must be implemented in this file. More...

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <math.h>
#include "port_keyboard.h"
#include "port_system.h"
#include "stm32f4_system.h"
#include "stm32f4_keyboard.h"
#include "keyboards.h"

Functions

stm32f4_keyboard_hw_t_stm32f4_keyboard_get (uint8_t keyboard_id)
 Get the keyboard struct with the given ID. More...
 
void _timer_scan_column_config (void)
 Configure the timer that controls the duration of the column scanning. More...
 
void port_keyboard_init (uint8_t keyboard_id)
 Configure the HW specifications of a given keyboard. More...
 
void port_keyboard_excite_row (uint8_t keyboard_id, uint8_t row_idx)
 Set the given row to high and lower the others. More...
 
void port_keyboard_start_scan (uint8_t keyboard_id)
 Start the scanning of a keyboard. More...
 
void port_keyboard_stop_scan (uint8_t keyboard_id)
 Stop the scanning of a keyboard. More...
 
void port_keyboard_excite_next_row (uint8_t keyboard_id)
 Update the row to be excited. More...
 
bool port_keyboard_get_key_pressed_status (uint8_t keyboard_id)
 Return the status of the keyboard (pressed or not). More...
 
void port_keyboard_set_key_pressed_status (uint8_t keyboard_id, bool status)
 Set the status of the keyboard (pressed or not). More...
 
bool port_keyboard_get_row_timeout_status (uint8_t keyboard_id)
 Return the status of the column timeout flag. More...
 
void port_keyboard_set_row_timeout_status (uint8_t keyboard_id, bool status)
 Set the status of the row timeout flag. More...
 
char port_keyboard_get_key_value (uint8_t keyboard_id)
 Return the char representing the key pressed of a given keyboard based on its row that is being excited. This assumes that the matrix of chars is flattened (i.e., it is not a 2D array, but all rows are in a single array), thus it is necessary to calculate only one index. More...
 
char port_keyboard_get_invalid_key_value (uint8_t keyboard_id)
 Return the null key value of a given keyboard. More...
 

Variables

static GPIO_TypeDef * keyboard_main_row_ports []
 Array of GPIO ports for the rows of the main keyboard. More...
 
static uint8_t keyboard_main_row_pins []
 Array of GPIO pins for the rows of the main keyboard. More...
 
static GPIO_TypeDef * keyboard_main_col_ports []
 Array of GPIO ports for the columns of the main keyboard. More...
 
static uint8_t keyboard_main_col_pins []
 Array of GPIO pins for the columns of the main keyboard. More...
 
stm32f4_keyboard_hw_t keyboards_arr []
 Array of elements that represents the HW characteristics of the keyboards connected to the STM32F4 platform. More...
 

Detailed Description

Portable functions to interact with the keyboard FSM library. All portable functions must be implemented in this file.

Author
Sistemas Digitales II
Date
2026-01-01

Function Documentation

◆ _stm32f4_keyboard_get()

stm32f4_keyboard_hw_t* _stm32f4_keyboard_get ( uint8_t  keyboard_id)

Get the keyboard struct with the given ID.

Parameters
keyboard_idKeyboard ID.
Returns
Pointer to the keyboard struct.
NULL If the keyboard ID is not valid.

◆ _timer_scan_column_config()

void _timer_scan_column_config ( void  )

Configure the timer that controls the duration of the column scanning.

This function configures the timer to generate an internal interrupt to control the duration of a column scanning. The duration of the column scanning is defined in the PORT_KEYBOARDS_TIMEOUT_MS macro. This function is called by the port_keyboard_init() public function to configure the timer that controls the column scanning.

Use the timer indicated in the main page.

There are 2 ways to calculate the ARR and PSC values. You can implement any of them to configure the timer duration according to the PORT_KEYBOARDS_TIMEOUT_MS macro. Do not set the values directly!

Option 1. Efficient algorithm.
This option is the most efficient way to calculate the ARR and PSC values. It is based on the fact that the ARR value is near or equal to its maximum value (65535.0). And only one update of the PSC is needed. This eliminates the need for a loop, which could be slow if ARR is much larger than 0xFFFF.

Option 2. Iterative algorithm.
This option, on the other hand, starts with PSC equals 0 and re-computes in an iterative loop the values of ARR and PSC. ⚠️ WARNING! This option is not recommended. You should consider that this option is much slower than the efficient algorithm and this option will make the test_port_keyboard take a long time to execute (several seconds up to few minutes).

Warning
It is important to work with double type numbers to avoid the loss of precision when computing the temporary values of the ARR and PSC registers. The math.h library is included to use the round() function.
To store the temporary values of the ARR and PSC registers, cast the result of the round() function to uint32_t.
All the values must be represented as double in the formulas.
An arbitrary example of C code:
#include <math.h>
...
double sysclk_as_double = (double)SystemCoreClock; // Important to cast to double
double ms_as_double = (double)duration_ms; // Important to cast to double
double what_ever_value = (sysclk_as_double / ms_as_double) + 1.0; // Important to use 1.0 instead of 1!!! This formula is just an example!!!
TIMx->ARR = (uint32_t)(round(what_ever_value)); // Important to round before casting to uint32_t

TODO alumnos:

✅ 1. Enable the clock of the timer that controls the column scanning. Check the reference manual of the STM32F4 to know if the timer is connected to the APB1 or APB2 bus (i.e. RCC->APB1ENR or RCC->APB2ENR).
✅ 2. Disable the counter of the timer (register CR1 of the timer) because we want to configure it.
✅ 3. Enable the autoreload preload (bit ARPE of the register CR1) to enable the autoreload register.
✅ 4. Set the counter of the timer to 0 (register CNT) to start counting from 0.
✅ 5. Compute the prescaler and the auto-reload register to set the duration of the timer interrupt. The duration is defined in the PORT_KEYBOARDS_TIMEOUT_MS macro. Use any of the two options to calculate the ARR and PSC values:

Option 1. Efficient algorithm.
     ➡️ 6a. Convert SystemCoreClock to double and store them in local variables. This is to ensure that the subsequent calculations are performed in floating-point arithmetic.
     ➡️ 6b. Compute an initial value for the PSC register considering the maximum value of ARR (65535.0). Store it in a local variable of type double.
    💡 Use all variable and numbers as double (i.e., use 1.0 instead of 1).
    💡 Use the function round() from the math.h library to round the value of the PSC. Use round instead of casting to double, because casting trunks the decimal value.
     ➡️ 6c. Re-compute the value of the ARR register with the previously computed PSC value. Store it in a local variable of type double.
    💡 This would make ARR less than or equal to 65535.0, and we will adjust ARR and PSC if necessary. This eliminates the need for a loop, which could be slow if ARR is much larger than 0xFFFF.
    💡 Use the function round() to round the value of the ARR.
     ➡️ 6d. Check if the new value of ARR is greater than 0xFFFF (or 65535.0). If it is, increment PSC by 1.0 and recalculate ARR. This is to ensure that ARR does not exceed 0xFFFF.
    💡 Because the re-computed value of ARR will be close to its maximum, adding 1.0 to the PSC is enough to ensure that the ARR does not exceeds it maximum value and we do not need to iterate over it, making this algorithm faster than using a while(){} loop.
     ➡️ 6e. Continue in ✅ 7.


Option 2. Iterative algorithm.
     ➡️ 6a. Convert SystemCoreClock to double and store them in local variables. This is to ensure that the subsequent calculations are performed in floating-point arithmetic.
     ➡️ 6b. Compute an initial value for the ARR register considering a minimum value of PSC (0.0). Store it in a local variable of type double.
      💡 Use all variable and numbers as double (i.e., use 1.0 instead of 1).
      💡 Use the function round() from the math.h library to round the value of the ARR. Use round instead of casting to double, because casting trunks the decimal value.
     ➡️ 6c. If the value of ARR is greater than 0xFFFF (or 65535.0), increment the PSC by 1.0 and recalculate ARR. This is to ensure that ARR does not exceed 0xFFFF.
     ➡️ 6d. Do this in an iterative while(){} loop.
     ➡️ 6e. Continue in ✅ 7.

✅ 6. Load the values computed for ARR and PSC into the corresponding registers of the timer.
    💡 Ensure that values are rounded before casting.
✅ 7. The PSC and ARR values are currently in the preload registers. To load them into the active registers we need an update event. We achieve this by setting the UG bit of the EGR register. It is important to do this at the end of the configuration of the timer.
✅ 8. Clear the update interrupt flag (bit UIF of the register SR) to avoid an unwanted interrupt.
✅ 9. Enable the interrupts of the timer by setting the UIE bit of the DIER register.
✅ 10. Set the priority of the timer interrupt in the NVIC using the NVIC_SetPriority() function and the TIMx_IRQn interrupt with the level of priority and sub-priority shown in the main page.

Note
Do not enable the timer yet. This will be done when the column scanning must start.

◆ port_keyboard_excite_next_row()

void port_keyboard_excite_next_row ( uint8_t  keyboard_id)

Update the row to be excited.

✅ 1. Update the current_excited_row to move to the next row. If the index equals or greater than the number of rows indicated in the layout, move to the first row.
    💡 You can use the % (modulo) operator to get the remainder of the division
✅ 2. Call the function port_keyboard_excite_row() to excite the new row.

Parameters
keyboard_idKeyboard ID. This index is used to get the correct keyboard status struct.

◆ port_keyboard_excite_row()

void port_keyboard_excite_row ( uint8_t  keyboard_id,
uint8_t  row_idx 
)

Set the given row to high and lower the others.

TODO alumnos:

✅ 1. Retrieve the keyboard configuration struct calling _stm32f4_keyboard_get()
✅ 2. Iterate through all the rows indexes (you can get the number of rows from the keyboard's layout) and set them to LOW to ensure that only one row is excited at the same time.
✅ 3. Set the given row to HIGH.
    💡 You can use the BSRR or call the stm32f4_system_gpio_write() function.

Parameters
keyboard_idID of the keyboard to be scanned.
row_idxIndex of the row to be excited.

◆ port_keyboard_get_invalid_key_value()

char port_keyboard_get_invalid_key_value ( uint8_t  keyboard_id)

Return the null key value of a given keyboard.

This function retrieves the null key value defined in the keyboard layout.

TODO alumnos:

✅ 1. Retrieve and return the null_key field from the keyboard layout. Call the function _stm32f4_keyboard_get() to get the keyboard struct.

Parameters
keyboard_id
Returns
char

◆ port_keyboard_get_key_pressed_status()

bool port_keyboard_get_key_pressed_status ( uint8_t  keyboard_id)

Return the status of the keyboard (pressed or not).

TODO alumnos:

✅ 1. Return the value of the field flag_key_pressed. Call the function _stm32f4_keyboard_get() to get the keyboard struct.

Parameters
keyboard_idKeyboard ID. This index is used to get the correct keyboard status struct.
Returns
true If the keyboard has been pressed
false If the keyboard has not been pressed

◆ port_keyboard_get_key_value()

char port_keyboard_get_key_value ( uint8_t  keyboard_id)

Return the char representing the key pressed of a given keyboard based on its row that is being excited. This assumes that the matrix of chars is flattened (i.e., it is not a 2D array, but all rows are in a single array), thus it is necessary to calculate only one index.

✅ 1. Determine the value (char) of the key pressed based on the index of the current scanned row and the index of the column that provoked the interrupt, and return it.
    💡 You can calculate the key index as: key index = (excited row * num columns) + column interrupting.

Parameters
keyboard_idKeyboard ID. This index is used to get the correct keyboard status struct.
Returns
char Key value of the key pressed.

◆ port_keyboard_get_row_timeout_status()

bool port_keyboard_get_row_timeout_status ( uint8_t  keyboard_id)

Return the status of the column timeout flag.

TODO alumnos:

✅ 1. Return the value of the field flag_row_timeout. Call the function _stm32f4_keyboard_get() to get the keyboard struct.

Parameters
keyboard_idKeyboard ID. This index is used to get the correct keyboard status struct.
Returns
true If the column timeout has occurred
false If the column timeout has not occurred

◆ port_keyboard_init()

void port_keyboard_init ( uint8_t  keyboard_id)

Configure the HW specifications of a given keyboard.

Assuming we are using an STM32F4-based platform, this function must call the following functions:

TODO alumnos:

✅ 1. Retrieve the keyboard configuration struct calling _stm32f4_keyboard_get()
✅ 2. Call function stm32f4_system_gpio_config() with the right arguments to configure the rows as outputs with no pull up neither pull down connections (you can get the number of rows from the keyboard's layout).
✅ 3. Call function stm32f4_system_gpio_config() with the right arguments to configure the columns as inputs with pull down connections. Call also function stm32f4_system_gpio_config_exti() with the right parameters to to configure interruption mode in rising and falling edges and enabling the interrupt line for each column. Finally, call function stm32f4_system_gpio_exti_enable() with priority level to 1 and the subpriority level to 1 for all columns. All keyboards will have the same priority levels for their columns in the basic implementation. You can get the number of columns from the keyboard's layout.
✅ 4. Clean/set all the fields of the keyboard struct to their initial values. Set the value of current_excited_row to -1 to indicate that no row is being excited at the beginning.
✅ 5. Configure the timer that controls the duration of the column scanning by calling the function _timer_scan_column_config()

    💡 If any of the IRQs associated with the EXTI lines of the columns coincides with other IRQs in the system, the priority levels of this IRQ will be equal to the priority levels of the other peripheral.

Parameters
keyboard_idKeyboard ID. This index is used to select the element of the keyboards_arr[] array

◆ port_keyboard_set_key_pressed_status()

void port_keyboard_set_key_pressed_status ( uint8_t  keyboard_id,
bool  status 
)

Set the status of the keyboard (pressed or not).

This function is used to update the status of the keyboard when a key press is detected or cleared.

TODO alumnos:

✅ 1. Set the value of the field flag_key_pressed to the given status. Call the function _stm32f4_keyboard_get() to get the keyboard struct.

Parameters
keyboard_id
status

◆ port_keyboard_set_row_timeout_status()

void port_keyboard_set_row_timeout_status ( uint8_t  keyboard_id,
bool  status 
)

Set the status of the row timeout flag.

This function is used to update the status of the row timeout flag when a timeout occurs or is cleared.

TODO alumnos:

✅ 1. Set the value of the field flag_row_timeout to the given status. Call the function _stm32f4_keyboard_get() to get the keyboard struct.

Parameters
keyboard_idKeyboard ID. This index is used to get the correct keyboard status struct.
statusNew status of the row timeout flag.

◆ port_keyboard_start_scan()

void port_keyboard_start_scan ( uint8_t  keyboard_id)

Start the scanning of a keyboard.

This function starts the scanning of a keyboard by enabling the timer that controls the duration of the column scanning and setting the first row to HIGH.

TODO alumnos:

✅ 1. Reset the flag_row_timeout to indicate that a new scan is starting.
✅ 2. Reset the counter (CNT) of the timer.
✅ 3. Set the first row to be excited to HIGH and the others to LOW. You must use the appropiate function to do it.
✅ 4. Enable the timer interrupt in the NVIC using the NVIC_EnableIRQ() function and the TIMx_IRQn interrupt. ✅ 5. Enable the counter of the timer (register CR1 of the timer) to start the column scanning.

Parameters
keyboard_idKeyboard ID. This index is used to get the correct keyboard status struct.

◆ port_keyboard_stop_scan()

void port_keyboard_stop_scan ( uint8_t  keyboard_id)

Stop the scanning of a keyboard.

TODO alumnos:

✅ 1. Disable the counter of the timer (register CR1 of the timer) to stop the column scanning.
✅ 2. Disable the timer interrupt in the NVIC using the NVIC_DisableIRQ() function and the TIMx_IRQn interrupt.
✅ 3. Set all rows to LOW.

Parameters
keyboard_idKeyboard ID. This index is used to get the correct keyboard status struct.

Variable Documentation

◆ keyboard_main_col_pins

uint8_t keyboard_main_col_pins[]
static
Initial value:

Array of GPIO pins for the columns of the main keyboard.

◆ keyboard_main_col_ports

GPIO_TypeDef* keyboard_main_col_ports[]
static

◆ keyboard_main_row_pins

uint8_t keyboard_main_row_pins[]
static

◆ keyboard_main_row_ports

GPIO_TypeDef* keyboard_main_row_ports[]
static

◆ keyboards_arr

stm32f4_keyboard_hw_t keyboards_arr[]

Array of elements that represents the HW characteristics of the keyboards connected to the STM32F4 platform.

Array of elements that represents the HW characteristics of the keyboards.

This is an extern variable that is declared in stm32f4_keyboard.h. It represents an array of hardware keyboards.

STM32F4_KEYBOARD_MAIN_COL_1_PIN
#define STM32F4_KEYBOARD_MAIN_COL_1_PIN
Definition: stm32f4_keyboard.h:35
STM32F4_KEYBOARD_MAIN_ROW_3_PIN
#define STM32F4_KEYBOARD_MAIN_ROW_3_PIN
Definition: stm32f4_keyboard.h:29
STM32F4_KEYBOARD_MAIN_COL_1_GPIO
#define STM32F4_KEYBOARD_MAIN_COL_1_GPIO
Definition: stm32f4_keyboard.h:34
STM32F4_KEYBOARD_MAIN_ROW_0_GPIO
#define STM32F4_KEYBOARD_MAIN_ROW_0_GPIO
Definition: stm32f4_keyboard.h:22
STM32F4_KEYBOARD_MAIN_ROW_1_PIN
#define STM32F4_KEYBOARD_MAIN_ROW_1_PIN
Definition: stm32f4_keyboard.h:25
STM32F4_KEYBOARD_MAIN_COL_2_GPIO
#define STM32F4_KEYBOARD_MAIN_COL_2_GPIO
Definition: stm32f4_keyboard.h:36
SystemCoreClock
uint32_t SystemCoreClock
Definition: stm32f4_system.c:39
STM32F4_KEYBOARD_MAIN_ROW_1_GPIO
#define STM32F4_KEYBOARD_MAIN_ROW_1_GPIO
Definition: stm32f4_keyboard.h:24
STM32F4_KEYBOARD_MAIN_COL_0_GPIO
#define STM32F4_KEYBOARD_MAIN_COL_0_GPIO
Definition: stm32f4_keyboard.h:32
STM32F4_KEYBOARD_MAIN_ROW_0_PIN
#define STM32F4_KEYBOARD_MAIN_ROW_0_PIN
Definition: stm32f4_keyboard.h:23
STM32F4_KEYBOARD_MAIN_COL_0_PIN
#define STM32F4_KEYBOARD_MAIN_COL_0_PIN
Definition: stm32f4_keyboard.h:33
STM32F4_KEYBOARD_MAIN_ROW_2_PIN
#define STM32F4_KEYBOARD_MAIN_ROW_2_PIN
Definition: stm32f4_keyboard.h:27
STM32F4_KEYBOARD_MAIN_ROW_3_GPIO
#define STM32F4_KEYBOARD_MAIN_ROW_3_GPIO
Definition: stm32f4_keyboard.h:28
STM32F4_KEYBOARD_MAIN_COL_2_PIN
#define STM32F4_KEYBOARD_MAIN_COL_2_PIN
Definition: stm32f4_keyboard.h:37
STM32F4_KEYBOARD_MAIN_COL_3_GPIO
#define STM32F4_KEYBOARD_MAIN_COL_3_GPIO
Definition: stm32f4_keyboard.h:38
STM32F4_KEYBOARD_MAIN_COL_3_PIN
#define STM32F4_KEYBOARD_MAIN_COL_3_PIN
Definition: stm32f4_keyboard.h:39
STM32F4_KEYBOARD_MAIN_ROW_2_GPIO
#define STM32F4_KEYBOARD_MAIN_ROW_2_GPIO
Definition: stm32f4_keyboard.h:26