Désormais, personne ne peut être surpris par les microcontrôleurs avec une mémoire non volatile (le plus souvent Flash) de 512 kilo-octets ou plus. Leur coût diminue progressivement et l'accessibilité, au contraire, augmente. La présence d'un tel volume de mémoire non volatile permet d'écrire des applications «lourdes» en termes de mémoire occupée, tout en facilitant la maintenance ultérieure du code grâce à l'utilisation de solutions toutes faites de différentes bibliothèques standard. Cependant, cela conduit à une augmentation du volume du fichier firmware de l'appareil cible, qui doit à chaque fois être complètement rechargé dans la mémoire non volatile du microcontrôleur au moindre changement de code.
Le but de l'article est de parler de la méthode de construction d'un projet en C et / ou C ++, dans laquelle, en cas de modification de la section de code, qui est déboguée le plus souvent, la majeure partie du projet n'avait pas besoin d'être réécrite. Et montrez également pourquoi cette méthode n'est pas toujours une solution efficace.
Exigences du lecteur
Au cours du récit, je suppose que le lecteur:
- Il parle couramment le C et le C ++;
- possède une expérience de travail avec des microcontrôleurs basés sur des cœurs Cortex-M3 / Cortex-M4 (par exemple, la série stm32f4);
- sait comment construire le fichier de couture final (elf / bin) à partir des sources du projet;
- Imagine à quoi servent les fichiers de script de l'éditeur de liens;
- a une idée du texte, des bss, des données et d'autres sections;
- travaillé avec l'une des distributions Linux;
- possède au minimum bash;
- a de l'expérience avec gcc pour l'architecture des processeurs Cortex-M3 / Cortex-M4 (toolchain arm-none-eabi);
- a des compétences initiales avec cmake.
L'essence de la méthode
Dans un projet «classique» pour les microcontrôleurs, toutes les données immuables (texte, sections rodata, valeurs de données initiales et autres) sont généralement situées «en ligne», à partir de l'adresse de début de la mémoire non volatile (dans le cas d'un microcontrôleur basé sur le noyau Cortex-M3 / Cortex-M4 - c Adresse 0x08000000). Sous une forme simplifiée, la carte d'utilisation de la mémoire non volatile d'un programme de microcontrôleur basé sur le noyau Cortex-M3 / Cortex-M4, écrite en C ++, ressemble à ceci:

Le fichier mem.ld d'un tel projet ressemble le plus souvent à ceci:
MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 768K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 112K }
Ici, toute la mémoire non volatile est une seule partition nommée "FLASH", et toute la RAM est une partition nommée "RAM". Sous cette forme, lorsque l'une des sections de code change, tout le reste commence à «décaler». Pour éviter cela, vous pouvez «diviser» le fichier du firmware en certains blocs logiques. Par exemple, comme suit:
- tableau des vecteurs d'interruption;
- propres bibliothèques;
- bibliothèques tierces (qui ne devraient pas être modifiées);
- code fréquemment modifiable.
Dans ce cas, lors de la modification d'une section de code, dans le fichier bin final, seule la section dans laquelle le code a changé et la section qui y était en quelque sorte connectée seront modifiées (par exemple, la table des vecteurs d'interruption si la position du gestionnaire dans certains des sections).
Essentiellement,
des bibliothèques statiques sont ajoutées au projet.
Après avoir reçu le bac du fichier de projet, il peut être divisé en sections et flasher chaque section indépendamment. Ainsi, seules les zones modifiées seront cousues. Cela entraîne également le manque de micrologiciel avant le débogage, car il est supposé que le microcontrôleur aura immédiatement le dernier micrologiciel dans le microcontrôleur et vous pouvez immédiatement commencer le débogage.
Ensuite, je vais décrire en détail comment implémenter cela sur un vrai projet. Les avantages et les inconvénients d'une telle décision seront donnés à la fin de l'article.
Champ d'expérimentation
Avant de suggérer toute sorte d'innovation au travail, j'essaie ceci sur mon
projet domestique . Étant donné que sa taille est proche de celle des projets de routine au travail, il est possible de comprendre si l'innovation est viable ou non et quelles nuances elle comporte.
Description du projet
Le projet contient:
- le code du projet principal en C ++ 14 utilisant des tables virtuelles, new / delete (en utilisant un tas de FreeRTOS), shared_ptr (et d'autres pointeurs intelligents) et d'autres délices des bibliothèques C ++ 14 standard;
- FreeRTOS utilisant environ 6 tâches pour maintenir l'infrastructure matérielle périphérique et environ 10 sur la logique métier ( bibliothèque graphique MakiseGUI , traitement des clics, travail avec fat ( FatFS ), etc.);
- 16 référentiels avec leurs propres bibliothèques pour interagir avec les périphériques matériels de la carte, les talons pour les appels système, etc.
Avec les paramètres d'assemblage -O0 -g3, le code dans une implémentation complète avec prise en charge de l'unicode, du cyrillique et d'autres choses prend environ 700 Ko. Cependant, au stade actuel, lorsque les périphériques matériels sont stables et que seule la logique métier doit être déboguée, la quantité de code à modifier est d'environ 20 Ko. Pour cette raison, à première vue, il semble que l'approche actuelle soit une solution idéale au problème (l'option avec simulation sur ordinateur n'est pas envisagée pour une raison quelconque).
Liste d'actions
Pour implémenter la méthode décrite, vous aurez besoin de:
- assembler tous les sous-modules en tant que bibliothèques statiques (la description de cet élément n'est pas incluse dans la liste des éléments analysés de cet article);
- réécrire mem.ld;
- réécrire section.ld;
- ajouter un utilitaire au projet principal pour extraire des sections du fichier bin final;
- ajouter au projet un appel au script pour mettre à jour la mémoire non volatile du microcontrôleur lors de la mise à jour du fichier firmware.
Réécriture mem.ld
La première étape consiste à affiner la mémoire «standard» du concept actuel. Lors de la finalisation, il convient de garder à l'esprit que la mémoire non volatile est nettoyée par secteurs. Pour en savoir plus sur la structure des secteurs dans un microcontrôleur particulier, lisez la documentation (dans le cas des microcontrôleurs stm32 - dans le manuel de référence). Chaque section peut occuper au moins un secteur (d'autres peuvent l'être), sinon une section en écrasera une autre.
Il convient également de garder à l'esprit que si une bibliothèque utilise des variables globales, pour cette bibliothèque, vous devez réserver une place dans la RAM au stade de la liaison. Sinon, vous pourriez rencontrer des bogues désagréables qui seront extrêmement difficiles à attraper. Par exemple, le code de la bibliothèque FatFS sera dans la section ROM_EXTERNAL_LIBRARIES, mais il nécessite 4 octets de RAM au stade de la construction. Vous devez donc vous assurer qu'il existe une section dans la RAM pour les champs que le code de ROM_EXTERNAL_LIBRARIES utilisera. Dans cet exemple, il s'agit de RAM_EXTERNAL_LIBRARIES.
La dernière section de la mémoire non volatile mérite une attention particulière. Tout ce qui n'a pas été décomposé dans les sections correspondantes plus tôt, selon section.ld (à ce sujet plus tard), y entrera.
Dans le contexte du projet en cours, mem.ld ressemblera à ceci. /* 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 }
Réécrire section.ld
Une fois que la carte mémoire existante a été divisée en sections, il convient de décrire la partition qui sera placée. Pour chaque bibliothèque (s'il existe une section correspondante dans la bibliothèque), indiquez où se trouvent les sections .text, .rodata, .data, .bss et autres. La liste des sections disponibles dans la bibliothèque peut être consultée à l'aide de objdump. Par exemple, pour la bibliothèque libstdc ++ _ nano.a, vous devez spécifier où placer le texte, ARM.attributes, rodata, data, bss, COMMON sections.
Dans le contexte du projet en cours, section.ld ressemblera à ceci. /* 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) } }
Ajouter un utilitaire au projet principal pour extraire des sections du fichier bin final
Malheureusement, il n'a pas été possible de trouver des indicateurs dans objcopy ou objdump pour extraire le code entre des adresses spécifiques du fichier elf. Il existe un indicateur
--only-section , cependant, il ne prend pas en compte le fait qu'après toutes les entités de la section répertoriées dans section.ld, les informations de débogage sont toujours placées dans la mémoire non volatile. Sans cela, le firmware final, assemblé à partir de morceaux, ne fonctionnera pas (pour des raisons évidentes). Par conséquent, j'ai dû écrire un
utilitaire simple qui prend un fichier bin commun et extrait la section requise dans un fichier séparé pour la plage d'adresses spécifiée. Cependant, la nuance suivante apparaît ici. Par défaut, objcopy remplit l'espace entre les sections avec 0s. Cependant, l'espace vide dans la mémoire flash est 0xFF. Pour résoudre ce problème, vous devez composer le fichier bin de sortie avec l'indicateur
--gap-fill = 0xff .
Ajouter au projet un appel au script pour mettre à jour la mémoire non volatile du microcontrôleur lors de la mise à jour du fichier firmware
Pour suivre les modifications dans le projet, après chaque reconstruction du fichier elf, vous devez appeler un script de validation qui extraira le fichier bin final du fichier elf, en comparera la section souhaitée, le comparera avec le fichier précédemment extrait et, s'il y a des différences, mettez à jour la section dans la mémoire du microcontrôleur.
Code de script de comparaison Dans le projet lui-même, vous pouvez appeler la fonction cmake, qui fera tout ce dont vous avez besoin:Fonction de mise à jour de 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)
La fonction utilise stm32programmer pour l'écriture.Un exemple d'utilisation d'une fonction du code de projet 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 ()
Conclusions
Les avantages de cette approche:- dans 95% des cas, ce qui est vraiment nécessaire est mis à jour;
Inconvénients de cette approche:- Il n'y a pas de gain de vitesse, car avant chaque firmware, il est nécessaire de charger un chargeur de démarrage dans le microcontrôleur pour flasher la mémoire non volatile (cela se fait automatiquement par stm32programmer). Au contraire, lorsque le projet est complètement remonté, vous devez souvent recoudre toutes les sections;
- la taille de section.ld décourage tout désir d'ajouter ou de modifier quoi que ce soit. Si vous devez appliquer cette méthodologie dans un projet réel, vous devrez écrire une interface graphique pratique pour éditer ce fichier;
- si l'appareil contrôle sa propre alimentation, vous ne remarquerez peut-être pas que l'une des partitions n'était pas correctement câblée (avec une chute de tension, par exemple) et déboguez les partitions de différents assemblages pendant longtemps :).
Vous pouvez voir la version de travail de la méthode actuelle dans ce commit . Le projet peut être assemblé dans CLion, après avoir préalablement compilé un utilitaire pour extraire la section du fichier bin.