How to use CCM SRAM for Cortex-M4?

How to compile in gcc 4.9.3 for CCM SRAM usage?

Parents
  • This depends on your linker-script.

    Normally, there are 3 main sections (these 3 were invented more than 35 years ago actually):

    1. The TEXT section
    2. The DATA section
    3. The BSS section

    The functionality of the TEXT section, is to contain code. It's also possible to store read-only data there (which is usually done for every subroutine when using an ARM architecture).

    The functionality of the DATA section, is to supply an initial value to a writable variable. The DATA section should be copied from Flash-memory to RAM during the startup.

    The functionality of the BSS section, is to initialize all values to zero. The BSS section should be zeroed during startup.

    The names that GCC uses for these sections are: ".text", ".data" and ".bss". There are other possible section names as well; for instance ".rodata" which would be data that is not copied during startup, but these data will be stored in the flash memory, so they can't be changed.

    See Writing your own startup code for Cortex-M if you are interested in the details on the startup process.

    -But the compiler does not know that you're compiling for a microcontroller. It just puts the code together and forms an object file.

    It's the job of the linker, to mix the object files into an executable file; the linker takes all the .o files and checks if they contain the entry point (usually known as  'main', but it can be changed if you wish). After finding the file containing entry point, the linker goes through all the symbols that this file needs. It collects all those symbols, until there are no more references (or there are no more .o files).

    Up until this point, the linker does not need to know anything about what goes where; it just put things together.

    But as microcontrollers are a little more advanced than the computers we had in the 80's, there might be multiple storage locations, and they don't even have to be contiguous. So we'll need to tell the linker what has to go where. This is done using a linker-script (these usually ends in .ld).

    Since you're speaking of CCM, hopefully I can assume you're using a microcontroller from STMicroelectronics. If that is the case, you should download STM32CubeF4 (see the software section on this page), if you haven't done so already. In the Projects folder, you'll find folders with different boards, and there are linker scripts for these projects. For instance, in STM32F4-Discovery/Examples/DMA/DMA_FLASHToRAM/TrueSTUDIO/STM32F4-Discovery/ you will find STM32F407VG_FLASH.ld. Open this file in your favourite text-editor, and find the part, which says MEMORY{ ... }. This describes what memory areas are avalable for this microcontroller. You should see 3 memory areas: FLASH, RAM and CCMRAM. Now we know the name of the CCM RAM area, so we'll search the file for the word 'CCMRAM'. There's one occurrence of the uppercase word. This is where it tells the linker, where the data from selected sections should go.

    This is the interesting block:

      .ccmram :

      {

        . = ALIGN(4);

        _sccmram = .;       /* create a global symbol at ccmram start */

        *(.ccmram)

        *(.ccmram*)

        . = ALIGN(4);

        _eccmram = .;       /* create a global symbol at ccmram end */

      } >CCMRAM AT> FLASH

    It basically means that all sections mentioned inside the curly braces, should go into the memory area called CCMRAM.

    We only find one section name inside the curly braces; the .ccmram section. Actually there are two: ".ccmram" and ".ccmram*". This means that all sections named ".ccmram" will be placed first, then all sections starting with ".ccmram" will be placed after the ".ccmram" section. So you can make your own section called ".ccmram-tables" for instance, and it would go into its own block of sections.

    This will only work, though, if the startup-code actually copies the block between _sccmram and _eccmram to the destination address _siccmram.

    Let's have a look at the startup code; it's called startup_stm32f407xx.s...

    First we'll search for the well-known .data section; the addresses for this is called _sdata, _edata and _sidata. It appears that the code is copying the data from Flash memory to SRAM alright. But it seems that there is no code to copy from the _sccmram  to _siccmram.

    So we'll have to duplicate some code, and modify it, in order to get this to work.

    Copy the block "movs r1,#0" ... "bcc CopyDataInit"; this new block should go above the first "movs r1,#0" line. rename the labels, from "CopyData" to "CopyCCMData" for instance, so they won't conflict, also rename "_sidata" to "_siccmdata", "_sdata" to "_sccmdata" and "_edata" to "_eccmdata". It could look like this:

            movs    r1,#0

            b       LoopCopyDataInit

    CopyCCMDataInit:

            ldr     r3,=_siccmdata

            ldr     r3,[r3,r1]

            str     r3,[r0,r1]

            adds    r1,r1,#4

      

    LoopCopyCCMDataInit:

            ldr     r0,=_sccmdata

            ldr     r3,=_eccmdata

            adds    r2,r0,r1

            cmp     r2,r3

            bcc     CopyCCMDataInit

    Now, when all this is done, you should be able to place data in the CCMRAM.

    -But how do you actually control where your data is going ?

    For assembly language, it's simple; you just enter the ".section" directive followed by the section name and some options. From that point on, everything will go into the specified section, until another .section directive is met.

    But for C, you'll need to specify the __attribute__ keyword. Here's an (untested) example:

    __attribute__((section(".ccmram-strings"))) const char *helloString = "Hello World";

    Thus you can create a macro, which makes things look a little more tidy:

    #define CCMRAM __attribute__((section(".ccmram")))

    Then you should be able to use it in this way:

    CCMRAM const char *helloString = "Hello World";

    ...or...

    const char *helloString CCMRAM = "Hello World";

    I think you'll need to specify this each time you want the data to go in a special section (I haven't used it much myself, so I haven't looked for any shortcuts).

    As far as I understand, you can specify it on a prototype or a declaration as well; that should make things a bit easier.

Reply
  • This depends on your linker-script.

    Normally, there are 3 main sections (these 3 were invented more than 35 years ago actually):

    1. The TEXT section
    2. The DATA section
    3. The BSS section

    The functionality of the TEXT section, is to contain code. It's also possible to store read-only data there (which is usually done for every subroutine when using an ARM architecture).

    The functionality of the DATA section, is to supply an initial value to a writable variable. The DATA section should be copied from Flash-memory to RAM during the startup.

    The functionality of the BSS section, is to initialize all values to zero. The BSS section should be zeroed during startup.

    The names that GCC uses for these sections are: ".text", ".data" and ".bss". There are other possible section names as well; for instance ".rodata" which would be data that is not copied during startup, but these data will be stored in the flash memory, so they can't be changed.

    See Writing your own startup code for Cortex-M if you are interested in the details on the startup process.

    -But the compiler does not know that you're compiling for a microcontroller. It just puts the code together and forms an object file.

    It's the job of the linker, to mix the object files into an executable file; the linker takes all the .o files and checks if they contain the entry point (usually known as  'main', but it can be changed if you wish). After finding the file containing entry point, the linker goes through all the symbols that this file needs. It collects all those symbols, until there are no more references (or there are no more .o files).

    Up until this point, the linker does not need to know anything about what goes where; it just put things together.

    But as microcontrollers are a little more advanced than the computers we had in the 80's, there might be multiple storage locations, and they don't even have to be contiguous. So we'll need to tell the linker what has to go where. This is done using a linker-script (these usually ends in .ld).

    Since you're speaking of CCM, hopefully I can assume you're using a microcontroller from STMicroelectronics. If that is the case, you should download STM32CubeF4 (see the software section on this page), if you haven't done so already. In the Projects folder, you'll find folders with different boards, and there are linker scripts for these projects. For instance, in STM32F4-Discovery/Examples/DMA/DMA_FLASHToRAM/TrueSTUDIO/STM32F4-Discovery/ you will find STM32F407VG_FLASH.ld. Open this file in your favourite text-editor, and find the part, which says MEMORY{ ... }. This describes what memory areas are avalable for this microcontroller. You should see 3 memory areas: FLASH, RAM and CCMRAM. Now we know the name of the CCM RAM area, so we'll search the file for the word 'CCMRAM'. There's one occurrence of the uppercase word. This is where it tells the linker, where the data from selected sections should go.

    This is the interesting block:

      .ccmram :

      {

        . = ALIGN(4);

        _sccmram = .;       /* create a global symbol at ccmram start */

        *(.ccmram)

        *(.ccmram*)

        . = ALIGN(4);

        _eccmram = .;       /* create a global symbol at ccmram end */

      } >CCMRAM AT> FLASH

    It basically means that all sections mentioned inside the curly braces, should go into the memory area called CCMRAM.

    We only find one section name inside the curly braces; the .ccmram section. Actually there are two: ".ccmram" and ".ccmram*". This means that all sections named ".ccmram" will be placed first, then all sections starting with ".ccmram" will be placed after the ".ccmram" section. So you can make your own section called ".ccmram-tables" for instance, and it would go into its own block of sections.

    This will only work, though, if the startup-code actually copies the block between _sccmram and _eccmram to the destination address _siccmram.

    Let's have a look at the startup code; it's called startup_stm32f407xx.s...

    First we'll search for the well-known .data section; the addresses for this is called _sdata, _edata and _sidata. It appears that the code is copying the data from Flash memory to SRAM alright. But it seems that there is no code to copy from the _sccmram  to _siccmram.

    So we'll have to duplicate some code, and modify it, in order to get this to work.

    Copy the block "movs r1,#0" ... "bcc CopyDataInit"; this new block should go above the first "movs r1,#0" line. rename the labels, from "CopyData" to "CopyCCMData" for instance, so they won't conflict, also rename "_sidata" to "_siccmdata", "_sdata" to "_sccmdata" and "_edata" to "_eccmdata". It could look like this:

            movs    r1,#0

            b       LoopCopyDataInit

    CopyCCMDataInit:

            ldr     r3,=_siccmdata

            ldr     r3,[r3,r1]

            str     r3,[r0,r1]

            adds    r1,r1,#4

      

    LoopCopyCCMDataInit:

            ldr     r0,=_sccmdata

            ldr     r3,=_eccmdata

            adds    r2,r0,r1

            cmp     r2,r3

            bcc     CopyCCMDataInit

    Now, when all this is done, you should be able to place data in the CCMRAM.

    -But how do you actually control where your data is going ?

    For assembly language, it's simple; you just enter the ".section" directive followed by the section name and some options. From that point on, everything will go into the specified section, until another .section directive is met.

    But for C, you'll need to specify the __attribute__ keyword. Here's an (untested) example:

    __attribute__((section(".ccmram-strings"))) const char *helloString = "Hello World";

    Thus you can create a macro, which makes things look a little more tidy:

    #define CCMRAM __attribute__((section(".ccmram")))

    Then you should be able to use it in this way:

    CCMRAM const char *helloString = "Hello World";

    ...or...

    const char *helloString CCMRAM = "Hello World";

    I think you'll need to specify this each time you want the data to go in a special section (I haven't used it much myself, so I haven't looked for any shortcuts).

    As far as I understand, you can specify it on a prototype or a declaration as well; that should make things a bit easier.

Children