一、 概述
本文将基于 S32K11x 平台介绍基于 A/B 分区交换的 Bootloader 实现方法,Bootloader 程序作为引导加载程序,其最根本的目的是为了正确引导加载用户代码,即将用户代码加载到指定的位置然后跳转到该位置运行,当然,也有可能用户代码已经事先加载完成,Bootloader只需要完成跳转即可,一旦跳转到用户代码那么 Bootloader 程序的使命也就完成了。利用Bootloader,我们可以实现固件升级的功能。本文介绍的 Bootloader 基于 A/B 分区交换,即用户代码分区总共有两个分区(分区A 和分区 B),用户代码可以运行在 A 分区或者 B 分区,Bootloader 运行后会先判断新固件要加载到哪个分区,然后等待接收新固件,加载好新固件后会跳转到新固件所在的分区去执行。由于用户代码可能在 A 分区或者 B 分区执行,因此用户的代码需要使用位置无关代码,关于位置无关代码的生成在此就不细说。A/B 分区交换的运作流程大致如下图:
图 1.1 A/B 分区交换运作流程示意图
二、 Bootloader 分区
实现 Bootloader 之前我们需要规划代码的分区,即规定每个分区的位置和大小,以及分区所存放的内容,本文所讲解的 Bootloader 例程是基于 S32K116 这颗芯片,我们先了解下这颗芯片的 Flash 规格。
图 2.1 S32K1xx Flash 规格
本文基于 S32K116 的 A/B 分区交换的 Bootloader 分区如下:
图 2.2 S32K116 的Bootloader分区
从上图可以看到 Bootloader 代码被分配到 DFlash 内存块,Bootloader 的中断向量表位于 0x0 起始地址,A 分区起始地址为 0x800,依次存放固件信息、中断向量表、应用代码,B 分区起始地址为 0x10800。
我们可以通过修改链接文件来配置代码的链接地址,链接文件存放路径为:工程所在路径/Project_Settings/Linker_Files
图 2.3 修改链接文件
如上图所示,我们将 m_text 段的地址修改为 0x10000000,我们的 Bootloader 代码便放在该段上。
三、 程序流程
Bootloader 代码的主要流程如下图所示:
图 3.1 Bootloader 代码流程
- 初始化时钟、CAN、串口、Flash 等驱动
- 查看 A 区和 B 区 固件信息(查看固件是否有效,两个区固件都无效指定 A 区为 new 区 ;只有其中一个区固件无效直接指定此区为 new 区 ;都有效则继续查看版本号) ,从而知道当前哪一个是正在运行的应用程序(称为 old 区,即版本号大的区),哪一个是如果要更新时可擦除存放的区域(称为 new 区,即版本号小的区),然后分别获取 old 区和 new 区基地址 ,顺便获取 old 区的版本号
- 进入一个大循环 ,查看 old 区的版本号是否有效(若为0xffffffff 则无效,反之则有效),是则表明两个区固件都无效 ,一直处于等待接收代码的状态 ,此时不设置超时 ,因为无固件跳转过去是一种错误 。如果 old 区的版本号是有效的 ,则等待接收新固件,此时需要设置超时,如果在超时之前正确接收到新固件,则跳转到 New 区执行,否则跳转到 Old 区执行。
四、 数据接收
关于 BootLoader 的数据接收部分,本文将以 CAN 作为传输方式进行介绍,实际上用户可以根据自身需求采取其他的通信方式。
如下表为 CAN 消息帧的定义
帧传输 |
CAN ID |
载荷大小(字节) |
载荷 |
方向 |
作用 |
起始 |
0x200 |
4 |
0x15151515 |
主机->Bootloader |
说明外部要更新固件 |
地址 |
0x100 |
4 |
固件信息或固件相对地址 |
主机->Bootloader |
接下来的数据存放的相对基地址的偏移地址 |
地址 |
0x100 |
4 |
0x53535353 |
主机->Bootloader |
相应的数据已经传输 |
数据 |
0x300 |
32 |
固件信息或固件的内容 |
主机->Bootloader |
固件信息或固件内容,应存放到PFlash |
应答 |
0x400 |
4 |
0x04040404 |
Bootloader->主机 |
Bootloader已经接收处理完毕,课继续传输 |
应答 |
0x400 |
4 |
0x55555555 |
Bootloader->主机 |
Bootloader 接收错误 ,停止传输 |
表 4.1 CAN 消息帧定义
消息的通信流程如下图所示:
图 4.2 消息传输流程
大致的消息传输流程为:
- 主机端发起消息传输请求
- 主机端发送固件头的地址信息和数据
- 主机端发送固件的逻辑地址和数据
五、 程序跳转
当 Bootloader 正确加载好应用代码后便会跳转到对应的分区去执行应用代码,下面来说下跳转代码,跳转代码的源码如下所示:
void JumpToApplication(uint32_t start_address)
{
uint32_t __attribute__((unused)) pc;
uint32_t __attribute__((unused)) sp;
S32_SCB->VTOR=(uint32_t)(start_address); // 更新中断向量基地址
sp = *((volatile uint32_t*)start_address); // 获取栈顶
__asm (" msr msp, r3");
pc = *((volatile uint32_t *)(start_address + 4)); // 直接跳转到复位函数
__asm("mov pc, r3");
}
该跳转函数的输入参数为跳转的绝对地址,例如要跳转到 A 分区,则该地址便设置为0x1000,该地址必须为向量表的起始地址。跳转函数主要执行三个命令:
- 设置向量表的偏移
- 设置 SP 堆栈指针的值
- 设置 PC 指针的值
向量表的偏移值即是要跳转的绝对地址,而 SP 堆栈指针的值则存放在向量表的第一个向量,PC 指针的值则指向复位向量的地址,即向量表中的第二个向量。只要这三个步骤正确执行,那么便可以跳转指定地址去执行代码了。
评论