DÍA 126 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

El Problema del Bucle Bloqueante en HMI

El controlador HD44780 de la LCD necesita tiempo para asimilar los datos. Cuando le envías el pulso Enable (EN), el fabricante exige que lo mantengas estable al menos unos microsegundos y que no envíes el siguiente comando muy rápido. Al usar un bucle `for` para imprimir "Hola Mundo", la función obliga al procesador a quedarse sentado esperando pacientemente (usando retardos) a que la LCD procese la 'H' antes de enviar la 'o'. En tiempo de CPU, una pausa de 2 milisegundos equivale a desperdiciar 168,000 ciclos de reloj en un STM32 a 84MHz.

Despachador Asíncrono (Time-Slicing)

La solución Bare-Metal es dividir la escritura en Rebanadas de Tiempo (Time-Slices) utilizando una Máquina de Estados (State Machine) gobernada por nuestra variable milisegundos_globales del SysTick.

En lugar de una función que imprima todo de golpe, crearemos un Búfer Circular de Transmisión (FIFO). Cuando tu código principal quiera imprimir "Hola", simplemente copiará esa palabra en el búfer y seguirá su camino. En el bucle principal, una tarea en segundo plano llamada lcd_procesar_tareas() tomará una letra del búfer, la enviará por I2C, y cambiará su propio estado a "Esperando 2ms". En la siguiente iteración del Súper Bucle, la función verá que aún no pasan los 2ms y retornará instantáneamente en 1 ciclo de reloj, permitiendo que el CPU atienda la telemetría.

ARQUITECTURA DE CPU: BLOQUEANTE VS MÁQUINA DE ESTADOS ENFOQUE NOVATO (Delay Bloqueante) Tiempo (milisegundos) Sensores lcd_print("Total: $120.00") -> TRABA EL CPU 40 ms Red RS485 Llega mensaje Modbus (¡Ignorado!) ENFOQUE BARE-METAL (Time-Slicing / Asíncrono) Tiempo (milisegundos) Sensores lcd_async(str) (Copia a FIFO) Red RS485 'T' Sensores Red RS485 'o' Mensaje Modbus (¡Atrapado!) 't' Máquina de Estados

⚙️ Ejercicios Prácticos

Ejercicio 1: Transforma el driver bloqueante del Día 125. Implementaremos un Búfer Circular (FIFO) para almacenar los caracteres que deseamos imprimir. Luego, crearemos la función lcd_procesar_tareas() gobernada por un switch-case y la variable de tiempo del sistema (SysTick). Esta función entrará y saldrá instantáneamente hasta que la pantalla termine visualmente de renderizar el mensaje de forma asíncrona.


/**
 * @file i2c_lcd_async.c
 * @brief Driver I2C LCD 20x4 No Bloqueante (Time-Slicing)
 */

#include "stm32f401xe.h"
#include <stdint.h>
#include <stdbool.h>

#define LCD_BUFFER_SIZE 128
#define LCD_I2C_ADDR    0x4E

// Variables externas requeridas
extern volatile uint32_t milisegundos_globales; // Del SysTick (Día 98)
extern void lcd_enviar_i2c(uint8_t dato);       // Envío físico I2C (Día 125)

// --- FIFO BÚFER CIRCULAR ---
char lcd_fifo[LCD_BUFFER_SIZE];
uint8_t fifo_head = 0;
uint8_t fifo_tail = 0;

// --- ESTADOS DE LA MÁQUINA (ENUM) ---
typedef enum {
    STATE_IDLE,
    STATE_SEND_HIGH_NIBBLE_EN,
    STATE_WAIT_HIGH_NIBBLE,
    STATE_SEND_LOW_NIBBLE_EN,
    STATE_WAIT_LOW_NIBBLE
} LCD_State_t;

LCD_State_t estado_lcd = STATE_IDLE;
uint32_t tiempo_espera_lcd = 0;
uint8_t caracter_actual = 0;

/**
 * @brief Función para el Súper Bucle. 
 *        Añade el string a la cola y retorna inmediatamente (0 ms delay).
 */
void lcd_escribir_async(const char* string) {
    while (*string) {
        uint8_t next_head = (fifo_head + 1) & (LCD_BUFFER_SIZE - 1);
        if (next_head != fifo_tail) { // Si hay espacio en el búfer
            lcd_fifo[fifo_head] = *string;
            fifo_head = next_head;
        }
        string++;
    }
}

/**
 * @brief El corazón del HMI. Debe llamarse en cada iteración del while(1).
 *        No bloquea el CPU. Se administra a sí mismo en el tiempo.
 */
void lcd_procesar_tareas(void) {
    
    // Si estamos en un retardo asíncrono, salimos instantáneamente
    if ((milisegundos_globales - tiempo_espera_lcd) < 2 && estado_lcd != STATE_IDLE) {
        return; 
    }
    
    // Banderas de control de la pantalla (RS=1 Datos, BL=1 Backlight)
    const uint8_t CTRL_BITS = 0x01 | 0x08; 

    switch (estado_lcd) {
        
        case STATE_IDLE:
            // ¿Hay caracteres pendientes en la cola por imprimir?
            if (fifo_tail != fifo_head) {
                caracter_actual = lcd_fifo[fifo_tail];
                fifo_tail = (fifo_tail + 1) & (LCD_BUFFER_SIZE - 1);
                estado_lcd = STATE_SEND_HIGH_NIBBLE_EN;
            }
            break;
            
        case STATE_SEND_HIGH_NIBBLE_EN:
            // Enviamos 4 bits altos con el pin Enable=1
            lcd_enviar_i2c((caracter_actual & 0xF0) | CTRL_BITS | 0x04);
            tiempo_espera_lcd = milisegundos_globales;
            estado_lcd = STATE_WAIT_HIGH_NIBBLE;
            break;
            
        case STATE_WAIT_HIGH_NIBBLE:
            // Bajamos el Enable para que la LCD procese la mitad de la letra
            lcd_enviar_i2c((caracter_actual & 0xF0) | CTRL_BITS);
            tiempo_espera_lcd = milisegundos_globales;
            estado_lcd = STATE_SEND_LOW_NIBBLE_EN;
            break;
            
        case STATE_SEND_LOW_NIBBLE_EN:
            // Enviamos 4 bits bajos con el pin Enable=1
            lcd_enviar_i2c(((caracter_actual << 4) & 0xF0) | CTRL_BITS | 0x04);
            tiempo_espera_lcd = milisegundos_globales;
            estado_lcd = STATE_WAIT_LOW_NIBBLE;
            break;
            
        case STATE_WAIT_LOW_NIBBLE:
            // Bajamos el Enable terminando la letra.
            lcd_enviar_i2c(((caracter_actual << 4) & 0xF0) | CTRL_BITS);
            tiempo_espera_lcd = milisegundos_globales;
            estado_lcd = STATE_IDLE; // ¡Letra completa! Volver a buscar más.
            break;
    }
}

int main(void) {
    // SysTick_Init();
    // inicializar_i2c1();
    // lcd_inicializar_bloqueante_boot(); // El encendido sí requiere delays.
    
    while(1) {
        
        // --- 1. Tareas Rápidas (Decisiones lógicas, RS485, Sensores) ---
        // ejecutar_telemetria();
        // vigilar_caudalimetro();
        
        // --- 2. Enviar información a la Pantalla de forma asíncrona ---
        // (En la vida real usarías banderas para no saturar la FIFO en cada ciclo)
        if (venta_completada) {
            lcd_escribir_async("Venta Terminada!    ");
            venta_completada = false;
        }
        
        // --- 3. Ejecutor (Dispatcher) de la LCD ---
        // Esta función toma menos de 1 microsegundo si no le toca hacer nada,
        // garantizando que el While(1) siga girando a velocidad extrema.
        lcd_procesar_tareas();
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Determinismo de Tiempo Real y Concurrencia de Sistema.

Un ingeniero de sistemas junior desarrolla en modo "Procedural": *Paso 1: Lee botón. Paso 2: Imprime pantalla (espera 40ms). Paso 3: Revisa red (espera 20ms).* Ese enfoque convierte un microcontrolador de 84MHz en una máquina lenta y torpe, perdiendo pulsos críticos de la turbina metrológica durante las pausas.

Al refactorizar tu Interfaz Humano-Máquina (HMI) hacia una arquitectura No Bloqueante guiada por Máquina de Estados (State Machine), lograste que tu Cortex-M atienda la pantalla "a pedacitos" intercalados con el escaneo de seguridad. Ahora, mientras la lenta pantalla Hitachi de los años 80 se toma su dulce tiempo para encender sus cristales líquidos, tu STM32 procesa miles de ecuaciones de fluidos y atiende interrupciones sin sudar. Esta es la diferencia absoluta entre código aficionado y Firmware Industrial de Nivel 3.

📝 Resumen del Día