Conditions initiales
Il existe un appareil basé sur un microcontrôleur (par exemple, stm32f405rgt6 sera utilisé). Lorsqu'il est allumé, il ajuste ses périphériques en fonction des préférences de l'utilisateur ou des paramètres par défaut. L'utilisateur peut modifier les paramètres pendant le fonctionnement de l'appareil (en règle générale, uniquement lors de l'intégration dans le complexe) via l'une des interfaces possibles (menu CLI ou utilitaire pour définir les paramètres de fonctionnement via le protocole binaire). Après avoir défini les paramètres, l'utilisateur enregistre les paramètres avec une commande spéciale (également via l'une des interfaces possibles).Tâche
- Pour réduire le temps nécessaire pour accéder aux variables des paramètres de l'appareil pendant le fonctionnement, vous devez conserver les valeurs actuelles dans la RAM (généralement, la quantité de ces données varie d'une douzaine de variables à 3 kilo-octets selon l'appareil).
- Il est nécessaire de stocker les paramètres utilisateur en double dans le flash du microcontrôleur.
- Chaque instance de paramètres utilisateur doit se terminer par CRC32 immédiatement après la charge utile.
- Pour chaque instance de paramètres utilisateur, une page distincte est utilisée dans la mémoire flash (même si les données utiles sont de 2 Ko et que les pages sont divisées par 128 Ko, alors la page entière est donnée sous un seul bloc)
- Le code du logiciel du microcontrôleur doit stocker les paramètres par défaut, qui doivent être les blocs de paramètres utilisateur si les deux contiennent des données corrompues.
Tentatives de résolution
GCC (au moment de la rédaction) n'a pas de drapeau pour obtenir une copie de la section. Compléter le script LD avec des << lignes magiques >> échoue également. On pourrait travailler avec objcopy, mais cette approche est très implicite et conduit à des erreurs subtiles.Décision
La solution consiste à créer des copies implicites de l'entité souhaitée (variable, structure, tableau, etc.) dans le code utilisateur, suivi de leur emplacement en mémoire.Création d'une macro pour la copie cachée de structures
Créons une macro avec laquelle nous réserverons une place pour la structure en RAM, 2 instances de mémoire flash sur des pages séparées et dans le code utilisateur (pour les valeurs initiales).#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__;
À l'aide d'une macro, 4 instances de la structure avec des noms différents seront créées. Dans le code du projet (à l'exception du système pour travailler avec les pages de mémoire des paramètres utilisateur), tous les modules doivent utiliser une structure sans préfixe (le nom spécifié dans la macro) qui sera en RAM (il sera possible de changer les modules qui sont responsables de l'interaction avec l'utilisateur. Le reste ne devrait que lire).Un exemple d'utilisation d'une macro dans le code utilisateur: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
})
Une fois que l'instance de la structure name_st est disponible à partir du code de projet.Création de macros pour la copie cachée d'autres entités
Pour créer une variable, il vous suffit de faire la substitution de macro pour créer des structures.Pour les tableaux, ajoutez le nombre d'éléments.#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__;
Le principe d'utilisation est similaire à l'utilisation de la structure.Finalisation du script LD
Lors de la création de copies d'entités, il a été indiqué dans quelles sections elles se trouvent. Vous devez maintenant créer ces sections dans le script LD. Pour F4, le script LD augmenté de ST ressemblera à ceci:Script LDMEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K - 256K
USER_CFG_PAGE_1 (rx) : ORIGIN = 0x080C0000, LENGTH = 128K - 4
USER_CFG_PAGE_2 (rx) : ORIGIN = 0x080E0000, LENGTH = 128K - 4
CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
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;
__stack = ORIGIN(RAM) + LENGTH(RAM);
_estack = __stack;
__Main_Stack_Size = 1024 ;
PROVIDE ( _Main_Stack_Size = __Main_Stack_Size ) ;
__Main_Stack_Limit = __stack - __Main_Stack_Size ;
PROVIDE ( _Main_Stack_Limit = __Main_Stack_Limit ) ;
_Minimum_Stack_Size = 512 ;
PROVIDE ( _Heap_Begin = _end_noinit ) ;
PROVIDE ( _Heap_Limit = __stack - __Main_Stack_Size ) ;
SECTIONS
{
.isr_vector :
{
KEEP(*(.isr_vector))
KEEP(*(.cfmconfig))
*(.after_vectors .after_vectors.*)
. = ALIGN(4);
} >FLASH
.inits :
{
. = ALIGN(4);
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(.preinit_array_sysinit .preinit_array_sysinit.*))
KEEP(*(.preinit_array_platform .preinit_array_platform.*))
KEEP(*(.preinit_array .preinit_array.*))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4);
} >FLASH
.flashtext :
{
. = ALIGN(4);
*(.flashtext .flashtext.*)
. = ALIGN(4);
} >FLASH
.text :
{
. = ALIGN(4);
*(.text .text.*)
*(.rodata .rodata.*)
*(vtable)
KEEP(*(.eh_frame*))
*(.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.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > FLASH
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
__exidx_end = .;
. = ALIGN(4);
_etext = .;
__etext = .;
_sidata = _etext;
.data : AT ( _sidata )
{
. = ALIGN(4);
_sdata = . ;
__data_start__ = . ;
*(.data_begin .data_begin.*)
*(.data .data.*)
*(.data_end .data_end.*)
*(.ramfunc*)
. = ALIGN(4);
_edata = . ;
__data_end__ = . ;
} >RAM
.bss (NOLOAD) :
{
. = ALIGN(4);
__bss_start__ = .;
_sbss = .;
*(.bss_begin .bss_begin.*)
*(.bss .bss.*)
*(COMMON)
*(.bss_end .bss_end.*)
. = ALIGN(4);
__bss_end__ = .;
_ebss = . ;
} >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
PROVIDE ( end = _end_noinit );
PROVIDE ( _end = _end_noinit );
PROVIDE ( __end = _end_noinit );
PROVIDE ( __end__ = _end_noinit );
._check_stack :
{
. = ALIGN(4);
. = . + _Minimum_Stack_Size ;
. = ALIGN(4);
} >RAM
.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) }
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
.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) }
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
}
Dans cet exemple, la structure de configuration ne réside pas dans la zone RAM, mais dans CCMRAM, pour améliorer les performances. Je note également que les blocs de données des paramètres utilisateur sont les 2 dernières pages flash, chacune de 128 ko.Initialisation et contrôle des copies
Le soin de ce qui se trouvera dans la RAM appartient au programmeur. Avant d'utiliser les données de cette zone pour la première fois, vous devez initialiser la zone. En flash, lors du chargement du programme dans le microcontrôleur, les données seront préenregistrées avec les valeurs initiales spécifiées dans le code du programme. Cela fera des copies non valides des blocs de paramètres utilisateur dans des pages distinctes s'ils se terminent par CRC32 (car aucun calcul de CRC32 n'est effectué. Votre code peut le faire. Aussi jusqu'à la première utilisation des paramètres de configuration).Pour écrire un module pour travailler avec des blocs de configuration, vous devrez utiliser les variables du script LD. Les éléments suivants seront requis: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;
N'oubliez pas non plus que user_cfg_data_flash_page_size et user_cfg_data_ram_page_size peuvent être affectés comme valeurs normales. Mais user_cfg_data_flash_page_1_start et les autres variables stockant l'adresse doivent être spécifiés via &.