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.
La estrategia Bare-Metal para evitar el desgaste es el Append-Only Log (Registro de Solo Adición) con Wear Leveling Lineal.
0xFFFFFFFF.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.
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;
}
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).
0xFFFFFFFF). El algoritmo localiza la "cabeza" de la memoria buscando el primer índice que contenga este valor virgen.