概述
在日常开发过程中,为了产品后续维护,版本号管理是必不可少的一项,不管是开发中测试、量产、还是后期升级迭代都是需要版本号管理来区分。在此写一篇文章做个笔录,需要用的知识分散加载,如还不太懂这方面的知识,可去“百度”或者“deepseek”科普,这里不再赘述。
这里介绍有两种方式:
1、第一种,使用__attribute__((at(ADDR_BASE))))
2、第二种,使用__attribute__((section(".version_info"))),需要在.sct文件中修改,才能使用
区别是第一种比第二种占用空间大,后面会介绍。
一、开发环境
1、硬件平台
STM32F401CEU6
内部Flash : 512Kbytes,SARM : 96 Kbytes
二、STM32CubeMx配置
2.1、系统时钟配置

2.2、下载调试配置

2.3、生成代码


2.4、编译工程

2.5、查看该型号芯片Falsh的地址、大小信息


三、编码
1、main.c
/* USER CODE BEGIN 0 */
#define ENABLE_SCT 1 //需要添加 .sct文件里面的内容才行。
/* 编译信息结构体 - 将存储在Flash中 */
typedef struct {
char compile_data[12]; /* 编译日期 "MMM DD YYYY" */
char compile_time[10]; /* 编译时间 "HH:MM:SS" */
char compiler_info[10]; /* 编译器信息 */
char hardware_version[10]; /* 硬件版本号 */
char firmware_version[10]; /* 软件版本号 */
char mcu_type[15]; /* MCU型号 */
} __attribute__((packed)) build_info_t;
//#define INFO_ADDR_BASE (0x8000000 + 0x80000 - 0x800) //最后的1k地址,起始地址: 0x8000000, 大小是: 0x80000(512k), 预留2k空间存储, 有个弊端就是固件空间占很大
#define INFO_ADDR_BASE (0x8000000 + 0x4000) //最后的1k地址,起始地址: 0x8000000, 偏移16k地址用来存放, 通过map得知,改个合理的地址专门存放该信息即可
/* 编译信息实例 - 使用特定段存储 */
//const build_info_t firmware_build_info __attribute__((section(".build_info"), used)) = {
#if ENABLE_SCT
const build_info_t firmware_build_info __attribute__((section(".build_info"))) = { //要在.sct文件中修改,才能使用
#else
const build_info_t firmware_build_info __attribute__((at(INFO_ADDR_BASE + 0x00))) = {
#endif
__DATE__,
__TIME__,
#ifdef __ICCARM__
"IAR-ARM", /* IAR编译器*/
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
"KEIL-MDK", /* keil编译器 */
#else
"Unknown",
#endif
"HW_V1.2.1", /* 硬件版本 */
"FW_V1.0.1", /* 软件版本 */
"STM32F401CEUx" /* MCU型号 */
};
/* 详细版本信息字符串 */
//const char firmware_build_info[] __attribute__((section(".version_info"), used)) =
#if ENABLE_SCT
const char detailed_version_info[] __attribute__((section(".version_info"))) =
#else
const char detailed_version_info[] __attribute__((at(INFO_ADDR_BASE + 0x400))) = // 偏移1k地址空间
#endif
"=== heihei === \r\n"
"Firmware Version: 1.0.1\r\n"
"Build Data:" __DATE__ "\r\n"
"Build TIME:" __TIME__ "\r\n"
"Cocyright (c) 2025 heihei\r\n";
////------------------------------------------------------------------------------#include //------------------------------------------------------------------------------
//#define VERINFO_ADDR_BASE (0x8004F00) // 版本信息在FLASH中的存放地址
//const char Hardware_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x00))) = "Hardware: 1.0.0";
//const char Firmware_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x20))) = "Firmware: 1.0.0";
//const char Compiler_Date[] __attribute__((at(VERINFO_ADDR_BASE + 0x40))) = "Date: "__DATE__;
//const char Compiler_Time[] __attribute__((at(VERINFO_ADDR_BASE + 0x60))) = "Time: "__TIME__;
////------------------------------------------------------------------------------
//volatile int test_num __attribute__((at(0x20018040))) = 0;
//volatile int num __attribute__((at(0x8080400))) = 0;
//
//void AA(void) __attribute__((section("RAM1")));
//void AA(void)
//{
// volatile int a = 1;
//}
//void BB(void) __attribute__((section("ROM1")));
//void BB(void)
//{
// volatile int test_num = 2;
//}
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 1 */
// void AA(void);
// AA();
// void BB(void);
// BB();
// test_num = 9;
// num = 6;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM1_Init();
MX_IWDG_Init();
MX_USART6_UART_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);
HAL_UART_Receive_IT_Enable();
printf("heihei min task \r\n");
// printf("buildData: %s\r\n", firmware_build_info.compile_data);
// printf("buildTime: %s\r\n", firmware_build_info.compile_time);
// printf("buildInfo: %s\r\n", firmware_build_info.compiler_info);
// printf("firwareVersion: %s\r\n", firmware_build_info.firmware_version);
// printf("mcu_type: %s\r\n", firmware_build_info.mcu_type);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
四、结果
A、第一种方式
1、在main.c文件第 62行代码
#define ENABLE_SCT 1
改成
#define ENABLE_SCT 0


2、再次编译工程,如下所示

3、编译时间

4、使用.map文件查看地址分配信息

5、使用ST- LINK 查看hex文件内容




6、使用软件debug模式,查看内容信息


B、第二种方式
1、在main.c文件第 62行代码
#define ENABLE_SCT 1
改成
#define ENABLE_SCT 0

2、找到.sct文件,重新命名避免原文件的覆盖。


3、修改.sct文件内容


; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00080000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
; 新增代码 start
ER_BUILD_INFO 0x08040000 0x1000 {
main.o (.build_info, +First) ;
}
ER_VERSION_INFO 0x08050000 0x1000 {
main.o (.version_info, +First) ;
}
; 新增代码 end
RW_IRAM1 0x20000000 0x00018000 { ; RW data
.ANY (+RW +ZI)
}
}
4、同理上面都修改好后,再次编译工程。


5、使用.map文件查看地址分配信息

6、使用ST-LINK 查看hex固件内容


五、总结
好了,讲解完毕,希望能帮助到大家,蟹蟹参阅。
浙公网安备 33010602011771号