灵动微课堂 (第248讲)|mm32-2nd-bootloader技术白皮书(4)——设计实现简单的2nd bootloader
来源:灵动MM32MCU发布时间:2023-03-232946浏览
询问 AI
点击蓝字添加“星标★” 关注我们
什么是 2nd Bootloader?
引导加载程序(Bootloader)是为了能够正确执行应用程序,在执行应用程序之前对系统进行的一系列初始化操作,并且在完成一系列初始化后,能够引导 CPU 去执行应用程序的程序。
为什么 Bootloader 要在前面加上一个 2nd?因为在执行2nd Bootloader 之前,MCU 中就已经在执行了一个 Bootloader,可以称为 1st Bootloader,这个 Bootloader 的目的是等待片内时钟稳定,确定 MCU 的 BOOT 引脚电平,来了解自己应该执行哪块区域的代码——是用户代码,还是 ISP 代码,如图 1 所示。

图1 1st Bootloader
1st Bootloader 已经在 MCU 出厂的时候,固化在芯片内部,用户无法改动这块区域的内容。为了满足客制化需求,可以在在 1st Bootloader 的基础上,再进入一次 Bootloader,做一些客制化的事情,这就是 2nd Bootloader,如图2所示。

图2 简单的 2nd Bootloader
甚至,可以通过 2nd Bootloader,在 MCU 中存储两套应用程序,选择其一执行,就像电脑的双系统那样,也可以实现定制化的 ISP 功能,OTA 功能,如图3所示:

图3多功能的 2nd Bootloader
2nd Bootloader 的基本需求
当程序下载到 QSPI Flash 之后,为了能够执行 QSPI Flash 中的程序,就需要在 MCU 复位之后,把 QSPI 外设和 GPIO 引脚配置好,QSPI 外设和 GPIO 引脚看上去像是 BSP 相关的事情(确实是与 BSP 相关),能否放在应用程序的 “board_init()” 中呢?当然不行,把 QSPI 外设和 GPIO 引脚配置好这些事情要在进入应用程序之前完成,这样才能正常执行应用程序。
因此, 2nd Bootloader 第一个基本需求就是:初始化 QSPI 外设和使用到的 GPIO 引脚。
初始化好 QSPI 外设和 GPIO 引脚后,第二个问题随之而来,怎么跳转到应用程序中呢?
第二个基本需求就出来了:跳转并执行应用程序。
快速体验
MindSDK 提供了简单的 2nd Bootloader 的样例,方便用户开发:
在 demo_apps/basic 中选择 bootloader_qspi 工程,如图 4 所示。

图4 选择 demo_apps/basic/bootloader_qspi 工程
编译并下载到 PLUS-F5270 中即可使用,十分方便。
但本文将继续讲解 2nd Bootloader 的实现方式,以了解 2nd Bootloader 的工作原理,方便继续开发 2nd Bootloader。
软件实现
初始化 QSPI 外设和 GPIO 引脚
PLUS-F5270 使用到的 QSPI Flash 引脚如表 1 所示。

表1 PLUS-F5270使用的QSPI引脚
初始化系统时钟, QSPI GPIO 外设时钟:
voidBOARD_InitBootClocks(void)
{
CLOCK_ResetToDefault();
/*QSPI.*/
RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_QSPI,true);
RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_QSPI);
/*GPIOA.*/
RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOA,true);
RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOA);
/*GPIOB.*/
RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB,true);
RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOB);
/*GPIOG.*/
RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOG,true);
RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOG);
}
初始化 GPIO 引脚:
voidBOARD_InitPins(void)
{
GPIO_Init_Typegpio_init;
/*PB10-QSPI_CS.*/
gpio_init.Pins=GPIO_PIN_10;
gpio_init.PinMode=GPIO_PinMode_AF_PushPull;
gpio_init.Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,gpio_init);
GPIO_PinAFConf(GPIOB,gpio_init.Pins,GPIO_AF_10);
/*PG7-QSPI_SCK.*/
gpio_init.Pins=GPIO_PIN_7;
gpio_init.PinMode=GPIO_PinMode_AF_PushPull;
gpio_init.Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,gpio_init);
GPIO_PinAFConf(GPIOG,gpio_init.Pins,GPIO_AF_10);
/*PG6-QSPI_D0.*/
gpio_init.Pins=GPIO_PIN_6;
gpio_init.PinMode=GPIO_PinMode_AF_PushPull;
gpio_init.Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,gpio_init);
GPIO_PinAFConf(GPIOG,gpio_init.Pins,GPIO_AF_10);
/*PA3-QSPI_D1.*/
gpio_init.Pins=GPIO_PIN_3;
gpio_init.PinMode=GPIO_PinMode_AF_PushPull;
gpio_init.Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,gpio_init);
GPIO_PinAFConf(GPIOA,gpio_init.Pins,GPIO_AF_10);
/*PB3-QSPI_D2.*/
gpio_init.Pins=GPIO_PIN_3;
gpio_init.PinMode=GPIO_PinMode_AF_PushPull;
gpio_init.Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,gpio_init);
GPIO_PinAFConf(GPIOB,gpio_init.Pins,GPIO_AF_10);
/*PG8-QSPI_D3.*/
gpio_init.Pins=GPIO_PIN_8;
gpio_init.PinMode=GPIO_PinMode_AF_PushPull;
gpio_init.Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,gpio_init);
GPIO_PinAFConf(GPIOG,gpio_init.Pins,GPIO_AF_10);
}
初始化 QSPI 直接读模式:这里我们采用四线快读的方式访问 QSPI Flash。
voidBOARD_InitExternalFlash(void)
{
/*initqspihardware.*/
QSPI_Init_Typeqspi_init;
qspi_init.SckDiv=BOARD_QSPI_SCK_DIV;
qspi_init.CsHighLevelCycles=BOARD_QSPI_CS_HIGH_LEVEL_TIME;
qspi_init.RxSampleDelay=BOARD_QSPI_RX_DELAY_CYCLES;
qspi_init.SpiMode=QSPI_SpiMode_3;/*suggestusingSPIMode3.*/
/*initqspi.*/
QSPI_Init(QSPI,qspi_init);
/*enableqspidirectreadmode.*/
QSPI_DirectXferConf_Typedirect_conf;
direct_conf.CmdBusWidth=BOARD_EXT_FLASH_CMD_BUS_WIDTH;
direct_conf.CmdValue=BOARD_EXT_FLASH_CMD_VALUE;
direct_conf.AddrBusWidth=BOARD_EXT_FLASH_ADDR_BUS_WIDTH;
direct_conf.AddrWordWidth=BOARD_EXT_FLASH_ADDR_WORD_WIDTH;
direct_conf.AltBusWidth=QSPI_BusWidth_None;
direct_conf.DummyCycles=BOARD_EXT_FLASH_DUMMY_CYCLES;
direct_conf.DataBusWidth=BOARD_EXT_FLASH_DATA_BUS_WIDTH;
QSPI_EnableDirectRead(QSPI,direct_conf);
}
代码中涉及到的宏定义:
/*APPBASE.*/
#defineBOARD_APP_BASE0x90000000
/*QSPI.*/
#defineBOARD_QSPI_SCK_DIV4u
#defineBOARD_QSPI_RX_DELAY_CYCLES1u
#defineBOARD_QSPI_CS_HIGH_LEVEL_TIME2u
/*cmd.*/
#defineBOARD_EXT_FLASH_CMD_BUS_WIDTHQSPI_BusWidth_1b
#defineBOARD_EXT_FLASH_CMD_VALUE0x6B/*fastreadquad.*/
#defineBOARD_EXT_FLASH_ADDR_BUS_WIDTHQSPI_BusWidth_1b
#defineBOARD_EXT_FLASH_ADDR_WORD_WIDTHQSPI_WordWidth_24b
#defineBOARD_EXT_FLASH_DUMMY_CYCLES8u
#defineBOARD_EXT_FLASH_DATA_BUS_WIDTHQSPI_BusWidth_4b
QSPI Flash 的地址映射空间为 0x90000000 ~ 0x9FFFFFFF,共 256MB 大小,这段信息来自微控制器的 UM 地址映射章节,如图 5 所示。

图5 QSPI Flash 在 MCU中的地址映射
跳转执行应用程序
在跳转执行应用程序之前,先看下应用程序的二进制代码长什么样。
以 MindSDK 的 hello_world 工程为例,生成一份程序的二进制文件,打开 MDK 工程,点击魔术棒(Options for Target...),点击 User 列表,如图x所示,在指定位置(红框中的 User Command)加入下面这句话,并在前面打上对勾:
fromelf.exe --bin -o "@L.bin" "#L"
然后编译工程,就能在工程文件所在的目录下找到生成的 bin 文件。

图6 生成二进制文件
可以使用如 VS Code 等软件查看这个二进制文件的内容,如图5所示:

图7 应用程序的二进制片段
看下应用程序的开头内容:开头是 “00 C0 01 30” 的数据,它是 0x3001C000 的小端模式表示,而 0x3001C000 是应用程序的 SP (堆栈指针)初始化值。后面紧接着的的 “09 17 00 08” 则代表 0x08001709,Reset_Handler() 的指针。前面一段内容,就是中断向量表,通过中断向量表可以知道这段应用程序的 SP 起始地址,Reset_Handler() 函数的指针,而这两个数据,再加上存放这些数据的起始地址,是能够正确执行应用程序的关键。
uint32_t*vtor;
voidjump_to_app(uint32_tapp_base)
{
vtor=(uint32_t*)app_base;
......
}
vtor[0] 就是 SP 指针,vtor[1] 就是 Reset_Handler() 函数指针。
可以发现,vtor 变量是以全局变量的方式进行存储的,而并不是在函数中定义变量(局部变量)保存,其原因是修改 MSP 和 PSP 指针后,局部变量会发生意外的变化,导致后面无法正确跳转程序。
修改中断向量表:
voidjump_to_app(uint32_tapp_base)
{
......
__disable_irq();
SCB->VTOR=app_base;
__ISB();
__DSB();
......
}
修改中断向量表是为了保证应用程序的中断程序能够正确执行,可以做一个实验,将修改中断向量表的代码注释掉,编译下载这个 2nd Bootloader,发现应用程序开始是能正常执行的,但是呢,一旦触发了中断,程序就不知道跑到一个莫名的位置上了,这个莫名的位置就是 2nd Bootloader 的中断程序。
为了保证修改中断向量表,以及后面修改 MSP 和 PSP 指针时不会有中断干扰,需手动关闭所有中断;使用 __disable_irq() 关闭所有中断。
Cortex-M0 的中断向量表修改方法和 Cortex-M3,M4,Star-MC1 等内核不同,它没有 VTOR 寄存器,而是将应用程序的中断向量表复制到 SRAM 中,通过修改某一个寄存器的字段值(非 Cortex-M0 内核寄存器,需参考具体芯片的手册进行修改),将绝对地址 0x00000000 起始的映射区间(不同 MCU 的映射区间大小可能不同)由片内 Flash 起始地址变为 SRAM 的起始地址,而绝对地址 0x00000000 就是 Cortex-M0 内核芯片的中断向量表起始地址。
修改 MSP PSP:voidjump_to_app(uint32_tapp_base)
{
......
__set_MSP(vtor[0]);
__set_PSP(vtor[0]);
......
}
当这两个指针改变后,再读取函数中的局部变量,会发现其值发生了变化,这是由于局部变量是通过 SP 指针加相对地址进行访问的,当SP 指针发生变化后,局部变量的实际存储位置也会发生变化,修改后,再访问局部变量的时候,由于还是通过堆栈指针加相对地址进行访问,则局部变量的绝对地址发生了变化,访问到的值就不再是实际值。
跳转执行应用程序:
voidjump_to_app(uint32_tapp_base)
{
......
__enable_irq();
((void(*)(void))vtor[1])();
......
}
在跳转执行应用程序前,我们需要将前面已经关闭的中断再次打开,使其在跳转应用程序后,中断函数能够正确执行;使用 __enable_irq() 打开所有中断。
跳转执行应用程序的方法就是执行应用程序的 Reset_Handler() ,Reset_handler() 的指针就存储在中断向量表的第二个四字节地址中,因此我们读取vtor[1]的值,并将其强制转换成指向函数的指针,并执行这个函数即可实现应用程序的跳转。
跳转程序完整代码:
uint32_t*vtor;
voidjump_to_app(uint32_tapp_base)
{
vtor=(uint32_t*)app_base;
__disable_irq();
SCB->VTOR=app_base;
__ISB();
__DSB();
__set_MSP(vtor[0]);
__set_PSP(vtor[0]);
__enable_irq();
((void(*)(void))vtor[1])();
}
初始化板子相关的事情后,就可以使用这个函数进行跳转程序:
intmain(void)
{
BOARD_Init();
/*jumptoapp.*/
jump_to_app(BOARD_APP_BASE);
while(1)
{
}
}
至此,简单的 2nd Bootloader 就已经完成了,现在,有一个能下载程序到 QSPI Flash 的下载算法,有一个能跳转执行应用程序的 2nd Bootloader,至于验证,还需要等下一章编译出一个存储在 QSPI Flash 上的应用程序后,才能验证。
结语
2nd Bootloader 本质上其实就是一个应用程序,是一段由 1st Bootloader 引导执行的应用程序。它的好处是可以根据客户需求进行定制,自由修改。
本文只是先实现一个简单的 2nd Bootloader,其作用只是帮助 MCU 去执行存储在 QSPI Flash 中的应用程序。
本文使用的访问 QSPI Flash 方法为四线快读模式,其实某些 QSPI Flash 还支持 QPI 模式,发送 0x38(不同 QSPI Flash 的进入方法可能不同) 指令后,即可进入,发送 0xFF 后即可退出;进入 QPI 模式后,访问 QSPI Flash 的所有阶段均为 4 线模式,加快了访问 QSPI Flash 的速度,这一步操作我们也可以在 2nd Bootloader 中实现。不过这会出现一个问题:需要在下载算法中判断 QSPI Flash 是否进入了 QPI 模式,该用何种方式下载程序,这会增加下载算法的代码量。
往期精彩
第247讲|mm32-2nd-bootloader技术白皮书(3)——设计实现QSPI Flash的下载算法
第246讲|mm32-2nd-bootloader技术白皮书(2)——QSPI外设简介
第245讲|mm32-2nd-bootloader技术白皮书(1)——配置软硬件环境
公众号推送机制改版
感兴趣的动态频频“失联”?
简单两步点亮“星标★”
不再错过每一篇灵动动态!

关于灵动
上海灵动微电子股份有限公司成立于 2011 年,是中国本土领先的通用 32 位 MCU 产品及解决方案供应商。公司基于 Arm Cortex-M 系列内核开发的 MM32 MCU 产品拥有 F/G/L/A/SPIN/W 六大系列,目前已量产 200多款型号,累计交付超 4 亿颗,每年都有近亿台配备了灵动 MM32MCU 的优秀产品交付到客户手中,在本土通用 32 位 MCU 公司中位居前列。

灵动客户涵盖智能工业、汽车电子、通信基建、医疗健康、智慧家电、物联网、个人设备、手机和电脑等应用领域。灵动是中国为数不多的同时获得了 Arm-KEIL、IAR、SEGGER 官方支持的本土 MCU 公司,并建立了独立、完整的通用 MCU 生态体系。灵动始终秉承着“诚信、承诺、创新、合作”的精神,为客户提供从硬件芯片到软件算法、从参考方案到系统设计的全方位支持。

灵动股份
微信号:MindMotion-MMCU


长按识别二维码关注我们

MORE
官网:www.mm32mcu.com
微信公众号:灵动MM32MCU
灵动MM32MCU技术论坛:
bbs.21ic.com/iclist-696-1.html
新闻来源:灵动MM32MCU,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。

