Tornamos o processo de desenvolvimento de software pesado para microcontroladores mais conveniente (não)

Agora ninguém pode se surpreender com os microcontroladores com memória não volátil (na maioria das vezes em Flash) de 512 kilobytes ou mais. Seu custo está diminuindo gradualmente e a acessibilidade, pelo contrário, está aumentando. A presença desse volume de memória não volátil torna possível escrever aplicativos "pesados" em termos de memória ocupada, facilitando a manutenção subsequente do código por meio do uso de soluções prontas de várias bibliotecas padrão. No entanto, isso leva a um aumento no volume do arquivo de firmware do dispositivo de destino, que é necessário cada vez que é completamente recarregado na memória não volátil do microcontrolador com a menor alteração no código.

O objetivo do artigo é falar sobre o método de construção de um projeto em C e / ou C ++, no qual, no caso de alterar a seção de código, que é depurada com mais frequência, a maioria do projeto não precisa ser reescrita. E também mostre por que esse método nem sempre é uma solução eficaz.

Requisitos do leitor


No curso da narrativa, assumirei que o leitor:

  • Ele é fluente em C e C ++;
  • possui experiência no trabalho com microcontroladores baseados em núcleos Cortex-M3 / Cortex-M4 (por exemplo, a série stm32f4);
  • sabe como construir o arquivo de costura final (elf / bin) a partir das fontes do projeto;
  • Imagina para que servem os arquivos de script do vinculador;
  • tem uma idéia do texto, bss, dados e outras seções;
  • trabalhou com qualquer uma das distribuições linux;
  • possui minimamente bash;
  • possui experiência com o gcc na arquitetura dos processadores Cortex-M3 / Cortex-M4 (toolchain arm-none-eabi);
  • tem habilidades iniciais com cmake.

A essência do método


Em um projeto "clássico" para microcontroladores, todos os dados imutáveis ​​(texto, seções rodata, valores de dados iniciais e outros) geralmente estão localizados "em uma linha", começando no endereço inicial da memória não volátil (no caso de um microcontrolador baseado no núcleo Cortex-M3 / Cortex-M4 - c Endereço 0x08000000). De uma forma simplificada, o mapa de uso de memória não volátil de um programa de microcontrolador baseado no núcleo Cortex-M3 / Cortex-M4, escrito em C ++, se parece com o seguinte:



O arquivo mem.ld para esse projeto geralmente se parece com isso:

MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 768K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 112K } 

Aqui, toda a memória não volátil é uma única partição denominada “FLASH” e toda a RAM é uma partição denominada “RAM”. Dessa forma, quando uma das seções de código é alterada, todo o resto começa a "mudar". Para evitar isso, você pode "dividir" o arquivo de firmware em alguns blocos lógicos. Por exemplo, da seguinte maneira:

  • tabela de vetores de interrupção;
  • bibliotecas próprias;
  • bibliotecas de terceiros (que não devem ser alteradas);
  • código frequentemente modificável.

Nesse caso, ao alterar uma seção do código, no arquivo bin final, apenas a seção na qual o código foi alterado e a seção que estava de alguma forma conectada a ele serão alteradas (por exemplo, a tabela de vetores de interrupção, se a posição do manipulador em alguma das seções).

Basicamente, bibliotecas estáticas são adicionadas ao projeto.

Depois de receber a bandeja do arquivo do projeto, ele pode ser dividido em seções e piscar cada seção independentemente. Assim, somente as áreas alteradas serão costuradas. Isso também leva à falta de necessidade de firmware antes da depuração, pois é assumido que o microcontrolador terá imediatamente o firmware mais recente no microcontrolador e você poderá iniciar a depuração imediatamente.

A seguir, descreverei em detalhes como implementar isso em um projeto real. Os prós e contras de tal decisão serão dados no final do artigo.

Campo para experimento


Antes de sugerir qualquer tipo de inovação no trabalho, tento isso no meu projeto de casa . Como seu tamanho é próximo ao tamanho dos projetos de rotina em funcionamento, é possível entender se a inovação é viável ou não e quais as nuances que ela carrega.

Descrição do Projeto


O projeto contém:

  • o código do projeto principal em C ++ 14 usando tabelas virtuais, new / delete (trabalhando com um monte de FreeRTOS), shared_ptr (e outros ponteiros inteligentes) e outras delícias das bibliotecas C ++ 14 padrão;
  • FreeRTOS usando cerca de 6 tarefas para manter a infraestrutura periférica de hardware e cerca de 10 na lógica de negócios ( biblioteca de gráficos MakiseGUI , processamento de cliques, trabalhando com gordura ( FatFS ) etc.);
  • 16 repositórios com suas próprias bibliotecas para interagir com periféricos de hardware na placa, stubs para chamadas do sistema e muito mais;

Com os parâmetros de montagem -O0 -g3, o código em uma implementação completa com suporte para unicode, cirílico e outras coisas leva cerca de 700 KB. No entanto, no estágio atual, quando os periféricos de hardware são estáveis ​​e apenas a lógica de negócios precisa ser depurada, a quantidade de código a ser alterada é de aproximadamente 20 KB. Por esse motivo, à primeira vista, parece que a abordagem atual é uma solução ideal para o problema (a opção com simulação em um computador não é considerada por algum motivo).

Lista de ações


Para implementar o método descrito, você precisará de:

  • reunir todos os submódulos como bibliotecas estáticas (a descrição deste item não está incluída na lista de itens analisados ​​deste artigo);
  • reescrever mem.ld;
  • reescreva section.ld;
  • adicione um utilitário ao projeto principal para extrair seções do arquivo bin final;
  • adicione ao projeto uma chamada ao script para atualizar a memória não volátil do microcontrolador ao atualizar o arquivo de firmware.

Reescrevendo mem.ld


O primeiro passo é refinar o mem.ld “padrão” para o conceito atual. Ao finalizar, deve-se ter em mente que a memória não volátil é limpa por setores. Leia mais sobre como os setores são estruturados em um microcontrolador específico deve ser lido na documentação (no caso dos microcontroladores stm32 - no manual de referência). Cada seção pode ocupar pelo menos um setor (mais pode ser); caso contrário, uma seção substituirá a outra.

Também deve-se ter em mente que, se uma biblioteca usa variáveis ​​globais, para essa biblioteca você precisa reservar um lugar na RAM no estágio de vinculação. Caso contrário, você poderá encontrar bugs desagradáveis ​​que serão extremamente difíceis de detectar. Por exemplo, o código da biblioteca FatFS estará na seção ROM_EXTERNAL_LIBRARIES, mas requer 4 bytes de RAM no estágio de compilação. Portanto, você precisa garantir que haja uma seção na RAM para os campos que o código de ROM_EXTERNAL_LIBRARIES usará. Neste exemplo, é RAM_EXTERNAL_LIBRARIES.

A última seção da memória não volátil merece atenção especial. Tudo o que não foi decomposto nas seções correspondentes anteriormente, de acordo com section.ld (sobre isso mais tarde), entrará nele.

No contexto do projeto atual, o mem.ld terá esta aparência.
 /*    stm32f405rgt6   ChiptunePlayer-2.22-MainBoard-v2-Firmware. */ MEMORY { /*-----------------------------FLASH-------------------------------*/ /*  0-1  . */ ROM_BOOTLOADER (RX) : ORIGIN = 0x08000000, LENGTH = 32K /*  2     . */ ROM_SYSCFG_PAGE_1 (R) : ORIGIN = 0x08008000, LENGTH = 16K /*  3      . */ ROM_SYSCFG_PAGE_2 (R) : ORIGIN = 0x0800C000, LENGTH = 16K /*  4 . */ ROM_RESERVE (R) : ORIGIN = 0x08010000, LENGTH = 16K /*  5, 6, 7      (FATFS, FREERTOS...). */ ROM_EXTERNAL_LIBRARIES (RX) : ORIGIN = 0x08020000, LENGTH = 384K /*  8, 9      ( ,  ...). */ ROM_USER_LIBRARIES (RX) : ORIGIN = 0x08080000, LENGTH = 384K /*  5, 6      . */ ROM_MAIN_PROGRAMM (RX) : ORIGIN = 0x080E0000, LENGTH = 128K /*-----------------------------RAM---------------------------------*/ /*      RAM    . */ RAM_PAGE_1 (RW) : ORIGIN = 0x20000000, LENGTH = 112K RAM_PAGE_2 (RW) : ORIGIN = 0x2001C000, LENGTH = 16K /*           FATFS  FreeRTOS. */ RAM_EXTERNAL_LIBRARIES (RW) : ORIGIN = 0x20000000, LENGTH = 10K /*        . */ RAM_USER_LIBRARIES (RW) : ORIGIN = 0x20002800, LENGTH = 90K /*    RAM    . */ RAM_MAIN_PROGRAMM (RW) : ORIGIN = 0x20019000, LENGTH = 27K /*   RAM    .    FreeRTOS. */ RAM_MAIN_PROGRAMM_STACK (RW) : ORIGIN = 0x2001FC00, LENGTH = 1K } 

Reescreva section.ld


Depois que o cartão de memória existente for dividido em seções, deve-se descrever qual partição será colocada. Para cada biblioteca (se houver uma seção correspondente na biblioteca), indique onde estão localizados os textos .text, .rodata, .data, .bss e outras seções. A lista de seções disponíveis na biblioteca pode ser visualizada usando o objdump. Por exemplo, para a biblioteca libstdc ++ _ nano.a, é necessário especificar onde colocar o texto, ARM.attributes, rodata, data, bss, seções COMMON.

No contexto do projeto atual, o section.ld terá esta aparência.
 /*             RAM. */ __estack = ORIGIN(RAM_MAIN_PROGRAMM_STACK) + LENGTH(RAM_MAIN_PROGRAMM_STACK); /*   . */ __stack_size = LENGTH(RAM_MAIN_PROGRAMM_STACK); /*     Reset_Handler. */ ENTRY(Reset_Handler) /*  . */ SECTIONS { /*---------------------ROM  ------------------------*/ .section_bootloader : ALIGN(4) { /*     .             .          .o ,     .*/ . = ALIGN(4); KEEP(*(.user_code_isr_vector .user_code_isr_vector*)) . = ALIGN(4); } >ROM_BOOTLOADER /*----------------ROM    -----------------*/ /* . */ .section_external_libraries_text : ALIGN(4) { /*  . */ . = ALIGN(4); *libstdc++_nano.a:*(.text .text*); . = ALIGN(4); *libgcc.a:*(.text .text*); . = ALIGN(4); *libg_nano.a:*(.text .text*); /*   */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.text .text*); . = ALIGN(4); *libFATFS.a:*(.text .text*); . = ALIGN(4); *libFREERTOS.a:*(.text .text*); . = ALIGN(4); *libMAKISE_GUI.a:*(.text .text*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /* ,   */ .section_external_libraries_required_by_the_compiler : ALIGN(4) { /*  . */ . = ALIGN(4); *libgcc.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libstdc++_nano.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libg_nano.a:*(.ARM.attributes .ARM.attributes*); /*   */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libFATFS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libFREERTOS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMAKISE_GUI.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /*    . */ .section_external_libraries_rodata : ALIGN(4) { /*  . */ . = ALIGN(4); *libgcc.a:*(.rodata .rodata*); . = ALIGN(4); *libstdc++_nano.a:*(.rodata .rodata*); . = ALIGN(4); *libg_nano.a:*(.rodata .rodata*); /*   */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.rodata .rodata*); . = ALIGN(4); *libFATFS.a:*(.rodata .rodata*); . = ALIGN(4); *libFREERTOS.a:*(.rodata .rodata*); . = ALIGN(4); *libMAKISE_GUI.a:*(.rodata .rodata*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /*----------------------- ---------------------*/ /* . */ .section_user_libraries_text : ALIGN(4) { . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.text .text*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.text .text*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.text .text*); . = ALIGN(4); *libMC_HARDWARE.a:*(.text .text*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.text .text*); . = ALIGN(4); *libUSER_STARTUP.a:*(.text .text*); . = ALIGN(4); *libBUTTONS.a:*(.text .text*); . = ALIGN(4); *libCHIPTUNE.a:*(.text .text*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.text .text*); . = ALIGN(4); *libLCD_DRIVER.a:*(.text .text*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.text .text*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.text .text*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.text .text*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.text .text*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.text .text*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.text .text*); . = ALIGN(4); } >ROM_USER_LIBRARIES /* ,   */ .section_user_libraries_required_by_the_compiler : ALIGN(4) { . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMC_HARDWARE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libUSER_STARTUP.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libUSER_CODE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libBUTTONS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libCHIPTUNE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libLCD_DRIVER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /*    . */ .section_user_libraries_rodata : ALIGN(4) { . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.rodata .rodata*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.rodata .rodata*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.rodata .rodata*); . = ALIGN(4); *libMC_HARDWARE.a:*(.rodata .rodata*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.rodata .rodata*); . = ALIGN(4); *libUSER_STARTUP.a:*(.rodata .rodata*); . = ALIGN(4); *libBUTTONS.a:*(.rodata .rodata*); . = ALIGN(4); *libCHIPTUNE.a:*(.rodata .rodata*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.rodata .rodata*); . = ALIGN(4); *libLCD_DRIVER.a:*(.rodata .rodata*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.rodata .rodata*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.rodata .rodata*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.rodata .rodata*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.rodata .rodata*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.rodata .rodata*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.rodata .rodata*); . = ALIGN(4); } >ROM_USER_LIBRARIES /*------------------------- ------------------------*/ /* . */ .section_user_code_text : ALIGN(4) { . = ALIGN(4); *(.text .text.*) . = ALIGN(4); } >ROM_MAIN_PROGRAMM /* ,   */ .sections_user_code_required_by_the_compiler : ALIGN(4) { . = ALIGN(4); *(.glue_7 .glue_7*) /*  -  ARMv7 */ . = ALIGN(4); *(.glue_7t .glue_7t*) . = ALIGN(4); *(.vfp11_veneer .vfp11_veneer*) /*   . */ . = ALIGN(4); *(.v4_bx .v4_bx*) . = ALIGN(4); *(.iplt .iplt*) . = ALIGN(4); *(.rel.dyn .rel.dyn*) . = ALIGN(4); KEEP(*(.eh_frame .eh_frame*)) /*     CPP. */ . = ALIGN(4); *(.eh_framehdr .eh_framehdr*) . = ALIGN(4); *(.ARM.attributes .ARM.attributes.*) /*    ,  . */ . = ALIGN(4); *(vtable) /* C++ virtual tables */ PROVIDE_HIDDEN (__preinit_array_start = .); /*  ,   . */ . = ALIGN(4); KEEP(*(.preinit_array_sysinit .preinit_array_sysinit*)) . = ALIGN(4); KEEP(*(.preinit_array_platform .preinit_array_platform.*)) . = ALIGN(4); KEEP(*(.preinit_array .preinit_array.*)) PROVIDE_HIDDEN (__preinit_array_end = .); PROVIDE_HIDDEN (__init_array_start = .); /*    . */ . = ALIGN(4); KEEP(*(SORT(.init_array.*))) . = ALIGN(4); KEEP(*(.init_array)) . = ALIGN(4); PROVIDE_HIDDEN (__init_array_end = .); PROVIDE_HIDDEN (__fini_array_start = .); /*    . */ . = ALIGN(4); KEEP(*(SORT(.fini_array.*))) . = ALIGN(4); KEEP(*(.fini_array)) . = ALIGN(4); PROVIDE_HIDDEN (__fini_array_end = .); . = ALIGN(4); KEEP(*(.cfmconfig)) . = ALIGN(4); *(.after_vectors .after_vectors.*) . = ALIGN(4); } >ROM_MAIN_PROGRAMM /*    . */ .section_user_code_rodata : ALIGN(4) { . = ALIGN(4); *(.rodata .rodata.*) . = ALIGN(4); } >ROM_MAIN_PROGRAMM /*  stack trace. */ .ARM.exidx : { . = ALIGN(4); *(.ARM.extab* .gnu.linkonce.armextab.*) . = ALIGN(4); *(.ARM.exidx* .gnu.linkonce.armexidx.*) . = ALIGN(4); } > ROM_MAIN_PROGRAMM /*-------------------------------RAM-----------------------------*/ /*    . */ .section_external_libraries_data : ALIGN(4) { . = ALIGN(4); __external_lib_data_start = . ; /*  . */ . = ALIGN(4); *libgcc.a:*(.data .data*); . = ALIGN(4); *libstdc++_nano.a:*(.data .data*); . = ALIGN(4); *libg_nano.a:*(.data .data*); /*   */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.data .data*); . = ALIGN(4); *libFATFS.a:*(.data .data*); . = ALIGN(4); *libFREERTOS.a:*(.data .data*); . = ALIGN(4); *libMAKISE_GUI.a:*(.data .data*); . = ALIGN(4); __external_lib_data_end = . ; } >RAM_EXTERNAL_LIBRARIES AT> ROM_EXTERNAL_LIBRARIES /*       RAM */ .section_external_libraries_bss : ALIGN(4) { . = ALIGN(4); __external_lib_bss_start = .; /*  . */ . = ALIGN(4); *libgcc.a:*(.bss .bss*); . = ALIGN(4); *libstdc++_nano.a:*(.bss .bss*); . = ALIGN(4); *libg_nano.a:*(*COMMON); . = ALIGN(4); *libgcc.a:*(*COMMON); . = ALIGN(4); *libstdc++_nano.a:*(*COMMON); . = ALIGN(4); *libg_nano.a:*(*COMMON); /*   */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.bss .bss*); . = ALIGN(4); *libFATFS.a:*(.bss .bss*); . = ALIGN(4); *libFREERTOS.a:*(.bss .bss*); . = ALIGN(4); *libMAKISE_GUI.a:*(.bss .bss*); . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(*COMMON); . = ALIGN(4); *libFATFS.a:*(*COMMON); . = ALIGN(4); *libFREERTOS.a:*(*COMMON); . = ALIGN(4); *libMAKISE_GUI.a:*(*COMMON); . = ALIGN(4); __external_lib_bss_end = .; } >RAM_EXTERNAL_LIBRARIES /*    . */ .section_user_libraries_data : ALIGN(4) { . = ALIGN(4); __user_lib_data_start = . ; . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.data .data*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.data .data*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.data .data*); . = ALIGN(4); *libMC_HARDWARE.a:*(.data .data*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.data .data*); . = ALIGN(4); *libUSER_STARTUP.a:*(.data .data*); . = ALIGN(4); *libBUTTONS.a:*(.data .data*); . = ALIGN(4); *libCHIPTUNE.a:*(.data .data*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.data .data*); . = ALIGN(4); *libLCD_DRIVER.a:*(.data .data*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.data .data*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.data .data*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.data .data*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.data .data*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.data .data*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.data .data*); . = ALIGN(4); __user_lib_data_end = . ; } >RAM_USER_LIBRARIES AT> ROM_USER_LIBRARIES .section_user_libraries_bss : ALIGN(4) { . = ALIGN(4); __user_lib_bss_start = .; . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.bss .bss*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.bss .bss*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.bss .bss*); . = ALIGN(4); *libMC_HARDWARE.a:*(.bss .bss*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.bss .bss*); . = ALIGN(4); *libUSER_CODE.a:*(.bss .bss*); . = ALIGN(4); *libBUTTONS.a:*(.bss .bss*); . = ALIGN(4); *libCHIPTUNE.a:*(.bss .bss*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.bss .bss*); . = ALIGN(4); *libLCD_DRIVER.a:*(.bss .bss*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.bss .bss*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.bss .bss*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.bss .bss*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.bss .bss*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.bss .bss*); . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.bss .bss*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.bss .bss*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(*COMMON); . = ALIGN(4); *libMC_INTERRUPT.a:*(*COMMON); . = ALIGN(4); *libMC_HARDWARE.a:*(*COMMON); . = ALIGN(4); *libPCB_HARDWARE.a:*(*COMMON); . = ALIGN(4); *libUSER_CODE.a:*(*COMMON); . = ALIGN(4); *libBUTTONS.a:*(*COMMON); . = ALIGN(4); *libCHIPTUNE.a:*(*COMMON); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(*COMMON); . = ALIGN(4); *libLCD_DRIVER.a:*(*COMMON); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(*COMMON); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(*COMMON); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(*COMMON); . = ALIGN(4); *libSHIFT_REGISTER.a:*(*COMMON); . = ALIGN(4); *libWAVE_GENERATORS.a:*(*COMMON); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.COMMON*); . = ALIGN(4); __user_lib_bss_end = .; } >RAM_USER_LIBRARIES /*    . */ .section_user_code_data : ALIGN(4) { . = ALIGN(4); __user_code_data_start = . ; . = ALIGN(4); *(.data .data.*) . = ALIGN(4); __user_code_data_end = . ; } >RAM_MAIN_PROGRAMM AT> ROM_MAIN_PROGRAMM .section_user_code_bss : ALIGN(4) { . = ALIGN(4); __bss_start__ = .; __user_code_bss_start = .; *(.bss .bss.*) *(COMMON) . = ALIGN(4); __bss_end__ = .; __user_code_bss_end = .; } >RAM_MAIN_PROGRAMM __external_lib_data_in_rom_start = LOADADDR(.section_external_libraries_data); __user_lib_data_in_rom_start = LOADADDR(.section_user_libraries_data); __user_code_data_in_rom_start = LOADADDR(.section_user_code_data); /*------------------------- -----------------*/ /* 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) } .debug_macro 0 : { *(.debug_macro) } .debug_ranges 0 : { *(.debug_ranges) } } 

Adicione um utilitário ao projeto principal para extrair seções do arquivo bin final


Infelizmente, não foi possível encontrar sinalizadores no objcopy ou no objdump para extrair código entre endereços específicos do arquivo elf. O sinalizador --only-section existe , no entanto, não leva em consideração o fato de que, depois de todas as entidades listadas na seção.ld, as informações de depuração ainda são colocadas na memória não volátil. Sem ele, o firmware final, montado a partir de peças, não funcionará (por razões óbvias). Portanto, tive que escrever um utilitário simples que pega um arquivo bin comum e extrai a seção necessária em um arquivo separado para o intervalo de endereços especificado. No entanto, a nuance a seguir surge aqui. Por padrão, o objcopy preenche o espaço entre as seções com 0s. No entanto, o espaço vazio na memória flash é 0xFF. Para resolver esse problema, você precisa compor o arquivo da bandeja de saída com o sinalizador --gap-fill = 0xff .

Adicione uma chamada ao script do projeto para atualizar a memória não volátil do microcontrolador ao atualizar o arquivo de firmware


Para rastrear as mudanças no projeto, após cada reconstrução do arquivo elf, você precisa chamar um script de verificação que extrairá o arquivo bin final do arquivo elf, compare a seção desejada dele, compare-a com a extraída anteriormente e, se houver alguma diferença, atualize a seção na memória do microcontrolador.

Código do script de comparação
 #!/bin/bash # # $1 -  .   ,    . # $2 -     elf . # $3 -      STM32. # echo "Old file name: $1" echo "New file name: $2" # ,     . flag_rewrite=0 #    ,         #  ,      (   #     ). #    ,   ,   -    #  ,     .  ,   . if [ -e $1 ] then #         . echo "Both files exist." #  md5   ,     . buf=$(md5sum $1 --binary) md5_old=${buf:0:32} #      md5   . #   32 . buf=$(md5sum $2 --binary) md5_new=${buf:0:32} echo "Started file comparison." if [ $md5_old == $md5_new ] then #     ,  . echo "The file has not been updated." echo "The new file will be deleted." rm $2 echo "Removed." else #   ,    . echo "The file has been modified." echo "Old will be replaced by new." mv $2 $1 echo "Replaced." flag_rewrite=1 #    . fi else #    . echo "Old file does not exist." echo "New will be renamed to old." mv $2 $1 #    . flag_rewrite=1 #    . echo "Renamed." fi #       ,     . if [ $flag_rewrite -eq 1 ] then echo "Started flashing." echo "CMD params: $3" $3 fi 


No próprio projeto, você pode chamar a função cmake, que fará tudo o que você precisa:
Função de atualização Cmake
 function(write_sector SECTOR ADDR_BASE ADDR_START ADDR_END) add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD COMMAND ${ARM_OBJCOPY} --output-target=binary --gap-fill=0xff ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_all.bin COMMENT "Creating a binary file of the <<${SECTOR}>> sector" COMMAND ${BIN_EXTRACTOR} -p ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_all.bin -o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}_new.bin -b ${ADDR_BASE} -s ${ADDR_START} -e ${ADDR_END} COMMAND cd ${CMAKE_SOURCE_DIR} && ./cmp.sh ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}.bin ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}_new.bin "${STM32PROG} -c port=${STM32PROG_PORT} freq=${STM32PROG_FREQ} -w ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}.bin ${ADDR_START}") endfunction(write_sector) 

A função usa stm32programmer para escrever.
Um exemplo de uso de uma função do código do projeto
 if (STM32PROG_USE STREQUAL "ON") write_sector("bootloader" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_SYSCFG_PAGE_1_ADDRESS}) write_sector("external_libraries" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_EXTERNAL_LIB_ADDRESS} ${SECTION_USER_LIBRARIES_ADDRESS}) write_sector("user_libraries" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_USER_LIBRARIES_ADDRESS} ${SECTION_USER_CODE_ADDRESS}) write_sector("main_programm" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_USER_CODE_ADDRESS} ${ADDR_END_FLASH}) endif () 


Conclusões


As vantagens desta abordagem:

  1. em 95% dos casos, o que é realmente necessário é atualizado;

Contras desta abordagem:
  1. Não há ganho de velocidade, pois antes de cada firmware é necessário carregar um gerenciador de inicialização no microcontrolador para piscar a memória não volátil (isso é feito automaticamente pelo stm32programmer). Pelo contrário, quando o projeto é remontado completamente, é necessário costurar todas as seções novamente;
  2. o tamanho de section.ld desencoraja qualquer desejo de adicionar ou alterar algo nele. Se você precisar aplicar essa metodologia em um projeto real, precisará escrever uma GUI conveniente para editar este arquivo;
  3. se o dispositivo controlar sua própria energia, você poderá não perceber que uma das partições não foi conectada corretamente (com queda de tensão, por exemplo) e depurar partições de diferentes montagens por um longo tempo :).

Você pode ver a versão de trabalho do método atual neste commit . O projeto pode ser montado no CLion, tendo compilado anteriormente um utilitário para extrair a seção do arquivo bin.

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


All Articles