1 ### **Resumen General de la Arquitectura del Software** 2 3 El código implementa un sistema embebido basado en un microcontrolador STM32. La 4 arquitectura es un sistema cooperativo, no apropiativo y controlado por eventos, 5 comúnmente conocido como "super-loop" o bucle principal. 6 7 El flujo de ejecución es el siguiente: 1. **Inicialización (`main.c` -> 8 `app_init`)**: Se configuran el hardware, los periféricos y se inicializan todas 9 las tareas (módulos) del sistema una sola vez. 2. **Bucle Infinito (`main.c` -> 10 `app_update`)**: El programa entra en un bucle infinito donde se ejecutan las 11 funciones de actualización de las tareas de manera secuencial. 3. **Base de 12 Tiempo (`stm32f1xx_it.c`)**: Una interrupción periódica del temporizador 13 `SysTick` (configurada típicamente a 1 ms) actúa como el "corazón" del sistema, 14 proporcionando una base de tiempo para que las tareas se ejecuten a intervalos 15 regulares. 4. **Modelo de Tareas**: El sistema se divide en dos tareas 16 principales: 17 * `task_sensor`: Se encarga de leer el estado de los botones, implementar un 18 antirrebote (debounce) por software y generar eventos. 19 * `task_menu`: Gestiona la lógica de un menú interactivo, procesa los 20 eventos generados por `task_sensor` y actualiza una pantalla LCD. 5. 21 **Comunicación entre Tareas**: Las tareas se comunican de forma asíncrona 22 mediante una cola de eventos (`queue`). `task_sensor` es el productor de 23 eventos y `task_menu` es el consumidor. 24 25 --- 26 27 ### **Análisis Detallado de los Archivos** 28 29 #### **1. `main.c`** Este archivo es el punto de entrada de la aplicación. 30 * **`main()`**: 31 * Llama a `HAL_Init()` y otras funciones de configuración del hardware 32 (`SystemClock_Config`, `MX_GPIO_Init`, etc.), que son generadas por 33 STM32CubeMX. 34 * Llama a **`app_init()`** una sola vez para inicializar la lógica de la 35 aplicación y todas sus tareas. 36 * Entra en un bucle infinito `while(1)` donde llama repetidamente a 37 **`app_update()`**. Esta es la esencia del planificador "super-loop". 38 39 #### **2. `stm32f1xx_it.c`** Este archivo contiene las rutinas de servicio de 40 interrupción (ISR). 41 * **`SysTick_Handler()`**: Es la función más importante para la lógica del 42 programa. Se ejecuta cada vez que el temporizador SysTick se desborda 43 (usualmente, cada 1 ms). 44 * Incrementa tres contadores de "ticks" globales y volátiles: 45 * `g_app_tick_cnt`: Un contador general para el planificador en `app.c`. 46 * `g_task_sensor_tick_cnt`: Un contador específico para la tarea del 47 sensor. 48 * `g_task_menu_tick_cnt`: Un contador específico para la tarea del menú. 49 * Estos contadores actúan como semáforos de tiempo, indicando a las 50 funciones `update` que ha transcurrido un milisegundo y que deben 51 ejecutarse. 52 53 #### **3. `app.c`** Este archivo actúa como el planificador principal o 54 "dispatcher" de tareas. 55 * **`task_cfg_list[]`**: Define un arreglo con las tareas del sistema. Cada 56 tarea tiene una función de inicialización (`task_x_init`) y una de 57 actualización (`task_x_update`). 58 * **`app_init()`**: 59 * Inicializa un contador de ciclos de alta precisión (`cycle_counter_init`) 60 para medir tiempos de ejecución. 61 * Recorre `task_cfg_list` y llama a la función `_init` de cada tarea (ej., 62 `task_sensor_init()` y `task_menu_init()`). 63 * Inicializa en `0` el `WCET` (Worst-Case Execution Time o Tiempo de 64 Ejecución de Peor Caso) para cada tarea. 65 * **`app_update()`**: 66 * Verifica si `g_app_tick_cnt` es mayor que cero. Si lo es, significa que la 67 ISR de `SysTick` ha ocurrido y es momento de ejecutar el ciclo de tareas. 68 * Dentro de un bucle `while`, procesa cada "tick" pendiente. 69 * En cada ciclo: 1. Reinicia `g_app_runtime_us` a cero. 2. Recorre la 70 lista de tareas. Para cada una: 71 * Reinicia el contador de ciclos. 72 * Ejecuta la función `_update` de la tarea (ej., 73 `task_sensor_update()`). 74 * Obtiene el tiempo de ejecución de la tarea en microsegundos. 75 * **Acumula este tiempo en `g_app_runtime_us`**. 76 * **Compara el tiempo de ejecución actual con el `WCET` almacenado. 77 Si es mayor, actualiza el `WCET`**. 3. Decrementa 78 `g_app_tick_cnt` para marcar que un tick ha sido procesado. 79 * El uso de `__asm("CPSID i")` (deshabilitar interrupciones) y `__asm("CPSIE 80 i")` (habilitar interrupciones) protege el acceso a las variables de tick 81 para evitar condiciones de carrera. 82 83 #### **4. `task_sensor.c`** Implementa una máquina de estados para leer botones, 84 incluyendo un filtro antirrebote. 85 * **`task_sensor_cfg_list[]`**: Configura los pines de los botones (ENT, NEX, 86 ESC), el estado que indica que están presionados, y los eventos que deben 87 generar. 88 * **`task_sensor_update()`**: Se ejecuta periódicamente gracias a su contador 89 `g_task_sensor_tick_cnt`. Su única función es llamar a 90 `task_sensor_statechart()`. 91 * **`task_sensor_statechart()`**: 92 * Lee el estado físico de cada botón (`HAL_GPIO_ReadPin`). 93 * Implementa una máquina de estados de 4 estados para cada botón (`UP`, 94 `FALLING`, `DOWN`, `RISING`) para filtrar el ruido (debounce). 95 * Cuando se detecta una transición válida (ej., de `UP` a `DOWN` después de 96 un tiempo de espera), no genera el evento inmediatamente. Espera un tiempo 97 (`tick_max`) en el estado `FALLING`. Si al final de ese tiempo el botón 98 sigue presionado, considera la pulsación válida. 99 * Al confirmar una pulsación válida, llama a **`put_event_task_menu()`** 100 para encolar el evento correspondiente (ej., `EV_MEN_ENT_ACTIVE`). 101 102 #### **5. `task_menu_interface.c` y `task_menu_attribute.h`** Estos archivos 103 definen la interfaz de comunicación para la tarea del menú. 104 * **`task_menu_attribute.h`**: Define los tipos de datos para los eventos 105 (`task_menu_ev_t`) y estados (`task_menu_st_t`) de la máquina de estados del 106 menú. 107 * **`task_menu_interface.c`**: Implementa una **cola de eventos** circular 108 (buffer circular) muy simple. 109 * `put_event_task_menu()`: Añade un evento a la cola (llamada por 110 `task_sensor`). 111 * `get_event_task_menu()`: Extrae un evento de la cola (llamada por 112 `task_menu`). 113 * `any_event_task_menu()`: Verifica si hay eventos pendientes en la cola. 114 115 #### **6. `task_menu.c`** Gestiona la lógica del menú y la interacción con la 116 pantalla LCD. 117 * **`task_menu_init()`**: 118 * Inicializa la cola de eventos. 119 * Inicializa la pantalla LCD (`displayInit`). 120 * Escribe los mensajes de bienvenida en la pantalla. 121 * **`task_menu_update()`**: Se ejecuta periódicamente gracias a 122 `g_task_menu_tick_cnt` y llama a `task_menu_statechart()`. 123 * **`task_menu_statechart()`**: 124 * Primero, comprueba si hay un evento en la cola con 125 `any_event_task_menu()`. Si es así, lo extrae. 126 * Implementa una máquina de estados simple con dos estados: `ST_MEN_XX_IDLE` 127 y `ST_MEN_XX_ACTIVE`. 128 * Se transita de `IDLE` a `ACTIVE` al recibir el evento `EV_MEN_ENT_ACTIVE`. 129 * En el estado `ACTIVE`, un contador de tiempo (`tick`) se decrementa. 130 Cuando llega a cero, se actualiza un número en la pantalla LCD (un 131 contador basado en `g_task_menu_cnt`) y se reinicia el contador de tiempo. 132 Esto hace que la pantalla se refresque periódicamente. 133 * Se vuelve al estado `IDLE` con el evento `EV_MEN_ENT_IDLE`. 134 135 #### **7. `display.c`** Este es el driver de bajo nivel para controlar una 136 pantalla LCD de caracteres, probablemente compatible con el estándar Hitachi 137 HD44780, en modo de 4 bits. 138 * **`displayInit()`**: Envía la secuencia de comandos necesaria para inicializar 139 la pantalla LCD (configurar modo de 4 u 8 bits, número de líneas, encender 140 display, etc.). 141 * **`displayCharPositionWrite()`**: Envía un comando para ubicar el cursor en 142 una posición (fila y columna) específica. 143 * **`displayStringWrite()`**: Envía una cadena de caracteres para ser mostrada 144 en la pantalla a partir de la posición actual del cursor. 145 * Las funciones de bajo nivel como `displayCodeWrite`, `displayPinWrite` y 146 `displayDataBusWrite` se encargan de manipular los pines GPIO para enviar los 147 datos y comandos a la pantalla. 148 149 --- 150 151 ### **Análisis de la Evolución de Variables** 152 153 A continuación, se detalla cómo evolucionan las variables que consultaste. 154 155 #### **Nota sobre `g_task_test_tick_cnt`** La variable `g_task_test_tick_cnt` 156 **no existe** en ninguno de los archivos proporcionados. Es posible que sea un 157 error tipográfico. Las variables similares que sí existen y que son 158 incrementadas por `SysTick_Handler` son: 159 * `g_app_tick_cnt` 160 * `g_task_sensor_tick_cnt` 161 * `g_task_menu_tick_cnt` 162 163 Todas ellas se incrementan en 1 cada milisegundo. Luego, son decrementadas en 164 las funciones `_update` correspondientes a medida que se procesa el trabajo 165 asociado a ese "tick". 166 167 #### **`g_app_runtime_us`** 168 * **Unidad de medida**: Microsegundos ($µs$). 169 * **Inicialización (`app_init`)**: No se inicializa aquí. 170 * **Evolución durante `app_update`**: 1. Al comienzo de cada ciclo de ejecución 171 principal (es decir, cada vez que `g_app_tick_cnt > 0`), esta variable **se 172 reinicia a `0`**. 2. Inmediatamente después, el planificador ejecuta 173 `task_sensor_update()`. El tiempo que tarda esta función en ejecutarse (medido 174 en $µs$) se suma a `g_app_runtime_us`. 3. Luego, el planificador ejecuta 175 `task_menu_update()`. Su tiempo de ejecución también se suma a 176 `g_app_runtime_us`. 4. Al final del bucle `for` que recorre las tareas, 177 `g_app_runtime_us` contiene la **suma total del tiempo de ejecución de todas 178 las tareas para un único tick de 1 ms**. 179 * **Ejemplo**: Si `task_sensor_update` tarda 15 $µs$ y `task_menu_update` 180 tarda 80 $µs$, al final del ciclo, `g_app_runtime_us` valdrá `95 µs`. En 181 el siguiente ciclo de 1 ms, se volverá a poner a cero antes de medir de 182 nuevo. Su valor fluctúa constantemente. 183 184 #### **`task_dta_list[index].WCET`** 185 * **Unidad de medida**: Microsegundos ($µs$). 186 * **Inicialización (`app_init`)**: En `app_init`, el `WCET` de cada tarea 187 (`task_dta_list[0].WCET` para el sensor y `task_dta_list[1].WCET` para el 188 menú) se inicializa a **`0`**. 189 * **Evolución durante `app_update`**: 1. Después de que una tarea (ej. 190 `task_sensor_update`) se ejecuta, su tiempo de ejecución de ese ciclo 191 (`cycle_counter_time_us`) se compara con el `WCET` almacenado para esa tarea 192 (`task_dta_list[0].WCET`). 2. **Si `cycle_counter_time_us` es mayor que el 193 `WCET` actual, el `WCET` se actualiza a este nuevo valor más alto.** 3. Si el 194 tiempo de ejecución es menor o igual, el `WCET` no cambia. 195 * **Comportamiento**: Esta variable **solo puede aumentar o mantener su 196 valor; nunca disminuye**. Representa el "récord" o el tiempo máximo que ha 197 tardado una tarea en ejecutarse desde que se encendió el sistema. Es una 198 métrica crucial en sistemas de tiempo real para garantizar que la suma de 199 los peores tiempos de ejecución de todas las tareas no exceda el período 200 del sistema (en este caso, 1 ms). Por ejemplo, si una vez la tarea del 201 menú tarda 120 $µs$ debido a una actualización de la pantalla, el `WCET` 202 para esa tarea se establecerá en 120 y no cambiará a menos que en una 203 ejecución futura tarde aún más.
