DÍA 148 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

El Límite Físico del Silicio

Una celda de memoria Flash está formada por transistores de puerta flotante. Cada vez que fuerzas a esos transistores a borrarse mediante altos voltajes, la capa de óxido microscópica que aísla los electrones se degrada. Después de 10,000 a 100,000 borrados, la capa se rompe y los electrones se escapan; la memoria se vuelve incapaz de retener datos.

Si reservas el Sector 6 (128 Kilobytes) de tu STM32 para guardar un registro diario de ventas, y lo borras completo cada día, te durará 100,000 días (273 años). ¡Excelente! Pero, si necesitas guardar parámetros en tiempo real cada 5 minutos, el sector morirá en 347 días.

Rotación Circular y el Marcador Mágico

La estrategia Bare-Metal para evitar el desgaste es el Append-Only Log (Registro de Solo Adición) con Wear Leveling Lineal.

  1. Toda la memoria borrada tiene el valor 0xFFFFFFFF.
  2. En lugar de borrar el sector para sobrescribir el dato anterior, simplemente avanzamos nuestro puntero hacia el primer espacio vacío (0xFF) que encontremos en los 128KB y escribimos ahí el nuevo dato.
  3. El dato más a la derecha (el último que escribimos antes de un espacio vacío) siempre será el dato "actual" válido. Los anteriores quedan como historial obsoleto.
  4. Solo cuando llegamos al final físico del Sector de 128KB, ejecutamos la orden de Borrado y volvemos a empezar en la posición 0.

Para diferenciar un valor válido (que por casualidad podría ser 0xFFFFFFFF) de un espacio realmente vacío, almacenamos una estructura (Struct) o un Marcador Mágico (Magic Word) al lado del dato, por ejemplo 0xAA55.

WEAR LEVELING LINEAL (APPEND-ONLY LOG) EN SECTOR FLASH Sector 6 (128 KB) Capacidad: 16,384 Bloques de 8 Bytes [0xAA55] Data: 100 Obsoleto [0xAA55] Data: 110 Obsoleto [0xAA55] Data: 115 Obsoleto . . . [0xAA55] Data: 120 VÁLIDO Puntero de Lectura [0xFFFF] 0xFFFF Vacío Siguiente Escritura Aquí [0xFFFF] 0xFFFF Vacío . . . Lógica de Inicialización en el Boot (Búsqueda de la Cabeza) for (addr = SECTOR_BASE; addr < SECTOR_END; addr += 8) { if (*(uint32_t*)addr == 0xFFFFFFFF) break; // ¡Espacio libre encontrado! }

⚙️ Ejercicios Prácticos

Ejercicio 1: Utilizaremos el Sector 6 (0x08040000, 128KB). Definiremos una estructura de registro de 8 bytes (4 para la Palabra Mágica 0xAA55BEEF y 4 para el Dato). En el arranque de la máquina, barreremos el sector de 8 en 8 bytes hasta encontrar el primer 0xFFFFFFFF para saber dónde escribir la próxima vez y cuál es el último valor guardado. Cuando el sector se llene, el driver detendrá todo, ejecutará un Borrado de Sector, y escribirá el dato nuevo al inicio.


/**
 * @file wear_leveling.c
 * @brief Algoritmo Linear Wear Leveling (Append-Only Log) para Flash STM32
 */

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

// Parámetros del Sector 6 (128 KB)
#define FLASH_SECTOR_WL_ADDR 0x08040000
#define FLASH_SECTOR_WL_NUM  6
#define FLASH_SECTOR_SIZE    (128 * 1024)

// Palabra Mágica que certifica que el dato que sigue es válido
#define MAGIC_WORD_VALID     0xAA55BEEF
#define MAGIC_WORD_EMPTY     0xFFFFFFFF

// Estructura de 8 bytes (Se alinea perfecto a la arquitectura ARM)
typedef struct {
    uint32_t magia;
    uint32_t dato_carga;
} LogRecord_t;

#define RECORD_SIZE sizeof(LogRecord_t) // 8 bytes

// Puntero en RAM que rastrea dónde nos toca escribir la próxima vez
static uint32_t ptr_proxima_escritura = FLASH_SECTOR_WL_ADDR;
static uint32_t ultimo_dato_valido = 0; // Guardamos el valor actual en RAM

// Dependencias (Día 146)
extern void flash_desbloquear(void);
extern void flash_bloquear(void);
extern void flash_borrar_sector(uint8_t sector);
extern void flash_escribir_palabra(uint32_t direccion, uint32_t dato);

/**
 * @brief Busca en la Flash el último registro válido para inicializar el puntero
 *        Debe llamarse al arrancar el Súper Bucle.
 */
void wear_leveling_inicializar(void) {
    uint32_t addr_actual = FLASH_SECTOR_WL_ADDR;
    uint32_t limite = FLASH_SECTOR_WL_ADDR + FLASH_SECTOR_SIZE;
    
    ultimo_dato_valido = 0; // Valor por defecto si todo está vacío
    
    while (addr_actual < limite) {
        
        LogRecord_t* registro_flash = (LogRecord_t*)addr_actual;
        
        // Si encontramos la zona virgen, nos detenemos
        if (registro_flash->magia == MAGIC_WORD_EMPTY) {
            break;
        }
        
        // Si encontramos un registro válido, actualizamos nuestro caché en RAM
        if (registro_flash->magia == MAGIC_WORD_VALID) {
            ultimo_dato_valido = registro_flash->dato_carga;
        }
        
        addr_actual += RECORD_SIZE; // Avanzar 8 bytes
    }
    
    // Guardamos dónde toca escribir el siguiente
    ptr_proxima_escritura = addr_actual;
    
    // Si la Flash está llena de basura o el sector llegó al final
    if (ptr_proxima_escritura >= limite) {
        // En un caso real, borraríamos el sector y guardaríamos 
        // `ultimo_dato_valido` al principio de la nueva página.
    }
}

/**
 * @brief Guarda un nuevo valor en el registro cíclico (Wear Leveling)
 * @param nuevo_dato Valor de 32-bits a guardar
 */
void wear_leveling_guardar(uint32_t nuevo_dato) {
    
    uint32_t limite = FLASH_SECTOR_WL_ADDR + FLASH_SECTOR_SIZE;
    
    // Si el valor no ha cambiado, no desgastamos la Flash innecesariamente
    if (nuevo_dato == ultimo_dato_valido) return;
    
    // 1. VERIFICAR SI HAY ESPACIO EN EL SECTOR
    if ((ptr_proxima_escritura + RECORD_SIZE) > limite) {
        // ¡El sector está completamente lleno!
        // Debemos reciclarlo. Esta operación bloquea el CPU por 1 segundo.
        
        __disable_irq(); // Apagar interrupciones por seguridad (Día 147)
        flash_desbloquear();
        
        flash_borrar_sector(FLASH_SECTOR_WL_NUM);
        
        // Reiniciamos el puntero al inicio del sector vacío
        ptr_proxima_escritura = FLASH_SECTOR_WL_ADDR;
        
        flash_bloquear();
        __enable_irq();
    }
    
    // 2. ESCRIBIR EL NUEVO DATO EN LA ZONA VACÍA
    // Desactivamos IRQ brevemente para que la escritura atómica no se corrompa
    __disable_irq();
    flash_desbloquear();
    
    // Escribir la magia y luego el dato
    flash_escribir_palabra(ptr_proxima_escritura, MAGIC_WORD_VALID);
    flash_escribir_palabra(ptr_proxima_escritura + 4, nuevo_dato);
    
    flash_bloquear();
    __enable_irq();
    
    // 3. ACTUALIZAR CACHÉ RAM Y PUNTEROS
    ultimo_dato_valido = nuevo_dato;
    ptr_proxima_escritura += RECORD_SIZE;
}

int main(void) {
    
    // Al prender la máquina, barrer el sector para recuperar la memoria
    wear_leveling_inicializar();
    
    // Imprimiríamos en la LCD el último valor conocido tras un apagón
    // lcd_imprimir_num(ultimo_dato_valido);
    
    while(1) {
        
        // Simulación: El administrador cambia el Precio por Litro desde RS485 Modbus
        // uint32_t nuevo_precio_rs485 = procesar_comando_modbus();
        // 
        // if (nuevo_precio != ultimo_dato_valido) {
        //     wear_leveling_guardar(nuevo_precio_rs485);
        // }
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Ciclo de Vida Extendido e Historial Resiliente.

Muchos equipos comerciales económicos fallan súbitamente a los 14 meses de instalación. El fabricante asegura que "se quemó la tarjeta madre", pero en el 90% de los casos, simplemente destruyeron un sector de la EEPROM por sobre-escritura masiva de variables de uso diario.

Un Sector 6 de 128 Kilobytes tiene espacio para 16,384 registros de 8 bytes. Si tu Despachador modifica una configuración crítica 10 veces al día (cambios de turno, ajustes de densidad, cambios de precio), el puntero recorrerá la memoria en 1,638 días (casi 4 años y medio) antes de tener que borrar el sector por primera vez. Considerando que la Flash soporta 100,000 borrados, le has dado a tu equipo una vida teórica útil de cientos de miles de años. Este es el grado de confiabilidad implacable que exigen las certificaciones industriales internacionales (UL, ATEX, NOM).

📝 Resumen del Día