← Índice del curso

Sistemas Embebidos · Tema 28 · Bloque 6

Los tipos de memoria de un microcontrolador

En una computadora de escritorio «la memoria» es una sola cosa: unos gigabytes de RAM y un disco. En un microcontrolador son cuatro cosas distintas, con reglas distintas: los registros, la SRAM, la flash y, a veces, la EEPROM. Cada una guarda algo diferente, se escribe de otra manera, sobrevive o no a un corte de energía y se gasta de otro modo. Escribir bien firmware empieza por saber qué va en cada una y cuánto cuesta usarla.

01 · El problema de fondo

Por qué hay varias memorias

Lo ideal sería una memoria que fuera a la vez rápida, barata, grande, no volátil (que conserve los datos sin energía) y regrabable infinitas veces. No existe. Cada tecnología sacrifica algo, y por eso un microcontrolador combina varias, cada una para lo que mejor hace:

  • Lo que debe ser rapidísimo y cambia todo el tiempo vive en registros.
  • Lo que cambia mientras el programa corre (variables, pila, buffers) vive en SRAM: rápida y regrabable sin límite, pero volátil.
  • Lo que debe sobrevivir sin energía (el programa, las tablas constantes) vive en flash: no volátil y densa, pero lenta de escribir, con borrado por bloques y desgaste.
  • Los pocos datos que el equipo debe recordar y cambiar de a poco (una configuración, un contador) pueden vivir en EEPROM: no volátil y escribible por byte, pero lenta y chica.
Las memorias dentro de un microcontrolador Un núcleo con sus registros se conecta mediante buses a la flash, que guarda programa y constantes y no es volátil, a la SRAM, que guarda variables y pila y es volátil, a la EEPROM, presente en algunos chips, y a los periféricos, cuyos registros controlan el hardware. Las flechas indican que el núcleo lee la flash para ejecutar instrucciones y lee y escribe la SRAM y los registros de los periféricos. NÚCLEO (CPU)registros R0, R1…SP, PC FLASHSRAMEEPROM (opcional)PERIFÉRICOS programa y constantes · no volátil variables y pila · volátil datos de configuración · no volátil registros que controlan el hardware Cada flecha es un bus: el núcleo lee, escribe o ejecuta según la memoria El núcleo ejecuta la flash, lee y escribe la SRAM y configura los periféricos. En varios chips (STM32, nRF52, ESP32) no hay EEPROM: se emula con flash.
Resumen comparativo de las memorias de un microcontrolador (órdenes de magnitud típicos).
Memoria¿Sobrevive sin energía?LecturaEscrituraDesgasteTamaño típico
RegistrosNoInmediataInmediataNingunoDecenas de bytes
SRAMNo1 ciclo de reloj1 ciclo de relojNinguno2 KB a algunos cientos de KB
FlashSíRápida (con ciclos de espera y caché)Lenta: borrar un bloque (ms) y luego programar10 000 a 100 000 borrados por sector16 KB a algunos MB
EEPROMSíRápida (pocos ciclos)Lenta: unos milisegundos por byte100 000 a 1 000 000 escrituras por byte256 bytes a pocos KB

02 · La más rápida y la más cercana

Registros

La palabra «registro» designa en realidad dos cosas distintas que conviene no mezclar:

Los dos significados de «registro».
TipoDónde estáPara qué sirveQuién lo maneja
Registros del núcleoDentro de la CPUGuardan los operandos y resultados de las instrucciones; el contador de programa (PC), el puntero de pila (SP)El compilador: en C no se nombran, se usan sin verlos
Registros de periféricosEn el espacio de direcciones, con una dirección fijaConfigurar y controlar el hardware: pines, temporizadores, comunicaciónEl programador, mediante punteros a direcciones fijas

Los registros del núcleo son la memoria más veloz que existe: la CPU opera directamente sobre ellos. Un núcleo ARM Cortex-M tiene 13 registros de uso general (R0 a R12) más el puntero de pila, el registro de enlace y el contador de programa; un AVR de 8 bits tiene 32 registros de un byte. Son pocos justamente porque son rápidos y caros de construir: si falta lugar, el compilador «derrama» valores a la pila (a la SRAM), y el programa se vuelve más lento y más grande.

Los registros de periféricos se parecen a una memoria (se leen y escriben con una dirección) pero no se comportan como memoria. Escribir un bit puede encender un pin, y leer un registro de datos puede vaciar el dato recibido. Por eso en C se declaran con volatile: le dicen al compilador que cada lectura y cada escritura son efectos reales que no puede eliminar ni reordenar (tema 16):

/* Registro de salida del puerto C de un STM32F103: base 0x40011000, desplazamiento 0x0C */
#define GPIOC_ODR   (*(volatile uint32_t *)0x4001100C)

GPIOC_ODR |=  (1u << 13);      /* leer, poner el bit 13 en 1 y escribir: el pin PC13 pasa a nivel alto */
GPIOC_ODR &= ~(1u << 13);      /* poner el bit 13 en 0 */

/* Sin 'volatile', el compilador podría fusionar o eliminar estas escrituras
   porque, mirando solo el programa, «nadie lee ese valor». */

La consecuencia práctica: los registros de periféricos no son un lugar para guardar datos. No se los usa como variables, y su contenido tras un reinicio no es el que dejó el programa, sino el que define el hardware (los valores de reinicio de la hoja de datos).

03 · La memoria de trabajo

SRAM: la memoria de trabajo

La SRAM (RAM estática) es donde viven las variables mientras el programa corre. Cada bit se guarda en una celda de seis transistores que se mantiene mientras haya energía, sin necesidad de refrescarla (de ahí «estática», en contraste con la DRAM de una PC, que se refresca continuamente y que casi ningún microcontrolador tiene).

Sus propiedades definen cómo se programa:

  • Es rápida y simétrica. Leer y escribir cuestan lo mismo, normalmente un ciclo de reloj, y se puede hacer con cualquier tamaño de dato en cualquier dirección.
  • No se desgasta. Se puede escribir mil millones de veces por segundo sin consecuencias. Es el único lugar donde está permitido modificar datos sin pensar.
  • Es volátil. Al cortar la energía, o cuando baja demasiado la tensión (brown-out, tema 37), el contenido se pierde. Al arrancar, la SRAM contiene basura: por eso el código de inicio (tema 8) la inicializa, copiando los valores iniciales de las variables desde la flash (.data) y poniendo en cero el resto (.bss).
  • Es cara y por eso escasa. Ocupa mucha área de silicio por bit: un chip puede tener 512 KB de flash y 64 KB de SRAM, y un AVR clásico tiene apenas 2 KB. En la práctica la SRAM suele ser el recurso que se agota primero.

En la SRAM conviven tres usos que compiten por el mismo espacio y que el tema 29 va a mostrar en el mapa de memoria: las variables globales y estáticas (tamaño fijo, conocido al enlazar), la pila (variables locales y direcciones de retorno, crece y decrece con las llamadas) y el heap (memoria dinámica con malloc, que se evita en la mayoría de los sistemas embebidos, tema 30).

Una SRAM puede conservar sus datos mientras el microcontrolador duerme, con un consumo mínimo: es una de las razones por las que los modos de bajo consumo (tema 34) pueden «despertar» y seguir donde se quedaron. Y en algunos chips existe una pequeña SRAM de respaldo, alimentada por una batería o por el reloj de tiempo real, que sobrevive incluso a un reinicio completo del núcleo.

04 · La memoria del programa

Flash: el programa y los datos fijos

La flash guarda el programa (.text) y los datos de solo lectura (tablas, textos, constantes: .rodata), y también la copia inicial de las variables inicializadas. Es no volátil, densa y barata por bit, y por eso es la memoria más grande del chip. Pero tiene una forma de escritura muy particular que hay que entender a fondo.

Cómo funciona una celda

Cada bit es un transistor con una compuerta flotante: una capa aislada donde se atrapan electrones. Con carga atrapada el transistor se lee como un 0; sin ella, como un 1. Meter electrones (programar) es una operación rápida y se puede hacer bit por bit. Sacarlos (borrar) exige una tensión alta que se aplica a un bloque entero a la vez, llamado sector o página. De esta asimetría salen las tres reglas de la flash:

Las tres reglas de la flash.
ReglaQué significaConsecuencia en el programa
1. Borrada = todo en 1Un sector borrado contiene 0xFF en todos sus bytesUna flash virgen se lee como 0xFF; ese valor sirve como marca de «vacío»
2. Programar solo baja bitsEscribir pasa bits de 1 a 0; nunca de 0 a 1. Escribir sobre un byte ya escrito da el AND de ambosNo se puede «pisar» un dato: escribir 0xA5 sobre 0x5A deja 0x00
3. Borrar es por sectorPara subir un solo bit hay que borrar todo su sector (1 KB, 2 KB, 4 KB…)Cambiar un byte exige leer el sector entero, borrarlo y reescribirlo: hace falta un búfer en SRAM del tamaño del sector

El programa de Python de este tema reproduce exactamente esta regla, y el laboratorio te deja probarla: vas a ver con tus propios ojos cómo escribir dos veces seguidas en la misma dirección destruye el dato.

Escribir en la flash desde el programa

Casi nunca se escribe en la flash desde el programa de la aplicación (se la graba con el programador, tema 5), pero cuando hace falta (guardar una configuración o actualizar el firmware, temas 40 y 42), el procedimiento siempre tiene la misma forma: desbloquear, borrar, programar, bloquear. El ejemplo es para una página de 1 KB de un STM32F103 con 64 KB (la última, reservada para datos):

/* Guardar una palabra de 16 bits en la última página de un STM32F103C8 (64 KB) con la HAL de ST */
#define PAGINA_DATOS  0x0800FC00u     /* 0x08000000 + 64 KB - 1 KB */

bool guardar_config(uint16_t valor) {
    FLASH_EraseInitTypeDef e = { .TypeErase = FLASH_TYPEERASE_PAGES, .PageAddress = PAGINA_DATOS, .NbPages = 1 };
    uint32_t error_pagina;
    bool ok = false;

    HAL_FLASH_Unlock();                                        /* 1. quitar la protección contra escritura */
    if (HAL_FLASHEx_Erase(&e, &error_pagina) == HAL_OK) {      /* 2. borrar la página: TODA, aunque se cambie un dato */
        ok = (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, PAGINA_DATOS, valor) == HAL_OK);   /* 3. programar */
    }
    HAL_FLASH_Lock();                                          /* 4. volver a proteger */
    return ok;
}

uint16_t leer_config(void) {
    return *(volatile const uint16_t *)PAGINA_DATOS;           /* leer es como leer cualquier memoria */
}

Tres detalles que muerden a los principiantes. Primero, mientras se borra o programa, la flash no se puede leer en la mayoría de los microcontroladores simples: si el código que se está ejecutando está en la misma flash, la CPU se detiene durante el borrado, que puede llevar decenas de milisegundos, y mientras tanto no atiende interrupciones. Segundo, el código de reserva de la página de datos tiene que estar excluido de la zona que usa el programa (en el enlazador, tema 29): si el código llega hasta esa página, el borrado destruye el propio firmware. Tercero, un corte de energía a mitad del borrado deja la página en un estado indefinido: el tema 40 muestra cómo guardar datos de modo que ese corte no los arruine.

Desgaste y retención

Cada borrado deteriora un poco el aislante de la compuerta flotante. Los fabricantes garantizan dos números, y ninguno de los dos es «para siempre»:

  • Endurancia: cuántos ciclos de borrado/programación soporta un sector, típicamente 10 000 a 100 000. Después de eso, la celda puede dejar de retener bien.
  • Retención: cuánto tiempo conserva los datos sin energía, típicamente 10 a 20 años a temperatura moderada, menos a temperaturas altas y menos aún en un sector muy desgastado.

El desgaste se cuenta por sector, no por byte: borrar 4 KB para cambiar un byte consume un ciclo de todo el sector. Por eso los datos que cambian seguido no deben guardarse siempre en la misma dirección: se reparten entre varias posiciones (nivelación del desgaste, tema 40). La Parte 2 del programa de Python calcula cuánto dura cada alternativa.

Ejecutar desde la flash: velocidad y esperas

La flash es más lenta que el núcleo. En un STM32F103 a 72 MHz, cada lectura necesita 2 ciclos de espera (0 hasta 24 MHz, 1 hasta 48 MHz y 2 hasta 72 MHz); sin ayuda, el programa corre a un tercio de la velocidad teórica. Los fabricantes lo resuelven con búferes de captación anticipada y cachés de instrucciones: como el código se ejecuta casi siempre en orden, se leen varias instrucciones por adelantado. La contrapartida es que el tiempo de ejecución de un fragmento deja de ser perfectamente predecible, un detalle que importa para el tiempo real duro (tema 13).

Existen dos familias de flash, con usos distintos:

Flash NOR y flash NAND.
TipoAccesoDensidadDónde se la encuentra
NORAleatorio por byte: se puede ejecutar código directamente (XIP, execute in place)MenorLa flash interna de los microcontroladores y los chips SPI de arranque
NANDPor páginas y bloques; no sirve para ejecutar, y tiene bloques defectuosos de fábricaMucho mayor y más barata por bitTarjetas SD, memorias eMMC y SSD (tema 41)

05 · Los datos que hay que recordar

EEPROM y otras memorias no volátiles

La EEPROM (memoria de solo lectura programable y borrable eléctricamente) resuelve exactamente el problema de la flash: se puede escribir un byte suelto, sin tener que borrar un bloque, porque cada byte tiene su propia circuitería de borrado. A cambio, es mucho menos densa (por eso es chica) y más lenta: una escritura de un byte en un AVR tarda unos 3,4 ms. Y soporta más ciclos: típicamente 100 000 escrituras por byte en las internas, hasta un millón en las externas.

En la práctica es la memoria ideal para los datos que el equipo debe recordar (la configuración del usuario, un contador de uso, un número de serie) y para los que no sirve la SRAM, porque se pierden al cortar la energía. En AVR se usa con funciones de la biblioteca estándar:

#include <avr/eeprom.h>

#define DIR_BRILLO  ((uint8_t *)0)        /* dirección 0 de la EEPROM */

uint8_t leer_brillo(void)        { return eeprom_read_byte(DIR_BRILLO); }

void guardar_brillo(uint8_t v) {
    eeprom_update_byte(DIR_BRILLO, v);    /* 'update': lee y escribe SOLO si el valor cambió (ahorra desgaste) */
}

La función eeprom_update_byte merece subrayarse: cada escritura desgasta el byte, y escribir el mismo valor que ya está es un desgaste inútil. Leer antes de escribir es una de las técnicas más baratas para alargar la vida de cualquier memoria no volátil.

Si el chip no tiene EEPROM

Muchos microcontroladores modernos (STM32, nRF52, RP2040, ESP32) no incluyen EEPROM: el mercado se movió a la flash, que es más densa y barata. Dos soluciones comunes:

  • Emulación de EEPROM en flash: una biblioteca reserva dos o más páginas y, en lugar de reescribir un dato, agrega un registro nuevo (dirección, valor) al final de la página; cuando se llena, copia los datos vigentes a otra página y borra la anterior. Aprovecha la regla 2 de la flash (programar sin borrar es posible mientras queden bits en 1) y reparte el desgaste. El tema 40 la desarrolla.
  • EEPROM o flash externa por I²C o SPI (tema 41): un chip de pocos centavos que se agrega a la placa.

FRAM y otras tecnologías

Algunos microcontroladores (por ejemplo, la familia MSP430FR de Texas Instruments) integran FRAM (memoria ferroeléctrica): no volátil como la flash, pero escribible por byte, a la velocidad de la RAM y con una endurancia de más de 1012 ciclos, prácticamente ilimitada. Es más cara por bit y menos común, pero muestra que las reglas de este tema no son leyes de la física: son la consecuencia de una tecnología particular, y conviene leer qué tecnología usa el chip que vas a emplear.

06 · El resto de la familia

Otras memorias del chip

Además de las cuatro principales, aparecen otras con las que conviene familiarizarse, porque se cruzan con los temas siguientes:

Otras memorias que se encuentran en un microcontrolador o a su lado.
MemoriaQué esPara qué sirve
ROM de arranqueCódigo grabado en la fábrica, imposible de modificarEl primer programa que corre al encender (por ejemplo, un cargador por UART o USB que permite grabar la flash); temas 8 y 42
Bytes de opción y fusiblesPocos bits no volátiles que configuran el chipElegir el reloj, la protección de lectura del código y el nivel del brown-out; un error puede dejar el chip inaccesible
OTP (one-time programmable)Bits que se programan una sola vezNúmeros de serie, claves y calibraciones de fábrica
Caché y búfer de captaciónUna SRAM pequeña y muy rápidaEsconder la lentitud de la flash o de una memoria externa
SRAM acoplada al núcleo (TCM, CCM)SRAM con acceso garantizado de un ciclo, sin pasar por el bus compartidoCódigo crítico y pila de las rutinas de interrupción, cuando hay que tener tiempos exactos
Memorias externas (flash SPI/QSPI, PSRAM, tarjeta SD)Chips aparte, conectados por busMás espacio para código, datos, imágenes o registros; más lentas y con más consumo (tema 41). En un RP2040 o un ESP32, el programa vive en una flash externa

Aprovechar estas diferencias no es un lujo de especialistas: determina, por ejemplo, si una ISR de 5 µs se cumple siempre o a veces, o si un equipo con la protección de lectura mal puesta termina clonado.

07 · Los números reales

Cuánta memoria hay: chips reales

Los órdenes de magnitud cambian enormemente de un chip a otro, y conviene tenerlos en la cabeza antes de elegir uno o de escribir una sola línea. Los valores corresponden a las versiones más comunes de cada familia (las hojas de datos indican las variantes):

Memoria de microcontroladores de uso frecuente.
ChipFlashSRAMEEPROMObservación
ATmega328P (Arduino Uno)32 KB2 KB1 KB8 bits; arquitectura Harvard
STM32F103C8 («Blue Pill»)64 KB20 KB—Cortex-M3; el silicio suele traer 128 KB
STM32F411CE («Black Pill»)512 KB128 KB—Cortex-M4 con unidad de punto flotante
nRF528401 MB256 KB—Cortex-M4 con radio Bluetooth Low Energy
RP2040 (Raspberry Pi Pico)2 MB, externa264 KB—Doble núcleo; el código se ejecuta desde una flash QSPI
ESP324 MB típicos, externa520 KB—Wi-Fi y Bluetooth; la flash está en el módulo

Dos lecturas útiles. Primero, la proporción SRAM/flash: va desde 1:16 (ATmega328P) hasta 1:3 (STM32F103), y condiciona el estilo de programación: con 2 KB de SRAM no se usan printf con coma flotante, ni buffers grandes, ni malloc; con 128 KB, se puede ser más cómodo. Segundo, el tamaño absoluto separa las clases de problema: en un chip con 32 KB de flash se cuenta cada función, y en uno con 2 MB aparece un problema nuevo, el de la organización del código.

08 · De las memorias al código

Dónde va cada cosa en C

Cada declaración de un programa en C termina, después de compilar y enlazar, en una de estas memorias. El tema 29 va a mostrar el mapa en detalle; por ahora, la regla general (válida para un Cortex-M) es:

Dónde queda cada elemento de un programa en C en un microcontrolador típico.
ElementoEjemploVive en
Código de las funcionesvoid led_alternar(void)Flash
Constantes y tablasconst uint8_t senos[256] = {...};Flash (en Cortex-M)
Literales de texto"Error de sensor"Flash (en Cortex-M)
Variable global inicializadaint umbral = 50;SRAM, con el valor inicial copiado desde la flash al arrancar
Variable global sin inicializarint lecturas[100];SRAM, puesta en cero al arrancar
Variable localint i; dentro de una funciónRegistro o pila (SRAM)
Registro de periféricoGPIOC_ODREspacio de periféricos (no es memoria de datos)
Dato que debe sobrevivir al apagadoBrillo elegido por el usuarioEEPROM o una página de flash, escritas a propósito

La nota «en Cortex-M» importa. En los ARM, la flash y la SRAM comparten un solo espacio de direcciones (arquitectura von Neumann a nivel de programador), y una tabla const se lee con un puntero común, directamente desde la flash. En los AVR la arquitectura es Harvard: el programa y los datos están en espacios de direcciones separados, y un const sin más se copia a la SRAM al arrancar, ocupando justo el recurso más escaso. Para dejar una tabla solo en flash, hay que decirlo y leerla con una función especial:

#include <avr/pgmspace.h>

const uint8_t tabla_en_sram[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };            /* AVR: se COPIA a la SRAM (8 bytes que no se recuperan) */
const uint8_t tabla_en_flash[8] PROGMEM = { 1, 2, 4, 8, 16, 32, 64, 128 };   /* AVR: queda solo en la flash */

uint8_t potencia(uint8_t i) {
    return pgm_read_byte(&tabla_en_flash[i]);    /* hay que LEERLA con una instrucción distinta (LPM) */
}

/* En un Cortex-M, la misma tabla 'const' queda en flash sin atributos, y se lee con tabla[i] de siempre. */

La diferencia puede parecer menor, pero con 2 KB de SRAM, una tabla de 256 bytes olvidada en el lugar equivocado consume un octavo de la memoria de trabajo. Los temas 29 y 32 retoman este tipo de decisión con el mapa del enlazador a la vista.

09 · Qué cuesta cada operación

El costo de cada operación

«Cuánto cuesta» no tiene una respuesta única: hay cuatro costos distintos, y cada memoria tiene un perfil propio. Los números son órdenes de magnitud típicos de microcontroladores de uso general, y deben verificarse en la hoja de datos del chip concreto:

Los cuatro costos de cada memoria.
CostoSRAMFlashEEPROM
Tiempo de lectura1 ciclo1 ciclo con aceleradores; 2 a 4 ciclos de espera sin ellosPocos ciclos
Tiempo de escritura1 cicloProgramar: decenas a cientos de µs; borrar un sector: decenas de msUnos 3 a 5 ms por byte
DesgasteNinguno104 a 105 borrados por sector105 a 106 escrituras por byte
Efecto en el sistemaNingunoPuede detener la CPU y las interrupciones durante el borrado; necesita energía estable (corriente de pico al escribir)Una escritura puede quedar a medias si se cae la tensión
Costo por bitAltoBajoMuy alto

Algunas reglas de oro que salen de esta tabla, y que se aplican incluso cuando uno no recuerda los números:

  1. Los datos que cambian todo el tiempo van en SRAM; un dato que cambia más de unas pocas veces por hora no se guarda en memoria no volátil cada vez que cambia.
  2. Escribir en flash o EEPROM es una operación rara y deliberada, no un efecto secundario de asignar una variable. Se hace en el momento justo (por ejemplo, al apagar o cuando el usuario confirma un cambio), nunca en un bucle.
  3. Antes de escribir, leer: si el valor ya es el que se quiere, no se escribe.
  4. La escritura en flash o EEPROM se hace con la energía asegurada. Una caída a mitad de operación corrompe el dato; el tema 37 explica cómo detectar una tensión que baja, y el tema 40 cómo diseñar el formato para sobrevivir al corte.
  5. Un dato que se guarda a un ritmo constante tiene una vida útil calculable. Hacer la cuenta es parte del diseño, no una curiosidad (la Parte 2 del programa de Python lo hace).

10 · Comprobar que funciona

Verificación y diagnóstico

Comprobaciones sobre las memorias de un proyecto.
Qué verificarCómoResultado esperado
Tamaño del programa y de los datosEjecutar arm-none-eabi-size firmware.elf (tema 2) o leer el resumen del enlazadortext + data cabe en la flash; data + bss deja margen en la SRAM para la pila
Que una constante esté en flashBuscarla en el archivo .map (tema 7) o con nm: ¿en qué dirección quedó?Dirección en el rango de la flash (0x0800… en el STM32F103), no de la SRAM (0x2000…)
Que el dato sobreviva a un apagadoGuardar, cortar la alimentación, volver a conectar y leerEl mismo valor; y, tras borrar el chip, un valor «de fábrica» sensato y no 0xFF crudo
Que la flash esté realmente vacíaLeer una página recién borradaTodos los bytes valen 0xFF
Frecuencia de escrituraContar las escrituras con un contador de depuración o un analizador lógico en el bus de la memoriaCoincide con la cuenta de diseño (la vida útil depende de ella)
Síntomas frecuentes y qué revisar.
SíntomaCausa probableQué hacer
Al guardar un dato en flash queda un valor distinto al escritoNo se borró el sector antes: el resultado es el AND del valor viejo con el nuevoBorrar la página y luego programar; verificar el código de retorno de cada paso
El programa se «congela» un instante al guardarLa CPU está detenida por el borrado de la flash donde corre el códigoGuardar cuando no importe, o ejecutar la rutina de borrado desde la SRAM; con dos bancos, escribir en el que no se ejecuta
El firmware deja de funcionar tras guardar un datoLa página de datos se superpone con el códigoReservar la página en el script del enlazador y comprobarlo en el .map
Un contador guardado «se pierde» a los meses de usoSe escribió siempre en la misma dirección y se agotó su enduranciaEscribir solo si cambió, repartir entre posiciones, guardar con menos frecuencia
El programa se queda sin SRAM con constantes grandes (AVR)Las tablas const se copiaron a la SRAMDeclararlas con PROGMEM y leerlas con pgm_read_byte
Un registro de estado «cambia solo» en el depuradorLeer un registro de periférico tiene efectos (por ejemplo, vaciar un dato recibido)No inspeccionar registros con lectura destructiva sin saber qué hacen; verificar en la hoja de datos

11 · Analogía con programación

Probar el modelo en Python

El programa tiene tres partes: un modelo mínimo de flash que respeta las reglas de la compuerta flotante (se programa con AND y se borra por sector), el cálculo de cuánto dura una memoria según cada cuánto se escribe y entre cuántas posiciones se reparte el desgaste, y la verificación de si un proyecto (con su código, sus constantes y sus variables) cabe en cada chip de la tabla.

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

# Memorias de un microcontrolador: flash (se borra por sectores, solo se baja de 1 a 0), EEPROM (por byte) y desgaste.
class Flash:
    def __init__(self, sectores=4, tam_sector=8, endurancia=10_000):
        self.tam, self.endurancia = tam_sector, endurancia
        self.datos = [0xFF] * (sectores * tam_sector)            # borrada = todo en 1
        self.borrados = [0] * sectores                           # ciclos de borrado de cada sector

    def escribir(self, dir_, valor):
        """Programar: solo puede pasar bits de 1 a 0, igual que el hardware real."""
        antes = self.datos[dir_]
        self.datos[dir_] = antes & valor                         # AND: un 0 nunca vuelve a 1 sin borrar
        return antes, self.datos[dir_]

    def borrar_sector(self, n):
        for i in range(n * self.tam, (n + 1) * self.tam): self.datos[i] = 0xFF
        self.borrados[n] += 1

print("Parte 1: la flash solo baja bits")
f = Flash()
for valor in (0x5A, 0xA5):
    antes, despues = f.escribir(0, valor)
    print(f"  escribir {valor:#04x} sobre {antes:#04x} -> queda {despues:#04x}  ({antes:08b} AND {valor:08b} = {despues:08b})")
f.borrar_sector(0)
antes, despues = f.escribir(0, 0xA5)
print(f"  tras borrar el sector, escribir 0xa5 -> queda {despues:#04x}   (sectores borrados: {f.borrados})")

print()
print("Parte 2: cuánto dura un dato que se actualiza seguido")
def vida(escritura_cada_s, endurancia, posiciones=1):
    """Años hasta agotar la endurancia si se rota entre 'posiciones' direcciones."""
    segundos = endurancia * posiciones * escritura_cada_s
    return segundos / (365 * 24 * 3600)

casos = [("flash, un sector (10 000 ciclos)", 10_000), ("EEPROM interna (100 000 ciclos)", 100_000), ("EEPROM externa (1 000 000 ciclos)", 1_000_000)]
for etiqueta, e in casos:
    print(f"  {etiqueta}")
    for periodo, texto in ((1, "cada 1 s"), (60, "cada 1 min"), (3600, "cada 1 h")):
        print(f"    {texto:<11}: 1 posición = {vida(periodo, e):10.3f} años | rotando 100 posiciones = {vida(periodo, e, 100):12.2f} años")

print()
print("Parte 3: ¿entra el proyecto? (código, constantes y variables contra la memoria del chip)")
CHIPS = {                       # (flash KB, SRAM KB, EEPROM KB)
    "ATmega328P": (32, 2, 1), "STM32F103C8": (64, 20, 0), "STM32F411CE": (512, 128, 0),
    "nRF52840": (1024, 256, 0), "RP2040 + flash QSPI 2 MB": (2048, 264, 0),
}
codigo_kb, constantes_kb, variables_kb, pila_kb = 28, 6, 1.5, 0.5
print(f"  proyecto: código {codigo_kb} KB + constantes {constantes_kb} KB en flash; variables {variables_kb} KB + pila {pila_kb} KB en SRAM")
for chip, (fl, sr, ee) in CHIPS.items():
    flash_usada, sram_usada = codigo_kb + constantes_kb, variables_kb + pila_kb
    ok = flash_usada <= fl and sram_usada <= sr
    print(f"  {chip:<26} flash {flash_usada / fl * 100:6.1f} %   SRAM {sram_usada / sr * 100:6.1f} %   -> {'entra' if ok else 'NO entra'}")

Resultado, parte 1: escribir 0x5A sobre una flash borrada (0xFF) deja 0x5A; escribir 0xA5 encima no da 0xA5 sino 0x00, porque 01011010 AND 10100101 = 00000000; recién tras borrar el sector (que pasa a tener 1 borrado) se puede escribir 0xA5. Parte 2: con una sola posición, un dato que se guarda cada segundo agota los 10 000 ciclos de una flash en menos de 3 horas, y los 100 000 de una EEPROM interna en poco más de un día; guardado cada minuto, dura 0,019 años (unos 7 días) en la flash y 0,19 años (unos 2 meses) en la EEPROM; guardado cada hora, 1,14 y 11,4 años. Si se reparte el desgaste entre 100 posiciones, todo se multiplica por 100: cada minuto, 1,9 años en flash y 19 en EEPROM interna; cada hora, 114 años en flash. Parte 3: el proyecto de ejemplo (34 KB de flash y 2 KB de SRAM) no entra en el ATmega328P (106,2 % de la flash) y sí entra en los otros cuatro, usando entre el 53,1 % de la flash en el STM32F103C8 y el 1,7 % en el RP2040 con 2 MB.

Probá cambiar codigo_kb a 20: el proyecto pasa a ocupar 26 KB de flash y el ATmega328P justo entra (la SRAM queda al 100 %, sin margen alguno); si además subís variables_kb a 2, vuelve a no entrar, esta vez por la SRAM. También podés cambiar la endurancia a 1 000 000 en la Parte 2 para simular una memoria mucho más resistente. Cambiar un dato y ver el efecto en segundos es lo que hace que el cálculo de memoria sea una herramienta de diseño y no una estimación a ojo.

12 · Exploración interactiva

Flash y EEPROM

El laboratorio simula una memoria de 32 bytes, repartida en 4 sectores de 8 bytes, que podés configurar como flash o como EEPROM. Para que el desgaste sea visible, la endurancia de la demostración es de solo 10 ciclos (la real es de miles). Escribí un valor en hexadecimal en una dirección y mirá qué pasa con los bits, con el sector y con el contador de desgaste.

Escribir, borrar y desgastar

Tipo—
Última operación—
Bytes en FF (borrados)—
Ciclos (por sector o por byte)—
Mayor desgaste—

Contenido de la memoria (hexadecimal)

—

Escribí un valor.

Recorrido guiado

  1. Con Flash, apretá «Demo: 5A en la 0»: la dirección 0 pasa de FF a 5A. Es la escritura normal sobre una memoria borrada.
  2. Apretá ahora «Demo: A5 en la 0»: esperabas A5 y queda 00. Programar solo baja bits, y el resultado es el AND de lo que había con lo que se escribió. El aviso de abajo lo indica.
  3. Apretá «Borrar el sector…»: los ocho bytes del sector 0 vuelven a FF y el contador de ese sector sube a 1, aunque solo querías cambiar un byte. Escribí A5 otra vez: ahora sí queda.
  4. Escribí el valor 0F en la dirección 3 y después F0 en la misma dirección: queda 00. Probá el orden inverso y comprobá que sigue siendo 00: el AND es conmutativo, y por eso no hay manera de «pisar» sin borrar.
  5. Repetí «Escribir» y «Borrar» en el mismo sector hasta llegar a 10 borrados: el sector se declara desgastado y ya no acepta operaciones. Mirá que los otros sectores siguen con su contador en cero: el desgaste se cuenta por sector.
  6. Cambiá a EEPROM: ahora escribir A5 sobre 5A da A5 sin borrar nada, porque la EEPROM borra y escribe sola. Pero cada escritura gasta una de las 10 de ese byte. Escribí 10 veces en la dirección 0 y el byte queda desgastado, mientras que el 1 está intacto: en la EEPROM el desgaste se cuenta por byte.
Desafío: ¿cómo se guarda un contador que cambia seguido en una flash de 4 sectores?

Con una flash, el dato no se reescribe en el mismo lugar, sino que se agrega. Cada vez que cambia, se escribe un registro nuevo en la siguiente posición libre del sector (aprovechando que una posición borrada, FF, se puede programar); la posición vigente es la última escrita. Cuando el sector se llena, se borra el siguiente y se sigue ahí, y el primero se borra recién cuando hace falta. Con un sector de 8 posiciones, cada borrado se reparte entre 8 escrituras, y con 4 sectores se reparte entre 32: la vida útil se multiplica por 32. Es el principio de la emulación de EEPROM y de los sistemas de archivos livianos, y el tema 40 lo desarrolla completo, incluyendo cómo sobrevivir a un corte de energía en el medio.

13 · Método de trabajo

Método: elegir dónde va un dato

  1. Preguntá si cambia. Si es constante durante toda la vida del producto (una tabla, un texto, un coeficiente de calibración fijo), va en flash. Si cambia mientras el programa corre, va en SRAM.
  2. Preguntá si debe sobrevivir al apagado. Si sí, necesita memoria no volátil, y esa escritura debe ser rara, deliberada y con energía asegurada.
  3. Calculá la frecuencia de escritura. Tomá la endurancia del chip, dividila por las escrituras por día y obtené los años. Si no alcanza (y el producto debe durar 10 años), repartí el desgaste, escribí menos seguido o cambiá de tecnología.
  4. Elegí la tecnología según el patrón. Pocos bytes que cambian a veces: EEPROM (o su emulación). Bloques grandes que casi no cambian: flash. Muchos datos que cambian seguido: memoria externa o FRAM.
  5. Confirmá dónde quedó cada cosa con el archivo .map y con el tamaño del programa, no por suposición. Una constante puede haber terminado en la SRAM sin que lo notes.
  6. Hacé el presupuesto de memoria desde el principio: flash y SRAM, con margen, y la pila como un consumidor más. Un chip que se llena al 95 % al empezar no tiene futuro.
  7. Leé la hoja de datos del chip concreto: las cifras de endurancia, retención, tiempos de borrado y esperas son números del fabricante, no de este curso.

14 · Comprensión

Confusiones frecuentes

«Flash y RAM son lo mismo, solo que una guarda el programa»

Se parecen en que ambas se leen por dirección, pero son tecnologías distintas con reglas distintas. La RAM se escribe libremente y no se desgasta, pero se borra al cortar la energía; la flash conserva los datos, pero se escribe con reglas especiales (borrado por sector, solo 1→0) y se desgasta. Tratar la flash como RAM es la causa de la mayoría de los datos corruptos al guardar.

«Puedo escribir en la flash cada vez que cambia una variable»

A 10 000 borrados por sector, una variable que se guarda una vez por segundo en la misma posición agota la flash en menos de tres horas. Los datos que cambian seguido se mantienen en SRAM y se guardan en memoria no volátil solo en momentos específicos, y repartiendo el desgaste.

«Escribir un byte en la flash gasta un byte»

Gasta un ciclo de borrado de todo el sector, que puede tener cientos o miles de bytes, y que exige un búfer en SRAM del tamaño del sector para conservar el resto. El costo de cambiar un solo byte es el costo de reescribir el sector.

«Si lo escribí con el programador, ya está en la memoria y no se puede perder»

La flash retiene los datos durante años, no para siempre: la retención baja con la temperatura y con el desgaste. Y un sector que tiene datos importantes no está protegido de un borrado accidental si el programa tiene un error y escribe donde no debe (tema 31).

«La EEPROM es mejor que la flash porque se escribe por byte»

Es mejor para guardar pocos datos que cambian de a uno, pero es mucho más cara por bit, más chica y más lenta de escribir. Por eso casi no está en los chips modernos: la flash con una buena emulación cubre el mismo caso a menor costo. «Mejor» depende de qué se quiera guardar.

«Un registro de periférico es una variable más»

Se lee y se escribe como una variable, pero cada acceso tiene un efecto: encender un pin, iniciar una conversión, vaciar un dato. Hay que declararlo volatile, no usarlo para guardar datos del programa, y no leerlo «por curiosidad» si la lectura tiene efectos.

«Un const siempre queda en la flash»

Depende de la arquitectura. En un Cortex-M, sí. En un AVR (arquitectura Harvard), un const sin PROGMEM se copia a la SRAM al arrancar. Es el tipo de detalle que se confirma mirando el mapa de memoria, no suponiendo.

«Con más flash resuelvo la falta de SRAM»

Son recursos independientes: la flash que sobra no se puede usar como memoria de trabajo. Un programa que se queda sin SRAM se resuelve reduciendo variables, buffers y pila, moviendo constantes a la flash, o cambiando de chip; no agregando código.

15 · Práctica guiada

Ejercicios

1. ¿Dónde vive cada cosa?

En un programa para un STM32F103, indicá en qué memoria queda cada elemento: (a) const char texto[] = "Hola";, (b) static uint16_t lecturas[64];, (c) la variable local int i de un bucle, (d) el registro GPIOC_ODR, (e) la configuración que el usuario elige y debe recordarse tras apagar.

Ver solución

(a) Flash (.rodata): es una constante, y en un Cortex-M se lee directamente desde ahí. (b) SRAM, en la zona .bss (global sin inicializar, puesta en cero al arrancar): son 128 bytes. (c) Normalmente un registro del núcleo, porque el compilador lo mantiene ahí mientras dure el bucle; si faltan registros, la pila (SRAM). (d) Ninguna memoria de datos: es un registro de periférico en el espacio de direcciones del hardware. (e) Memoria no volátil: una página de flash (el chip no tiene EEPROM), escrita a propósito y con cuidado del desgaste.

2. El resultado de escribir

Una posición de flash vale 0xF0. Sin borrar, se escribe 0x3C. ¿Qué valor queda? ¿Y si antes estaba en 0xFF?

Ver solución

0xF0 AND 0x3C: en binario, 11110000 AND 00111100 = 00110000, que es 0x30. Si estaba en 0xFF (borrada), 11111111 AND 00111100 = 00111100, o sea 0x3C: se escribe sin sorpresas. Para dejar 0x3C sobre 0xF0 hay que borrar el sector entero y reescribir todo lo que no se quiera perder.

3. Vida útil de un contador

Un equipo guarda un contador de uso en una EEPROM de 100 000 ciclos, una vez cada 10 segundos, siempre en la misma dirección. ¿Cuánto dura? ¿Y si se reparte entre 50 posiciones?

Ver solución

En una posición: 100 000 × 10 s = 1 000 000 s ≈ 11,6 días. Repartido entre 50 posiciones: 50 × 1 000 000 s = 50 000 000 s ≈ 1,6 años. Es mejor, pero todavía insuficiente para un producto de diez años: hay que escribir menos seguido (por ejemplo, guardar solo cada 10 minutos o al detectar un apagado) o repartir entre más posiciones. Escribir una vez cada 10 minutos entre 50 posiciones daría casi 95 años.

4. ¿Entra en el chip?

Un proyecto necesita 40 KB de código, 8 KB de tablas constantes, 6 KB de variables y 2 KB de pila. ¿Entra en un STM32F103C8 (64 KB de flash y 20 KB de SRAM)? ¿Cuál es el uso porcentual de cada memoria?

Ver solución

Flash: 40 + 8 = 48 KB sobre 64 KB, o sea 75 %. SRAM: 6 + 2 = 8 KB sobre 20 KB, o sea 40 %. Entra en las dos. La flash, al 75 %, deja un margen razonable para crecer, pero es el recurso al que hay que mirar: si el producto va a recibir funciones nuevas, conviene saberlo antes. Si hubiera que guardar una página de datos, serían 1 KB más.

5. Una tabla en un AVR

Un programa para un ATmega328P incluye una tabla de 512 bytes declarada const uint8_t onda[512] = {...};. El enlazador informa que la SRAM está casi llena (1800 bytes usados de 2048), pero las variables de la lógica suman bastante menos. ¿Qué sospechás y cómo lo resolvés?

Ver solución

Sospecho que el const se copió a la SRAM, porque en un AVR (arquitectura Harvard) un const sin PROGMEM no queda solo en la flash: ocupa 512 bytes de los 2048 de SRAM, un cuarto de toda la memoria de trabajo. Se confirma mirando el tamaño de .data. La solución: declararla como const uint8_t onda[512] PROGMEM = {...}; y leer cada valor con pgm_read_byte(&onda[i]); así queda solo en flash y se recuperan los 512 bytes.

6. ¿Qué tecnología uso?

Elegí flash, EEPROM o FRAM para cada caso: (a) el firmware, (b) el volumen elegido por el usuario, que cambia unas veces al mes, (c) una cuenta de pasos que se actualiza varias veces por segundo y debe sobrevivir a cortes de energía, (d) una tabla de calibración grabada en fábrica.

Ver solución

(a) Flash: grande, no volátil, se escribe solo al actualizar. (b) EEPROM o su emulación en flash: un byte que casi no cambia, con ciclos de sobra. (c) FRAM (o SRAM respaldada por batería): la frecuencia agotaría una flash o una EEPROM en horas o días, y la FRAM tiene endurancia prácticamente ilimitada; la alternativa es mantener la cuenta en SRAM y guardarla solo al detectar la caída de tensión. (d) Flash (o OTP si no debe cambiar nunca): se escribe una sola vez y se lee siempre.

16 · Síntesis

Ideas para recordar

  • Un microcontrolador tiene cuatro memorias con propiedades distintas: registros, SRAM, flash y (a veces) EEPROM. Ninguna reúne a la vez velocidad, capacidad, no volatilidad y regrababilidad ilimitada.
  • Hay dos clases de registros: los del núcleo (operandos de la CPU, que maneja el compilador) y los de periféricos (que controlan el hardware, se declaran volatile y no sirven para guardar datos).
  • La SRAM es rápida, sin desgaste y volátil; guarda variables, pila y heap, y es casi siempre el recurso más escaso.
  • La flash es no volátil y densa, pero tiene tres reglas: borrada es 0xFF, programar solo baja bits (el resultado es el AND) y se borra por sector. Soporta 104 a 105 borrados y retiene datos 10 a 20 años.
  • La EEPROM se escribe por byte y aguanta más ciclos, pero es chica, lenta y cara; en muchos chips se la emula con flash, y la FRAM la supera en velocidad y endurancia.
  • Escribir en memoria no volátil es una operación rara, deliberada y con energía asegurada: se lee antes de escribir, se reparte el desgaste y se calcula la vida útil con la endurancia y la frecuencia.
  • Dónde queda cada cosa depende de la arquitectura: en un Cortex-M una tabla const vive en flash; en un AVR hace falta PROGMEM y una lectura especial para que no ocupe SRAM.
  • El tamaño de las memorias es el primer dato de diseño: 2 KB de SRAM y 520 KB piden estilos de programación distintos.

¿Listo para el tema siguiente?

Antes de avanzar, tomá la hoja de datos de un microcontrolador que tengas a mano y anotá: el tamaño de flash y de SRAM, si tiene EEPROM, la endurancia y la retención de la flash, el tamaño del sector y los ciclos de espera a su frecuencia máxima. Después hacé el presupuesto de un proyecto imaginario: qué iría en cada memoria, con qué margen, y cuántos años duraría el dato que más seguido se guarda. Si podés responder con números, ya tenés la base para leer un mapa de memoria.

En el próximo tema vemos cómo el enlazador reparte el programa en estas memorias: el mapa de memoria, con las secciones .text, .data, .bss, la pila y el heap.