初始状态
有一个基于微控制器的设备(例如,将采用stm32f405rgt6)。启用后,它将根据用户首选项或默认设置调整外围设备。用户可以通过一种可能的界面(CLI菜单或用于设置通过二进制协议工作的工作参数的实用程序)在设备运行期间更改设置(通常,仅在集成到组合系统时)。设置完参数后,用户可以使用特殊命令(也可以通过可能的接口之一)保存设置。任务
- 为了减少运行期间访问设备设置变量所需的时间,您需要将当前值保留在RAM中(通常,这些数据的数量从十几个变量到3 KB不等,具体取决于设备)。
- 必须将用户设置一式两份存储在微控制器的闪存中。
- 用户设置的每个实例都必须在有效负载之后立即以CRC32结尾。
- 对于每个用户设置实例,闪存中都会使用一个单独的页面(即使有用数据为2 kb,并且这些页面除以128 kb,然后整个页面都在一个块内给出)
- 微控制器软件代码必须存储默认设置,如果两者均已损坏,则默认设置应为用户设置块
尝试解决
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,来自ST的增强LD脚本将如下所示:LD脚本MEMORY
{
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) }
}
在此示例中,配置结构不位于RAM区域中,而是位于CCMRAM中,以提高性能。我还注意到,用户设置数据块是最后2个闪存页面,每个页面均为128 kb。副本的初始化和控制
内存将由程序员负责。首次使用该区域中的数据之前,需要初始化该区域。在闪存中,将程序加载到微控制器中时,数据将以程序代码中指定的初始值进行预记录。如果用户设置块以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和其他存储地址的变量。