文章转载自 Alson
网上关于讲解 STM32 IAP 的资料非常多,这里只分析笔者自己对 STM32 IAP 的理解。
STM32 IAP 的全称为 in-application programming,目的就是通过 应用 来更新 内容
,内容
包括但不限于:
- 内部 FLASH 中的固件
- 外挂 FLASH 中的数据,例如字库、图片、设备的一些产品信息等
- 外挂 EEPROM 中的数据,例如字库、图片、设备的一些产品信息等
- 外部连接的设备的参数配置,例如传感器参数等
文档以更新内部 FLASH 中的固件为例进行讲解。
IAP
官方例程
IAP example 最好还是参考 ST 官方发布的 example。官方 IAP example 一般都会包含 代码 和对应的 Application Note,二者结合可以快速实现自己的 IAP。
以笔者最近在使用的 STM32F103RCT6 为例,介绍如何在 ST 官网寻找到对应的或者相似的 IAP example。
-
在 ST 官网中的 Tools&Software —> Embedded Software —> MCU & MPU Embedded Software —> STM32 Embedded Software
-
STM32 Standard Peripheral Library Expansion
这里面会包含一些 STM32 的一些 examples。
-
Product selector
在搜索栏中输入 IAP,会得到基于 STM32 MCU 的 IAP examples。一般 IAP 的 Middleware 都是
Bootloader
。如果有网络功能的话也可以参考 Middleware 是Bootloader, TCP/IP
的。笔者使用的是 STM32F103RCT6,没有完全对应的 example。所以这里可以参考
STM32, STM32L1
的。注意下载下来的固件是基于 KEIL v4 版本的,用 KEIL v5 打开的话一般推荐使用 Install Legacy Support 方式。 -
Get Software
这里下载的是固件,一般来说还会有对应的 Application Note。这里的固件是基于 ST 官方开发板,通过 USART 实现 IAP,重点关注 IAP 的实现逻辑。
-
Documentation
这里的 Application Note AN3310 就是对刚才下载的固件的应用说明,阅读 AN3310 可以帮助我们快速入门 IAP。
实现
以笔者最近在使用的 STM32F103RCT6 为例,Flash 大小为 256KB。以下内容结合 AN3310 文档和 STM32F103RCT6 实际情况结合完成,并非完全针对 STM32 IAP example。。
文档的最终目的是将 STM32 IAP example 移植到笔者使用的 STM32F103RCT6 上,并去除掉一些无关逻辑,只实现以下逻辑:
- MCU 上电等待 USART 传输过来的应用固件
- 应用固件传输完成后,通过 USART 输入指令跳转到应用
IAP 的实现逻辑很简单。AN3310 文档中 User program conditions 章节介绍了 IAP 的原理。
简单的说就是整个 Flash 被逻辑上分为三个区域:
- IAP Code (必须放在 Flash 起始地址 0x00000000 处,即 Map 后的逻辑地址为 0x08000000)
- User Code 1
- User Code 2 (可选)
系统上电后首先运行 IAP Code 处的代码,根据一些条件有所选择的执行逻辑:
- 更新 Flash 固件
- 运行 User Code 1 中的代码
- 运行 User Code 2 中的代码 (可选)
分析
从 AN3310 文档中可知,IAP Code 放置于 Flash 起始地址 0x00000000 处,即 Map 后的逻辑地址为 0x08000000 处,大小为 12KB。应用固件放置于 Flash 起始地址 0x00003000 处,即 Map 后的逻辑地址为 0x8003000,大小为 500KB。
通过对 main
函数的分析及结合 AN3310 文档,官方 IAP example 通过 USART1 接收应用固件,协议为 YMODEM,通过 USART1 接收输入指令来执行不同的操作。
/** * @brief Main program. * @param None * @retval None */
int main(void)
{
/* Unlock the Flash Program Erase controller */
FLASH_If_Init();
/* Initialize Key Button mounted on STM32L15xx-EVAL board */
STM_EVAL_PBInit(BUTTON_KEY, BUTTON_MODE_GPIO);
/* Test if Key push-button on STM32L15xx-EVAL Board is pressed */
if (STM_EVAL_PBGetState(BUTTON_KEY) != 0x00)
{
/* Execute the IAP driver in order to reprogram the Flash */
IAP_Init();
/* Display main menu */
Main_Menu ();
}
/* Keep the user application running */
else
{
/* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
Jump_To_Application = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
Jump_To_Application();
}
}
while (1)
{
}
}
了解了官方 IAP example 的逻辑之后,仅需要将 IAP example 下的 C 源文件及头文件直接拷贝到自己的工程中即可。
- common.c
- flash_if.c
- menu.c
- ymodem.c
移植这部分没什么可以说的,无非就是将代码里面的一些 STM32L1
库函数中的 API 替换成 STM32F1
库函数中的 API 即可。
问题总结
-
从 IAP 中跳转到 APP 中一般参考官方 IAP example 中的跳转代码即可。不过官方 IAP example 中是没有关中断的操作,不过笔者在参考网上诸多资料后发现在某些情况下由于在跳转的过程中发生了中断导致 IAP 跳转 APP 失败(笔者没遇到过),为了保险起见,建议还是在跳转之前调用
__disable_irq();
关闭中断,在 APP 中调用__enable_irq();
开启中断。if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000) { __disable_irq(); JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4); /* Jump to user application */ Jump_To_Application = (pFunction) JumpAddress; /* Initialize user application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); Jump_To_Application(); }
-
IAP 和 APP 是单独的中断向量表。对于 APP,一般启动文件中
Reset_Handler
会在调用__main
之前调用SystemInit
接口,在SystemInit
中有关闭中断和设置中断向量表的操作:; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT __main IMPORT SystemInit LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP
/** * @brief Setup the microcontroller system * Initialize the Embedded Flash Interface, the PLL and update the * SystemCoreClock variable. * @note This function should be used only after reset. * @param None * @retval None */ void SystemInit (void) { ... #ifdef STM32F10X_CL /* Reset PLL2ON and PLL3ON bits */ RCC->CR &= (uint32_t)0xEBFFFFFF; /* Disable all interrupts and clear pending bits */ RCC->CIR = 0x00FF0000; /* Reset CFGR2 register */ RCC->CFGR2 = 0x00000000; #elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL) /* Disable all interrupts and clear pending bits */ RCC->CIR = 0x009F0000; /* Reset CFGR2 register */ RCC->CFGR2 = 0x00000000; #else /* Disable all interrupts and clear pending bits */ RCC->CIR = 0x009F0000; #endif /* STM32F10X_CL */ ... #ifdef VECT_TAB_SRAM SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */ #else SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */ #endif }
所以在 APP 中,可以将宏
VECT_TAB_OFFSET
设置为实际的 APP 固件偏移量或者直接在main
函数开始处显示设置中断向量表。(笔者这里中断向量表的偏移地址为 0x3000)SCB->VTOR = FLASH_BASE | 0x3000;
-
YMODEM 上位机可以使用开源工具 WindTerm
文章评论