Skip to content
luni64 edited this page Dec 6, 2020 · 16 revisions

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.

Quick Guide

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

By default, static and global variables are stored in RAM code is stored in FLASH and local variables are stored on the STACK.

int i;                // <- RAM
const int k = 5       // <- FLASH

void func(void)       // <- FLASH
{
    static float f;   // <- RAM
    double d;         // <- STACK
    // do something
}

Since reading from FLASH is a bit slower than reading from RAM you might consider moving time critical functions to RAM:

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

Teensy 4.x

Global, static and local variables behave like they do for the T3.x boards. However, Teensy 4.x uses external flash memory which is significantly slower than RAM. Thus, The T4.x linker moves all code to RAM per default. If you need a lot of RAM you might consider not to copy code which is not time critical to the RAM:

int i;                           // <- RAM
char txt2[]{"Hello RAM"};        // <- RAM
const char txt[]{"Hello FLASH"}; // <- FLASH

FLASHMEM                         // do not copy this function to RAM
void func(void)                  // <- FLASH
{
    static float f;              // <- RAM
    double d;                    // <- STACK
    // do something
}

The T4.x boards have a second RAM block which, per default, is used for DMA buffers and the HEAP. Optionally you can solder up to 16MB external RAM on the bottom side of the 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 RAM

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

Memory Maps

Teensy LC and Teensy 3.x

The Teensy LC and all of the Teensy 3.x boards have a very simple memory model: There is a single block of flash and a single block of RAM memory. The size of RAM and flash depend on the board type. For all boards the flash memory starts at address 0x0000'0000. The image below shows the flash and RAM sizes and the respective start/end addresses.

image

Please note: the processors used on the T3.x boards distinguish between a low (< 0x1FFF'FFFF) and high (> 0x1FFF'FFFF) part of the RAM. Usually this splitting has no consequences. However, in rare cases it can generate weird alignment errors if the linker places a variable across the bank border (see here for more information on this issue).

For the T-LC and T3.x boards, both, RAM and flash memory are part of the processor and can be accessed very quickly. The maximum wait cycles to access RAM is 2 cycles (for consecutive reads the processor often can access the RAM without any wait cycle). Data from the flash can be read with maximal 4 wait cycles.

Teensy 4.x is different

to be described

Memory Map T4x

to be described

What is stored where

The linker will place different objects, like constants, variables, code etc. into various locations in the given memory. (For those, interested in technical details, these locations are defined in so called linker scripts. Here for example the one for the T3.2 and here the script for the T4.1)

Global variables are variables defined outside of any functions, static variables are defined inside a function using the keyword static. Static variables keep their values between function calls. Here some examples.

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

void setup(){
  static int k;
}

void loop(){
}

The linker places such variables at fixed locations in the RAM. These locations are determined at link-time and can not be changed at runtime.

Experiments with the MemoryTool

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.

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
Clone this wiki locally