En sistemas embebidos, hay dos actores manejando la memoria. El Productor es usualmente el hardware (la Interrupción UART) que inserta bytes tan pronto como llegan. El Consumidor es tu Súper Bucle (main) que extrae esos bytes para analizarlos cuando la CPU está libre.
Para no pisarnos los pies, usamos dos índices independientes:
El búfer nos dice su estado basándose en la posición de ambos punteros:
Ejercicio 1: Implementaremos el búfer de recepción (RX) del UART para asegurar que ningún comando del ESP32 se pierda. Utilizaremos el modificador volatile extensivamente, protegiendo las variables que comparten el contexto principal y la Interrupción (Recordando el Día 26).
#include <stdint.h>
#include <stdbool.h>
// --- 1. DEFINICIONES DEL BÚFER FIFO ---
// El tamaño debe ser suficiente para almacenar el payload más largo del sistema.
// 64 Bytes es un estándar industrial excelente para comandos UART.
#define RX_BUFFER_SIZE 64
// Encapsulamos la lógica en una estructura global
typedef struct {
volatile uint8_t data[RX_BUFFER_SIZE]; // El arreglo físico en SRAM
volatile uint8_t head; // Índice donde escribirá la ISR
volatile uint8_t tail; // Índice donde leerá el Main
} UART_RingBuffer_t;
UART_RingBuffer_t rx_fifo = { .head = 0, .tail = 0 };
// --- 2. EL PRODUCTOR (Hardware / ISR) ---
// Esta función es disparada por el pin físico UART_RX cuando llega 1 byte.
void UART_RX_Interrupt_Handler(uint8_t byte_recibido) {
// Calculamos matemáticamente cuál sería la SIGUIENTE posición de la cabeza
uint8_t siguiente_head = (rx_fifo.head + 1) % RX_BUFFER_SIZE;
// Verificamos colisión (Buffer Lleno)
if (siguiente_head != rx_fifo.tail) {
// Hay espacio. Escribimos el byte en la RAM.
rx_fifo.data[rx_fifo.head] = byte_recibido;
// Avanzamos oficialmente la cabeza
rx_fifo.head = siguiente_head;
} else {
// ERROR: Buffer Overflow Asíncrono (Hardware más rápido que Software)
// El byte se pierde para proteger la integridad de los datos antiguos.
}
}
// --- 3. EL CONSUMIDOR (Software / Súper Bucle) ---
// Esta función la llamas tú, cuando la CPU esté disponible.
bool Leer_Byte_Del_Buffer(uint8_t* byte_destino) {
// Si la cabeza y la cola están en el mismo cajón, el búfer está vacío.
if (rx_fifo.head == rx_fifo.tail) {
return false;
}
// Extraemos el byte más antiguo (el que señala la cola)
*byte_destino = rx_fifo.data[rx_fifo.tail];
// Avanzamos la cola matemáticamente persiguiendo a la cabeza
rx_fifo.tail = (rx_fifo.tail + 1) % RX_BUFFER_SIZE;
return true; // Lectura exitosa
}
// =====================================================================
// SIMULACIÓN EN LA MÁQUINA DE ESTADOS
// =====================================================================
int main(void) {
uint8_t byte_entrante = 0;
while (1) {
// En cada vuelta del Súper Bucle, vaciamos la cola si hay datos.
// Mientras el búfer tenga datos (Read == true), procesamos.
while (Leer_Byte_Del_Buffer(&byte_entrante)) {
// Si el ESP32 mandó el comando 0xFF (Falla), reaccionamos.
if (byte_entrante == 0xFF) {
// Activar protocolos de seguridad de las válvulas
}
}
// Continuamos monitoreando la presión del gas sin habernos bloqueado
// nunca a "esperar" que el ESP32 terminara de hablar.
}
return 0;
}
Objetivo del día: Recepción Telemetría Asíncrona (Non-Blocking UART).
Un despachador de gas que utiliza funciones como UART_Receive_Wait() está condenado al fracaso: si el ESP32 se congela y no envía el último byte, el STM32 se quedará bloqueado esperando infinitamente, ignorando el flujo del gas. Al usar el paradigma Head/Tail en un Ring Buffer, has desacoplado el hardware del software. El hardware atrapa los datos en microsegundos y el software los lee a su propio ritmo seguro. Esta es la base de un RTOS (Real Time Operating System) construida a mano en C puro.
if (indice == MAX) { indice = 0; }, reemplazándolo por una instrucción matemática lineal sumamente rápida en la ALU.volatile es la única garantía de que el compilador no destruirá la sincronización asíncrona.