El registro de recepción del UART (DR) tiene espacio para **un solo byte**. Si el Servidor Central envía la palabra "STOP", la letra 'S' entra al registro. Si el CPU está ocupado midiendo gas y no rescata esa 'S' rápidamente, cuando llegue la 'T' aplastará a la 'S'. Esto genera un error por hardware llamado Overrun Error (ORE), el cual bloquea permanentemente el puerto serial hasta que limpias la bandera de error a mano.
Para evitar esto sin usar Polling bloqueante, encendemos el bit RXNEIE (RX Not Empty Interrupt Enable). Cada vez que entra un byte al DR, el hardware pausa lo que esté haciendo el CPU, salta al vector de interrupción (ISR), lee la letra, la guarda en un Búfer (Arreglo en RAM) y regresa al Súper Bucle. Todo en menos de 1 microsegundo.
Si el servidor envía "PRECIO", el microcontrolador recibirá 6 interrupciones diferentes (una por cada letra). La ISR simplemente irá apilando estas letras en el arreglo rx_buffer. Pero, ¿cómo sabe el STM32 que el servidor ya terminó de hablar y que es hora de ejecutar el comando?
La industria estandariza el uso de un Carácter de Terminación. Usualmente es el Carriage Return (\r) o el Line Feed (\n). Cuando la interrupción detecta que el byte recién llegado es un \n, le inyecta un Terminador Nulo de C (\0) al final del buffer, y levanta una bandera: comando_listo = true. El main() verá esta bandera y procesará el comando completo de una sola vez.
Ejercicio 1: Modificaremos el archivo de inicialización del `USART2` del Día 130 para encender el bit de interrupción `RXNEIE` y habilitaremos el vector `USART2_IRQn` en el NVIC. Luego, crearemos la Rutina de Servicio de Interrupción para capturar letras y armar el comando. Finalmente, diseñaremos un intérprete muy ligero (Parser) para tomar decisiones basadas en el texto recibido.
/**
* @file uart_rx_interrupt.c
* @brief Recepción Asíncrona de Comandos UART (Bare-Metal Parser)
*/
#include "stm32f401xe.h"
#include <stdbool.h>
// --- ESTRUCTURAS DEL BÚFER DE RECEPCIÓN ---
#define RX_BUFFER_SIZE 64
volatile char rx_buffer[RX_BUFFER_SIZE];
volatile uint8_t rx_indice = 0;
volatile bool comando_listo = false;
// Variables simuladas del sistema
uint32_t precio_actual_x100 = 1050; // $10.50
bool bomba_bloqueada = false;
// Dependencia del Día 131 para responder a la PC
extern void Telemetria_Log(const char* nivel, const char* mensaje, uint32_t valor);
extern void uart_escribir_cadena(const char *texto);
void inicializar_usart2_interrupcion(void) {
// (Asumimos Relojes, Pines PA2/PA3 y BRR a 115200 configurados como en el Día 130)
USART2->CR1 = 0;
// Habilitar TX y RX
USART2->CR1 |= USART_CR1_TE | USART_CR1_RE;
// IMPORTANTE: Habilitar interrupción de recepción (RX Not Empty)
USART2->CR1 |= USART_CR1_RXNEIE;
// Habilitar la interrupción en el Árbitro Central (NVIC)
NVIC_SetPriority(USART2_IRQn, 3); // Prioridad media (El hardware ADC y PVD importan más)
NVIC_EnableIRQ(USART2_IRQn);
USART2->CR1 |= USART_CR1_UE; // Encender periférico
}
/**
* @brief Vector de Interrupción de Hardware del USART2
* Se ejecuta AUTOMÁTICAMENTE cada vez que llega 1 byte físico.
*/
void USART2_IRQHandler(void) {
// Verificar si la interrupción fue causada por un byte entrante (RXNE)
if (USART2->SR & USART_SR_RXNE) {
// Al leer el Data Register (DR), la bandera RXNE se limpia sola
char caracter_entrante = (char)USART2->DR;
// Ignoramos el Carriage Return, usamos el Line Feed (\n) como fin oficial
if (caracter_entrante == '\r') {
return;
}
// Evaluar Fin de Comando
if (caracter_entrante == '\n') {
rx_buffer[rx_indice] = '\0'; // Inyectar terminador nulo de C
comando_listo = true; // Levantar bandera para el main
}
else {
// Prevenir desbordamiento del arreglo (Buffer Overflow)
if (rx_indice < (RX_BUFFER_SIZE - 1)) {
rx_buffer[rx_indice] = caracter_entrante;
rx_indice++;
} else {
// Si el comando es demasiado largo (Ruido o ataque), limpiar el búfer
rx_indice = 0;
}
}
}
}
/**
* @brief Intérprete Bare-Metal de cadenas (Compara strings a nivel de byte)
* Retorna 'true' si ambas cadenas son idénticas.
*/
bool comparar_comando(volatile char* str1, const char* str2) {
while (*str1 && *str2) {
if (*str1 != *str2) return false;
str1++;
str2++;
}
return (*str1 == *str2); // Ambos deben terminar en '\0' al mismo tiempo
}
/**
* @brief Analiza y ejecuta el texto guardado en rx_buffer
*/
void procesar_comando_remoto(void) {
if (comparar_comando(rx_buffer, "PING")) {
uart_escribir_cadena("PONG\r\n");
}
else if (comparar_comando(rx_buffer, "GET_PRECIO")) {
LOG_INFO("Precio_Actual", precio_actual_x100);
}
else if (comparar_comando(rx_buffer, "STOP_BOMBA")) {
bomba_bloqueada = true;
// VALVULA_OFF(); (Día 118)
LOG_WARN("BOMBA APAGADA VIA REMOTA", 0);
}
else {
uart_escribir_cadena("ERROR: Comando Desconocido\r\n");
}
// Limpiar el estado para recibir el siguiente comando
rx_indice = 0;
comando_listo = false;
}
int main(void) {
// inicializar_relojes();
inicializar_usart2_interrupcion();
LOG_INFO("Terminal Remota Lista", 0);
while(1) {
// --- 1. METROLOGÍA DE GAS ---
// calcular_caudal(); (El CPU invierte aquí casi todo su tiempo)
// --- 2. GESTIÓN DE RED (Parser) ---
// Revisa la bandera asíncrona. Si el ISR formó un comando completo (\n), lo atiende.
if (comando_listo) {
procesar_comando_remoto();
}
}
return 0;
}
Objetivo del día: Control Bi-Direccional y Sincronización Remota (SCADA Ready).
En el Día 131 aprendimos a enviar diagnósticos a la central, pero la comunicación en la industria requiere Control y Mando. Si la oficina detecta una fuga de gas en la estación mediante las cámaras de seguridad, el administrador necesita poder presionar un "Botón Rojo de Emergencia" en el software de la PC para detener todos los Despachadores instantáneamente.
Al implementar el **UART RX por Interrupción**, tu procesador jamás se queda "sordo". Mientras tu máquina de estados sigue imprimiendo letras en la LCD y el DMA guarda presiones en RAM, si el mensaje "STOP_BOMBA\n" viaja por el cable, el `USART2_IRQHandler` secuestra momentáneamente al CPU por un microsegundo por cada letra. Al llegar la `\n`, el comando entero está servido en la RAM (`rx_buffer`). El Súper Bucle lo evalúa en la siguiente iteración y cierra la válvula, salvando el día. Tu firmware es ahora un nodo remoto totalmente controlable.
main() (Súper Bucle) para no congelar las demás interrupciones críticas de la máquina.