目录
最近搞freertos+lvgl的一个项目,当我想为这个项目移植一个bootloader功能是出现了之前没有遇到的问题---任务调度无法被正确开启。最开始我以为是某一些内存泄漏造成的,排查之后没有找到问题所在,之后经过查阅相关资料便有了这篇文章---重定位向量。
在STM32使用FreeRTOS时如何重定位向量表实现Bootloader跳转
在STM32和其他Cortex-M系列微控制器上,通常会使用Bootloader加载应用程序,以便实现固件更新或多应用启动等功能。当我们的应用程序地址不是默认的`0x08000000`时(比如从`0x08008000`开始),FreeRTOS的`vTaskStartScheduler()`有时无法正常启动,这主要是由于中断向量表的基地址问题。本文将详细解释该问题的原因以及如何解决。
什么是向量表?
向量表(Vector Table)是存储在微控制器闪存(Flash)或存储器中的一个地址表,包含了系统异常和中断的服务程序地址。在 Cortex-M 系列的微控制器(如 STM32)上,向量表通常位于 Flash 的起始地址
0x08000000
或 SRAM 起始地址。向量表的作用是告诉微控制器,当发生特定的中断或异常时,应该跳转到哪个位置去执行相应的代码。
向量表的结构
向量表的结构是一组固定顺序的地址,每个地址指向一个中断或异常的服务函数(即中断服务例程,ISR)。通常向量表的前几个地址具有固定功能:
-
主堆栈指针(MSP)初始值
向量表的第一个地址(地址
0x00000000
)通常存储主堆栈指针(Main Stack Pointer)的初始值,用于初始化堆栈。 -
复位向量(Reset Vector)
向量表的第二个地址(地址
0x00000004
)存储复位向量(即复位中断处理函数的地址),当系统上电复位或手动复位时,微控制器将跳转到这个地址开始执行。 -
其他异常和中断向量
后续的地址对应系统异常(如硬件故障、系统调用等)和各种中断的入口地址。
向量表的工作原理
当系统发生异常或中断时,微控制器会根据中断号去查找对应的向量表项,获取服务函数的地址,然后跳转到这个地址执行中断服务程序(ISR)。这种方式可以快速找到对应的中断处理代码,实现高效的中断响应。
为什么要重定位向量表?
在STM32微控制器中,默认的中断向量表位于`0x08000000`(Flash 的起始地址)。当启动FreeRTOS等实时操作系统时,系统中断(如`SysTick`定时器)会调度任务,若中断向量表地址不正确,系统将无法找到中断服务例程(ISR),导致任务调度失败。即应用程序并不是从默认的起始地址运行。例如,如果 Bootloader 占据了 Flash 的前 32KB,那么应用程序的起始地址可能会被设置为
0x08008000
。因此,如果应用程序的起始地址更改,必须在程序启动时将中断向量表基地址更新为新的起始地址。
在这种情况下,微控制器仍然会将中断请求指向 0x08000000
的向量表,从而导致中断无法正常处理。为了解决这个问题,我们可以通过修改 SCB->VTOR
寄存器,将向量表的基地址设置为应用程序的起始地址,使中断和异常能够正确跳转到新的服务函数地址。
重定位向量表的原因总结如下:
1.中断向量表重定位
Cortex-M系列的`SCB->VTOR`(向量表基地址寄存器)决定了中断向量表的位置。通过重定位向量表,我们确保了从非默认地址启动的应用程序依旧能够正确处理中断。
2.Bootloader与应用程序的分离
在Bootloader-应用程序结构中,Bootloader和应用程序通常存放在Flash的不同区域。例如,Bootloader在`0x08000000`到`0x08003FFF`,而应用程序在`0x08008000`开始。为了让应用程序正确运行,Bootloader跳转到应用程序地址后,必须重定位向量表。
3.FreeRTOS的中断需求
FreeRTOS依赖系统中断(例如`SysTick`)进行任务调度,若向量表未更新则会导致调度失败。
如何实现向量表重定位?
在STM32使用FreeRTOS并指定不同的起始地址时,向量表的重定位通常放在应用程序的初始化代码中,即`main()`函数开头。在启动FreeRTOS调度器之前,我们需要确保`SCB->VTOR`指向应用程序的起始地址。
具体步骤如下:
1.定义应用程序的起始地址
假设应用程序的起始地址为`0x08008000`,可以在代码中定义:
defineAPP_START_ADDRESS0x08008000
2.在main()中重定位向量表
在`main()`函数(当然也可也再boor程序中进行重新定位)中将`SCB->VTOR`设置为应用程序的起始地址:
int main(void)
{
//重定位中断向量表基地址到应用程序的起始地址
SCB->VTOR=APP_START_ADDRESS;
HAL_Init();//初始化HAL库
sys_stm32_clock_init(336,8,2,7);//配置系统时钟
delay_init(168);//延时初始化
//继续初始化和创建任务
usart_init(115200);//初始化USART
led_init();//初始化LED
key_init();//初始化按键
//启动FreeRTOS的任务调度器之前完成所有初始化
vTaskStartScheduler();//启动调度器
//不应该到达这里
while(1);
}
3.确认启动文件和链接脚本
确保启动文件和链接脚本(例如`.ld`文件)中定义的应用程序地址与实际的`APP_START_ADDRESS`一致。例如,将应用程序起始地址在链接脚本中配置为:
FLASH(rx):ORIGIN=0x08008000,LENGTH=448K
关键步骤解析
1.早期重定位`SCB->VTOR`
向量表的基地址应尽早重定位。在初始化HAL和FreeRTOS调度器之前完成,以确保在初始化过程中任何中断都能跳转到正确的服务例程。
2.Bootloader跳转到应用程序
如果系统包含Bootloader,Bootloader在跳转至应用程序时应设置堆栈指针(MSP)和程序计数器(PC),具体代码如下:
voidJumpToApplication(uint32_tappAddress)
{
//获取应用程序的复位向量地址(首地址+4)
uint32_tjumpAddress=(volatileuint32_t)(appAddress+4);
//设置主堆栈指针
__set_MSP((volatileuint32_t)appAddress);
//设置程序计数器为应用程序的复位向量地址
void(appResetHandler)(void)=(void)jumpAddress;
appResetHandler();
}
3.确保调试输出和错误排查
在Bootloader和应用程序中添加调试输出,如`printf()`,便于跟踪执行流程和查找可能的问题。
总结
在STM32系统上运行FreeRTOS且应用程序起始地址不为`0x08000000`时,重定位向量表是至关重要的一步。通过将`SCB->VTOR`设置为应用程序的起始地址,我们可以确保在FreeRTOS或其他实时操作系统中正确处理系统中断和异常。这一方法在Bootloader系统中尤为常见,因为它允许Bootloader和应用程序共存且相互隔离,提升了系统的可靠性和灵活性。
如果出现`vTaskStartScheduler()`无法启动的问题,很可能是因为向量表未正确重定位,导致中断跳转出错。通过以上方法,可以在非默认起始地址上可靠地启动FreeRTOS应用程序。
这样就可以确保我们的应用程序无论存储在哪个位置,都能正确初始化并运行FreeRTOS提供的功能。