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.
| Memoria | ¿Sobrevive sin energía? | Lectura | Escritura | Desgaste | Tamaño típico |
|---|---|---|---|---|---|
| Registros | No | Inmediata | Inmediata | Ninguno | Decenas de bytes |
| SRAM | No | 1 ciclo de reloj | 1 ciclo de reloj | Ninguno | 2 KB a algunos cientos de KB |
| Flash | Sí | Rápida (con ciclos de espera y caché) | Lenta: borrar un bloque (ms) y luego programar | 10 000 a 100 000 borrados por sector | 16 KB a algunos MB |
| EEPROM | Sí | Rápida (pocos ciclos) | Lenta: unos milisegundos por byte | 100 000 a 1 000 000 escrituras por byte | 256 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:
| Tipo | Dónde está | Para qué sirve | Quién lo maneja |
|---|---|---|---|
| Registros del núcleo | Dentro de la CPU | Guardan 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éricos | En el espacio de direcciones, con una dirección fija | Configurar y controlar el hardware: pines, temporizadores, comunicación | El 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:
| Regla | Qué significa | Consecuencia en el programa |
|---|---|---|
| 1. Borrada = todo en 1 | Un sector borrado contiene 0xFF en todos sus bytes | Una flash virgen se lee como 0xFF; ese valor sirve como marca de «vacío» |
| 2. Programar solo baja bits | Escribir pasa bits de 1 a 0; nunca de 0 a 1. Escribir sobre un byte ya escrito da el AND de ambos | No se puede «pisar» un dato: escribir 0xA5 sobre 0x5A deja 0x00 |
| 3. Borrar es por sector | Para 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:
| Tipo | Acceso | Densidad | Dónde se la encuentra |
|---|---|---|---|
| NOR | Aleatorio por byte: se puede ejecutar código directamente (XIP, execute in place) | Menor | La flash interna de los microcontroladores y los chips SPI de arranque |
| NAND | Por páginas y bloques; no sirve para ejecutar, y tiene bloques defectuosos de fábrica | Mucho mayor y más barata por bit | Tarjetas 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:
| Memoria | Qué es | Para qué sirve |
|---|---|---|
| ROM de arranque | Código grabado en la fábrica, imposible de modificar | El 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 fusibles | Pocos bits no volátiles que configuran el chip | Elegir 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 vez | Números de serie, claves y calibraciones de fábrica |
| Caché y búfer de captación | Una SRAM pequeña y muy rápida | Esconder 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 compartido | Có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 bus | Má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):
| Chip | Flash | SRAM | EEPROM | Observación |
|---|---|---|---|---|
| ATmega328P (Arduino Uno) | 32 KB | 2 KB | 1 KB | 8 bits; arquitectura Harvard |
| STM32F103C8 («Blue Pill») | 64 KB | 20 KB | — | Cortex-M3; el silicio suele traer 128 KB |
| STM32F411CE («Black Pill») | 512 KB | 128 KB | — | Cortex-M4 con unidad de punto flotante |
| nRF52840 | 1 MB | 256 KB | — | Cortex-M4 con radio Bluetooth Low Energy |
| RP2040 (Raspberry Pi Pico) | 2 MB, externa | 264 KB | — | Doble núcleo; el código se ejecuta desde una flash QSPI |
| ESP32 | 4 MB típicos, externa | 520 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:
| Elemento | Ejemplo | Vive en |
|---|---|---|
| Código de las funciones | void led_alternar(void) | Flash |
| Constantes y tablas | const uint8_t senos[256] = {...}; | Flash (en Cortex-M) |
| Literales de texto | "Error de sensor" | Flash (en Cortex-M) |
| Variable global inicializada | int umbral = 50; | SRAM, con el valor inicial copiado desde la flash al arrancar |
| Variable global sin inicializar | int lecturas[100]; | SRAM, puesta en cero al arrancar |
| Variable local | int i; dentro de una función | Registro o pila (SRAM) |
| Registro de periférico | GPIOC_ODR | Espacio de periféricos (no es memoria de datos) |
| Dato que debe sobrevivir al apagado | Brillo elegido por el usuario | EEPROM 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:
| Costo | SRAM | Flash | EEPROM |
|---|---|---|---|
| Tiempo de lectura | 1 ciclo | 1 ciclo con aceleradores; 2 a 4 ciclos de espera sin ellos | Pocos ciclos |
| Tiempo de escritura | 1 ciclo | Programar: decenas a cientos de µs; borrar un sector: decenas de ms | Unos 3 a 5 ms por byte |
| Desgaste | Ninguno | 104 a 105 borrados por sector | 105 a 106 escrituras por byte |
| Efecto en el sistema | Ninguno | Puede 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 bit | Alto | Bajo | Muy alto |
Algunas reglas de oro que salen de esta tabla, y que se aplican incluso cuando uno no recuerda los números:
- 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.
- 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.
- Antes de escribir, leer: si el valor ya es el que se quiere, no se escribe.
- 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.
- 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
| Qué verificar | Cómo | Resultado esperado |
|---|---|---|
| Tamaño del programa y de los datos | Ejecutar arm-none-eabi-size firmware.elf (tema 2) o leer el resumen del enlazador | text + data cabe en la flash; data + bss deja margen en la SRAM para la pila |
| Que una constante esté en flash | Buscarla 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 apagado | Guardar, cortar la alimentación, volver a conectar y leer | El mismo valor; y, tras borrar el chip, un valor «de fábrica» sensato y no 0xFF crudo |
| Que la flash esté realmente vacía | Leer una página recién borrada | Todos los bytes valen 0xFF |
| Frecuencia de escritura | Contar las escrituras con un contador de depuración o un analizador lógico en el bus de la memoria | Coincide con la cuenta de diseño (la vida útil depende de ella) |
| Síntoma | Causa probable | Qué hacer |
|---|---|---|
| Al guardar un dato en flash queda un valor distinto al escrito | No se borró el sector antes: el resultado es el AND del valor viejo con el nuevo | Borrar la página y luego programar; verificar el código de retorno de cada paso |
| El programa se «congela» un instante al guardar | La CPU está detenida por el borrado de la flash donde corre el código | Guardar 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 dato | La página de datos se superpone con el código | Reservar la página en el script del enlazador y comprobarlo en el .map |
| Un contador guardado «se pierde» a los meses de uso | Se escribió siempre en la misma dirección y se agotó su endurancia | Escribir 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 SRAM | Declararlas con PROGMEM y leerlas con pgm_read_byte |
| Un registro de estado «cambia solo» en el depurador | Leer 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
Contenido de la memoria (hexadecimal)
—
Escribí un valor.
Recorrido guiado
- Con Flash, apretá «Demo: 5A en la 0»: la dirección 0 pasa de
FFa5A. Es la escritura normal sobre una memoria borrada. - Apretá ahora «Demo: A5 en la 0»: esperabas
A5y queda00. 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. - Apretá «Borrar el sector…»: los ocho bytes del sector 0 vuelven a
FFy el contador de ese sector sube a 1, aunque solo querías cambiar un byte. EscribíA5otra vez: ahora sí queda. - Escribí el valor
0Fen la dirección 3 y despuésF0en la misma dirección: queda00. Probá el orden inverso y comprobá que sigue siendo00: el AND es conmutativo, y por eso no hay manera de «pisar» sin borrar. - 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.
- Cambiá a EEPROM: ahora escribir
A5sobre5AdaA5sin 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
- 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.
- 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.
- 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.
- 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.
- Confirmá dónde quedó cada cosa con el archivo
.mapy con el tamaño del programa, no por suposición. Una constante puede haber terminado en la SRAM sin que lo notes. - 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.
- 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
volatiley 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
constvive en flash; en un AVR hace faltaPROGMEMy 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.