← Índice del curso

Microcontroladores · Tema 8

Memorias: Flash, SRAM y EEPROM

En una computadora todo vive en «la memoria». En un microcontrolador hay tres, con tecnologías, tamaños y reglas distintas. Elegir la correcta para cada dato evita desde un programa que no entra hasta una calibración que se pierde o una memoria que se gasta en meses.

01 · Punto de partida

Tres memorias, tres papeles

En el curso anterior comparaste RAM, ROM, EEPROM y Flash como componentes sueltos, con sus buses y sus señales de control. Un microcontrolador las integra y las usa a las tres, cada una para algo distinto. En el tema 3 las vimos en el diagrama de bloques; ahora las estudiamos con la profundidad que necesita quien programa.

Para un programador de escritorio, la memoria es una sola cosa: un espacio donde reservar variables. Aquí, dónde se guarda un dato determina cuánto dura, cuánto tarda en escribirse y cuántas veces se puede cambiar. Un error de elección no siempre da un fallo inmediato: a veces el programa funciona semanas y después se degrada.

Retomamos el curso de circuitos secuenciales y memoria (EEPROM y Flash), el mapa de memoria del tema 3 y la salida de avr-size del tema 5.

02 · Panorama

Comparación general

Las tres memorias del ATmega328P. Los valores son los que indica su hoja de datos.
CaracterísticaFlashSRAMEEPROM
Tamaño32 KB2 KB1 KB
¿Se conserva sin energía?SíNoSí
LecturaA la velocidad de la CPU2 ciclos (LD/ST)Rápida, con registros de control
EscrituraPor páginas de 128 bytes, con borrado previoInmediata, byte a bytePor byte, unos milisegundos
Vida útil10 000 ciclos de borrado/escrituraIlimitada100 000 ciclos por celda
Retención de datos20 años a 85 °CMientras haya alimentación20 años a 85 °C
Qué guardaPrograma y constantesVariables, pila, heapConfiguración y datos persistentes

La regla de oro: el programa y los datos que no cambian van en Flash; los datos que cambian todo el tiempo van en SRAM; los datos que cambian de vez en cuando y deben sobrevivir al apagado van en EEPROM.

03 · El programa

La memoria Flash

La Flash guarda las instrucciones que ejecuta la CPU. Está organizada en páginas de 128 bytes (64 palabras) y tiene una restricción física fundamental: borrar pone todos los bits de una página en 1, y escribir solo puede pasar bits de 1 a 0. Para cambiar un byte de 0 a 1 hay que borrar la página completa y reescribirla.

De ahí salen varias consecuencias:

  • La Flash tolera unos 10 000 ciclos de borrado y escritura. Alcanza de sobra para grabar el programa muchas veces, pero no para guardar un dato que cambia continuamente.
  • Se lee a la velocidad de la CPU, porque de allí salen las instrucciones. En un AVR, leer una constante de la Flash exige instrucciones especiales (LPM), porque su espacio de direcciones es distinto del de la SRAM.
  • El programa puede escribir su propia Flash mediante la instrucción SPM, pero solo desde la sección de arranque (boot section). Es el mecanismo del cargador de arranque del tema 5.

El tamaño de la Flash se reparte entre el cargador de arranque (opcional, de 0,5 a 4 KB según se configure), el código de inicio, tu programa, las rutinas de biblioteca y las constantes. Hay un costo oculto: una variable global inicializada ocupa Flash y SRAM. El valor inicial se guarda en la Flash y el código de inicio lo copia a la SRAM antes de llamar a main().

04 · Los datos vivos

La SRAM: variables, heap y pila

La SRAM es memoria de acceso rápido y escritura libre, pero volátil: se pierde al cortar la energía. En el ATmega328P ocupa las direcciones 0x0100 a 0x08FF y se reparte, de abajo hacia arriba, en cuatro regiones:

Organización de la SRAM del ATmega328P Desde la dirección 0x0100 hacia arriba: variables inicializadas, variables en cero, heap que crece hacia arriba, zona libre, y la pila que crece hacia abajo desde 0x08FF. .data.bssheaplibrepila globales convalor inicial globales encero malloc()crece ↑ llamadas ylocales ↓ 0x01000x08FF las dos zonas crecen una hacia la otra Si se encuentran, el programa se corrompe sin avisar.
  • .data: variables globales y estáticas con valor inicial (uint8_t contador = 5;). Su valor inicial viaja en la Flash.
  • .bss: globales sin valor inicial; el código de inicio las pone en cero.
  • Heap: memoria dinámica (malloc, new). Crece hacia direcciones mayores.
  • Pila: direcciones de retorno, variables locales y registros guardados. Empieza en RAMEND (0x08FF) y crece hacia abajo.

El enlazador sabe cuánto ocupan .data y .bss (es lo que informa avr-size como «Data»). El heap y la pila, en cambio, dependen de lo que ocurra al ejecutar: el compilador no puede decir cuánta pila usará un programa con interrupciones y llamadas anidadas. Por eso se diseña con margen.

05 · Los datos que sobreviven

La EEPROM

La EEPROM guarda pocos datos que deben persistir: calibraciones, configuración elegida por el usuario, un contador de horas de uso, el último estado antes de un apagado. Se lee con rapidez y se escribe byte por byte, pero una escritura tarda unos milisegundos (alrededor de 3,4 ms según la hoja de datos) y gasta vida útil de esa celda: 100 000 ciclos.

Cien mil escrituras parecen muchas, pero un programa descuidado las gasta rápido. Si un contador se guarda una vez por segundo en el mismo byte, la celda llega al límite en 100 000 s, es decir, unas 28 horas. Las estrategias para alargar la vida útil son tres:

  1. Escribir solo cuando el valor cambia. La función eeprom_update_byte() lee primero y solo escribe si el dato es distinto.
  2. Escribir menos seguido. Guardar al apagar (con un detector de falla de alimentación) en lugar de cada vuelta del bucle.
  3. Repartir el desgaste (wear leveling): rotar la posición de escritura entre varias celdas, de modo que cada una recibe una fracción de las escrituras.

Si se corta la energía durante una escritura

Una escritura de unos milisegundos puede quedar a medias si la tensión cae. Un byte corrupto es el peor tipo de fallo porque parece un dato válido. Dos defensas habituales: guardar un código de verificación (suma de comprobación) junto con el dato, y descartar los datos cuya verificación falla; y mantener dos copias alternadas, de modo que siempre exista una anterior íntegra. El detector de caída de tensión (brown-out), que veremos con el reset, evita que el chip siga operando con una tensión insuficiente.

06 · Decidir

Dónde va cada dato

Criterio de elección para los datos de un proyecto.
DatoMemoriaMotivo
El código del programaFlashNo cambia durante la ejecución y debe sobrevivir al apagado
Tabla de constantes (senos, colores, caracteres de una pantalla)Flash, con PROGMEMNo cambia; en SRAM gastaría memoria escasa
Textos de mensajesFlashPueden ocupar cientos de bytes que la SRAM no tiene
Contadores, estados, buffers de lecturaSRAMCambian todo el tiempo; no se guardan al apagar
Variables locales y retornos de funciónSRAM (pila)Existen mientras la función se ejecuta
Calibración del sensor, configuración del usuarioEEPROMCambia rara vez y debe sobrevivir al apagado
Registro histórico de muchos datosEEPROM o memoria externaSi el volumen o la frecuencia superan lo permitido, se agrega un chip de memoria

Una pregunta práctica sirve para decidir: ¿cuánto cambia y cuánto debe durar? Si cambia siempre y puede perderse, SRAM. Si no cambia nunca, Flash. Si cambia a veces y no puede perderse, EEPROM.

07 · En el código

Declarar cada memoria en C

Con avr-libc se indica en la declaración dónde vive cada dato:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>

const uint8_t seno[] PROGMEM = {0, 25, 49, 71, 90, 106, 117, 124, 127};  // tabla en Flash
uint8_t EEMEM calibracion;       // variable en EEPROM
uint8_t contador = 5;            // .data: ocupa SRAM y también Flash (valor inicial)
uint8_t lecturas[64];            // .bss: SRAM, inicia en cero

int main(void) {
    uint8_t v = pgm_read_byte(&seno[3]);          // leer una constante de la Flash
    eeprom_update_byte(&calibracion, v);          // escribir en EEPROM solo si cambió
    uint8_t c = eeprom_read_byte(&calibracion);   // leer de la EEPROM
    while (1) { lecturas[0] = c + contador; }
}
  • PROGMEM coloca la tabla en la Flash. Para leerla se usa pgm_read_byte() (o pgm_read_word()), que genera la instrucción LPM. Si leés una variable declarada con PROGMEM como si fuera normal, obtenés basura.
  • EEMEM asigna una posición de la EEPROM. Se accede con funciones, no directamente.
  • Una constante declarada solo con const no se mueve a la Flash en un AVR: sigue copiándose a la SRAM. Es una particularidad de esta arquitectura Harvard; en la mayoría de los ARM, const alcanza.

El efecto en el presupuesto es grande. Una tabla de 512 bytes en SRAM consume la cuarta parte de los 2 KB; en Flash cuesta lo mismo que en SRAM, pero de una memoria 16 veces más grande.

08 · Analogía con programación

Modelar las reglas físicas de la Flash y la EEPROM

Dos clases de Python reproducen las reglas esenciales. La primera modela la Flash: programar es un AND, y solo el borrado de página devuelve los bits a 1. La segunda cuenta el desgaste de la EEPROM y compara escribir con actualizar, que es lo que hace eeprom_update_byte().

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

class Flash:
    """Memoria Flash: borrar pone todo en 1; programar solo puede pasar bits de 1 a 0."""

    PAGINA = 128

    def __init__(self, paginas=2):
        self.mem = bytearray([0xFF] * (paginas * self.PAGINA))
        self.borrados = 0

    def borrar_pagina(self, pagina):
        inicio = pagina * self.PAGINA
        self.mem[inicio:inicio + self.PAGINA] = bytes([0xFF] * self.PAGINA)
        self.borrados += 1

    def programar(self, direccion, valor):
        self.mem[direccion] &= valor          # un AND: solo se apagan bits


flash = Flash()
flash.programar(0, 0xA5)
print(f"Escribo 0xA5 sobre 0xFF  -> {flash.mem[0]:#04x}")
flash.programar(0, 0x5A)                      # sin borrar antes
print(f"Escribo 0x5A sin borrar  -> {flash.mem[0]:#04x}   (0xA5 AND 0x5A)")
flash.borrar_pagina(0)
flash.programar(0, 0x5A)
print(f"Borro la página y escribo -> {flash.mem[0]:#04x}   (borrados: {flash.borrados})")


class Eeprom:
    """EEPROM: cada escritura real gasta ciclos de vida de esa celda."""

    def __init__(self, tam=8):
        self.mem = bytearray([0xFF] * tam)
        self.desgaste = [0] * tam

    def escribir(self, direccion, valor):
        self.mem[direccion] = valor
        self.desgaste[direccion] += 1

    def actualizar(self, direccion, valor):  # como eeprom_update_byte()
        if self.mem[direccion] != valor:
            self.escribir(direccion, valor)


a, b = Eeprom(), Eeprom()
for hora in range(1000):
    temperatura = 21 + (hora // 100)           # solo cambia 9 veces
    a.escribir(0, temperatura)
    b.actualizar(0, temperatura)
print(f"1000 lecturas -> write: {a.desgaste[0]} ciclos gastados, update: {b.desgaste[0]}")
print(f"Vida restante (de 100 000): write {100_000 - a.desgaste[0]}, update {100_000 - b.desgaste[0]}")

Resultado: escribir 0x5A sobre 0xA5 sin borrar da 0x00, no 0x5A; con borrado previo, sí. Y con 1000 lecturas de las que solo cambian 10, escribir gasta 1000 ciclos y actualizar solo 10. Probá hacer que la temperatura cambie en todas las lecturas y observá cómo desaparece la ventaja.

El modelo omite que en la Flash real se borra y escribe por páginas completas de 128 bytes, con un buffer intermedio, y que cada operación tarda milisegundos. Pero basta para entender por qué un contador no puede vivir en Flash: cada cambio exigiría borrar una página.

09 · Exploración interactiva

Desgaste y mapa de la SRAM

La primera herramienta calcula cuánto dura una celda que se reescribe periódicamente, con o sin update y repartiendo el desgaste. La segunda dibuja la SRAM del ATmega328P con los tamaños de cada región.

Banco de memoria

1. ¿Cuánto dura la memoria?

Vida útil estimada—
Ritmo de escritura—

2. Mapa de la SRAM (2048 bytes)

SRAM libre—
Direcciones—

El cálculo de vida útil supone que las escrituras se reparten por igual entre las posiciones y que la especificación de ciclos es un mínimo garantizado por celda, como indica el fabricante. No modela la corrupción por cortes de energía ni la temperatura.

Recorrido guiado

  1. Con los valores iniciales (EEPROM, una vez por segundo, una posición), la vida es de unas 28 horas. Cambiá la memoria a Flash: bajan a menos de 3 horas.
  2. Subí a 100 posiciones en rotación: la vida se multiplica por 100 (unos 116 días).
  3. Pasá a «cada 10 minutos» con una posición: unos 694 días. Con 10 posiciones, 19 años.
  4. Con 1 posición y un guardado por hora, bajá la proporción de cambios a 10 %: la vida pasa a más de un siglo, porque update casi nunca escribe.
  5. En el mapa de SRAM subí la pila a 1200 bytes: las regiones no caben y aparece el desborde.
Desafío: un registro de 1 Hz que dure 10 años

Querés guardar un dato nuevo cada segundo durante 10 años. Son unos 315 millones de escrituras. Con 100 000 ciclos por celda harían falta más de 3 000 posiciones en rotación, y la EEPROM de 1 KB solo ofrece 1024. Hay que cambiar de estrategia: guardar en SRAM y volcar a la EEPROM de a bloques, reducir la frecuencia, o usar una memoria externa (una Flash serie o una FRAM, que tolera muchísimos más ciclos).

10 · Método de trabajo

Presupuesto de memoria

  1. Listá los datos del proyecto y clasificalos con la pregunta de la sección 6: ¿cuánto cambia y cuánto debe durar?
  2. Sumá Flash y SRAM con la salida de avr-size: Program contra 32 256 o 32 768 bytes; Data contra 2048.
  3. Reservá la pila. Estimá la profundidad máxima de llamadas más las interrupciones y sumá un margen. Evitá recursión.
  4. Decidí qué mover a Flash. Tablas y textos con PROGMEM. A menudo es la mayor ganancia de SRAM.
  5. Calculá el desgaste de la EEPROM para cada dato que persiste y usá update y rotación cuando haga falta.
  6. Medí la SRAM libre en ejecución. Una técnica conocida es llamar a una función que calcula la distancia entre el final del heap y la cima de la pila. Se puede hacer en distintos puntos del programa.

Con el multímetro y el osciloscopio no se ve la memoria, pero sí sus efectos: un reinicio inexplicable cada tanto o valores que se corrompen suelen indicar que la pila invadió el heap. Y una tensión de alimentación inestable durante una escritura en EEPROM explica datos persistentes dañados.

11 · Comprensión

Confusiones frecuentes

«Si la Flash guarda el programa, puedo guardar ahí mis variables»

Puede escribirse desde el programa solo en condiciones muy particulares y con una vida de 10 000 ciclos. Las variables se reservan en SRAM; lo que va a Flash son constantes y el código.

«const alcanza para ahorrar SRAM en un AVR»

No: en AVR el compilador copia las constantes a la SRAM al arrancar. Hay que usar PROGMEM y leer con las funciones pgm_read_*.

«La EEPROM se puede escribir en cada vuelta del bucle»

Cada escritura gasta vida útil y bloquea unos milisegundos. A 100 escrituras por segundo, una celda dura unos 17 minutos. Se escribe solo cuando el dato cambia y con la mínima frecuencia posible.

«Si compila y entra, la memoria está bien»

El compilador no sabe cuánta pila usará el programa ni cuánto crecerá el heap. Un programa puede compilar sin errores y fallar en ejecución por falta de pila.

«La SRAM conserva su contenido tras un reset»

No hay garantía. Aunque a veces los valores sobreviven a un reinicio breve, no se puede confiar en ello. Todo lo que debe persistir va a EEPROM.

12 · Práctica guiada

Predecí antes de comprobar

1. ¿Qué memoria?

Elegí Flash, SRAM o EEPROM para: (a) el número de serie del producto grabado en fábrica, (b) la temperatura actual, (c) una tabla de 256 valores de un seno, (d) la última configuración elegida por el usuario.

Ver solución razonada

(a) Flash (o EEPROM si lo escribe el programa de fabricación): nunca cambia. (b) SRAM: cambia constantemente. (c) Flash con PROGMEM: es constante y de 256 bytes. (d) EEPROM: cambia rara vez y debe sobrevivir al apagado.

2. Vida de la EEPROM

Un dispositivo guarda su estado una vez cada 10 minutos en una sola posición. ¿Cuánto dura la celda? ¿Y si rota entre 10 posiciones?

Ver solución

144 escrituras por día. Con una posición: 100 000 / 144 ≈ 694 días (unos 1,9 años). Con 10 posiciones: 6 940 días, casi 19 años.

3. Presupuesto de SRAM

Un programa tiene .data = 150, .bss = 600, sin heap, y una pila máxima estimada de 200 bytes. ¿Cuánta SRAM queda libre?

Ver solución

2048 − (150 + 600 + 0 + 200) = 1098 bytes libres (53,6 %). Es una situación cómoda.

4. Mover una tabla a Flash

Un programa tiene una tabla de 512 bytes en SRAM. ¿Qué porcentaje de la SRAM libera moverla a Flash con PROGMEM, y qué cambio hay que hacer en el código que la lee?

Ver solución

512 / 2048 = 25 % de la SRAM. Hay que declararla con PROGMEM y reemplazar cada acceso tabla[i] por pgm_read_byte(&tabla[i]).

5. Corte de energía

Un dispositivo guarda un contador de 16 bits en EEPROM. Se corta la alimentación después de escribir el primer byte y antes del segundo. ¿Qué problema aparece y cómo se evita?

Ver solución razonada

El contador queda con un byte nuevo y uno viejo: un valor que nunca existió. Se evita guardando junto con el dato una suma de verificación (y descartando datos con suma incorrecta) o alternando dos copias, de modo que siempre quede una íntegra. También ayuda detectar la caída de tensión antes de que sea irreversible.

13 · Síntesis

Ideas para recordar

  • El ATmega328P tiene 32 KB de Flash, 2 KB de SRAM y 1 KB de EEPROM, cada una con un papel distinto.
  • La Flash guarda programa y constantes; se borra por páginas y tolera unos 10 000 ciclos.
  • En Flash, borrar pone bits en 1 y escribir solo los pasa a 0.
  • La SRAM guarda .data, .bss, heap y pila; heap y pila crecen una hacia la otra y no hay aviso si chocan.
  • Una global inicializada ocupa Flash y SRAM; una constante grande conviene en Flash con PROGMEM.
  • La EEPROM guarda datos persistentes, con unos 100 000 ciclos por celda y escrituras de milisegundos.
  • Para cuidar la EEPROM: escribir solo si cambia, escribir poco y rotar posiciones; proteger los datos con verificación.

¿Listo para el tema 9?

Antes de avanzar, decí en qué memoria pondrías una tabla de constantes, un contador de lecturas y una calibración; calculá la vida de una celda que se escribe una vez por minuto; y dibujá de memoria el mapa de la SRAM. Si podés justificar las tres respuestas, ya sabés dónde viven los datos.

En el próximo tema vamos a ver los registros y el mapa de memoria con más detalle: cómo se organizan los registros de los periféricos y cómo se leen en la hoja de datos.