bootloader一般分为三个部分,上位机客户端,boot程序和App程序。
上位机客户端软件:用来将mcu的app程序文件发送给mcu,发送的通讯方式有很多种,常见的如串口,can,甚至以太网。上位机发送的文件类型有很多种格式,如bin、hex、s19等。
boot程序:boot程序的主要功能分为三部分,第一,接收客户端软件发来的程序;第二,将接收到的程序写入到mcu的flash中,即对片内flash进行编程;第三,跳转到App程序。
App程序:就是我们正常运转的应用程序,和普通引用的程序不同的是它是由bootloader引导才开始执行的,不是reset之后执行的默认程序。
对于客户端软件如何将app发送给mcu,方案非常多。以个人当前程序为例,就是上位机通过串口将程序bin文件发送给mcu,每次发送128字节,直至发送完成。在这里就不过多赘述。本文将以s32k如何将app程序写入片内flash和app程序跳转为重点讲述bootloader。
将接收到的app程序写入mcu的flash,这个涉及到难点主要是mcu内部flash编程。s32k148有2M 程序flash,地址从0x00000000开始到0x00200000结束,其中每4kB又作为一个sector,且叫做扇区。flash编程主要涉及flash编程的初始化,扇区擦除,向flash写数据。这里我参考了这份代码,GitHub - Talendu/S32K144Bootloader: S32K14x系列单片机代码。首先初始化flash编程相关的时钟,然后初始化flash,flash_pflash_init();之后就是xmodem_write_image(uint8_t *p_data, uint16_t data_size)来对flash进行擦和写操作。在xmodem_write_image中主要用到flash_pflash_erase_sectors()和flash_write_PFLASH(),flash擦和写过程中,要关闭mcu中断。擦除操作最小单位是扇区,写的操作最小单位是8个字节,且这写操作的起始地址要以8字节为单位对齐。对于xmodem_write_image(...);调用需要注意几点,__g_flash_app_addr初始的设置要与4kb地址对齐,且4kb应该是每次写入flash数据长度的整数倍,如每次写入128kb,256kb等等,但是不要出现每次写入160kb,这虽满足与8字节想对齐,但是会遇到跨扇区编程的问题,就需要重新编写xmodem_write_image()函数。
void flash_clock_init(void)
{
//开时钟门
PCC->PCCn[PCC_FTFC_INDEX] |= PCC_PCCn_CGC_MASK;
// 等待命令完成
while((FTFC->FSTAT & FTFC_FSTAT_CCIF_MASK) != FTFC_FSTAT_CCIF_MASK);
//启用写入缓冲区及cache
LMEM->PCCCR = 0x85000001;
}
int main()
{
...
flash_clock_init();
flash_pflash_init();
...
while(1)
{
if (get_program_data)
{
xmodem_write_image(...);
}
}
}
app程序跳转程序的跳转主要参考s32k144 bootloader - 纯洁de小学生 - 博客园。
如何从bootloader程序跳转到App程序,见如下代码。注意这里的APP_ADDR与上文中__g_flash_app_addr初始化地址要保持一致。注意这里经试验证实不需要加入/* 设置栈顶指针*/ MSR_MSP(APP_ADDR);添加该代码反而无法实现跳转。
typedef void (*bootloader_fun)(void); /*定义函数指针类型*/
bootloader_fun jump2app; /*定义函数指针*/
..........
/* 函数指针指向app的复位向量表的地址。注意将地址强转成函数入口地址 */
jump2app = (bootloader_fun)*(uint32_t*)(APP_ADDR + 4);
/*将pc指针指向入函数地址(app地址)运行*/
jump2app();
作为app程序,也需要作出相应调整。
1、调整app程序的中断向量偏移地址
int main()
{
/* 此处偏移地址必须和bootloader中的一样 */
S32_SCB->VTOR = APP_ADDR;
/* 关闭全局中断*/
__asm volatile ("cpsie i" : : : "memory");
....
}
2、修改链接脚本,不同的ide,配置不一样,这里以s32DS为例,这里是APP程序.ld文件,红框中显示的偏移地址为0x2000,默认是0x0000.
APP程序修改完完成。
为什么要修改app 中断偏移向量地址,不修改,App中断向量入口和bootloader将会重合,那么App程序将会调用bootloader的中断服务程序,这显然是不对的。
至于为什么要修改链接脚本:函数的调用本质程序jump 到函数相应的入口地址而已。这个地址是什么,编译器是知道的,我们也可以通过map文件看到。同样的程序,如果链接脚本的偏移地址不一样,即使同一函数的访问地址也是不一样的。既然APP程序存储位置发生一定的偏移,就得告诉编译器偏移地址,不然编译器仍然以默认偏移地址生成函数的地址map,结果就是app 跳转到了错误的函数地址。
最后向引用网址的两位作者表示由衷感谢。