En un Teensy 4.x, declarar algo como const no significa lo que muchos suponen. Le dice al compilador que esos datos no deben modificarse, pero no implica que vayan a vivir únicamente en la flash. Paul Stoffregen, de PJRC, dedicó a ese malentendido una entrega de su columna en SparkFun News, armada a partir de las consultas que responde en el foro de PJRC.
Por defecto, el Teensy 4.x pone los datos corrientes en la RAM1 salvo que se le pida otra cosa. PJRC describe la RAM1 como memoria fuertemente acoplada, optimizada para velocidad, mientras que DMAMEM ubica las variables en la RAM2 y está pensada sobre todo para búferes grandes o para acceso directo a memoria.
¿Por qué una tabla constante termina en la RAM1?
Esa distinción pesa cuando uno arma algo como una tabla de comandos.
struct cmd_pair {
const char *const command;
const char *const parameters;
const char *const descr;
const cmd_func_t func;
enum { par_yes, par_no, par_optional } par_t;
};
constexpr const cmd_pair cmd_pairs[] {
{ "help", "", "this help", cmd_help, cmd_pair::par_no },
{ "disassemble", "pc=/n=", "show current instruction",
cmd_disassemble, cmd_pair::par_yes },
};La sorpresa es que eso puede aparecer igual en la RAM1. El detalle importante es que la estructura guarda punteros. Aplicarle PROGMEM al arreglo deja los valores de los punteros en la flash, pero las cadenas de texto son objetos separados. Si esas cadenas no se colocan también en flash, el arreglo termina siendo una lista de punteros en flash que apuntan a texto en RAM.
La versión que sí queda entera en flash
La forma explícita es algo incómoda de leer, aunque queda clara.
struct cmd_pair {
const char *const command;
const char *const parameters;
const char *const descr;
enum { par_yes, par_no, par_optional } par_t;
};
PROGMEM const char str1[] = "help";
PROGMEM const char str2[] = "";
PROGMEM const char str3[] = "this help";
PROGMEM const char str4[] = "disassemble";
PROGMEM const char str5[] = "pc=/n=";
PROGMEM const char str6[] = "show current instruction";
PROGMEM const cmd_pair cmd_pairs[] {
{ str1, str2, str3, cmd_pair::par_no },
{ str4, str5, str6, cmd_pair::par_yes },
};La idea de fondo es que ahí hay siete objetos distintos, las seis cadenas más la tabla. Cada uno necesita su propia colocación en flash.
Un arreglo más limpio
La sugerencia de Stoffregen es guardar los caracteres dentro de la estructura en lugar de guardar punteros.
struct cmd_pair {
const char command[12];
const char parameters[12];
const char descr[32];
enum { par_yes, par_no, par_optional } par_t;
};
PROGMEM const cmd_pair cmd_pairs[] {
{ "help", "", "this help", cmd_pair::par_no },
{ "disassemble", "pc=/n=", "show current instruction",
cmd_pair::par_yes },
};Ahora las cadenas son parte del objeto de la estructura, así que un solo PROGMEM cubre la tabla y el texto. El costo es más espacio en flash, porque cada campo reserva su tamaño máximo. En muchos proyectos con Teensy ese cambio conviene. La flash suele ser menos escasa que la RAM1, y la sintaxis queda bastante más fácil de leer y de mantener.
¿Y por qué no usar DMAMEM?
Es la alternativa tentadora y es la herramienta equivocada para datos constantes inicializados. DMAMEM traslada el almacenamiento a la RAM2, lo que sirve para búferes grandes y para memoria accesible por acceso directo. No es la opción correcta para nombres de comandos, textos de ayuda ni cadenas de consulta. Para ese tipo de datos, PROGMEM calza mejor.
¿La flash es demasiado lenta?
No necesariamente. El acceso a flash en el Teensy 4.x no se resume en la palabra lento. El i.MX RT1062 usa un núcleo Arm Cortex-M7 con cachés de instrucciones y de datos, así que el acceso repetido a constantes que viven en flash muchas veces se resuelve desde la caché.
La RAM1 sigue siendo el lugar más rápido y más determinista para los datos que se leen todo el tiempo. Pero las tablas de comandos, los textos de ayuda, los menús y las cadenas de consulta, que casi siempre se leen y no se escriben, son buenos candidatos para la flash.
Lo que conviene recordar
PROGMEM sirve para los datos constantes que uno quiere mantener fuera de la RAM1. Cuando la estructura contiene punteros const char *, hay que tener presente que el puntero y la cadena son objetos diferentes. O cada cadena lleva su propio PROGMEM, o se rediseña la estructura para que el texto quede guardado dentro de la tabla.
El artículo está basado en el hilo why is this in RAM1? del foro de PJRC y en la conversación que siguió entre los miembros.






