Ayer vimos lo doloroso y lento que es escribir en una memoria EEPROM / Flash por SPI (hasta 5 milisegundos de espera y cuidado con los límites de página). La buena noticia es que leer memoria es instantáneo y sin límites.
En el hardware interno de la EEPROM, el puntero de dirección de lectura se incrementa automáticamente (Auto-increment). Esto significa que si le mandas el comando de lectura (0x03) seguido de la dirección de memoria 0x0000, la memoria te enviará el primer byte. Si mantienes el pin CS abajo y le sigues mandando pulsos de reloj (Bytes de basura 0xFF), el chip te seguirá escupiendo el byte 1, el 2, el 3... ¡hasta que leas los miles de bytes de todo el chip en una sola transacción fluida!
En un entorno industrial, cuando un equipo se reinicia o se conecta un auditor, se solicita un Volcado de Memoria. Esto consiste en extraer bloques inmensos de registros en bruto (Raw bytes) hacia un arreglo en RAM o enviarlos directamente al UART para su análisis. Con el bus SPI del STM32 corriendo a 5 Megabits por segundo, leer el estado completo de la máquina toma fracciones de microsegundo.
Ejercicio 1: Crea la función EEPROM_SequentialRead(). A diferencia de la escritura, aquí no habilitamos el WREN. Simplemente enviamos el comando, la dirección y ejecutamos un bucle for inyectando 0xFF para capturar cada byte devuelto por el chip. Luego, en el main(), utilizaremos esta función para restaurar la estructura RegistroVenta (del Día 120) en el instante que la placa recibe energía eléctrica.
/**
* @file eeprom_read.c
* @brief Volcado secuencial de memoria SPI EEPROM
*/
#include "stm32f401xe.h"
#include <stdint.h>
#define EEPROM_CMD_READ 0x03 // Leer Datos
// Dependencias de nuestro driver SPI1 (Días 119/120)
extern void SPI_CS_BAJO(void);
extern void SPI_CS_ALTO(void);
extern uint8_t spi_transferir_byte(uint8_t dato);
/**
* @brief Lee una secuencia ininterrumpida de bytes desde la memoria.
* @param direccion_16b: Dirección de inicio en la EEPROM.
* @param buffer_destino: Puntero al arreglo en SRAM donde se guardarán los datos.
* @param longitud: Cantidad de bytes a leer (Puede ser el chip entero).
*/
void EEPROM_SequentialRead(uint16_t direccion_16b, uint8_t* buffer_destino, uint16_t longitud) {
// 1. Iniciar la transacción (Despertar esclavo)
SPI_CS_BAJO();
// 2. Enviar el comando de LECTURA (0x03)
spi_transferir_byte(EEPROM_CMD_READ);
// 3. Enviar la Dirección Inicial (MSB First)
spi_transferir_byte((uint8_t)(direccion_16b >> 8)); // Parte Alta
spi_transferir_byte((uint8_t)(direccion_16b & 0xFF)); // Parte Baja
// 4. Iniciar la "Metralleta" de Reloj (Generar Dummy Bytes)
// El chip EEPROM ignorará lo que le enviamos (0xFF) y nos enviará la memoria.
for(uint16_t i = 0; i < longitud; i++) {
// Guardamos directamente en el arreglo RAM el resultado de la transferencia
buffer_destino[i] = spi_transferir_byte(0xFF);
}
// 5. Cortar la comunicación (El puntero interno de la EEPROM se detiene)
SPI_CS_ALTO();
}
// ==========================================================
// RECUPERACIÓN DE ESTADO (POWER-ON RESET RECOVERY)
// ==========================================================
// Estructura idéntica a la del Día 120
typedef struct {
uint32_t litros_despachados_x1000;
uint32_t precio_total_centavos;
uint32_t crc32; // Código de verificación de integridad
} RegistroVenta;
extern RegistroVenta transaccion_actual; // Variable global del sistema
/**
* @brief Extrae el último estado de la máquina tras un reinicio físico.
*/
void restaurar_estado_antiapagones(void) {
// Creamos un puntero "crudo" que apunte al espacio en memoria de nuestra estructura
uint8_t* array_bytes = (uint8_t*)&transaccion_actual;
uint16_t tamaño_bytes = sizeof(RegistroVenta);
// Volcamos la memoria de la EEPROM directamente encima de nuestro Struct RAM
EEPROM_SequentialRead(0x0000, array_bytes, tamaño_bytes);
// (En un sistema de grado aeroespacial, aquí verificaríamos el 'crc32'.
// Si el CRC no coincide, significa que la memoria está virgen o corrupta).
}
int main(void) {
// 1. Inicializar Hardware Base
// inicializar_relojes();
// inicializar_spi1();
// 2. RUTINA CRÍTICA DE ARRANQUE (BOOT)
restaurar_estado_antiapagones();
// 3. Lógica de reanudación
if (transaccion_actual.litros_despachados_x1000 > 0) {
// ¡Se detectó una venta interrumpida por falta de energía!
// Bloqueamos la máquina en estado "Cobro Pendiente" y lo mostramos en la LCD.
// lcd_mostrar_advertencia_cobro(transaccion_actual.precio_total_centavos);
}
while(1) {
// Máquina de estados normal...
}
return 0;
}
Objetivo del día: Resiliencia ante Cortes de Energía y Auditoría Histórica.
Ayer garantizamos que la venta en curso se guardara constantemente. Hoy cerramos el círculo con el **Protocolo de Resurrección (Boot Recovery)**. Cuando tu placa base pierde energía (intencional o accidentalmente), el Cortex-M se apaga y el hardware de las válvulas se cierra por seguridad física (Relés/Resortes). Pero al regresar la corriente, la primera tarea absoluta del main() antes de encender las luces de la HMI es realizar un volcado de memoria (Memory Dump).
Gracias al casting de punteros en C ((uint8_t*)&struct), los bytes de la EEPROM se inyectan en tu memoria SRAM armando el rompecabezas perfecto de las variables. El Despachador despierta recordando exactamente que le faltaba cobrarle $340.50 pesos al cliente de la manguera 1. Tu Tesis ya no es un "experimento"; cuenta con tolerancia a fallas de grado comercial.
SCK necesaria para recibir los datos de la EEPROM, el Maestro debe enviar bytes "Dummy" o de relleno (comúnmente 0xFF o 0x00).(uint8_t*)&estructura permite transformar tipos de datos complejos (Structs de 32 bits) en arreglos planos de 8 bits, facilitando el transporte por buses de comunicación.main() asegura la resiliencia a los reinicios por el Perro Guardián (WWDG/IWDG) o apagones eléctricos.