Getting [const] Data Out of RAM1 on Teensy Boards

A constant command table can still consume RAM on Teensy 4.x. Here's how to correctly store both your lookup table and its strings in flash.

On Teensy 4.x, const doesn't always mean what you might expect. It tells the compiler your data should not be modified, but it does not automatically mean the data will live only in flash.

Paul Stoffregen

Paul's Deep Dives

Hello SparkFans! Paul here from PJRC. I spend a lot of time on the PJRC forum helping people solve all kinds of tricky problems. Some of those threads turn into deeper dives on how things really work.

In Paul’s Deep Dives, we'll revisit some of my favorite forum discussions. Along the way, we fill in the gaps, add context, and connect the dots so you not only see what worked — but understand why it worked. Enjoy!

By default, Teensy 4.x places ordinary data in fast RAM1 unless you explicitly ask otherwise. PJRC describes RAM1 as tightly coupled memory, optimized for speed, while DMAMEM places variables in RAM2 and is mainly intended for large buffers or DMA use.

 

That distinction matters when you build something like a command table:

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 },
};

The surprise is that this can still show up in RAM1. A important detail is that the struct stores pointers. Applying PROGMEM to the array stores the pointer values in flash, but the string literals themselves are separate objects.

Unless those strings are also placed in flash, the array can end up holding flash-resident pointers to RAM-resident strings.

The explicit flash-only version

The fully flash-resident version is a little awkward, but still clear:

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 },
};

The important idea is that there are really seven separate objects here: six strings plus the table. Each one needs flash placement.

A cleaner trade-off

My suggestion would be a cleaner approach: store the character data inside the struct instead of storing pointers.

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 },
};

Now the strings are part of the struct object, so a single PROGMEM applies to the table and the text. The cost is extra flash space, because every field reserves its maximum size.

In many Teensy projects, that is a good trade. Flash is usually less scarce than RAM1, and the syntax is much easier to read and maintain.

Why not DMAMEM?

One tempting alternative is DMAMEM, but that is the wrong tool for initialized constant data. DMAMEM moves storage to RAM2, which is useful for large buffers and DMA-accessible memory. It is not the right choice for initialized command names, help strings, or lookup text.

For that kind of data, PROGMEM is the better fit.

Is flash too slow?

Not necessarily. Flash access on Teensy 4.x is not as simple as “slow.” The i.MX RT1062 uses an Arm Cortex-M7 core with instruction and data caches, so repeated access to flash-resident constants can often be served from cache.

RAM1 is still the fastest and most deterministic place for frequently accessed data, but command tables, help strings, menus, lookup text, and similar mostly-read data are often excellent candidates for flash.

Memory Region Performance Typical Capacity Common Uses
RAM1 Fastest (tightly coupled memory) 512 KB Variables, stacks, frequently accessed data, and (by default) program code
Flash (PROGMEM) Cached; very fast for repeated reads 2–8 MB (board dependent) Read-only constants, lookup tables, menus, help text, and code marked FLASHMEM
RAM2 (used by DMAMEM) Slower than RAM1 512 KB DMA buffers, large arrays, frame buffers, and other bulk data

Key Takeaway

Use PROGMEM for constant data you want kept out of RAM1. But when your struct contains const char *, remember that the pointer and the string are different objects.

Either put each string in PROGMEM too, or redesign the struct so the string storage lives inside the table.

References

Footnote

This article is based on the PJRC forum thread why is this in RAM1? and the discussion that followed between forum members.