Начальные условия


Есть устройство на базе микроконтроллера (для примера будет взят stm32f405rgt6). При включении оно настраивает свою периферию на основе предпочтений пользователя или настроек по-умолчанию. Пользователь может менять настройки во время работы устройства (как правило, только во время интеграции в комплекс) через один из возможных интерфейсов (CLI меню или утилита установки параметров работы, работающая через бинарный протокол). После установки параметров пользователь сохраняет настройки специальной командой (так же через один из возможных интерфейсов).

Задача


  • Для снижения времени обращения к переменным настроек устройства во время работы требуется держать актуальные значения в RAM (обычно объем таких данных варьируется от десятка переменных до 3 килобайт в зависимости от устройства).
  • Необходимо хранить настройки пользователя в двух экземплярах во flash микроконтроллера.
  • Каждый экземпляр настроек пользователя должен оканчиваться CRC32, лежащей сразу после полезных данных
  • Под каждый экземпляр настроек пользователя используется отдельная страница во flash памяти (даже если полезных данных 2 кб, а страницы делятся по 128 кб, то вся страница отдается под один блок)
  • В коде программного обеспечения микроконтроллера должны храниться настройки по-умолчанию, которыми должны будут стать блоки настроек пользователя, если в обоих будут поврежденные данные

Попытки решения


В GCC (на момент написания статьи) отсутствует флаг для получения копии секции. Дополнить LD скрипт <<магическими строками>> так же не удастся. Можно было бы поработать с objcopy, но этот подход очень неявный и ведет к трудно уловимым ошибкам.

Решение


Решение заключается в создании в коде пользователя неявных копий нужной сущности (переменной, структуры, массива и т.д.) с последующим расположением их в памяти.

Создание макроса для скрытого создания копий структур


Создадим макрос, с помощью которого будем резервировать место под структуру в RAM, 2 экземплярах flash-памяти на отдельных страницах и в коде пользователя (под начальные значения).

#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__;

Использовав макрос будет создано 4 экземпляра структуры с разными именами. В коде проекта (за исключением системы работы с страницами памяти настроек пользователя) все модули должны использовать структуру без приставки (заданное в макросе имя), которая будет находиться в RAM (ее можно будет изменять модулям, которые отвечают за взаимодействие с пользователем. Остальные должны только читать из нее).

Пример использования макроса в коде пользователя:

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
})

После экземпляр структуры name_st доступен из кода проекта.

Создание макросов для скрытого создания копий других сущностей


Для создания переменной потребуется просто сделать подстановку макроса создания структур.
Для массивов же — добавить количество элементов.

#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__;

Принцип использования аналогичен использованию структуры.

Доработка LD скрипта


При создании копий сущностей было указано, в каких секциях они лежат. Теперь следует создать эти секции в LD скрипте. Для F4 дополненный LD скрпит от ST будет выглядеть так:

LD скрипт
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) }
}

В этом примере структура конфигурации лежит не в RAM области, а в CCMRAM, для повышения быстродействия. Так же замечу, что блоки данных настроек пользователя — это последние 2 страницы flash, каждая из которых 128 кб.

Инициализация и контроль копий


Забота о том, что будет лежать в RAM лежит на программисте. До первого использования данных из этой области потребуется инициализировать область. Во flash же при загрузке программы в микроконтроллер будут находиться презаписанные начальными значениями, указанными в коде программы данные. Это сделает невалидными копии блоков настроек пользователя в отдельных страницах, если они оканчиваются CRC32 (посколькуо никакого рассчета CRC32 не производится. Это может делать ваш код. Так же до первого использования параметров конфигурации).

Для написания модуля работы с блоками конфигурации потребуется воспользоваться переменными из LD скрипта. Потребуются следующие:

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;

Так же не следует забывать, что user_cfg_data_flash_page_size и user_cfg_data_ram_page_size — можно присваивать как обычные значения. А вот user_cfg_data_flash_page_1_start и прочие переменные, хранящие адрес требуется указывать через &.