Saltar a contenido

5. Versión 2: teclado matricial

En la Versión 1 aprendimos a gestionar un único botón mediante interrupciones. Sin embargo, ¿qué ocurre si nuestro sistema necesita 12, 16 o más botones? Si utilizáramos la estrategia anterior, necesitaríamos una línea de interrupción y un pin GPIO por cada botón, agotando rápidamente los recursos del microcontrolador.

Para solucionar esto, utilizamos la técnica del barrido o scanning en un teclado matricial. Esta técnica aprovecha la persistencia temporal para leer muchos pulsadores utilizando pocos pines, organizándolos en filas y columnas.

Bibliografía

  1. “Fundamentos teóricos de sistemas basados en microcontrolador STM32” 3

  2. Datasheet “STM32F446xC/E” 4

  3. Reference manual “RM0390. STM32F446xx advanced Arm-based 32-bit MCUs’ 5

En este capítulo vamos a crear una librería que nos permita gestionar un teclado matricial 4x4 (16 teclas) como el de la figura. A diferencia del botón, que funcionaba solo teníamos una interrupción, aquí utilizaremos un temporizador que nos interrumpe para realizar una excitación periódica de las filas y esperaremos interrupciones de las columnas.

Teclado matricial de membrana 4x4 usado en el proyecto Simone.

Como ya hicimos en las versiones anteriores, (i) vamos a implementar la parte portable PORT dependiente del HW para gestionar los niveles lógicos de las filas y la lectura de las columnas, y lo probaremos con un test unitario. (ii) Después, vamos a crear la lógica de la FSM para gestionar el barrido y el anti-rebotes (la parte COMMON), y lo probaremos con un test unitario. (iii) Por último, montaremos el HW y probaremos el funcionamiento con un programa de ejemplo.

El teclado matricial es un array de pulsadores conectados en intersecciones de filas y columnas. Cuando no se pulsa ninguna tecla, no hay conexión entre filas y columnas. Al pulsar una tecla, se cortocircuita una fila con una columna específica. Las características a destacar del sistema de la Versión 2 se muestran en la siguiente tabla.

Parámetro Valor
Pin fila 1 PA0
Pin fila 2 PA1
Pin fila 3 PA4
Pin fila 4 PB0
Modo filas Salida
Pull up/ down filas No push, no pull
Timeout de excitación de filas \(25 ms\)
Pin/ EXTI/ ISR columna 1 PA8/ EXTI8/ EXTI9_5_IRQHandler
Pin/ EXTI/ ISR columna 2 PB10/ EXTI10/ EXTI15_10_IRQHandler
Pin/ EXTI/ ISR columna 3 PB4/ EXTI4/ EXTI4_IRQHandler
Pin/ EXTI/ ISR columna 4 PB5/ EXTI5/ EXTI9_5_IRQHandler
Modo columnas Entrada
Pull up/ down columnas Pull down
Prioridad todas las columnas 1
Subprioridad todas las columnas 1
Tiempo anti-rebotes todas las columnas \(100-200 ms\)

5.1 Características del teclado matricial en Versión 2

Circuitería teclado Esquema matricial
(a) Circuio de un teclado matricial de membrana, (b) Esquema de conexiones.

Si tiene la oportunidad de abrir en casa cualquier sistema que tenga un teclado o botonera1, seguramente encuentre una circuitería como la de la figura (a). La goma gris es la cara interna de los botones, que está sobre la PCB verde. Las almohadillas negras que ve son contactos metálicos que, cuando se pulsa el botón, se cortocircuitan con el metal de la PCB y cierran el circuito2.

La idea de colocar los botones así, haciendo una rejilla, es muy inteligente. Haciendo un enrejillado no es necesario tener un cable para cada botón, porque un cable por cada botón implicaría tener un pin de entrada en nuestro microcontrolador por cada uno (si no se usan multiplexores, claro), y los pines no es algo que sobre, generalmente, en los encapsulados de los chips. Por ejemplo, en el teclado de la figura (a) se usan 10 cables para 24 botones (ahorro del \(58.3\%\) de conexiones/pines), y en el del teclado del laboratorio se usarán 8 conexiones para 16 botones (ahorro del \(50.0\%\)).

Pero esta idea no sale gratis. A cambio de reducir el hardware necesario, tenemos que complicar el software un poquito. Debemos ir excitando —poniendo tensión— las filas o columnas de forma cíclica para poder detectar qué botón se ha pulsado (lo hacemos leyendo las columnas o filas respectivamente). Si alguna columna detecta esa tensión, sabemos qué tecla exacta (intersección fila-columna) se ha pulsado. Este proceso se repite para todas las filas rápidamente. En nuestro caso excitaremos filas. Fíjate que solo podemos tener una fila con tensión a la vez porque, de otro modo, no seríamos capaces de distinguir entre los botones de una misma columna. Si haces un dibujo, lo verás fácilmente.

No te preocupes, la gestión de filas y columnas tiene fácil solución si trabajamos con las máquinas de estado; pues para controlar la excitación de las filas del teclado matricial tendremos la FSM del teclado.

Diagrama de temporización de excitación de filas.

El diagrama de temporización muestra cómo se excitan las filas del teclado. Cada \(25 ms\) se excita una fila diferente poniendo un nivel lógico alto en el pin correspondiente. Durante ese tiempo que está la fila en alto, se puede producir interrupción si se pulsa alguna tecla de esa fila. Luego, se desactiva la fila (nivel lógico bajo) y se excita la siguiente fila. Así, en un período de \(100 ms\) se excitan todas las filas del teclado. Este tiempo es suficientemente rápido para que el usuario no note que las filas se excitan de forma secuencial.

Ejemplo de excitación de fila R1, pulsando tecla 3, generando interrupción en pin conectado a columna C3.

La figura de ejemplo muestra un ejemplo de cómo funcionará el sistema. En este caso, la FSM del teclado está excitando la fila R1 poniendo un nivel lógico alto en el pin correspondiente (cada \(25 ms\) excita una fila). Cuando se pulsa la tecla '3', se cierra el circuito entre la fila R1 y la columna C3, lo que hace que el pin conectado a C3 detecte un nivel lógico alto (\(3.3 V\)).

En este circuito vamos a activar las resistencias de pull-down ¡internas! de los pines del microcontrolador conectados a las columnas, por lo que cuando no se pulsa ninguna tecla, las columnas estarán a nivel lógico bajo (\(0 V\)). Al pulsar la tecla '3', la columna C3 pasa a nivel lógico alto debido a la conexión con la fila R1 que está excitada. Pasa lo contrario que con el circuito del botón, que tenía una resistencia de pull-up ¡externa! Esto genera una interrupción en el microcontrolador, que puede entonces identificar qué tecla se ha pulsado basándose en la fila actualmente excitada y la columna que ha generado la interrupción.

Recuerda que, como hicimos con el botón, estamos desarrollando una librería. El teclado matricial no tiene por qué saber nada de las acciones que hace el sistema cuando se pulsa una tecla. Es por eso que en nuestro proyecto, el teclado se encargará solo de guardar la tecla pulsada y de avisar de que ha habido una pulsación. El sistema que use esta librería —sea en este proyecto u otro— deberá comprobar dicho valor guardado con un get. La idea es exactamente la misma que la que implementó en el botón. Así, cada vez que se quiera añadir un teclado, le asociaremos una FSM. Las particularidades de dónde están conectadas las filas y columnas son cosas específicas del HW, por lo que estarán en PORT.

Estructura HW teclado Estructura FSM del teclado
(a) Estructura del HW del teclado en PORT, (b) Estructura de la FSM del teclado en COMMON.

Las figuras de estructuras HW y SW muestran las estructuras que vamos a necesitar para el teclado. La estructura del HW del teclado en PORT. El PORT de otro microcontrolador podría implementar internamente una estructura diferente, por eso está dentro de la carpeta stm32f4. Por ejemplo, al portar el código para PC no tendría sentido definir la estructura de una GPIO. La estructura de la FSM del teclado en COMMON se muestra en la figura de la FSM.

Como en el caso del botón aquí, aunque no hay muelles, puede haber igualmente inestabilidades en la pulsación, rebotes, por lo e vamos a implementar, igualmente, un mecanismo antirebotes. Dejaremos unos tiempos de guarda de antirebotes también entre \(100-200 ms\).

En el caso del teclado matricial no tenemos circuito preestablecido, por lo que podemos elegir libremente si queremos usar resistencias de pull-up o pull-down en las columnas. En este caso, usaremos resistencias de pull-down en las columnas y excitaremos las filas poniendo un nivel lógico alto. Así, cuando se pulsa una tecla, la columna correspondiente pasa a nivel alto. Esto es al revés de como pasaba en el botón.

Ahora sí, comencemos. Preparemos el proyecto para poder añadir el teclado matricial:

  1. Descarga del repositorio de la asignatura los ficheros correspondientes a la parte PORT de la librería del keyboard correspondientes a la versión V2: https://github.com/sdg2DieUpm/simone/tree/simone_v2. Solo descarga por ahora: port_keyboard.h, stm32f4_keyboard.h, y stm32f4_keyboard.c y colócalos en las carpetas correspondientes. De la parte COMMON descarga solo keyboards.h, y keyboards.c, que incluyen los layouts de los teclados matriciales.
  2. Coloca cada uno donde corresponde: PORT o COMMON, en include, o src. Ten en cuenta que algunos ficheros de PORT están en la carpeta stm32f4 porque sus funciones reciben o devuelven estructuras específicas de la Nucleo-STM32F446RE.

Verás que no compila, y es que solo se te proporciona cierta parte del código. Los prototipos de gran parte de las funciones públicas no están definidos.

5.2 Layouts de teclados matriciales

Antes de ponernos a programar, conviene explicar qué son los ficheros keyboards.h y keyboards.c. Estos ficheros se han de colocar en la carpeta common/include y common/src respectivamente. Estos ficheros contienen los layouts de los teclados matriciales que queramos usar en nuestro proyecto. Un layout es una matriz que define qué carácter representa cada tecla del teclado. Por ejemplo, en un teclado 4x4 típico, la primera fila podría representar los caracteres '1', '2', '3', 'A'; la segunda fila '4', '5', '6', 'B'; y así sucesivamente, pero otro de 4x4 también podría tener una distribución diferente (solo letras, o solo números), ¡o tener otro de 1x3 de colores!...

Estos ficheros permiten definir múltiples layouts para diferentes teclados matriciales, facilitando su uso en la librería del teclado. En la Versión 5 podrías querer añadir un nuevo layout de algún teclado extra, o modificar el existente.

La estructura keyboard_t definida en keyboards.h contiene:

  • Un puntero a una matriz de caracteres (const char *keys), que representa el layout del teclado.
  • El número de filas (uint8_t rows) y columnas (uint8_t cols) del teclado.
  • Un caracter especial (char null_key) que indica que no se ha pulsado ninguna tecla. Por ejemplo el caracter ASCII nulo '\0'.

En el mismo fichero se declara standard_keyboard como un ejemplo de layout para un teclado matricial 4x4. El nombre es algo genérico y representativo. Este layout se define en keyboards.c como una matriz de caracteres que representa las teclas del teclado. Aquí se hace público para que pueda ser usado en otras partes del código; sus particularidades se definen en el .c.

5.3 PORT: cabeceras de la librería del teclado

Vamos a implementar el contrato con el usuario de la parte dependiente del HW de librería del teclado. Esta interfaz permitirá configurar las filas y columnas de nuestro teclado y realizar el barrido de excitación de filas y lectura de columnas para identificar la tecla pulsada.

El montaje de nuestro módulo teclado matricial tendrá un aspecto como el mostrado en la figura.

Montaje del teclado matricial con la Nucleo-STM32F446RE.

Más adelante lo implementaremos.

5.3.1 Cabecera port_keyboard.h

Esta cabecera depende del HW pero no de las particularidades del microcontrolador STM32F446RE. Vamos a seguir los siguientes pasos:

  1. Incluye todas las cabeceras necesarias según indica la API.
  2. Incluye los (#define) necesarios para el teclado: el identificador PORT_KEYBOARD_MAIN_ID que usaremos en el proyecto Simone, el timeout de excitación de filas, y el tiempo de antirebotes de las teclas (mismo tiempo para todas).

    Hemos decidido darle el nombre PORT_KEYBOARD_MAIN_ID al teclado que usaremos en el juego. En la Versión 5 podríamos querer añadir más teclados, y entonces habría que definir más identificadores con otros nombres representativos. 3. Define el enumerado que identifica los índices de las columnas del teclado, y que será de utilidad para hacer el código más legible en el manejo de las interrupciones. 4. Escribe los prototipos de las funciones públicas que aparecen en la API del fichero port_keyboard.h. 5. Puede ser buen momento ahora para documentar con Doxygen.

5.3.2 Cabecera stm32f4_keyboard.h

Esta cabecera define los pines físicos a los que están conectadas las filas y columnas de los teclados que usemos con nuestra placa Nucleo-STM32F446RE.

  1. Incluye todas las cabeceras necesarias.
  2. Define (#define) los valores de las GPIO y pines para las 4 filas y las 4 columnas según indica la tabla de características del teclado matricial.

  3. Declara la estructura stm32f4_keyboard_hw_t que contendrá la configuración física del teclado.

    Presta atención a la documentación que explica en cada campo qué deberá contener. Recuerda que las filas son salidas y las columnas entradas.

    Especial mención merecen los campos p_row_ports y p_col_ports, que son dobles punteros: punteros a arrays de punteros a estructuras GPIO_TypeDef. Estos campos permiten almacenar las referencias a los puertos GPIO de cada fila y columna del teclado. Esta es la forma de poder definir una estructura genérica sin saber el tamaño del teclado que va a gestionar. Nos da lo mismo que definamos un teclado de 2x2, 4x4 o 5x3; por eso estos campos son punteros que apuntarán a arrays de elementos de tipo GPIO_TypeDef* (por ejemplo, GPIOA, GPIOB, etc.). Así, podemos tener una lista dinámica de puertos para las filas y columnas del teclado, adaptándonos a cualquier configuración física que necesitemos.

    El campo p_keyboard es un puntero a una estructura keyboard_t, que contiene el layout del teclado. Esto nos permite asociar el diseño lógico del teclado con su configuración física. Se declara const porque el layout no debe modificarse en tiempo de ejecución.

    Fíjate también que la estructura NO guarda la tecla pulsada, sino que guarda el índice de la fila que se está excitando y la columna que genera la interrupción. La gestión de la tecla pulsada se hará en la FSM del teclado, que es independiente del HW.

  4. Declara el array de estructuras de tipo stm32f4_keyboard_hw_t como se hizo con el botón. Este array contendrá las características de todos los teclados que tengamos en el sistema.

  5. Documenta todo con Doxygen.

Ya hemos acabado con el encabezado (header) que interactúa con el HW. Todavía dará errores al compilar. Vamos ahora a implementar todas las funciones prototipadas en port_keyboard.h.

5.4 PORT: fuente de la librería del teclado

Vamos a portar las funciones necesarias para controlar los pines del teclado. Programaremos los ficheros fuente de la parte PORT, que todos estarán en el fichero stm32f4_keyboard.c.

5.4.1 Fuentes stm32f4_keyboard.c

La complejidad aquí reside en la gestión de múltiples pines y en la lógica de configuración de entrada/salida.

  1. Incluye las librerías necesarias.
  2. Verás que en la plantilla proporcionada se han definido 4 arrays que corresponden con los puertos y pines de las filas y columnas del teclado. Estos arrays se usan para inicializar la estructura del teclado. Estos son los arrays de elementos GPIO_TypeDef* (y uint8_t) de los que hablábamos anteriormente y a los que apuntará la estructura stm32f4_keyboard_hw_t del teclado principal KEYBOARD_MAIN.
  3. Define la variable global pública stm32f4_keyboard_hw_t keyboards_arr con la configuración física de nuestro teclado tal y como hicimos con el botón y que se declaró en el .h.. Los campos p_row_ports, p_row_pins, p_col_ports, y p_col_pins deben apuntar a los arrays definidos en el paso anterior. El campo p_keyboard debe apuntar a la dirección de memoria del teclado estándar definido en keyboards.c. El resto de campos se inicializan en la función port_keyboard_init().

  4. Codifica la función _stm32f4_keyboard_get() para recuperar la configuración del hardware, de modo análogo a como se hizo para el botón.

  5. Codifica la función port_keyboard_init() como se indica en la API.

    Recuerda que es muy importante indicar en el modo de las interrupciones de las columnas del teclado que, además de detectar ambos flancos (subida y bajada), debe habilitar la petición de interrupción (registro EXTI_IMR).

    Se recomienda usar bucles para configurar las filas y columnas, y evitar el spaghetti code. Esto hace que el código sea más compacto y fácil de leer y mantener.

  6. Codifica la función port_keyboard_excite_row() como se indica en la API. Esta función debe activar la fila indicada y ¡desactivar las restantes!

  7. Codifica la función port_keyboard_excite_next_row() como se indica en la API. Esta función llama a la anterior pero antes actualiza current_excited_row, que es el índice de la fila a ser excitada.

  8. Codifica todos los getters y setters que aparecen en el .h como indica la API. Especial atención a la función port_keyboard_get_key_value(), que debe devolver el carácter ASCII correspondiente a la tecla pulsada, usando el layout del teclado.

    Como en nuestra estructura no tenemos definida de manera fija el array del layout del teclado, sino que tenemos un puntero a una estructura keyboard_t, no podemos acceder directamente al array de teclas tratado como matriz bidimensional con los índices de la fila y columna. En su lugar, debemos tratar el array como unidimensional -que es, por otro lado, como está almacenado en memoria- y calcular la posición del caracter de la tecla pulsada usando la fórmula:

    key index = (excited row * num columns) + column interrupting
    

    Vamos a codificar ahora las funciones que gestionan el temporizador que controla la excitación de las filas.

    Parámetro Valor
    Temporizador TIM5
    Prescaler (a calcular para PORT_KEYBOARDS_TIMEOUT_MS)
    Periodo (a calcular para PORT_KEYBOARDS_TIMEOUT_MS)
    ISR TIM5_IRQHandler
    Prioridad 2
    Subrioridad 0
  9. Codifica la función _timer_scan_column_config() como indica la API. Esta función configura el temporizador que controla el tiempo de excitación de las filas de cualquier teclado que se monte en el juego; si hubiese más de uno, todos se excitarían a la vez. Para ello, apóyate en el ejemplo "timer para interrupción periódica" del libro de fundamentos teóricos 3.

    Esta función configura un temporizador para que genere una interrupción de PORT_KEYBOARDS_TIMEOUT_MS milisegundos desde que se habilita el mismo. El temporizador elegido se muestra en la tabla de características del teclado.

    Lo vamos a usar para que genere interrupciones periódicas. Lo activaremos cuando durante el juego sea turno del jugador que use el teclado matricial, y lo desactivaremos cuando se esté reproduciendo la secuencia de colores.

    Para saber qué fuente de reloj habilitar para el temporizador, consulta la tabla "Figure 3. STM32F446xC/E block diagram" del datasheet "STM32F446xC/E" 4. Allí podrás ver si nuestro temporizador está conectado al APB1 o al APB2, y tenemos que habilitar el reloj en el registro RCC->APB1ENR o RCC->APB2ENR respectivamente.

    Es importante que no pongas los valores de los registros de configuración del temporizador a mano, sino que uses las ecuaciones que se proporcionan en el libro de fundamentos teóricos 3 para calcular los valores de los registros TIMx->PSC y TIMx->ARR. En cualquier momento podríamos querer cambiar el periodo de excitación de filas y, si lo hacemos a mano, podríamos cometer errores, además de que es menos legible.

    **Es muy importante que la función _timer_scan_column_config() se llame desde la función port_keyboard_init(). Si no, no se podrán generar interrupciones para excitar filas y leer columnas.

  10. Codifica la función port_keyboard_start_scan() que se encarga de habilitar las interrupciones del temporizador y activar la cuenta (reseteando el contador). En esta función se resetea el flag flag_row_timeout, y se excita la primera fila del teclado.

  11. Codifica la función port_keyboard_stop_scan() que deshabilita las interrupciones del temporizador y detiene la cuenta. Del mismo modo, apaga todas las filas del teclado.

**¡Ya hemos acabado con la implementación de la parte HW stm32f\_keyboard.c del teclado. Ahora solo queda la ISR asociada a dicho temporizador en el fichero `interr.c``. Vamos a ello.

5.4.2 interr.c

Abre el fichero interr.c. Tenemos que codificar las ISR para gestionar las interrupciones de cada una de las columnas y del temporizador de excitación de filas.

Es muy importante que notes que algunas están compartidas ISR por distintas líneas. Esto ya lo vimos en la Versión 1 con el botón. Por ejemplo la ISR EXTI15_10_IRQHandler() gestiona las interrupciones de las líneas 10 a la 15 de cualquier GPIO, y es por ello que habíamos puesto un if para identificar la fuente. Es ahora cuando le vas a encontrar más sentido a ese bloque condicional.

Fíjate en las ISR de las interrupciones de las columnas del teclado matricial en la tabla de características del teclado: 2 de las columnas comparten ISR, y otra de ellas la comparte con el botón. Vamos a codificar dichas ISR:

  1. Completa la ISR EXTI15_10_IRQHandler para gestionar la interrupción de la columna PORT_KEYBOARD_COL_1 (la segunda) del teclado matricial. Recuerda que esta ISR también gestiona la interrupción del botón, por lo que debes mantener el bloque condicional if que ya estaba implementado.

    Fíjate, en la API, en el TODO para Versión 2.

    Note

    Tanto esta ISR como las siguientes hacen llamadas la función privada _check_column_interrupt(), que se encargará de gestionar el flag de pulsación de tecla y guardar el índice de la columna que ha generado la interrupción. Esta función auxiliar es opcional implementarla, pero hace el código más legible y evita repetir código. Si no quieres implementarla, asegúrate de hacer en cada ISR de cada columna lo que la API indica para esta función.

    Si quieres implementar _check_column_interrupt(), hazlo ahora. Debes colocarla antes de cualquier función que la use.

    Esta ISR, cuando salta, se encarga de llamar a la función correspondiente para settear el estado del flag de pulsación de tecla y guardar el índice de la columna que ha generado la interrupción.

  2. Codifica la ISR EXTI9_5_IRQHandler para gestionar las interrupciones de las columnas PORT_KEYBOARD_COL_0 (la primera) y PORT_KEYBOARD_COL_3 (la cuarta) del teclado matricial. Esta ISR gestiona las interrupciones de dos líneas del teclado matricial, por lo que debes mantener un bloque condicional if como en de la ISR anterior.

  3. Codifica la ISR EXTI4_IRQHandler para gestionar la interrupción de la columna PORT_KEYBOARD_COL_2 (la tercera) del teclado matricial. Como esta ISR no está compartida entre líneas del EXTI, no es necesario un bloque condicional.

  4. Por último, codifica la ISR TIM5_IRQHandler como se indica en la API. Recuerda que las ISR no reciben ni devuelven nada.

    Esta ISR, cuando salta, se encarga de llamar a la función correspondiente para settear el estado del flag de timeout que permitirá excitar la siguiente fila del teclado.

  5. Si queda algo por documentar puede ser buen momento ahora.

Si ahora compilas, el código no debería tener ningún error. ¡Ya hemos acabado con la implementación de portado de excitación de filas para lectura de teclas en un teclado matricial!. Vamos a probarlo con el test unitario de la parte PORT para esta parte.

5.5 PORT: Test unitario del teclado matricial

Vamos a comprobar que la parte PORT funciona correctamente pasando los test HW del código que hemos desarrollado antes de continuar.

¡Importante! Los test que se proporcionan comprueban solo algunos aspectos esenciales, pero no son exhaustivos. Es responsabilidad del alumno comprobar que el sistema final funciona correctamente. image Ten a mano y revisa el capítulo “Test unitarios y ejemplos de integración” del libro de fundamentos teóricos 3.

Descarga el fichero de test HW del teclado test_port_keyboard.c de https://github.com/sdg2DieUpm/simone/tree/simone_v2_test. Ponlo en la carpeta test/stm32f4 de tu proyecto.

  1. Conecta la placa Nucleo-STM32 al ordenador.

  2. Pulsa sobre el icono de depuración image y selecciona image Clean and Debug sobre la plataforma que queramos depurar (stm32f446re).

  3. En el desplegable que se abre, selecciona el test test_port_keyboard. Se compilará y se cargará en la placa.

  4. Comprueba que todos los test pasan correctamente en el texto mostrado en la terminal de depuración. Si no es así, lee los mensajes de error y corrige tu código hasta que pase todas las pruebas. Si no pasa las pruebas, no continúes programando, corrigelas.

  5. Termina la depuración pulsando (image) y repite el proceso hasta que pase todos los test.

¡Ya hemos acabado con la parte PORT del teclado! Vamos ahora a implementar la parte COMMON de la librería del teclado.

5.6 COMMON: cabecera de la FSM del teclado

5.6.1 Consideraciones de la FSM del teclado

Antes de ponernos a programar, conviene explicar algunos aspectos importantes de la FSM del teclado matricial.

  • La FSM almacena el caracter char de la última tecla pulsada.

  • El usuario debe solicitar/ comprobar el caracter mediante la función fsm_keyboard_get_key_value().

  • El valor de inicio del caracter al arrancar la FSM, y el valor de reinicio, debe ser le valor de tecla inválida que haya definido el layout del teclado. En nuestro caso, el valor de tecla inválida es el caracter nulo '\0', que está definido en la estructura keyboard_t del layout del teclado. En otro teclado podría ser otro, por lo que no hay que poner este valor a pincho, sino el que nos devuelva port_keyboard_get_invalid_key_value().

    ¡Ojo! 👁 ¡Al inicializar la FSM, hay que inicializar el port_keyboard_init(), porque de lo contrario, no podremos leer qué tecla es inválida según el layout!

  • Un valor de invalid_key significa que no ha habido una nueva pulsación del teclado.

  • El usuario debe reiniciar el valor de tecla pulsada una vez leído, de lo contrario, este valor puede ser malinterpretado por el usuario si se realizan sucesivas comprobaciones sin haber pulsado el teclado. Es análogo a lo que hacíamos con el botón. Para reiniciar el valor se debe llamar a la función fsm_keyboard_reset_key_value().

  • La FSM contiene información del identificador (ID) del teclado que maneja. Este ID es único y gestionado por el usuario en el PORT. Ahí es donde el usuario proporciona identificadores e información HW (GPIOs a la que está conectado y tiempo de anti-rebotes) para todos los teclados de su sistema.

Nuestra librería implementa la lógica de la FSM mostrada en el diagrama de la figura y que llamaremos fsm_keyboard (en los ficheros .c y .h). Es análoga a la del botón, salvo por una autotransición en el primer estado. Tiene 4 estados porque implementa también un mecanismo anti-rebotes SW. Nos centramos en la descripción de ese primer estado y su autotransición, para el resto, vaya la descripción hecha en las consideraciones de la FSM del botón:

  • KEYBOARD_RELEASED_WAIT_ROW: es el estado inicial de la máquina de estados, y es al estado al que vuelve cuando se pulsa ¡y se suelta! una tecla. En este estado, además, se comprueba si ha pasado el timeout de excitación de filas (flag flag_row_timeout), y si es así, se excita la siguiente fila del teclado, permaneciendo en este estado (autotransición). Esta excitación se hace llamando a la función port_keyboard_excite_next_row(). Si, durante la excitación de una fila, se detecta una interrupción de subida en alguna columna, se guardará el tick de tiempo en el que se pulsó la tecla; transcurrido el tiempo suficiente para evitar rebotes, al soltar y producirse una interrupción en flanco de bajada, se llamará a do_set_key_value() que guardará la tecla pulsada tras pedírselo al PORT.

    Será el usuario en su programa principal quien deba reiniciar el valor de tecla pulsada una vez leído, llamando a la función fsm_keyboard_reset_key_value(), como se hacía con el botón.

La parte COMMON de nuestra librería trabaja con la estructura (struct) pública que se muestra en la figura (b) de estructuras.

  1. Descarga del repositorio los ficheros correspondientes a la parte COMMON de la librería del teclado correspondientes a la versión V2: https://github.com/sdg2DieUpm/simone/tree/simone_v2. Solo descarga lo que faltaba por implementar, es decir, los ficheros fsm_keyboard.h y fsm_keyboard.c y ponlos en las carpetas correspondientes de tu proyecto.

    Ahora, vamos a completar la cabecera de la FSM del teclado, fsm_keyboard.h.

  2. Incluye las librerías necesarias, si falta alguna, según indique la API.

  3. Escribe el enum FSM_KEYBOARD con los nombres de los estados del diagrama de la separados por ,. No olvides poner un ; al final del enum.

  4. Declara la estructura fsm_keyboard_t para hacerla pública como indica la figura de la estructura de la FSM, y documenta cada campo con Doxygen.

    Continuamos con las declaraciones de funciones públicas de la librería. Procedamos:

  5. Escribe los prototipos de las funciones públicas que aparecen en la API del fichero fsm_keyboard.h y documenta cada función con Doxygen. Recuerda que la documentación va encima del nombre de cada función.

Ya hemos acabado con el encabezado. Quizás de errores al compilar. Vamos ahora a programar el fichero fuente fsm_keyboard.c.

5.7 COMMON: fuente de la FSM del teclado

Vamos a proceder con la implementación de las funciones del teclado. Deberás implementar todas las funciones públicas de las que ya has declarado el prototipo en el encabezado, y el resto de funciones privadas que aparecen en la API del fichero fsm_keyboard.c. También definiremos las variables globales y estructuras que sean necesarias. ¡Recuerda que las funciones privadas no se declaran en el .h!

  1. Incluye las cabeceras que indica la API.

    Ahora empezamos a codificar las funciones privadas de entrada o comprobación de la FSM check_.

  2. Codifica las funciones check_row_timeout(), check_keyboard_pressed(), check_keyboard_released(), y check_timeout() como se indica en la API.

    Puede ser buen momprivadento ahora para documentar las funciones con Doxygen. En este caso, como las funciones no están declaradas en el encabezado, la documentación irá en el .c, encima del nombre de cada función.

  3. Codifica las funciones do_excite_next_row(), do_store_tick_pressed(), y do_set_key_value() como se indica en la API.

    Documenta las funciones con Doxygen igual que antes.

  4. Definimos static fsm_trans_t fsm_trans_keyboard[] = ... justo después de la función do_set_key_value() siguiendo el diagrama de la FSM.

    Recuerda que debe haber una fila en la tabla por cada flecha de transición entre estados de la forma: EstadoIni, FuncCompruebaCondicion, EstadoSig, FuncAccionesSiTransicion. No olvides añadir la fila -1, NULL, -1, NULL. No olvides que el EstadoIni de la primera transición es el estado inicial de la FSM.

  5. Codifica las funciones fsm_keyboard_start_scan(), fsm_keyboard_stop_scan(), fsm_keyboard_get_key_value(), y fsm_keyboard_get_is_valid_key() como se indica en la API. Con esta última función, el usuario podrá comprobar si la última tecla pulsada es válida o no.

    Codifica también la función fsm_keyboard_reset_key_value() con la que el usuario podrá reiniciar el valor de la tecla pulsada a invalid_key tras leerla.

  6. Completa la función fsm_keyboard_init() como se indica en la API.

  7. Codifica la función fsm_keyboard_fire() de manera análoga a como se hizo en la FSM del botón.

  8. Documenta el código que esté sin comentar.

Ya hemos acabado con la programación de la librería del teclado. Toda esta lógica COMMON puede ser usada en cualquier sistema. Hemos hecho una librería de un teclado que tiene un anti-rebotes y nos devuelve el valor de la última tecla pulsada. Así pues, si compilas, no deberían aparecer errores.

5.8 COMMON Test unitario de la FSM del teclado

Vamos a probar el test del código que hemos desarrollado de la librería de la máquina de estados del teclado y probar que funciona antes de continuar con la siguiente versión. ¡Importante! Recuerda que los test que se proporcionan comprueban solo algunos aspectos esenciales, pero no son exhaustivos. Es responsabilidad del alumno comprobar que el sistema final funciona correctamente.

Descarga el fichero de test de la FSM del teclado test_fsm_keyboard.c de https://github.com/sdg2DieUpm/simone/tree/simone_v2_test. Ponlo en la carpeta test/ de tu proyecto. ¡No lo metas en stm32f4/, pues no es un test específico del microcontrolador!

  1. Con la placa Nucleo-STM32 conectada al ordenador.

  2. Pulsa sobre el icono Clean and Debug sobre la plataforma que queramos depurar (stm32f446re).

  3. En el desplegable que se abre, selecciona el test test_fsm_keyboard. Se compilará y se cargará en la placa.

  4. Ejecuta el test por completo, o pon puntos de parada si deseas ir paso a paso.

  5. Se habrá impreso por la terminal del gdb-server el resultado de las pruebas de los tests. Debería haber pasado todos los tests. Si no, lee el mensaje de error y corrige tu código hasta que pasen todas las pruebas. Si no pasan las pruebas, no continúes.

  6. Termina la depuración pulsando (image) y repite el proceso hasta que pasen todos los test.

5.9 Ejemplo de uso de la Versión 2

El test de integración no hace uso de la librería unity, sino que es como un pequeño programa de prueba sobre las funciones que hemos implementado y tiene su propio main.

En los test de integración es responsabilidad del alumno comprobar que la funcionalidad es la esperada, porque aquí no hay test unitarios que nos ayuden.

Nuestra librería de teclado devuelve el valor de la última tecla pulsada. Así pues, algunas de las comprobaciones que podemos hacer son: que todas las teclas de todas las columnas aparecen impresas por pantalla, que se reinicia adecuadamente el valor tras leerlo, que funciona el anti-rebotes…

Descarga el fichero de ejemplo example_v2.c de https://github.com/sdg2DieUpm/simone/tree/simone_v2_test. Ponlo en la carpeta example/ de tu proyecto.

Procedamos:

Para poder hacer el ejemplo del teclado matricial, necesitamos conectarlo como se muestra en el montaje de la figura. Fíjate que las filas son los pines de la izquierda si miramos el teclado de frente.

  1. Monta el circuito del teclado matricial como se muestra en la figura.

  2. Pulsa sobre el icono de depuración image y selecciona image Clean and Debug sobre la plataforma que queramos depurar (stm32f446re).

  3. En el desplegable que se abre, selecciona el test example_v2. Se compilará y se cargará en la placa.

  4. Se parará en la primera línea del main(). Ejecuta el test por completo, o pon puntos de parada si deseas ir paso a paso. Este código no termina, pues es un bucle while infinito.

  5. Abre la terminal del gdb-server para ver los mensajes que se van imprimiendo.

  6. Pulsa una a una todas las teclas del teclado.Deberías ver que se imprime por pantalla el caracter de la pulsación. Si no es así, revisa tu código.

  7. Haz distintas pruebas y asegúrate de que el comportamiento es el adecuado.

¡Hemos creado nuestra primera librería! Fíjate que es portable a cualquier plataforma solo con adaptar las funciones del PORT.

No dejes de documentar el código. Comprueba que la documentación del código se ha generado correctamente como se explica en la “Guía de instalación de herramientas para compilación multiplataforma en C” 6., o en el vídeo "[MatrixMCU] Documentación de código con Doxygen”.

Guarda una copia de su proyecto como simone_v2 para tener un punto de partida para la siguiente versión, y una copia de seguridad por si algo falla.


  1. Por ejemplo, un teclado de ordenador, una calculadora, un cajero automático, etc. No nos hacemos responsables de posibles daños que pueda ocasionar 😅. 

  2. Para un mejor contacto, el circuito de la imagen son dos pistas de cobre en zig-zag que se cortocircuitan al pulsar el botón. Lo más simple sería una cruz que no se toca, pero su contacto es menos fiable. En teclados más avanzados, puede haber circuitos adicionales para mejorar la durabilidad o la respuesta táctil. 

  3. Josué Pagán Ortiz, Pedro José Malagón Marzo, Román Cárdenas Rodríguez, and Juan José Gómez Valverde. Fundamentos teóricos de sistemas basados en microcontrolador STM32. Sistemas Digitales II, Sistemas Electrónicos. Josué Pagán Ortiz, Madrid, March 2025. URL: https://oa.upm.es/88460/

  4. STMicroelectronics. Stm32f446xc/e. Technical Report, STMicroelectronics, 2021. URL: https://www.st.com/resource/en/datasheet/stm32f446re.pdf

  5. STMicroelectronics. Rm0390 reference manual. stm32f446xx advanced arm-based 32-bit mcus. Technical Report, STMicroelectronics, 2021. URL: https://www.st.com/resource/en/reference_manual/rm0390-stm32f446xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

  6. Josué Pagán Ortiz, Pedro José Malagón Marzo, Román Cárdenas Rodríguez, Amadeo de Gracia Herranz, Sergio Esteban Romero, and Daniel Capellán Martín. Guía de instalación de herramientas para compilación multiplataforma en C. Sistemas Digitales II, Sistemas Electrónicos. Josué Pagán Ortiz, Madrid, March 2025. URL: https://oa.upm.es/92376/