我有一个问题。 我正在为我的 STM32F446RE 板开发 IAP(应用内编程)工具,但我陷入了困境。 我已经开发了所有必要的实用程序,以便让微控制器从 GUI 接收二进制 (.bin) 编译文件,将其写入特定的闪存扇区并执行它。 当我想从上传的代码再次跳转到存储在闪存扇区 0 上的引导加载程序时,我的问题出现了,我看到代码没有跳转到引导加载程序,而是继续执行用户应用程序代码。我已经调试了代码,发现引导加载程序代码的所有地址(msp 和重置处理程序)均已正确设置,并且与上传的代码的地址不同。
我想要实现的流程如下:
1 --> 执行扇区 0 中存储的引导加载程序代码(当接收到来自用户按钮的中断时,从地址 0x0800 0000 开始),并将新接收到的代码写入扇区 2(从地址 0x0800 8000 开始)
2 --> 设置 msp 地址 (@0x0800 8000) 和复位处理程序地址 (0x0800 8004)
3 --> 跳转到新代码的复位处理程序地址(@0x0800 8004)
4 --> 执行新上传的代码。
5 --> 在用户代码执行期间,如果收到中断(来自用户按钮),则设置引导加载程序 msp 地址、重置处理程序并跳转到引导加载程序
6 --> 从第一步开始再次重复。
这是用于从引导加载程序跳转到用户应用程序的代码:
IAP_loadProgram(&data);
//pointer to the user application reset handler address
void (*user_resetHandler)(void);
//set the user application MSP address (user application starts on the flash SECTOR2
uint32_t msp_addr = *(volatile uint32_t *)APPLICATION_ADDRESS;
__set_MSP(msp_addr);
//Set now the addres of the reset handler
uint32_t resetAddr = *(volatile uint32_t *)(APPLICATION_ADDRESS + 4);
user_resetHandler = (void *)resetAddr;
//When there, the bootloader sector will be leaved and the user code execution starts
user_resetHandler();
最后,用于从用户应用程序代码跳转到引导加载程序的代码是:
if(toBootloader){
toBootloader = 0;
//pointer to the user application reset handler address
void (*bootLoader_resetHandler)(void);
//set the user application MSP address (user application starts on the flash SECTOR2
uint32_t msp_addr = *(volatile uint32_t *)BOOTLOADER_ADDRESS;
__set_MSP(msp_addr);
//Set now the address of the reset handler
uint32_t bootLoaderResetAddr = *(volatile uint32_t *)(BOOTLOADER_ADDRESS + 4);
bootLoader_resetHandler = (void *)bootLoaderResetAddr;
//When there, the user code sector will be leaved and the bootloader code execution starts
bootLoader_resetHandler();
}
其中 APPLICATION_ADDRESS 为 0x0800 8000,BOOTLOADER_ADDRESS 为 0x0800 0000。
bootloader代码前两个地址的内容为:
0x08000000:20020000
0x08000004:080044DD
同时应用代码前两个地址的内容为:
0x08008000:20020000
0x08008004:0800A1F1
我所做的最后修改是在用户应用程序链接器 (.ld) 文件中,我将闪存起始位置设置为地址 0x0800 8000(而不是地址 0x0800 0000)。
所有中断都正常工作,在代码上传后,如果我进行硬件重置,结果是相同的,代码执行从用户应用程序代码开始,而不是从引导加载程序开始。 有什么建议吗?
您的问题描述不清楚,但调用应用程序的过程还远远不够。您需要确保应用程序的环境与 uC 重置后的环境相同。您还需要更改向量表地址。
我写了数十个引导加载程序,但我不明白你的问题
这里有一个应该如何完成的示例(从引导加载程序调用应用程序)
void startAPP(void)
{
static uint32_t *pAppPosition;
static voidFunc *appResetHandler;
static uint32_t newSP;
pAppPosition = (uint32_t *)(bankStartAddress[0] + (uint32_t)&_BOOTFlashSize);
appResetHandler = (voidFunc *)pAppPosition[1];
newSP = pAppPosition[0];
SPI_DeInit();
FLASH_DeInit();
I2C_DeInit();
IRQ_DeInit();
GPIO_DeInit();
__disable_irq();
__set_MSP(newSP);
__enable_irq();
SCB -> ICSR = 0x00000000; // reset value;
SCB -> SCR = 0;
SCB -> CCR = 0x00000200; // reset value
SCB -> SHP[0] = 0;
SCB -> SHCSR = 0;
SCB -> CFSR = (SCB_CFSR_DIVBYZERO_Msk | SCB_CFSR_UNALIGNED_Msk | SCB_CFSR_UNDEFINSTR_Msk | SCB_CFSR_NOCP_Msk | SCB_CFSR_INVPC_Msk | SCB_CFSR_INVSTATE_Msk);
SCB -> HFSR = (SCB_HFSR_DEBUGEVT_Msk | SCB_HFSR_FORCED_Msk | SCB_HFSR_VECTTBL_Msk);
SCB -> VTOR = bankStartAddress[0] + (uint32_t)&_BOOTFlashSize; // new vector table pos. I od not clear 8 LSB because APP start position is aligned to FLASH Sectors which are at least 2k aligned
// SysTick
SysTick -> CTRL = 0;
SysTick -> LOAD = 0;
SysTick -> VAL = 0;
appResetHandler();
__builtin_unreachable();
}
从应用程序运行引导加载程序的最简单、最安全的方法就是使用 CMSIS
NVIC_SystemReset()
功能发出软重置。
if( toBootloader )
{
NVIC_SystemReset() ;
}
通过直接调用跳转到引导加载程序是不必要的,也是不明智的。 虽然可以做到这一点,就像您可以从引导加载程序跳转到应用程序一样,您至少需要禁用中断/异常并将向量表从应用程序的向量表切换到引导加载程序的向量表。 您的应用程序代码和引导加载程序代码似乎都没有这样做。例如,请参阅ARM:如何编写引导加载程序。
发出复位的优点是可以将处理器和所有片上外设和 I/O 设置为已知的复位状态,因此您无需担心取消初始化 NVIC 或任何可能在您执行操作时生成中断的外设。正在切换向量表。
如果您需要从应用程序向引导加载程序传递信息,则片上 SRAM 的状态将在复位过程中保留下来,因此您可以保留运行时启动不会初始化的空间,以便在以下情况下将参数传递给引导加载程序:你需要。
这里有两个程序,用户应用程序和用户引导加载程序。
用户引导加载程序:您想在 0x08000000 位置运行它。因此您不必更改链接器
flash.ld
文件。
此外,由于您在 uC 内存的起始地址运行,因此您也不必移动中断向量表。 (默认情况下它将被放置在0x08000000)
用户应用程序:这里该程序被保存在0x08008000位置。所以你还必须更改中断向量表的默认位置(从0x08000000到0x08008000)。
要更改中断向量表地址,您可以使用
SCB->VTOR
寄存器。
或者如果您使用的是stm32cube ide。 在那里你会发现一个
system_stm32f4xx.c
文件。#define USER_VECT_TAB_ADDRESS
#define VECTOR_TABLE_OFFSET 0x8000