Search code examples
cstm32gnuramlinker-scripts

STM32 create RAM section


I work with STM32H7 microcontroller and GNU/GCC, in my code, I am using only DTCM RAM but I want to store some buffers in another memory which is accessible by DMA.
I am totally new to linker script, do I need to edit startup code ?
Here is my linker script, I added some code in SECTIONS

/* Memories definition */
MEMORY
{
  DTCMRAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K
  ITCMRAM    (xrw)    : ORIGIN = 0x00000000,   LENGTH = 64K
  RAM_D1    (xrw)    : ORIGIN = 0x24000000,   LENGTH = 512K
  RAM_D2    (xrw)    : ORIGIN = 0x30000000,   LENGTH = 288K
  RAM_D3    (xrw)    : ORIGIN = 0x38000000,   LENGTH = 64K
  FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 2048K
}

/* Sections */
SECTIONS
{
   ...
   
   .ram1block 0x24000000 :
  {
    KEEP(*(.ram1section))
  } > RAM_D1

   ...
}

I want to use this attribute :

uint8_t __attribute__(( section(".ram1section") )) ads_buf[READBACK_LENGTH];

Am I doing this right ?
Do you have any topics or advice ? I am new to these scripts and a bit lost

EDIT : Apparently there is a simpler solution :
In linker file

SECTIONS
{
   ads_buf=0x24000000;
   ...
}

In source

extern uint8_t ads_buf[READBACK_LENGTH];

Can anyone confirm this solution is valid ?


Solution

  • Your "simpler" solution means you'll have to assign addresses for all objects in that memory section manually, taking extra care to avoid overlap. This is precisely the thing linkers were invented to avoid, so I'd advise not to go that way.

    You don't need to specify the address again for the output section, just for the memory range. Whether you need to modify the startup code depends on whether you need that section initialized at startup.

    No initialization

    If you don't need initialization because you'll e.g. fill the buffer with incoming data before reading it, modify the linker script as follows:

       .ram1block (NOLOAD) :
      {
        KEEP(*(.ram1section))
      } > RAM_D1
    

    And you're done.

    Trivial initialization

    If you do need it initialized trivially, e.g. all zeroes, add address symbols as follows:

       .ram1blockBss (NOLOAD) :
      {
        . = ALIGN(4);
        _BeginRam1Bss = .;
        KEEP(*(.ram1sectionBss))
        . = ALIGN(4);
        _EndRam1Bss = .;
    
      } > RAM_D1
    

    Aligning the address makes initialization easier and faster. Note that I renamed the input section to ram1sectionBss to denote that this section is zero-initialized. Add something like this to the startup-code to zero-initialize the memory:

        ldr r0, =_BeginRam1Bss
        ldr r1, =_EndRam1Bss
        ldr r2, =0
    
        b 2f
    1:  str r2, [r0], #4
    2:  cmp r0, r1
        blo 1b
    

    This initializes the whole block with zeroes efficiently, so that C variables defined as

    uint8_t __attribute__(( section(".ram1sectionBss") )) ads_buf[READBACK_LENGTH];
    

    will be zero-initialized.

    Value initialization

    If you need to initialize the stuff in your memory with distinct values, modify the linker section definition as follows:

       .ram1blockData (NOLOAD) :
      {
        . = ALIGN(4);
        _BeginRam1Data = .;
        KEEP(*(.ram1sectionData))
        . = ALIGN(4);
        _EndRam1Data = .;
    
      } > RAM_D1 AT> FLASH
      _InitRam1Data = LOADADDR (.ram1blockData);
    

    Also append a . = ALIGN(4); into the last section before .ram1blockData that goes into FLASH to make sure the load address in flash is aligned too.

    Then add the following to the startup code:

        ldr r0, =_BeginRam1Data
        ldr r1, =_EndRam1Data
        ldr r2, =_InitRam1Data
    
        b 2f
    1:  ldr r3, [r2], #4
        str r3, [r0], #4
    2:  cmp r0, r1
        blo 1b
    

    If you then define your C variable as

    uint8_t __attribute__(( section(".ram1sectionData") )) ads_buf[READBACK_LENGTH] = { 1, 2, 3, 4 };
    

    it will be initialized correctly. Note again the renamed section (ram1sectionData to denote that the data is initialized).

    Trivial + value initialization

    If you need both zero-initialized and value-initialized data in your memory block, simply put both section definitions in the linker script and both assembly blocks in the startup code, to make C definitions like this work:

    uint8_t __attribute__(( section(".ram1sectionBss") )) ads_buf1[READBACK_LENGTH];
    uint8_t __attribute__(( section(".ram1sectionData") )) ads_buf2[READBACK_LENGTH] = { 1, 2, 3, 4 };
    

    PS: Note the identifiers like _BeginRam1Bss that begin with underscore + uppercase letter, which are reserved C identifiers. This means you can't accidentally use them in C code which would collide with the linker script. Linker-Script + Startup-Code are part of the implementation and should provide a conformant runtime for the regular C code, without "taking up" non-reserved identifiers that should be usable by the C code.