Incorpore breves notas do programador: duplicação de seção na memória do microcontrolador

Condições iniciais


Há um dispositivo baseado em um microcontrolador (por exemplo, stm32f405rgt6 será usado). Quando ligado, ajusta seus periféricos com base nas preferências do usuário ou nas configurações padrão. O usuário pode alterar as configurações durante a operação do dispositivo (como regra, apenas durante a integração ao complexo) por meio de uma das interfaces possíveis (menu ou utilitário CLI para definir parâmetros de operação que funcionam por meio do protocolo binário). Após definir os parâmetros, o usuário salva as configurações com um comando especial (também por meio de uma das interfaces possíveis).

Tarefa


  • Para reduzir o tempo necessário para acessar as variáveis ​​de configuração do dispositivo durante a operação, é necessário manter os valores atuais na RAM (geralmente a quantidade de dados varia de uma dúzia de variáveis ​​a 3 kilobytes, dependendo do dispositivo).
  • É necessário armazenar as configurações do usuário em duplicado no flash do microcontrolador.
  • Cada instância das configurações do usuário deve terminar com o CRC32 imediatamente após a carga útil.
  • Para cada instância das configurações do usuário, uma página separada é usada na memória flash (mesmo que os dados úteis sejam 2 kb e as páginas sejam divididas por 128 kb, a página inteira é fornecida em um bloco)
  • O código do software do microcontrolador deve armazenar as configurações padrão, que devem ser os blocos de configurações do usuário, se ambos tiverem dados corrompidos

Tentativas de resolver


O GCC (no momento da redação deste documento) não possui um sinalizador para obter uma cópia da seção. A suplementação do script LD com << linhas mágicas >> também falha. Pode-se trabalhar com objcopy, mas essa abordagem é muito implícita e leva a erros sutis.

Decisão


A solução é criar cópias implícitas da entidade desejada (variável, estrutura, matriz etc.) no código do usuário, seguido por sua localização na memória.

Criando uma macro para cópia oculta de estruturas


Vamos criar uma macro com a qual reservaremos um local para a estrutura na RAM, 2 instâncias de memória flash em páginas separadas e no código do usuário (para valores iniciais).

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

Usando uma macro, 4 instâncias da estrutura com nomes diferentes serão criadas. No código do projeto (com exceção do sistema para trabalhar com as páginas de memória das configurações do usuário), todos os módulos devem usar uma estrutura sem prefixo (o nome especificado na macro) que estará na RAM (será possível alterar os módulos responsáveis ​​pela interação do usuário. leia a partir dele).

Um exemplo de uso de uma macro no código do usuário:

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

Após a instância da estrutura, name_st estará disponível no código do projeto.

Criando macros para cópia oculta de outras entidades


Para criar uma variável, basta fazer a substituição de macro para criar estruturas.
Para matrizes, adicione o número de elementos.

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

O princípio do uso é semelhante ao uso da estrutura.

Finalização do script LD


Ao criar cópias de entidades, foi indicado em quais seções elas se encontram. Agora você deve criar essas seções no script LD. Para F4, o script LD aumentado do ST terá a seguinte aparência:

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

Neste exemplo, a estrutura de configuração não está na área de RAM, mas no CCMRAM, para melhorar o desempenho. Também observo que os blocos de dados das configurações do usuário são as duas últimas páginas flash, cada uma com 128 kb.

Inicialização e controle de cópias


O cuidado com o que estará na RAM é do programador. Antes de usar os dados desta área pela primeira vez, você precisa inicializar a área. Em flash, ao carregar o programa no microcontrolador, os dados serão pré-gravados com os valores iniciais especificados no código do programa. Isso fará cópias inválidas dos blocos de configurações do usuário em páginas separadas se eles terminarem no CRC32 (já que nenhum cálculo do CRC32 é realizado. Seu código pode fazer isso. Também até o primeiro uso dos parâmetros de configuração).

Para escrever um módulo para trabalhar com blocos de configuração, você precisará usar as variáveis ​​do script LD. Será necessário o seguinte:

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;

Além disso, não esqueça que user_cfg_data_flash_page_size e user_cfg_data_ram_page_size podem ser atribuídos como valores normais. Mas user_cfg_data_flash_page_1_start e outras variáveis ​​que armazenam o endereço devem ser especificadas através de &.

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


All Articles