STM32 BootLoader 刷新项目 (九) 跳转指定地址-命令0x55
前面我们讲述了几种BootLoader中的命令,包括获取软件版本号、获取帮助、获取芯片ID、读取Flash保护Level。
下面我们来介绍一下BootLoader中最重要的功能之一—跳转!就像BootLoader词汇中的Boot一词一样,就是启动跳转。
首先我们来介绍一下Boot跳转的应用。
1. BootLoader跳转指定地址的应用
STM32的BootLoader中跳转指定地址的应用场景主要包括以下几个方面:
-
固件升级(Firmware Upgrade):
- BootLoader允许在不改变硬件连接的情况下进行固件的在线升级。当新的固件版本需要部署到设备上时,BootLoader可以接收新的固件并将其烧录到指定的Flash地址,然后跳转到新固件的执行地址,从而实现固件的无缝更新。
-
多应用管理(Multi-Application Management):
- 在一些复杂的应用中,可能需要在同一个设备上运行多个应用程序。BootLoader可以通过跳转到不同的地址来选择性地加载和执行不同的应用程序,实现多应用的管理。
-
系统恢复(System Recovery):
- 如果设备在运行过程中出现软件故障,BootLoader可以作为一个恢复点,通过跳转到备份的固件地址来恢复系统的正常运行。
-
安全启动(Secure Boot):
- 在安全敏感的应用中,BootLoader可以检查固件的合法性,确保只有经过认证的固件才能被加载执行。这可以通过跳转到经过签名验证的固件地址来实现。
-
调试和测试(Debugging and Testing):
- 在开发和测试阶段,BootLoader可以方便地进行程序的调试和测试。开发者可以通过BootLoader跳转到不同的测试固件地址,快速验证新功能或修复bug。
-
节省资源(Resource Saving):
- 对于资源受限的嵌入式系统,BootLoader可以减少对外部存储器的需求,通过内部Flash存储固件,节省成本和空间。
-
产品差异化(Product Differentiation):
- 通过BootLoader,制造商可以为不同的市场或客户定制不同的固件版本,通过跳转到不同的固件地址来实现产品的差异化。
-
远程维护(Remote Maintenance):
- BootLoader支持远程固件更新,使得设备的维护和升级可以在不接触硬件的情况下完成,这对于远程或难以接触的设备尤为重要。
这些应用场景展示了BootLoader在STM32微控制器中的灵活性和重要性,它们使得设备能够更加智能、安全和易于维护。
2. 函数跳转的方法
在STM32的BootLoader中跳转到指定地址通常涉及到以下几个步骤:
- 验证目标地址:确保目标地址是有效的,并且位于应用程序的合法执行区域内。
- 设置堆栈指针:将堆栈指针(MSP)设置为应用程序的初始堆栈值。
- 跳转到应用程序:使用函数指针或者直接修改程序计数器(PC)来跳转到应用程序的入口点。
下面是一个详细的代码示例,包括注释,解释每一步的作用:
#include "stm32f10x.h" // 包含STM32F10x系列的头文件
// 假设应用程序的入口地址存储在特定的Flash地址
#define APP_ENTRY_ADDR (0x08005000) // 应用程序的入口地址
// 跳转到应用程序的函数
void Jump_To_Application(void) {
volatile uint32_t *appEntryAddr; // 指向应用程序入口地址的指针
void (*Reset_Handler)(void); // 应用程序的Reset_Handler函数指针
// 将appEntryAddr指向存储应用程序入口地址的位置
appEntryAddr = (uint32_t *) APP_ENTRY_ADDR;
// 检查应用程序入口地址是否有效
// 这里简单地检查地址是否在Flash范围内,实际应用中可能需要更复杂的检查
if ((*appEntryAddr & 0x2FFE0000) == 0x08000000) {
// 读取应用程序的入口地址
uint32_t appStartAddr = *appEntryAddr;
// 将Reset_Handler函数指针指向应用程序的Reset_Handler
Reset_Handler = (void (*)(void)) appStartAddr;
// 设置堆栈指针为应用程序的初始堆栈值
// 这通常是应用程序入口地址的下一个地址
__set_MSP(*((volatile uint32_t *) appStartAddr + 1));
// 关闭所有中断
NVIC->ICER[0] = 0xFFFFFFFF; // 禁用所有中断
NVIC->ICPR[0] = 0xFFFFFFFF; // 清除所有中断挂起位
// 跳转到应用程序的Reset_Handler
Reset_Handler();
} else {
// 应用程序入口地址无效,可以在这里处理错误
while(1) {
// 错误处理代码
}
}
}
代码解释:
- 包含头文件:包含STM32F10x系列的头文件,以便使用STM32的寄存器定义和宏。
- 定义应用程序入口地址:
APP_ENTRY_ADDR
是存储应用程序入口地址的位置。 - 跳转到应用程序的函数:
Jump_To_Application
函数执行跳转到应用程序的操作。 - 指向应用程序入口地址的指针:
appEntryAddr
指向存储应用程序入口地址的位置。 - 应用程序的Reset_Handler函数指针:
Reset_Handler
是一个函数指针,指向应用程序的Reset_Handler函数。 - 检查应用程序入口地址是否有效:通过检查地址是否在Flash范围内来验证地址的有效性。
- 读取应用程序的入口地址:从
appEntryAddr
读取应用程序的实际入口地址。 - 设置堆栈指针:将MSP设置为应用程序的初始堆栈值,通常是应用程序入口地址的下一个地址。
- 关闭所有中断:禁用所有中断并清除所有中断挂起位,以确保跳转过程中不会被中断干扰。
- 跳转到应用程序的Reset_Handler:通过
Reset_Handler
函数指针跳转到应用程序的Reset_Handler函数,开始执行应用程序代码。
这个代码示例展示了如何在BootLoader中跳转到应用程序的入口地址,包括地址验证、堆栈指针设置和实际的跳转操作。在实际应用中,你可能需要根据具体的硬件和应用程序需求调整这些步骤。
3. 0x55命令介绍–地址跳转
在本篇文章,我们的主要是介绍0x55的命令,这个命令主要是在BootLoader中让程序跳转到指定地址。
通过上位机发送10 Byte的数据,其中第1 Byte为整个数据的长度,第2Byte为指令码,第3-6 Byte为跳转的地址,第7-10 Byte为前6个Byte的CRC校验值。上位机通过串口UART发送给下位机,下位机回复地址是否跳转成功的标志。
下面是整个程序执行地址跳转的流程图:
4. 程序设计
下面我们来进行程序设计,下面是读取上位机指令,并解析指令的过程,通过switch case判断执行哪种命令。目前通过上位机执行BL_GO_TO_ADDR指令,然后执行bootloader_handle_go_cmd(bl_rx_buffer)函数。
void bootloader_uart_read_data(void)
{
uint8_t rcv_len=0;
printmsg_Host("BL_DEBUG_MSG: Receive CMD\n\r");
while (1)
{
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
memset(bl_rx_buffer, 0, 200);
//here we will read and decode the commands coming from host
//first read only one byte from the host , which is the "length" field of the command packet
HAL_UART_Receive(C_UART,bl_rx_buffer,1,HAL_MAX_DELAY);
rcv_len= bl_rx_buffer[0];
HAL_UART_Receive(C_UART,&bl_rx_buffer[1],rcv_len,HAL_MAX_DELAY);
switch(bl_rx_buffer[1])
{
case BL_GET_VER:
bootloader_handle_getver_cmd(bl_rx_buffer);
break;
case BL_GET_HELP:
bootloader_handle_gethelp_cmd(bl_rx_buffer);
break;
case BL_GET_CID:
bootloader_handle_getcid_cmd(bl_rx_buffer);
break;
case BL_GET_RDP_STATUS:
bootloader_handle_getrdp_cmd(bl_rx_buffer);
break;
case BL_GO_TO_ADDR:
bootloader_handle_go_cmd(bl_rx_buffer);
break;
default:
printmsg("BL_DEBUG_MSG:Invalid command code received from host \n");
break;
}
}
}
下面是地址跳转函数,通过过去输入的Buffer进行解析出跳转地址,判断跳转地址是否在正确的跳转范围内,如果在正确的跳转范围内,则向上位机发送能够跳转的回复,之后执行地址跳转。如果不在跳转范围内,则向上位机回复,地址不正确,请重新输入。
/*Helper function to handle BL_GO_TO_ADDR command */
void bootloader_handle_go_cmd(uint8_t *pBuffer)
{
uint32_t go_address=0;
uint8_t addr_valid = ADDR_VALID;
uint8_t addr_invalid = ADDR_INVALID;
printmsg("BL_DEBUG_MSG:bootloader_handle_go_cmd\n");
//Total length of the command packet
uint32_t command_packet_len = bl_rx_buffer[0]+1 ;
//extract the CRC32 sent by the Host
uint32_t host_crc = *((uint32_t * ) (bl_rx_buffer+command_packet_len - 4) ) ;
if (! bootloader_verify_crc(&bl_rx_buffer[0],command_packet_len-4,host_crc))
{
printmsg("BL_DEBUG_MSG:checksum success !!\n");
bootloader_send_ack(pBuffer[0],1);
//extract the go address
go_address = *((uint32_t *)&pBuffer[2] );
printmsg("BL_DEBUG_MSG:GO addr: %#x\n",go_address);
if( verify_address(go_address) == ADDR_VALID )
{
//tell host that address is fine
bootloader_uart_write_data(&addr_valid,1);
/*jump to "go" address.
we dont care what is being done there.
host must ensure that valid code is present over there
Its not the duty of bootloader. so just trust and jump */
/* Not doing the below line will result in hardfault exception for ARM cortex M */
//watch : https://www.youtube.com/watch?v=VX_12SjnNhY
go_address+=1; //make T bit =1
void (*lets_jump)(void) = (void *)go_address;
printmsg("BL_DEBUG_MSG: jumping to go address! \n");
lets_jump();
}else
{
printmsg("BL_DEBUG_MSG:GO addr invalid ! \n");
//tell host that address is invalid
bootloader_uart_write_data(&addr_invalid,1);
}
}else
{
printmsg("BL_DEBUG_MSG:checksum fail !!\n");
bootloader_send_nack();
}
}
5. 实战演练
下面是上位机的命令菜单,通过在终端调用Python脚本,然后在终端输入下位机连接的串口号,即可进入命令界面,目前可支持如下命令:
在上位机中输入命令5,即为在BootLoader中执行地址跳转命令0x55,MCU根据读取跳转地址0xXXXXXXXX的值,来进行跳转,并告诉上位机能否跳转成功,由下图可以看出,当前地址跳转成功。
目前我在STM32中刷了两段程序,其中BootLoader程序即为0x0800 0000-0x0800 7FFF处,后面0x08000 8000-0x080F FFFF为APP应用程序,则当我将跳转地址设为0x0800 8000时,MCU则会从BootLoader跳转进入到APP应用程序中。
但为什么我们在输入跳转指令地址为0x08008000的时候,并没有跳转到App程序呢,下面我通过Hex和大家解释一下。
下面我们用STM32官方提供的工具STM32CubeProgrammer工具读取MCU中的Hex信息。
Note:必须在连接ST-Link的时候才能显示Hex信息。
由上图我们可以看出在0x08008000地址开始处,其中第一个地址0x0800 8000地址中的值时0x20020000,这个值时Reset Handle,即中断向量表的起始地址。第二个地址0x0800 8004中的值才是实际App程序所在的Flash地址。
所以在下图中,我们执行地址跳转指令0x55,跳转地址设为0x0800 8B16,为什么比0x0800 8B15多1呢,这是因为我们使用的Thumb指令,执行的指令结尾必须+1.
下面我们通过串口监控一下Mcu是否跳转成功,由下图可以看出,MCU成功跳转到App程序中。
6. 系列文章
STM32 BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建
STM32 BootLoader 刷新项目 (二) 方案介绍
STM32 BootLoader 刷新项目 (三) 程序框架搭建及刷新演示
STM32 BootLoader 刷新项目 (四) 通信协议
STM32 BootLoader 刷新项目 (五) 获取软件版本号-命令0x51
STM32 BootLoader 刷新项目 (六) 获取帮助-命令0x52
STM32 BootLoader 刷新项目 (七) 获取芯片ID-0x53
STM32 BootLoader 刷新项目 (八) 读取Flash保护ROP-0x54