2. Flash分区规划与链接脚本配置
任务目标
在Bootloader差分OTA方案中,应用程序需要从固定地址启动(而非默认的0x08000000)。本案例将完成:修改链接脚本使应用程序链接到指定Flash地址,编写启动代码重定位中断向量表,并通过Bootloader跳转验证应用能否正确运行。
环境与依赖
- MCU:STM32F103系列(Flash基址0x08000000,SRAM基址0x20000000)
- 工具链:arm-none-eabi-gcc + Makefile(或STM32CubeIDE)
- Bootloader:位于0x08000000,跳转地址为0x08008000(即偏移32KB)
实现步骤
1. 链接脚本修改(app.ld)
将应用程序的Flash起始地址改为0x08008000,并保留中断向量表在段开头:
/* app.ld - 应用程序链接脚本 */
ENTRY(Reset_Handler)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 96K /* 偏移32KB,剩余96KB */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* 中断向量表必须位于起始地址 */
. = ALIGN(4);
} > FLASH
.text :
{
. = ALIGN(4);
*(.text*)
*(.rodata*)
. = ALIGN(4);
_etext = .;
} > FLASH
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data*)
. = ALIGN(4);
_edata = .;
} > RAM AT> FLASH
.bss :
{
. = ALIGN(4);
_sbss = .;
*(.bss*)
. = ALIGN(4);
_ebss = .;
} > RAM
}
2. 启动代码(startup.c)
在进入main前完成向量表重定位和必要的外设时钟使能:
/* startup.c - 应用程序启动代码 */
#include <stdint.h>
extern uint32_t _sdata, _edata, _sbss, _ebss;
extern int main(void);
/* 中断向量表 - 由链接脚本放置在0x08008000 */
extern void Reset_Handler(void);
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
(void (*)(void))0x20005000, /* 初始SP,指向RAM顶端 */
Reset_Handler, /* Reset */
/* 其他异常/中断可在此添加,本例省略 */
};
void Reset_Handler(void) {
/* 1. 拷贝.data段 */
uint32_t *src = &_sdata;
uint32_t *dst = (uint32_t*)&_sdata;
/* 注意:_sdata在RAM中,_etext在Flash中,需用__etext */
extern uint32_t __etext;
src = &__etext;
dst = &_sdata;
while (dst < &_edata) *dst++ = *src++;
/* 2. 清零.bss段 */
dst = &_sbss;
while (dst < &_ebss) *dst++ = 0;
/* 3. 重定位中断向量表到SRAM(可选,但推荐) */
/* 若直接运行在Flash,可跳过此步;此处演示SRAM重定位 */
/* 实际工程中需将0x08008000处的向量表复制到SRAM并设置VTOR */
/* 本最小示例直接使用Flash中的向量表,不执行重定位 */
/* 4. 跳转main */
main();
while (1);
}
3. 主程序(main.c)
/* main.c - 验证应用程序运行 */
#include <stdint.h>
/* 简单延时 */
static void delay(volatile uint32_t count) {
while (count--) {}
}
int main(void) {
/* 使能GPIOC时钟(RCC_APB2ENR 地址0x40021018) */
volatile uint32_t *RCC_APB2ENR = (uint32_t*)0x40021018;
*RCC_APB2ENR |= (1 << 4); /* IOPCEN */
/* PC13配置为推挽输出(CRH 地址0x40011004) */
volatile uint32_t *GPIOC_CRH = (uint32_t*)0x40011004;
*GPIOC_CRH = (*GPIOC_CRH & ~(0xF << 20)) | (0x2 << 20); /* 输出50MHz */
volatile uint32_t *GPIOC_ODR = (uint32_t*)0x4001100C;
while (1) {
*GPIOC_ODR ^= (1 << 13); /* 翻转PC13 */
delay(2000000);
}
}
4. 编译与链接命令
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb startup.c -o startup.o
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb main.c -o main.o
arm-none-eabi-ld -T app.ld startup.o main.o -o app.elf
arm-none-eabi-objcopy -O binary app.elf app.bin
Bootloader跳转验证
Bootloader侧跳转代码(供参考,不属于本案例编译范围):
/* Bootloader跳转函数 - 位于0x08000000的Bootloader中 */
void jump_to_app(void) {
uint32_t app_addr = 0x08008000;
void (*app_reset)(void) = (void (*)(void))(*(uint32_t*)(app_addr + 4));
__set_MSP(*(uint32_t*)app_addr); /* 设置主栈指针 */
app_reset(); /* 跳转 */
}
验证方法与判定标准
- 构建检查:编译链接无错误,
app.elf生成成功。 - 地址验证:执行
arm-none-eabi-nm app.elf | grep Reset_Handler,确认Reset_Handler地址为0x08008004(向量表偏移4字节处)。 - 烧录与运行:将Bootloader烧录至0x08000000,将
app.bin烧录至0x08008000。复位后观察PC13引脚LED以约1Hz频率闪烁。 - 跳转成功标志:若LED闪烁,说明Bootloader成功跳转且应用程序在0x08008000正常运行;若LED不亮或跑飞(进入HardFault),检查向量表首字是否为有效栈顶地址(0x20005000)及链接地址是否正确。
常见问题
- 跳转后无反应:确认
app.bin确实烧录在0x08008000,且Bootloader跳转代码中读取的向量表地址正确。 - 中断不工作:若应用程序使用中断,需在启动代码中设置SCB->VTOR = 0x08008000(Cortex-M3),本最小示例未使用中断故省略。
- 链接报错region overflow:检查
app.ld中FLASH长度是否足够,当前配置96KB适用于较小应用。