背景使用stm32f103c8t6RAM地址0x20000000大小0x5000flash地址为0x08000000大小为0x10000。新建两个工程分别是bootloader和app两个工程目录结构一致大概如下。keil设置首先将两个工程都改为扇区擦除然后再更改ROM的起始和大小将64KB分为两个32KB。bootloaderapp为啥这样改了就能指定将程序存储在指定flash位置呢因为分散加载文件的指导(.sct文件)。keil默认勾选了该选项所以在对话框更改ROM后rebuild后就会自动生成sct文件程序将会按照这个sct文件设置布局如果取消勾选就能使用自定义的sct文件。sct文件解释; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x08000000 0x00010000 { ; 加载域烧录Flash起始地址 ER_IROM1 0x08000000 0x00010000 { ; 运行域(Flash) *.o (RESET, First) ; RESET段向量表放最头部 *(InRoot$$Sections) ; Keil ARM 编译器内置特殊符号。 .ANY (RO) ; 代码、常量 .ANY (XO) ; execute-only只执行段(现代ARM编译器特性纯代码无数据)兼容新版编译器 } RW_IRAM1 0x20000000 0x00005000 { ; 第二个运行域(RAM) .ANY (RW ZI) ; 全局变量、bss零初始化区 } }第一行LR_IROM1表示加载域。作用告诉链接器下面所有代码 / 常量数据打包后烧录到 Flash0x08000000开始的区域。所有被大括号包裹的内容最终会打包进 bin/hex 文件。ER_IROM1表示运行域这个运行域跟LR_IROM1加载域相同因为大部分单片机的片内Flash都是Nor Flash是支持随机寻址的擦除需要按区擦除所以程序就在Flash运行。.ANYRO表示代码段和const常量。RESET段向量表放最头部啥意思呢这个RESET段就是start.s里定义的的RESET段存放向量表也可以叫向量表段类型为数据只读。RW_IRAM1也是运行域被括号包含的内容是运行在RAM里的包括RW和ZI段RW段就是普通的全局变量ZI段也叫bss段是未初始化或者初始化为0的全局变量。这些变量一般都会随着程序运行而修改所以只能在RAM运行方法1汇编跳转uartuaer.ctypedef unsigned int uint32_t; typedef struct { volatile uint32_t SR; volatile uint32_t DR; volatile uint32_t BRR; volatile uint32_t CR1; volatile uint32_t CR2; volatile uint32_t CR3; volatile uint32_t GTPR; } USART_InitTypeDef; void uart_init(void){ volatile unsigned int *p; //RCC的APB2EN寄存器 p(volatile unsigned int *)(0x400210000x18); *p | 0x4004; //GPIOA的CR寄存器 p(volatile unsigned int *)(0x400108000x04); //使能GPIOA与USART1 //PA9 *p ~((34) | (36));//清除对应位 *p | (14) | (26);//复用推挽输出10M模式 //PA10 *p ~((38) | (310)); *p | (08) | (110); USART_InitTypeDef *j(USART_InitTypeDef *)0x40013800; j-BRR | (44) | 5;//115200 j-CR1 |(113) | (012) | (010) | (12) | (13);//串口使能1起始8数据n停止无校验接收使能发送使能 j-CR2 ~(312);//1个停止位 } int Send_Data(char data){ USART_InitTypeDef *j(USART_InitTypeDef *)0x40013800; while((j-SR (17))0); j-DRdata; return 0; } int Receive_Data(void){ USART_InitTypeDef *j(USART_InitTypeDef *)0x40013800; while((j-SR (15))0); return j-DR; } void putstr(char *str) { USART_InitTypeDef *j(USART_InitTypeDef *)0x40013800; while(*str) { //Send_Data(*str); while((j-SR (17))0); j-DR*str; str; } }uart.h#ifndef _UART_H #define _UART_H void uart_init(void); int Send_Data(char data); int Receive_Data(void); void putstr(char *str); #endifbootloaderstart.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0 DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT main LDR SP, (0x200000000x5000) BL main ENDP start_app PROC EXPORT start_app [WEAK] mov sp,r0 BX r1 ENDP ENDmain.c#include uart.h void delay(int d) { while(d--); } extern void start_app(unsigned int sp,unsigned int pc); int main() { unsigned int *p(unsigned int *)0x8008000; unsigned int sp*p; unsigned int pc*(p1);//得到向量表第二项的地址 void (*app)(void);//函数指针 uart_init(); putstr(BootLoader\r\n); start_app(sp,pc); while(1) { } return 0; }appstart.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD (0x200000000x5000) DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT app_main BL app_main ENDP ENDmain.c#include uart.hvoid delay(int d){while(d--);}int app_main(){while(1){putstr(App...\r\n);delay(1000000);}return 0;}代码理解bootloader的start.s__Vectors就是向量表的定义第一项是0也就是说主栈指针为0即flash首地址的4字节值为0硬件上电自动执行MSP *(0x08000000)→ MSP 0 所以调试可以看到 SP0。这是非法栈但是马上进入 Reset_Handler 第一行LDR SP,0x20005000立刻覆盖修复短暂 SP0 不会死机。⚠️ 标准工程不会这么写标准是向量表第一项直接填栈顶地址硬件自动加载正确 SP当前app的start.s就是这样的第二项是Reset_Handler函数地址可以看到flash里显示的Reset_Handler地址是0x08000089但是PC寄存器显示的是0x08000088这是因为ARM Thumb 规则 函数地址最低位必须置 1表示使用 Thumb 指令集。Reset_Handler 在 Flash 的地址是0x08000088存入向量表时编译器自动|1→0x08000089。 后面BX跳转依靠这个 bit 切换但是bootloader的main.c里可以看到并没有pc pc - 1的代码因为指令会读取 R1 最低位切换内核进入 Thumb。 一切合法不需要代码手动 pc pc - 1查看app的flash部分可以看到第一项是栈指针第二项是Reset_Handler的地址1app程序如何硬件debug如何调试app程序呢首先将app代码修改一下增加uart初始化然后点击debug再将sp改为0x20005000将pc改为0x08008009-1Reset_Handler地址然后就能正常调试app啦方法2重定位Vectorkeil设置依旧不变串口代码也不变bootloaderstart.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0 DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT main LDR SP, (0x200000000x5000) BL main ENDP start_app PROC EXPORT start_app [WEAK] LDR sp,[r0] ;r0 is 0x08008000 LDR r1,[r0,#4] LDR r3,(0xE000ED08) STR r0,[r3] BX r1 ENDP ENDmain.c#include uart.h void delay(int d) { while(d--); } extern void start_app(unsigned int new_vector); int main() { unsigned int new_vector0x08008000; uart_init(); putstr(BootLoader\r\n); start_app(new_vector); while(1) { } return 0; }appstart.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD (0x200000000x5000) DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT app_main BL app_main ENDP ENDmain.c#include uart.h void delay(int d) { while(d--); } int app_main() { uart_init(); while(1) { putstr(App...\r\n); delay(1000000); } return 0; }代码理解这种方法的bootloader采用的是重定向向量表c8t6的VTOR寄存器为0xE000ED08将这个寄存器设为0x08008000即系统硬件会从0x08008000处去读取向量表方法三拷贝app到ram运行注意bootloader部分和方法二完全相同甚至都不用重新烧录bootloader相对跳转和绝对跳转编译器默认情况下BL指令优先使用相对跳转如果涉及到函数指针的地方则会使用绝对跳转所以如果程序程序的链接地址为内存并且函数里没有指针相关的操作那么凭借相对跳转app可能也能正常运行。不过注意要改向量表才行加载域和执行域加载域可以说就是程序存储的地方执行域就是程序执行的地方之前的程序除开RW段和ZI段其余的段加载域都等于执行域。keil设置取消勾选使用自定义sct文件sct文件LR_IROM1 0x08008000 0x00008000 { ; load region size_region ER_IROM1 0x20000000 0x00005000 { ; load address execution address *.o (RESET, First) .ANY (RO) .ANY (XO) .ANY (RW ZI) } }start.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD (0x200000000x5000) DCD 0x08008009 ; Reset Handler AREA |.text|, CODE, READONLY READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT app_main IMPORT copy_to_ram IMPORT |Image$$ER_IROM1$$Length| adr r0, Reset_Handler bic r0, r0, #0xFF ;清除低8位得到0x08008000 ldr r1, __Vectors ; r10x20000000 ldr r2, |Image$$ER_IROM1$$Length| ; LENGTH BL copy_to_ram BL app_main ENDP ENDmain.c#include uart.h void delay(int d) { while(d--); } void copy_to_ram(unsigned int *src, unsigned int *dst, unsigned int len){ int i 0; for(i 0; i len / 4 1; i){ dst[i] src[i]; } } int app_main() { uart_init(); while(1) { putstr(App...\r\n); delay(1000000); } return 0; }代码解释这里向量表第二项不能写Reset_Handler因为改了sct文件现在链接地址都是0x2的内存地址这时候还没把app拷贝到内存所以要手动指定为Reset_Handler在flash里的存储位置先在flash里执行Reset_Handler调用copy_to_ram将app拷贝到ram然后执行main函数就是在ram里了。查看dis反汇编文件错误写法方法四(bootloader拷贝app到ram)在方法三的基础上删除app部分自我拷贝的代码保留sct文件在bootloader里增加拷贝app的代码sct文件LR_IROM1 0x08008000 0x00008000 { ; load region size_region ER_IROM1 0x20000000 0x00005000 { ; load address execution address *.o (RESET, First) .ANY (RO) .ANY (XO) .ANY (RW ZI) } }appstart.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD (0x200000000x5000) DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT app_main BL app_main ENDP ENDmain.c#include uart.h void delay(int d) { while(d--); } int app_main() { uart_init(); while(1) { putstr(App...\r\n); delay(1000000); } return 0; }增加头部编译app后将app的bin文件使用mkimage工具加上头部-a表示load地址-e表示入口地址生成后用STM32 ST-LINK Utility工具烧录到flash的0x08008000用法bootloaderstart.sPRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0 DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT main LDR SP, (0x200000000x5000) BL main ENDP start_app PROC EXPORT start_app [WEAK] LDR sp,[r0] LDR r1,[r0,#4] LDR r3,(0xE000ED08) STR r0,[r3] BX r1 ENDP ENDmain.c里面要解析app代码的头部将除开头部的app拷贝到内存#include uart.h typedef unsigned int __be32; typedef unsigned char uint8_t; #define IH_NMLEN 32 /* Image Name Length */ typedef struct image_header { __be32 ih_magic; /* Image Header Magic Number */ __be32 ih_hcrc; /* Image Header CRC Checksum */ __be32 ih_time; /* Image Creation Timestamp */ __be32 ih_size; /* Image Data Size */ __be32 ih_load; /* Data Load Address */ __be32 ih_ep; /* Entry Point Address */ __be32 ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */ } image_header_t; void delay(int d) { while(d--); } unsigned int be32_to_cpu(unsigned int data) { uint8_t b0 (data 24) 0xff; uint8_t b1 (data 16) 0xff; uint8_t b2 (data 8) 0xff; uint8_t b3 data 0xff; return (b3 24) | (b2 16) | (b1 8) | b0; } void copy_to_ram(unsigned int *src, unsigned int *dst, unsigned int len){ int i 0; for(i 0; i len / 4 1; i){ dst[i] src[i]; } } extern void start_app(unsigned int new_vector); void relocate_and_start_app(unsigned int pos) { /* 读出头部 */ int i; unsigned int size; unsigned int load; image_header_t *head (image_header_t *)pos; unsigned int new_pos pos sizeof(image_header_t); /* 解析头部 */ size be32_to_cpu(head-ih_size); load be32_to_cpu(head-ih_load); /* 复制app */ copy_to_ram((unsigned int *)new_pos, (unsigned int *)load, size); /* 跳转执行 */ start_app(load); //start_app(new_pos)也可以 } int main() { unsigned int app_position0x08008000; uart_init(); putstr(BootLoader\r\n); relocate_and_start_app(app_position); while(1) { } return 0; }实验结果注意如果有些芯片不支持重定位向量表比如产生中断只能去flash起始地址查找。也有办法。可以实现bootloader的向量表对应的函数在里面直接调用app向量表对应的函数比如可以在bootloader里声明一个结构体结构体成员是很多个函数指针定义一个结构体指针结构体指针指向app的向量表地址比如0x08008000然后就能逐个实现函数在函数里面通过结构体指针调用到app里的中断函数