一、Hex格式
【Intel英特尔Hex格式官方文档】 【什么是Hex文件格式?】
除每行开头的 ‘:’ 外,其它均是2字节表示一个十六进制数。例如02表示0x02,F2表示0xF2
行开头:每行以ASCII的 : 为开头 长度:标识数据区域的字节数 地址:相对于基地址的偏移地址 类型标识:有6种,如下表 数据区域 行校验: 冒号后、校验和前的值累加,并对和值按位取反后加1
数据标识定义 | 指代本行数据类型 |
---|
00 | 数据 | 01 | 文件结束 | 02 | 扩展段地址。段地址始于8080CPU那个年代,现在段地址这个概念被弱化甚至尘封,忽略即可 | 03 | 起始段地址 | 04 | 扩展线性地址。 如上图中第1行标识了数据地址的高16位为0x0800即基地址为0x08000000,后面数据类型为数据的行的地址均为偏移地址,偏移地址都要加上基地址才能得出实际的地址,如出现下一个扩展线性地址,则表示指定新的基地址 | 05 | 起始线性地址。 告诉单片机从哪个地址开始运行程序 |
在Keil MDK中更改程序起始地址 0x8000000 -> 0x8005000 后, hex文件的地址变化如下:
二、烧写地址设置
在Keil MDK中,对Cortex-M3/4已经默认了程序烧录起始地址是0x08000000,并且可以选择在烧写程序前进行全片擦除或是按需擦除 。那么就可以划分出两块区域分别放Bootloader程序和APP程序。
例如如下划分
| Start | Size |
---|
Bootloader | 0x8000000 | 0x5000 (20K) | APP | 0x8005000 | 0x78000 (492K) | 或者 | | | APP | 0x8000000 | 0x78000 (492K) | Bootloader | 0x8078000 | 0x5000 (20K) |
Bootloader在前:单片机上电时先运行Bootloader,等待升级指令。无指令时计时,超时进入APP;有升级指令,则开始升级APP即接收升级数据写入Flash,写入完成后跳转APP。 Bootloader在后:单片机上电时直接进入APP,运行过程中接收到升级指令后跳转Bootloader进行APP升级。升级完成后跳转APP。 同样的,在APP中也可升级Bootloader,但一般不需要。 Bootloader的功能尽量单一,大小尽量小,且占用的地址不能与APP的重叠。
需要特别注意的是,如果不勾选“Use Memory Layout from Target Dialog”,则上面选项卡中设置的地址是不起作用的,需要自行修改分散加载文件(未研究)。
三、Bootloader跳转APP代码
跳转前需要检查APP的栈顶指针是否合法、关闭所有中断、重设栈顶指针、偏移中断向量表(可以在APP中设置)。
定义如下数据
#define APP_LOADED_ADDR 0x5000
typedef void (*pAppFunction) (void);
pAppFunction application;
__IO uint32_t JumpAddress = NULL;
升级APP完成后,调用如下代码
if (0x20000000 == ((*(__IO uint32_t*)APP_LOADED_ADDR) & 0x2FFE0000))
{
__disable_irq();
JumpAddress = *(__IO uint32_t*)(APP_LOADED_ADDR + 4);
application = (pAppFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) APP_LOADED_ADDR);
printf("APP地址%#x, \r\n", APP_LOADED_ADDR);
application();
}
- 宏 APP_LOADED_ADDR 是APP的起始地址,而APP程序的起始是栈顶指针。
- 为什么加载地址不是0x8005000?
因为选择了从Flash启动,所以Flash被映射到了0x00000000。所以访问0x080050000和0x5000效果是一样的。 - 所以如果正确写入了APP,则 地址 (__IO uint32_t *)APP_LOADED_ADDR 处的栈顶指针必然在该型号单片机的SRAM大小范围内。96K Sram地址范围为0x2000 0000 - 0x2001 7FFF,64K Sram地址范围为0x2000 0000 - 0x2000 FFFF。
- (0x20000000 == (((__IO uint32_t)APP_LOADED_ADDR) & 0x2FFE0000)) 表示的是从地址0x5000取出栈顶指针,通过运算检查栈顶地址是否落在0x2000 0000 - 0x2001 7FFF。64K的检查为(0x20000000 == (((__IO uint32_t)APP_LOADED_ADDR) & 0x2FFF0000))。相当于读出的值直接与Sram范围值比较。
- 跳转到APP不是跳转栈顶指针,而是跳转Reset_Handler,而Reset_Handler在栈顶指针后,所以需要偏移32位。
- application为一个void类型的函数指针,并将其指向Reset_Handler的地址。application()表示执行指针指向的函数,即Reset_Handler函数。
- 跳转前关闭中断,防止 1)已经跳转到APP了但来了一个中断并落在了Bootloader, 2)中断向量表已偏移但来了中断,就会落在错误的地方
需要特别注意的是,如果使能了Systick等内核级别的中断,用__disable_irq(); 之类的屏蔽中断的语句是无法关闭的,需要去停止Systick的运行。最优的是在bootloader跳转前关闭所有用到的资源以及中断,在APP中重新配置所需要的,以免在Bootloader中用到的中断在APP中没有用到,而缺少对应的中断处理导致中断死循环或HardFault。
四、APP中的处理
#define NVIC_VECTTAB_RAM ((uint32_t)0x20000000)
#define NVIC_VECTTAB_FLASH ((uint32_t)0x08000000)
#define NVIC_VECTTAB_OFFSET_MASK ((uint32_t)0x1FFFFF80)
#define USER_BOOT_EXIST 1
#define VECT_TAB_OFFSET 0x5000
#if (USER_BOOT_EXIST == 1)
nvic_vector_table_set(NVIC_VECTTAB_FLASH, VECT_TAB_OFFSET);
__enable_irq();
#endif
void nvic_vector_table_set(uint32_t nvic_vict_tab, uint32_t offset)
{
SCB->VTOR = nvic_vict_tab | (offset & NVIC_VECTTAB_OFFSET_MASK);
}
- 如果在Bootloader跳转APP前就设置了中断向量偏移,则略过。
- NVIC_VECTTAB_OFFSET_MASK掩码为0x1FFFFF80是因为VTOR寄存器的格式
- APP地址偏移不能随意,CortexM3 M4权威指南中有如下描述。且还要关注该型号单片机的Flash每页大小,因为写Flash时需要整页擦除,须避免APP和Bootloader代码在同一页。
|