[本文发布地址https://blog.csdn.net/Stack_/article/details/124683892,未经允许不得转载。转载须注明出处]
一、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文件的地址变化如下:
for (i = 0; i < 10; i++)
{
printf("%x, ", *((uint32_t *)0x8000000 + i));
}
printf("\r\n");
for (i = 0; i < 20; i++)
{
printf("%x, ", *((uint16_t *)0x8000000 + i));
}
printf("\r\n");
for (i = 0; i < 40; i++)
{
printf("%x, ", *((uint8_t *)0x8000000 + i));
}
加上以上代码以输出Flash地址的值,烧写程序之后,对比hex文件和打印结果
- Flash支持16位半字写入和32位整字写入,且为小端格式。
- 选择半字写入时,向地址0x08000000写入0x5B88,向地址0x08000002写入0x2000,向地址0x08000004写入0x0145,…
- 选择整字写入时,向地址0x08000000写入0x20005B88,向地址0x08000004写入0x08000145,向地址0x08000008写入0x0800014D,…
在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”,则上面选项卡中设置的地址是不起作用的,需要自行修改分散加载文件(未研究)。
跳转前需要检查APP的栈顶指针是否合法、关闭所有中断、重设栈顶指针、偏移中断向量表(可以在APP中设置)。
定义如下数据
#define APP_LOADED_ADDR 0x5000 //APP加载地址
typedef void (*pAppFunction) (void);
pAppFunction application;
__IO uint32_t JumpAddress = NULL;
升级APP完成后,调用如下代码
/* APP栈顶指针合法 */
if (0x20000000 == ((*(__IO uint32_t*)APP_LOADED_ADDR) & 0x2FFE0000))
{
__disable_irq(); //关闭所有中断
JumpAddress = *(__IO uint32_t*)(APP_LOADED_ADDR + 4); //Reset_Handler 入口地址
application = (pAppFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) APP_LOADED_ADDR); //APP程序堆栈指针起始
printf("APP地址%#x, \r\n", APP_LOADED_ADDR);
application(); //跳转到Reset_Handler即APP
}
- 宏 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跳转Bootloader亦应如此。
从串口接收到hex文件的一行,校验后调用以下函数写入FLASH。以下是简单的一个写入流程
void UpgradeAPP(xxx)
{
uint16_t i = 0;
uint16_t line_data_len = xxx; //数据域长度
static uint32_t base_addr = 0;
uint32_t offset_addr = 0x0000ffff & xxx; //偏移地址
uint32_t write_addr = 0;
static uint32_t has_erase_page = 0;
uint32_t need_erase_page = 0;
static bool APP_StartAddr_OK = false;
uint8_t line_type = xxx;
switch (line_type) //行数据类型
{
case 4: //指定基地址
base_addr = xxx;
base_addr = (base_addr << 16) & 0xffff0000;
【解锁FLASH】
break;
case 0: //写入Flash的数据
write_addr = base_addr | offset_addr;
if (APP_StartAddr_OK == false && ((APP_LOADED_ADDR + 0x08000000) == write_addr))
{
APP_StartAddr_OK = true; //APP Flash首地址正确
}
if (APP_StartAddr_OK == true)
{
for (i = 0; i < line_data_len; i += 4)
{
need_erase_page = ((write_addr + i) - 0x08000000) / 2048; //算出当前地址所在页,2K每页
if (need_erase_page != has_erase_page)
{
has_erase_page = need_erase_page;
【擦除该页】
}
【向指定地址以整字方式写入FLASH,或者接收到一页的量后采用页编程方式写入】
【读出对比】
printf("%x, ", *((volatile uint32_t *)(write_addr + i)));
}
printf("\r\n");
}
break;
case 1: //文件结束
【FLASH上锁】
break;
default:
break;
}
}
/* 基地址 */
#define NVIC_VECTTAB_RAM ((uint32_t)0x20000000) /*!< RAM */
#define NVIC_VECTTAB_FLASH ((uint32_t)0x08000000) /*!< Flash */
/* NVIC中断向量表偏移掩码 */
#define NVIC_VECTTAB_OFFSET_MASK ((uint32_t)0x1FFFFF80)
#define USER_BOOT_EXIST 1 //是否有bootloader
#define VECT_TAB_OFFSET 0x5000 //
#if (USER_BOOT_EXIST == 1)
/* APP中断向量表地址偏移 需要在Linker选项卡勾选“USE Memory Layout from Target Dialog”,并修改Target选项卡中的ROM的Start地址 */
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代码在同一页。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)