Memory Mapping - TeensyUser/doc GitHub Wiki

Here some information about how the various Teensies utilize the available on- and off chip memory, how you can specify which part of the memory to use and how to handle sketches with high memory demand.

Reference

Here a quick write up for those, only interested in how to generate variables, constants and functions in certain parts of the available memory:

Teensy 3.x and LC

Per default, code and constants are stored in FLASH and all variables (static, global, local and dynamic) are stored in RAM. Specifically, local variables are stored on the STACK, and dynamic variables, i.e. those created with malloc or new are stored in HEAP memory.

Examples:

unsigned i;                 // Global variable -> RAM
const float k = 5;          // Constant -> FLASH
constexpr float x = 3.14;   // Compile time constant usually not stored at all
char[] t1 = "Hello"         // array of char variables -> RAM
const char[] t2 = "World";  // array of char constants -> FLASH
double* d;                  // Global variable -> RAM

void func(void)             // Code -> FLASH
{
    static float f;         // Static variable -> RAM
    int  i;                 // local variable  -> STACK part of the RAM
    d = new double(2)       // dynamic variable -> HEAP part of the RAM *)
    //...
}
// *) the global pointer variable `d` is stored in RAM, the float
//    which d points to is stored in the HEAP.

Since reading from FLASH is a bit slower than reading from RAM you might consider moving time critical functions to RAM. To do so you need to use the attribute FASTRUN before the function definition:

FASTRUN               // <- move function to RAM
void func(void)       // <- RAM
{
    static float f;   // <- RAM
    double d;         // <- STACK
    // do something
}

Teensy 4.x

The Teensy 4.x boards have two 512kB RAM banks RAM1 and RAM2.

Global, static and local variables behave like they do for the T3.x boards. All of those variables use a part of the first RAM bank by default. The part of RAM1 used for variables is called DTCM (Data Tightly Coupled Memory).

Unlike the T3.x boards the FLASH memory of the Teensy 4.x is external and significantly slower than RAM (though there is a 32KB code cache.) Therefore, the linker associates all code and constants to the ITCM (Instruction Tightly Coupled Memory) section of RAM1. Teensy startup code performs the actual move to RAM1.

char t1[]       = "Hello"; // variable -> RAM1 (DTCM)
const char t2[] = "RAM";   // constant -> copied to DTCM

void func(void)            // code .>copied to ITCM (RAM1)
{
    char t4[] = "STACK";   // local -> RAM1 (STACK)
}

In case your application requires a lot of RAM you might consider leaving code which is not time critical or large constants in FLASH.

const char t1[]       = "RAM";   // constant, but copied to RAM
PROGMEM const char t2 = "FLASH"  // constant, leave in FLASH

FLASHMEM                         // do not copy this function to RAM
void func(void)
{
    // do something not time critical
}

The T4.x boards have a second RAM block which, per default, is used for DMA buffers and the HEAP. You can also place normal variables into RAM2 by using the DMAMEM attribute.

DMAMEM int k = 5;               // Place integer variable k into RAM2

byte* buf = new byte[1024];     // buf is a global 4 byte pointer variable and lives in DTCM (RAM1)
                                // it stores the address of a 1kB buffer in HEAP (RAM2).

Here a picture from the PJRC site (https://www.pjrc.com/store/teensy40_pins.html) showing the setup both RAM banks and the FLASH of the T4.x boards.

RAM2 is a little bit slower than RAM, and it is connected through a bus. To compensate for this disadvantage, there is a cache:

RAM2 / DMA

RAM2 has a 32KB cache. On startup, the cache is configured "Write Back". This means, if you store a value to RAM2, it may not be written to the RAM2 immedeately and may get held in the cache instead. DMA can only access RAM and will always use the values from RAM. For this purpose there are three functions:

  1. arm_dcache_flush(void *addr, uint32_t size) - Flush data from cache to memory - Normally arm_dcache_flush() is used when metadata written to memory will be used by a DMA or a bus-master peripheral. Any data in the cache is written to memory. A copy remains in the cache, so this is typically used with special fields you will want to quickly access in the future. For data transmission, use arm_dcache_flush_delete().

  2. arm_dcache_delete(void *addr, uint32_t size) - Delete data from the cache, without touching memory - Normally arm_dcache_delete() is used before receiving data via DMA or from bus-master peripherals which write to memory. You want to delete anything the cache may have stored, so your next read is certain to access the physical memory.

  3. arm_dcache_flush_delete(void *addr, uint32_t size) - Flush data from cache to memory, and delete it from the cache. Normally arm_dcache_flush_delete() is used when transmitting data via DMA or bus-master peripherals which read from memory. You want any cached data written to memory, and then removed from the cache, because you no longer need to access the data after transmission.

External RAM

Optionally, you can solder up to 16MB external RAM on the bottom side of a T4.1 board. If you want to place some variables in the second block (DMAMEM) or on the external RAM (EXTMEM) you can use the following code:

        uint8_t buffer1[100 * 1024];     // Place a 100kB buffer in DTCM (RAM1)
DMAMEM  uint8_t buffer2[300 * 1024];     // Place a 300kB buffer in the second RAM block
EXTMEM  uint8_t buffer3[3* 1024 * 1024]  // Place a 3MB buffer in the external RAM

Experiments

If you want to have a closer look yourself, you can use the MemoryTool which consists of only two files MemoryTool.h and MemoryTool.cpp which you can copy to your sketch folder. You can download it from here: https://github.com/luni64/TeensyHelpers.

Please note that this tool currently only works for T3.x Boards

int i;                    // global variable (uninitalized)
double x = 42;            // global variable (initalized)

void setup(){
    static int k;         // static variable, stored in the same place as global variables

    while (!Serial) {}    // wait until the pc connected
    printMemoryInfo(i);   // print info about the variables
    printMemoryInfo(x);
    printMemoryInfo(k);
}

void loop(){
}

For a T3.6 this prints:

i
  Start address: 0x1FFF'11EC
  End address:   0x1FFF'11EF
  Size:          4 Bytes
  Location:      RAM (not initialized)

x
  Start address: 0x1FFF'0738
  End address:   0x1FFF'073F
  Size:          8 Bytes
  Location:      RAM (initialized)

k
  Start address: 0x1FFF'11F0
  End address:   0x1FFF'11F3
  Size:          4 Bytes
  Location:      RAM (not initialized)

Lets see what happens if we change x to a constant:

int i;
const double x = 42;  // <== constant

void setup(){
    static int k;

    while (!Serial) {}
    printMemoryInfo(i);
    printMemoryInfo(x);
    printMemoryInfo(k);
}

void loop(){
}

Since one can't change a constant at runtime the linker leaves the constant in the flash as you can see in the printout below:

i
  Start address: 0x1FFF'11E4
  End address:   0x1FFF'11E7
  Size:          4 Bytes
  Location:      RAM (not initialized)

x
  Start address: 0x0001'08C8
  End address:   0x0001'08CF
  Size:          8 Bytes
  Location:      FLASH

k
  Start address: 0x1FFF'11E8
  End address:   0x1FFF'11EB
  Size:          4 Bytes
  Location:      RAM (not initialized)

What about c-strings:

char myString[]               = "This is a global c-string"; // can be changed at runtime
const char myConstantString[] = "I'm constant";              // can not be changed

void setup()
{
    while(!Serial){}

    char myLocalString[] = "Local c-string";

    printMemoryInfo(myString);
    printMemoryInfo(myConstantString);
    printMemoryInfo(myLocalString);
}

void loop(){
}

Here the printout which shows that if you want to place a string in the flash, all you need to do is declaring it const.

myString
  Start address: 0x1FFF'0734
  End address:   0x1FFF'074D
  Size:          26 Bytes
  Location:      RAM (initialized)

myConstantString
  Start address: 0x0001'0908
  End address:   0x0001'0914
  Size:          13 Bytes
  Location:      FLASH

myLocalString
  Start address: 0x2002'FFD0
  End address:   0x2002'FFDE
  Size:          15 Bytes
  Location:      STACK