A diferencia de la RAM (donde puedes reescribir un solo byte a placer), la memoria Flash de los microcontroladores es terca. Físicamente, solo puede convertir unos (1) en ceros (0). Si tienes guardado un 0x00 y quieres escribir un 0xFF, la Flash te dirá que no puede. Tienes que borrarla primero. Borrar significa regresar los transistores a estado 1 (0xFFFFFFFF).
Aquí radica el desafío Bare-Metal: No puedes borrar bytes individuales. Estás obligado a borrar un bloque entero de memoria llamado Sector. Y en el STM32F401RE (512 KB), la arquitectura de sectores es diabólicamente asimétrica.
Al igual que el Dominio VBAT, la memoria Flash está sellada lógicamente para evitar que un código corrupto (un puntero loco) se ponga a borrar el programa de tu propia máquina. Antes de realizar cualquier borrado o escritura, debes inyectar dos llaves criptográficas de 32 bits consecutivamente en el registro FLASH_KEYR.
Ejercicio 1: Vamos a crear un driver Bare-Metal para borrar y escribir en el Sector 5 (Dirección 0x08020000). Implementaremos la secuencia de desbloqueo, el comando de borrado de sector (Sector Erase), y escribiremos un arreglo de configuración simulando la temperatura del tanque de gas.
/**
* @file flash_memory.c
* @brief Driver Bare-Metal para almacenamiento persistente en Flash interna
*/
#include "stm32f401xe.h"
#include <stdint.h>
// El Sector 5 de un STM32F401 inicia en esta dirección física absoluta
#define DIR_SECTOR_5 0x08020000
#define NUM_SECTOR_5 5
/**
* @brief Desbloquea el Controlador de la Memoria Flash
*/
void flash_desbloquear(void) {
// Si la Flash está bloqueada (Bit LOCK = 1)
if (FLASH->CR & FLASH_CR_LOCK) {
// Secuencia secreta obligatoria según el Datasheet (Reference Manual)
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
}
/**
* @brief Bloquea la Flash para prevenir escrituras accidentales
*/
void flash_bloquear(void) {
FLASH->CR |= FLASH_CR_LOCK;
}
/**
* @brief Borra un sector completo (¡Ojo! Todos los bytes se volverán 0xFF)
* @param sector Número de sector (0 a 7)
*/
void flash_borrar_sector(uint8_t sector) {
// 1. Esperar a que la Flash esté libre (Busy flag)
while (FLASH->SR & FLASH_SR_BSY);
// 2. Limpiar bits de sector anteriores en el Control Register
FLASH->CR &= ~FLASH_CR_SNB;
// 3. Colocar el nuevo número de sector (Se desplaza 3 posiciones según manual)
FLASH->CR |= (sector << FLASH_CR_SNB_Pos);
// 4. Activar la orden de Borrado de Sector (Sector Erase)
FLASH->CR |= FLASH_CR_SER;
// 5. Apretar el gatillo (Start)
FLASH->CR |= FLASH_CR_STRT;
// 6. ADVERTENCIA BARE-METAL: El CPU se quedará paralizado aquí.
// Borrar un sector de 128KB toma casi 1 segundo o más.
// Durante este tiempo no se pueden procesar interrupciones eficientemente.
while (FLASH->SR & FLASH_SR_BSY);
// 7. Apagar la orden de borrado para regresar a la normalidad
FLASH->CR &= ~FLASH_CR_SER;
}
/**
* @brief Escribe una Palabra (Word de 32-bits) en una dirección de Flash
* @param direccion Puntero crudo a la memoria
* @param dato Número a grabar
*/
void flash_escribir_palabra(uint32_t direccion, uint32_t dato) {
// Esperar a que la Flash esté lista
while (FLASH->SR & FLASH_SR_BSY);
// Activar modo Programación (PG)
FLASH->CR |= FLASH_CR_PG;
// El tamaño a grabar depende del voltaje PSIZE. Por defecto es x32 (Palabras)
// Escribimos directamente en la memoria física mediante cast de punteros
*(__IO uint32_t*)direccion = dato;
// Esperar a que el proceso químico del silicio termine
while (FLASH->SR & FLASH_SR_BSY);
// Apagar modo programación
FLASH->CR &= ~FLASH_CR_PG;
}
/**
* @brief Lectura directa desde Flash (¡A la velocidad de la luz!)
*/
uint32_t flash_leer_palabra(uint32_t direccion) {
// La lectura de Flash no necesita desbloqueos. Se lee como si fuera RAM.
return *(__IO uint32_t*)direccion;
}
int main(void) {
// Ejemplo: Grabando la Tabla de Compensación API para LPG
// Esto lo ejecutaríamos una sola vez, cuando el Servidor Central envía
// la orden de actualización masiva por RS485.
// 1. Romper el seguro
flash_desbloquear();
// 2. Destruir los datos viejos (Obligatorio para que pasen a 0xFF)
// ADVERTENCIA: Apaga válvulas y suspende red, el CPU se va a congelar.
flash_borrar_sector(NUM_SECTOR_5);
// 3. Escribir nuevos datos en direcciones consecutivas (+4 bytes por palabra)
flash_escribir_palabra(DIR_SECTOR_5, 0xAABBCCDD); // Metadatos versión
flash_escribir_palabra(DIR_SECTOR_5 + 4, 1450); // Densidad factor 1
flash_escribir_palabra(DIR_SECTOR_5 + 8, 1520); // Densidad factor 2
// ... Podrías guardar 32,000 parámetros de 32-bits en este sector.
// 4. Volver a proteger el sistema
flash_bloquear();
while(1) {
// Durante la venta de gas, el STM32 puede leer las direcciones de memoria
// a 84MHz para buscar el factor correcto según la temperatura.
uint32_t factor = flash_leer_palabra(DIR_SECTOR_5 + 4);
}
return 0;
}
Objetivo del día: Almacenamiento Masivo y Parametrización en Silicio.
El Gas Licuado de Petróleo (LP) cambia radicalmente de volumen dependiendo del clima. Un litro en invierno pesa distinto que un litro en verano. Por norma metrológica, tu máquina expendedora debe tener cargada una Tabla de Conversión Dinámica API 2540, que mapea temperaturas contra coeficientes de expansión. ¡Esta tabla es inmensa! No cabe en la pequeña EEPROM I2C, ni mucho menos en los Backup Registers.
Al dominar el **Controlador de Memoria Flash Interna**, has habilitado a tu Despachador para almacenar Megabytes de datos estáticos en sus propios transistores sin añadir costos de componentes SD u otras memorias. El Servidor de la gasera puede mandar la gigantesca tabla por la red RS485; tu STM32 la recibirá por el DMA, borrará el Sector 5, y quemará permanentemente los coeficientes en su cerebro. La máquina ahora operará con precisión milimétrica sin importar las estaciones del año.
0xFFFFFFFF.0x45670123 y 0xCDEF89AB en el registro FLASH_KEYR, y activar el bit LOCK inmediatamente después de terminar.