上一篇介绍完如何在程序中添加版本信息后,这篇介绍一下如何在 MCU 程序中固定位置添加程序的版本信息等。
首先了解一下__attribute__
机制,它是个编译器指令,告诉编译器声明的特性,或者让编译器进行更多的错误检查和高级优化。
GUN C中可以使用__attribute__()
给变量、函数和类型设置各种属性,而__attribute__
的section选项可以改变段的特性;
其中__attribute__((section("section_name")))
的作用是将该定义的函数或数据变量放入指定名为”section_name”段中。
无论是 GNU 还是 ARM 的编译器, 都支持
__attribute__
所指定的编译属性。
打开keil的options…,取消勾选下图所示,然后点击“Edit…”。
自动弹出“*.sct”文件(先编译通过再操作),下面就是 Keil 中 STM32 的链接文件,编译器会根据链接文件和__attribute__
的section选项(可以自己添加一个段,分配地址和大小)等分配函数和数据变量在程序固件中的地址。
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00010000 { ; load region size_region
ER_IROM1 0x08000000 0x00010000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00005000 { ; RW data
.ANY (+RW +ZI)
}
}
这里不做过多介绍了,下面介绍的方式不需要自己修改“*.sct”文件,还是采用__attribute__
的section选项,只不过在section选项中指定位置即可。
__attribute__ ((section(".ARM.__at_0x08000020")))
1.同样的定义一个结构体,里面定义一些软件版本相关的信息
typedef struct
{
char szVersion[32]; // 软件版本
char szBuildDate[32]; // 程序编译日期
char szBuildTime[32]; // 程序编译时间
}AppInfo_t;
2.通过__attribute__
定义一个只读结构体变量(只读的目的:防止程序改变、节约RAM)并固定变量在程序固件中的位置,赋初值(其中__DATE_
和__TIME__
是C语言中的内置宏,分别是当前的编译日期和编译时间)。
const AppInfo_t __attribute__ ((section(".ARM.__at_0x08002000"))) sg_tAppInfo =
{
"STM32_V0.1.5",
__DATE__,
__TIME__,
};
注:STM32的代码起始地址是从0x08000000开始的,且存储中断向量表信息,因此在选择程序地址的时候一定要绕开,也不能太靠后,不然生成的bin文件超出了实际的代码固件大小,在实现bin文件升级的时候就会耗时太长。
3.编译成功后打开hex文件,查看一下0x08002000所在的内容,版本信息和编译时间(之后可以通过新增代码或者变量验证是不是位置变了)
4.在主函数添加打印,将版本信息输出到终端上,打印结果就不演示了,有兴趣的朋友可以翻开上一章。
这里采用的映像工具是srec_cat,网上有源码(点击阅读原文下载srec_cat.exe),可以编译成 Windows 或 Linux 的可执行文件;这里用这个工具来打包固件信息,比如版本号,同时修改文件名,即文件名=版本号。
通过Bat脚本实现以下功能:
根据目录结构树编写指定脚本
MDK_STM32
---- CORE
---- STM32F10x_FWLib
---- OBJ(编译生成的hex文件)
---- USER
tool
---- srec_cat.exe
---- pack.bat
实现 pack.bat:
:: 版本信息前缀和长度
set strPrefix=STM32_
set strPrefixlen=6
:: hex 文件路径和文件名
set hexFilePath=..\\MDK_STM32\\OBJ
set hexFileName=main.hex
:: 版本信息信息起始地址
set verStringAddr=0x08002000
if %time:~0,2% leq 9 (set hour=0%time:~1,1%) else (set hour=%time:~0,2%)
if %time:~0,2% leq 9 (set minute=%time:~2,2%) else (set minute=%time:~3,2%)
:: 打包时间格式为年月日时分 T2206111526
set strTime=T%date:~2,2%%date:~5,2%%date:~8,2%%hour%%minute%
set strVersion=%strPrefix%%strTime%
:: 版本信息的起始和结束位置
set /a InfoEnd=%verStringAddr%+%strPrefixlen%+11
:: 拷贝临时文件进行处理
copy %hexFilePath%\\%hexFileName% .\\
.\\srec_cat.exe -generate %verStringAddr% %InfoEnd% -repeat-string %strVersion% %hexFilePath%\\main.hex -intel -exclude %verStringAddr% %InfoEnd% -o .\\%strVersion%.hex -intel
:: 删除临时文件
del %hexFileName%
编译完成后,双击 pack.bat 生成添加版本信息后的固件,之后需要通过J-LINK工具包打开生成的固件进行烧录(通过Keil编译下载的没有用)。
如果实现了 bootloader 程序,那么一定会用到寄存器SCB->VTOR
重新设置中断向量表的起始地址了,所以干脆可以将版本信息放在 APP 程序区中的最开始位置,后面紧跟中断向量表的起始地址。
这样做的好处是不用担心程序编译后版本信息的位置超出了APP可执行程序的实际大小,而且在实现升级的时候bootloader程序在一开始就可以直接对版本信息进行校验等。
同时通过映像工具 srec_cat 将 bootloader 和 APP 程序固件进行合并。
也能通过 keil 在编译后自动执行脚本。
全部0条评论
快来发表一下你的评论吧 !