Embed programmer’s brief notes: section duplication in the microcontroller’s memory

Initial conditions


There is a device based on a microcontroller (for example, stm32f405rgt6 will be taken). When turned on, it adjusts its peripherals based on user preferences or default settings. The user can change the settings during the device’s operation (as a rule, only during integration into the complex) through one of the possible interfaces (CLI menu or utility for setting work parameters working via the binary protocol). After setting the parameters, the user saves the settings with a special command (also through one of the possible interfaces).

Task


  • To reduce the time it takes to access the device settings variables during operation, you need to keep the current values ​​in RAM (usually the amount of such data varies from a dozen variables to 3 kilobytes depending on the device).
  • It is necessary to store user settings in duplicate in the flash of the microcontroller.
  • Each instance of user settings must end with CRC32 immediately after the payload.
  • For each instance of user settings, a separate page is used in flash memory (even if the useful data is 2 kb, and the pages are divided by 128 kb, then the whole page is given under one block)
  • The microcontroller software code must store the default settings, which should be the user settings blocks if both have corrupted data

Attempts to solve


GCC (at the time of writing) does not have a flag to get a copy of the section. Supplementing the LD script with << magic lines >> also fails. One could work with objcopy, but this approach is very implicit and leads to subtle errors.

Decision


The solution is to create implicit copies of the desired entity (variable, structure, array, etc.) in the user code, followed by their location in memory.

Creating a macro for hidden copying of structures


Let's create a macro with which we will reserve a place for the structure in RAM, 2 instances of flash-memory on separate pages and in the user code (for initial values).

#define USER_CFG_DATA_STRUCT(TYPE,NAME,...) \
    __attribute__((aligned(4), section (".user_cfg_data_ram_page"))) \
    TYPE NAME = __VA_ARGS__; \
    __attribute__((aligned(4), section (".user_cfg_data_flash_default_page"))) \
    TYPE flash_default_page_##NAME = __VA_ARGS__; \
    __attribute__((aligned(4), section (".user_cfg_data_flash_page_1"))) \
    TYPE flash_page_1_##NAME = __VA_ARGS__; \
    __attribute__((aligned(4), section (".user_cfg_data_flash_page_2"))) \
    TYPE flash_page_2_##NAME = __VA_ARGS__;

Using a macro, 4 instances of the structure with different names will be created. In the project code (with the exception of the system for working with user settings memory pages), all modules must use a structure without a prefix (the name specified in the macro) that will be in RAM (it will be possible to change the modules that are responsible for user interaction. The rest should only read from it).

An example of using a macro in user code:

typedef struct _test_st {
    uint32_t a1;
    uint32_t a2;
} test_st_t;

USER_CFG_DATA_STRUCT(test_st_t, name_st, {
    .a1 = 1,
    .a2 = 2
})

After the instance of the structure name_st is available from the project code.

Creating macros for hidden copying of other entities


To create a variable, you just need to do the macro substitution for creating structures.
For arrays, add the number of elements.

#define USER_CFG_DATA_VAR USER_CFG_DATA_STRCUT
#define USER_CFG_DATA_ARRAY(TYPE,NAME,SIZE,...) \
    __attribute__((aligned(4), section (".user_cfg_data_ram_page"))) \
    TYPE NAME[SIZE] = __VA_ARGS__; \
    __attribute__((aligned(4), section (".user_cfg_data_flash_default_page"))) \
    TYPE flash_default_page_##NAME[SIZE] = __VA_ARGS__; \
    __attribute__((aligned(4), section (".user_cfg_data_flash_page_1"))) \
    TYPE flash_page_1_##NAME[SIZE] = __VA_ARGS__; \
    __attribute__((aligned(4), section (".user_cfg_data_flash_page_2"))) \
    TYPE flash_page_2_##NAME[SIZE] = __VA_ARGS__;

The principle of use is similar to the use of structure.

Finalization of LD script


When creating copies of entities, it was indicated in which sections they lie. Now you should create these sections in the LD script. For F4, the augmented LD script from ST will look like this:

LD script
MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K - 256K /* 256   user_cfg */
   USER_CFG_PAGE_1 (rx) : ORIGIN = 0x080C0000, LENGTH = 128K - 4     /* 4  CRC32 */
    USER_CFG_PAGE_2 (rx) : ORIGIN = 0x080E0000, LENGTH = 128K - 4     /* 4  CRC32 */
    CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
    RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}

/*       CRC32.
       .      - . */
user_cfg_data_flash_page_1_size = LENGTH(user_cfg_PAGE_1) + 4;
user_cfg_data_flash_page_2_size = LENGTH(user_cfg_PAGE_2) + 4;
user_cfg_data_flash_page_size = user_cfg_data_flash_page_1_size;

/*
 * The '__stack' definition is required by crt0, do not remove it.
 */
__stack = ORIGIN(RAM) + LENGTH(RAM);

_estack = __stack;     /* STM specific definition */

/*
 * Default stack sizes.
 * These are used by the startup in order to allocate stacks
 * for the different modes.
 */

__Main_Stack_Size = 1024 ;

PROVIDE ( _Main_Stack_Size = __Main_Stack_Size ) ;

__Main_Stack_Limit = __stack  - __Main_Stack_Size ;

/*"PROVIDE" allows to easily override these values from an object file or the command line. */
PROVIDE ( _Main_Stack_Limit = __Main_Stack_Limit ) ;

/*
 * There will be a link error if there is not this amount of
 * RAM free at the end.
 */
_Minimum_Stack_Size = 512 ;

/*
 * Default heap definitions.
 * The heap start immediately after the last statically allocated
 * .sbss/.noinit section, and extends up to the main stack limit.
 */
PROVIDE ( _Heap_Begin = _end_noinit ) ;
PROVIDE ( _Heap_Limit = __stack - __Main_Stack_Size ) ;

SECTIONS
{
    .isr_vector :
    {
        KEEP(*(.isr_vector))         /* Interrupt vectors */
        KEEP(*(.cfmconfig))            /* Freescale configuration words */
        *(.after_vectors .after_vectors.*)    /* Startup code and ISR */
        . = ALIGN(4);
    } >FLASH

    .inits :
    {
        . = ALIGN(4);

        /*
         * These are the old initialisation sections, intended to contain
         * naked code, with the prologue/epilogue added by crti.o/crtn.o
         * when linking with startup files. The standalone startup code
         * currently does not run these, better use the init arrays below.
         */
        KEEP(*(.init))
        KEEP(*(.fini))

        . = ALIGN(4);

        /*
         * The preinit code, i.e. an array of pointers to initialisation
         * functions to be performed before constructors.
         */
        PROVIDE_HIDDEN (__preinit_array_start = .);

        /*
         * Used to run the SystemInit() before anything else.
         */
        KEEP(*(.preinit_array_sysinit .preinit_array_sysinit.*))

        /*
         * Used for other platform inits.
         */
        KEEP(*(.preinit_array_platform .preinit_array_platform.*))

        /*
         * The application inits. If you need to enforce some order in
         * execution, create new sections, as before.
         */
        KEEP(*(.preinit_array .preinit_array.*))

        PROVIDE_HIDDEN (__preinit_array_end = .);

        . = ALIGN(4);

        /*
         * The init code, i.e. an array of pointers to static constructors.
         */
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        PROVIDE_HIDDEN (__init_array_end = .);

        . = ALIGN(4);

        /*
         * The fini code, i.e. an array of pointers to static destructors.
         */
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        PROVIDE_HIDDEN (__fini_array_end = .);
        . = ALIGN(4);

    } >FLASH

    /*
     * For some STRx devices, the beginning of the startup code
     * is stored in the .flashtext section, which goes to FLASH.
     */
    .flashtext :
    {
        . = ALIGN(4);
        *(.flashtext .flashtext.*)    /* Startup code */
        . = ALIGN(4);
    } >FLASH


    /*
     * The program code is stored in the .text section,
     * which goes to FLASH.
     */
    .text :
    {
        . = ALIGN(4);

        *(.text .text.*)            /* all remaining code */

        *(.rodata .rodata.*)         /* read-only data (constants) */

        *(vtable)                    /* C++ virtual tables */

        KEEP(*(.eh_frame*))

        /*
         * Stub sections generated by the linker, to glue together
         * ARM and Thumb code. .glue_7 is used for ARM code calling
         * Thumb code, and .glue_7t is used for Thumb code calling
         * ARM code. Apparently always generated by the linker, for some
         * architectures, so better leave them here.
         */
        *(.glue_7)
        *(.glue_7t)
    } >FLASH

    .user_cfg_data_flash_default_page :
    {
        . = ALIGN(4);
        user_cfg_data_flash_default_page_start = .;
        KEEP(*(.user_cfg_data_flash_default_page .user_cfg_data_flash_default_page.*))
        . = ALIGN(4);
        user_cfg_data_flash_default_page_stop = .;
    } >FLASH

    /* ARM magic sections */
    .ARM.extab :
       {
       *(.ARM.extab* .gnu.linkonce.armextab.*)
       } > FLASH

       __exidx_start = .;
       .ARM.exidx :
       {
       *(.ARM.exidx* .gnu.linkonce.armexidx.*)
       } > FLASH
       __exidx_end = .;

    . = ALIGN(4);
    _etext = .;
    __etext = .;

    /*
     * This address is used by the startup code to
     * initialise the .data section.
     */
    _sidata = _etext;

    /* MEMORY_ARRAY */
    /*
    .ROarraySection :
    {
         *(.ROarraySection .ROarraySection.*)
    } >MEMORY_ARRAY
    */

    /*
     * The initialised data section.
     * The program executes knowing that the data is in the RAM
     * but the loader puts the initial values in the FLASH (inidata).
     * It is one task of the startup to copy the initial values from
     * FLASH to RAM.
     */
    .data  : AT ( _sidata )
    {
        . = ALIGN(4);

        /* This is used by the startup code to initialise the .data section */
        _sdata = . ;            /* STM specific definition */
        __data_start__ = . ;
        *(.data_begin .data_begin.*)

        *(.data .data.*)

        *(.data_end .data_end.*)

        *(.ramfunc*)
        . = ALIGN(4);

        /* This is used by the startup code to initialise the .data section */
        _edata = . ;            /* STM specific definition */
        __data_end__ = . ;

    } >RAM


    /*
     * The uninitialised data section. NOLOAD is used to avoid
     * the "section `.bss' type changed to PROGBITS" warning
     */
    .bss (NOLOAD) :
    {
        . = ALIGN(4);
        __bss_start__ = .;         /* standard newlib definition */
        _sbss = .;              /* STM specific definition */
        *(.bss_begin .bss_begin.*)

        *(.bss .bss.*)
        *(COMMON)

        *(.bss_end .bss_end.*)
        . = ALIGN(4);
        __bss_end__ = .;        /* standard newlib definition */
        _ebss = . ;             /* STM specific definition */
    } >RAM

    .user_cfg_data_ram_page :
    {
        . = ALIGN(4);
        user_cfg_data_ram_page_start = .;
        KEEP(*(.user_cfg_data_ram_page .user_cfg_data_ram_page.*))
        . = ALIGN(4);
        user_cfg_data_ram_page_stop = .;
    } > CCM

    user_cfg_data_ram_page_size = user_cfg_data_ram_page_stop - user_cfg_data_ram_page_start;

    .user_cfg_data_page_1 :
    {
        . = ALIGN(4);
        user_cfg_data_flash_page_1_start = .;
         KEEP(*(.user_cfg_data_flash_page_1 .user_cfg_data_flash_page_1.*))
        . = ALIGN(4);
        user_cfg_data_flash_page_1_stop = .;
    } > user_cfg_PAGE_1

    .user_cfg_data_page_2 :
    {
        . = ALIGN(4);
        user_cfg_data_flash_page_2_start = .;
         KEEP(*(.user_cfg_data_flash_page_2 .user_cfg_data_flash_page_2.*))
        . = ALIGN(4);
        user_cfg_data_flash_page_2_stop = .;
    } > user_cfg_PAGE_2

    .noinit (NOLOAD) :
    {
        . = ALIGN(4);
        _noinit = .;

        *(.noinit .noinit.*)

         . = ALIGN(4) ;
        _end_noinit = .;
    } > RAM

    /* Mandatory to be word aligned, _sbrk assumes this */
    PROVIDE ( end = _end_noinit ); /* was _ebss */
    PROVIDE ( _end = _end_noinit );
    PROVIDE ( __end = _end_noinit );
    PROVIDE ( __end__ = _end_noinit );

    /*
     * Used for validation only, do not allocate anything here!
     *
     * This is just to check that there is enough RAM left for the Main
     * stack. It should generate an error if it's full.
     */
    ._check_stack :
    {
        . = ALIGN(4);
        . = . + _Minimum_Stack_Size ;
        . = ALIGN(4);
    } >RAM


    /* After that there are only debugging sections. */

    /* This can remove the debugging information from the standard libraries */
    /*
    DISCARD :
    {
     libc.a ( * )
     libm.a ( * )
     libgcc.a ( * )
     }
     */

    /* Stabs debugging sections.  */
    .stab          0 : { *(.stab) }
    .stabstr       0 : { *(.stabstr) }
    .stab.excl     0 : { *(.stab.excl) }
    .stab.exclstr  0 : { *(.stab.exclstr) }
    .stab.index    0 : { *(.stab.index) }
    .stab.indexstr 0 : { *(.stab.indexstr) }
    .comment       0 : { *(.comment) }
    /*
     * DWARF debug sections.
     * Symbols in the DWARF debugging sections are relative to the beginning
     * of the section so we begin them at 0.
     */
    /* DWARF 1 */
    .debug          0 : { *(.debug) }
    .line           0 : { *(.line) }
    /* GNU DWARF 1 extensions */
    .debug_srcinfo  0 : { *(.debug_srcinfo) }
    .debug_sfnames  0 : { *(.debug_sfnames) }
    /* DWARF 1.1 and DWARF 2 */
    .debug_aranges  0 : { *(.debug_aranges) }
    .debug_pubnames 0 : { *(.debug_pubnames) }
    /* DWARF 2 */
    .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
    .debug_abbrev   0 : { *(.debug_abbrev) }
    .debug_line     0 : { *(.debug_line) }
    .debug_frame    0 : { *(.debug_frame) }
    .debug_str      0 : { *(.debug_str) }
    .debug_loc      0 : { *(.debug_loc) }
    .debug_macinfo  0 : { *(.debug_macinfo) }
    /* SGI/MIPS DWARF 2 extensions */
    .debug_weaknames 0 : { *(.debug_weaknames) }
    .debug_funcnames 0 : { *(.debug_funcnames) }
    .debug_typenames 0 : { *(.debug_typenames) }
    .debug_varnames  0 : { *(.debug_varnames) }
}

In this example, the configuration structure does not lie in the RAM area, but in CCMRAM, to improve performance. I also note that the user settings data blocks are the last 2 flash pages, each of which is 128 kb.

Initialization and control of copies


The care of what will lie in RAM lies with the programmer. Before using data from this area for the first time, you need to initialize the area. In flash, when loading the program into the microcontroller, the data will be pre-recorded with the initial values ​​specified in the program code. This will make invalid copies of the user settings blocks in separate pages if they end in CRC32 (since no CRC32 calculation is performed. Your code can do this. Also until the first use of configuration parameters).

To write a module for working with configuration blocks, you will need to use the variables from the LD script. The following will be required:

extern uint32_t user_cfg_data_flash_default_page_start;
extern uint32_t user_cfg_data_flash_page_1_start;
extern uint32_t user_cfg_data_flash_page_2_start;
extern uint32_t user_cfg_data_ram_page_start;

extern uint32_t user_cfg_data_flash_page_size;
extern uint32_t user_cfg_data_ram_page_size;

Also, do not forget that user_cfg_data_flash_page_size and user_cfg_data_ram_page_size can be assigned as normal values. But user_cfg_data_flash_page_1_start and other variables storing the address must be specified via &.

Source: https://habr.com/ru/post/undefined/


All Articles