1. IAP介绍
在开发MCU应用时,很多时候都希望能够做到不断电就能升级Firmware,这样就能快速更新新功能或修复bug。这个时候就需要IAP功能,IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,IAP需要MCU能擦写内部Flash,并且具备软件复位功能。IAP的通信方式有很多选择,UART、SPI、IIC、USB、CAN等通信方式都可以选择,常用的可以选择UART、USB,如果需要远程升级,可以再搭配个2.4G、蓝牙等无线模块来实现OTA。这里将以ST的 NUCLEO-F103RB 开发板来实验下UART IAP的功能,开发自己的Bootloader程序。
STM32F103RB内置有官方的Bootloader程序,可以通过USART来升级,但这个Bootloader程序是无法修改的,而且进入的方式是上电时通过Boot Pin的电平来选择,不是特别方便。所以一般都会再自己实现一个Bootloader。
2. IAP说明
STM32F103RB内置有128K Flash Rom,可以划分为Bootloader和UserApp两个区域,Bootloader区域不会被更新,其功能是用来更新UserApp区域的,UserApp是真正的功能程序区域,用于写正常的用户程序。
Bootloader功能主要如下:
1)检查是否需要对UserApp区域进行更新
2)如果不需要更新则跳转到UserApp区域执行
3)执行更新操作,通过UART来接收数据
4)更新完成跳转到UserApp区域执行
Flash ROM空间划分
103RB 128K Rom空间被划分为Bootloader和UserApp两个区域,Bootloader使用0x08000000-0x08001FFF共8K的空间大小,UserApp使用0x08002000-0x0801FFFF 120K空间大小,并在最后的0x0801FFFC位置预留一个4byte空间,用来定义IAP Flag,具体值为 0x33CC55AA 。
Boot程序运行流程
- 上电复位时流程
103RB正常上电是从0x08000000地址上取栈值,从0x08000004取复位向量地址的。所以上电时是从Bootloader区域开始运行。Bootloader里开始就会判断0x0801FFFC的IAP Flag值是否正确,当为0x33CC55AA 时认为UserApp程序正常,就会跳转到UserApp起始地址(0x08002004)运行,跑正常的用户程序,当IAP Flag值不为0x33CC55AA时,认为UserApp不正常,会停留在Bootloader中执行,接下来可执行UART IAP操作。
- Bootloader UART IAP流程
Bootloader IAP Handler主要功能就是接收和解析Uart数据、擦写UserApp Rom。Uart数据的格式和大小,具体可以再定个私有的协议,配合上位机来通信,上述只是给个参考。
中断向量处理
由于IAP之架构会将MCU ROM分成两个区域运行,并相互不能干扰,但中断向量是有固定的入口,不做任何的话,中断向量都是在Bootloader区域中,此时UserApp的中断会无法正确执行,导致系统运行出错。所以需要对中断向量进行相应处理。
- 中断向量重定位
对于STM32F103RB来说,有相关寄存器可以设置中断向量为新的地址,所以在UserApp工程里SystemInit函数中设置VECT_TAB_OFFSET为UserApp偏移的地址,这样中断向量就会对应到UserApp的对应地址上。
// system_stm32f1xx.c -- SystemInit()
#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
对于像STM32F0 M0架构的MCU,没有中断向量重定向的寄存器的相关设置,但可以设置系统从SRAM来运行,
所以有一种巧妙的方法来到达向量重定义的目的,把UserApp前面中断向量相关的ROM复制到SRAM的开头来,
这样UserApp的中断也能得到正确响应。
- 直接进行调用
像M0架构的MCU,如不想改动太多,可以选择判断IAP Flag并调用相应的函数。当中断产生时,会运行Bootloader里的中断向量处理函数,在这里面再进行判断,调用相应的处理函数。下面为SysTick_Handler的处理例程,
1. 当IAP Flag正确时,通过地址调用UserApp中的处理函数
2. 不正确时,运行Bootloader里的处理函数
__irq void SysTick_Handler(void)
{
if((*((uint32_t*)0x0801FFFC)) == 0x33CC55AA)
{
((void(*)(void))*(uint32_t *)(APP_START_ADDRESS + SYSTICK_IRQ_OFFSET))(); //UserApp Interrupt handler
}
else
{
Boot_SysTick_Handler(); //Bootloader Interrupt handler
}
}
Bootloader和UserAPP之间的相互跳转
// typedef void (*pFunction)(void);
// pFunction JumpToApplication;
// uint32_t JumpAddress;
// #define IAP_FLAG_ADDRESS 0x0801FFFC
// #define IAP_FLAG_VALUE 0x33CC55AA
// #define APP_START_ADDRESS 0x08002000 // 8K
if( *(volatile uint32_t*)(IAP_FLAG_ADDRESS) == IAP_FLAG_VALUE)
{
if (((*(__IO uint32_t*)APP_START_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*)(APP_START_ADDRESS + 4);
JumpToApplication = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP( *(__IO uint32_t*)APP_START_ADDRESS );
JumpToApplication();
}
}
- UserApp跳转到Bootloader(两种方法)
- 擦除IAP Flag所在的Page,然后执行NVIC_SystemReset();
- 跳转到Bootloader的MN_IAPHandler位置(需要重新设置MSP)
__asm void Jmp_Boot(void)
{
LDR r0, = 0x08001001 //** bootloader MN_IAPHandler address = 0x8001000
}
3. Bootloader程序
ROM Setting
主函数
__attribute__((section(".ARM.__at_0x8001000"))) void MN_IAPHandler(void);
int main(void)
{
//
// #define IAP_FLAG_ADDRESS 0x0801FFFC
// #define IAP_FLAG_VALUE 0x33CC55AA
// #define APP_START_ADDRESS 0x08002000 // 8K
if( *(volatile uint32_t*)(IAP_FLAG_ADDRESS) == IAP_FLAG_VALUE)
{
if (((*(__IO uint32_t*)APP_START_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*)(APP_START_ADDRESS + 4);
JumpToApplication = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP( *(__IO uint32_t*)APP_START_ADDRESS );
JumpToApplication();
}
}
MN_IAPHandler();
}
MN_IAPHandler(at 0x0800 1000)
当前Bootloader中不使用中断,所以需要关闭所有中断,如果是从UserApp跳转过来的,要重新设置下MSP以及时钟和Uart等功能,并且相关用上的RAM变量要清零下。
void MN_IAPHandler(void)
{
uint32_t i;
__set_MSP(0x20001428); // Current Project MSP Value
__disable_irq(); // disable interrupt
//__set_PRIMASK(1);
//SCB->VTOR = 0x08000000;
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_AFIO);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
/** NOJTAG: JTAG-DP Disabled and SW-DP Enabled
*/
LL_GPIO_AF_Remap_SWJ_NOJTAG();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
LL_USART_DeInit(USART2);
MX_USART2_UART_Init();
LL_InitTick(64000000, 100); //10ms
wIAP_Recv_Cnts = 0;
bIAP_Recv_TmOut = 0;
bIAP_ProgramEnable = 0;
for(i = 0; i < IAP_RECV_LEN; i++)
{
bIAP_Recv_Buf
= 0x00;
}
while(1)
{
// Check if Usart2 data receive
if( USART2->SR & (0x1<<5) )
{
USART2->SR &=~(0x1<<5);
bIAP_Recv_Buf[wIAP_Recv_Cnts++] = USART2->DR;
if(wIAP_Recv_Cnts == IAP_RECV_LEN)
{
wIAP_Recv_Cnts = 0;
IAP_Recv_Handle();
}
}
}
}
Flash擦除操作函数
/**
* @brief LL_FLASH_Unlock
* @retval ErrorStatus
*/
ErrorStatus LL_FLASH_Unlock(void)
{
// FLASH->SR = 0x0;
// FLASH->CR = 0x80;
if(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != RESET)
{
/* Authorize the FLASH Registers access */
WRITE_REG(FLASH->KEYR, FLASH_KEY1);
WRITE_REG(FLASH->KEYR, FLASH_KEY2);
/* Verify Flash is unlocked */
if(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != RESET)
{
return ERROR;
}
}
return (SUCCESS);
}
/**
* @brief LL_FLASH_Lock
* @retval ErrorStatus
*/
ErrorStatus LL_FLASH_Lock(void)
{
SET_BIT(FLASH->CR, FLASH_CR_LOCK);
return (SUCCESS);
}
/**
* @brief LL_FLASH_PageErase
* @retval None
*/
void LL_FLASH_PageErase(uint32_t PageAddress)
{
SET_BIT(FLASH->CR, FLASH_CR_PER);
WRITE_REG(FLASH->AR, PageAddress & 0xFFFFFC00); // 1K
SET_BIT(FLASH->CR, FLASH_CR_STRT);
while(FLASH->SR & FLASH_SR_BSY);
CLEAR_BIT(FLASH->CR, FLASH_CR_PER);
// CLEAR_BIT(FLASH->CR, FLASH_CR_STRT);
}
/**
* @brief LL_FLASH_Program_Word
* @retval None
*/
void LL_FLASH_Program_Word(uint32_t Address, uint32_t Data)
{
SET_BIT(FLASH->CR, FLASH_CR_PG);
*(__IO uint16_t*)Address = (uint16_t)(Data & 0xFFFF);
while(FLASH->SR & FLASH_SR_BSY);
*(__IO uint16_t*)(Address+2) = (uint16_t)(Data>>16);
while(FLASH->SR & FLASH_SR_BSY);
CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
// Verify
// if(*(__IO uint32_t*)Address != Data)
// {
// return ERROR;
// }
}
4. UserApp程序
Rom Setting
在UserApp Rom最后位置定义IAP Flag常量,用于Bootloader中对UserApp Rom的判断
const uint32_t wIAP_FLAG __attribute__((at(0x801FFFC))) = 0x33CC55AA;
重定向中断向量
修改system_stm32f1xx.c中 VECT_TAB_OFFSET 为 0x2000
// system_stm32f1xx.c
/* #define VECT_TAB_SRAM */
#define VECT_TAB_OFFSET 0x00002000U /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
升级动作
当收到需要升级的命令时,跳转到Bootloader中执行IAPHandler。跳转的方法前面都有介绍。
bin文件的生成
一般升级时需要bin文件,里面都是原始的ROM数据,可以自己写个hex转bin文件的小程序,或者使用STM32CubeProgrammer工具,先加载hex文件,然后另存为bin文件。
小结
最后写了个串口上位机,试了下升级,功能整体还是OK的,有没有bug还要再测试下,总体来说,UART IAP对于升级程序还是很方便的。
1. IAP介绍
在开发MCU应用时,很多时候都希望能够做到不断电就能升级Firmware,这样就能快速更新新功能或修复bug。这个时候就需要IAP功能,IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,IAP需要MCU能擦写内部Flash,并且具备软件复位功能。IAP的通信方式有很多选择,UART、SPI、IIC、USB、CAN等通信方式都可以选择,常用的可以选择UART、USB,如果需要远程升级,可以再搭配个2.4G、蓝牙等无线模块来实现OTA。这里将以ST的 NUCLEO-F103RB 开发板来实验下UART IAP的功能,开发自己的Bootloader程序。
STM32F103RB内置有官方的Bootloader程序,可以通过USART来升级,但这个Bootloader程序是无法修改的,而且进入的方式是上电时通过Boot Pin的电平来选择,不是特别方便。所以一般都会再自己实现一个Bootloader。
2. IAP说明
STM32F103RB内置有128K Flash Rom,可以划分为Bootloader和UserApp两个区域,Bootloader区域不会被更新,其功能是用来更新UserApp区域的,UserApp是真正的功能程序区域,用于写正常的用户程序。
Bootloader功能主要如下:
1)检查是否需要对UserApp区域进行更新
2)如果不需要更新则跳转到UserApp区域执行
3)执行更新操作,通过UART来接收数据
4)更新完成跳转到UserApp区域执行
Flash ROM空间划分
103RB 128K Rom空间被划分为Bootloader和UserApp两个区域,Bootloader使用0x08000000-0x08001FFF共8K的空间大小,UserApp使用0x08002000-0x0801FFFF 120K空间大小,并在最后的0x0801FFFC位置预留一个4byte空间,用来定义IAP Flag,具体值为 0x33CC55AA 。
Boot程序运行流程
- 上电复位时流程
103RB正常上电是从0x08000000地址上取栈值,从0x08000004取复位向量地址的。所以上电时是从Bootloader区域开始运行。Bootloader里开始就会判断0x0801FFFC的IAP Flag值是否正确,当为0x33CC55AA 时认为UserApp程序正常,就会跳转到UserApp起始地址(0x08002004)运行,跑正常的用户程序,当IAP Flag值不为0x33CC55AA时,认为UserApp不正常,会停留在Bootloader中执行,接下来可执行UART IAP操作。
- Bootloader UART IAP流程
Bootloader IAP Handler主要功能就是接收和解析Uart数据、擦写UserApp Rom。Uart数据的格式和大小,具体可以再定个私有的协议,配合上位机来通信,上述只是给个参考。
中断向量处理
由于IAP之架构会将MCU ROM分成两个区域运行,并相互不能干扰,但中断向量是有固定的入口,不做任何的话,中断向量都是在Bootloader区域中,此时UserApp的中断会无法正确执行,导致系统运行出错。所以需要对中断向量进行相应处理。
- 中断向量重定位
对于STM32F103RB来说,有相关寄存器可以设置中断向量为新的地址,所以在UserApp工程里SystemInit函数中设置VECT_TAB_OFFSET为UserApp偏移的地址,这样中断向量就会对应到UserApp的对应地址上。
// system_stm32f1xx.c -- SystemInit()
#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
对于像STM32F0 M0架构的MCU,没有中断向量重定向的寄存器的相关设置,但可以设置系统从SRAM来运行,
所以有一种巧妙的方法来到达向量重定义的目的,把UserApp前面中断向量相关的ROM复制到SRAM的开头来,
这样UserApp的中断也能得到正确响应。
- 直接进行调用
像M0架构的MCU,如不想改动太多,可以选择判断IAP Flag并调用相应的函数。当中断产生时,会运行Bootloader里的中断向量处理函数,在这里面再进行判断,调用相应的处理函数。下面为SysTick_Handler的处理例程,
1. 当IAP Flag正确时,通过地址调用UserApp中的处理函数
2. 不正确时,运行Bootloader里的处理函数
__irq void SysTick_Handler(void)
{
if((*((uint32_t*)0x0801FFFC)) == 0x33CC55AA)
{
((void(*)(void))*(uint32_t *)(APP_START_ADDRESS + SYSTICK_IRQ_OFFSET))(); //UserApp Interrupt handler
}
else
{
Boot_SysTick_Handler(); //Bootloader Interrupt handler
}
}
Bootloader和UserAPP之间的相互跳转
// typedef void (*pFunction)(void);
// pFunction JumpToApplication;
// uint32_t JumpAddress;
// #define IAP_FLAG_ADDRESS 0x0801FFFC
// #define IAP_FLAG_VALUE 0x33CC55AA
// #define APP_START_ADDRESS 0x08002000 // 8K
if( *(volatile uint32_t*)(IAP_FLAG_ADDRESS) == IAP_FLAG_VALUE)
{
if (((*(__IO uint32_t*)APP_START_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*)(APP_START_ADDRESS + 4);
JumpToApplication = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP( *(__IO uint32_t*)APP_START_ADDRESS );
JumpToApplication();
}
}
- UserApp跳转到Bootloader(两种方法)
- 擦除IAP Flag所在的Page,然后执行NVIC_SystemReset();
- 跳转到Bootloader的MN_IAPHandler位置(需要重新设置MSP)
__asm void Jmp_Boot(void)
{
LDR r0, = 0x08001001 //** bootloader MN_IAPHandler address = 0x8001000
}
3. Bootloader程序
ROM Setting
主函数
__attribute__((section(".ARM.__at_0x8001000"))) void MN_IAPHandler(void);
int main(void)
{
//
// #define IAP_FLAG_ADDRESS 0x0801FFFC
// #define IAP_FLAG_VALUE 0x33CC55AA
// #define APP_START_ADDRESS 0x08002000 // 8K
if( *(volatile uint32_t*)(IAP_FLAG_ADDRESS) == IAP_FLAG_VALUE)
{
if (((*(__IO uint32_t*)APP_START_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*)(APP_START_ADDRESS + 4);
JumpToApplication = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP( *(__IO uint32_t*)APP_START_ADDRESS );
JumpToApplication();
}
}
MN_IAPHandler();
}
MN_IAPHandler(at 0x0800 1000)
当前Bootloader中不使用中断,所以需要关闭所有中断,如果是从UserApp跳转过来的,要重新设置下MSP以及时钟和Uart等功能,并且相关用上的RAM变量要清零下。
void MN_IAPHandler(void)
{
uint32_t i;
__set_MSP(0x20001428); // Current Project MSP Value
__disable_irq(); // disable interrupt
//__set_PRIMASK(1);
//SCB->VTOR = 0x08000000;
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_AFIO);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
/** NOJTAG: JTAG-DP Disabled and SW-DP Enabled
*/
LL_GPIO_AF_Remap_SWJ_NOJTAG();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
LL_USART_DeInit(USART2);
MX_USART2_UART_Init();
LL_InitTick(64000000, 100); //10ms
wIAP_Recv_Cnts = 0;
bIAP_Recv_TmOut = 0;
bIAP_ProgramEnable = 0;
for(i = 0; i < IAP_RECV_LEN; i++)
{
bIAP_Recv_Buf
= 0x00;
}
while(1)
{
// Check if Usart2 data receive
if( USART2->SR & (0x1<<5) )
{
USART2->SR &=~(0x1<<5);
bIAP_Recv_Buf[wIAP_Recv_Cnts++] = USART2->DR;
if(wIAP_Recv_Cnts == IAP_RECV_LEN)
{
wIAP_Recv_Cnts = 0;
IAP_Recv_Handle();
}
}
}
}
Flash擦除操作函数
/**
* @brief LL_FLASH_Unlock
* @retval ErrorStatus
*/
ErrorStatus LL_FLASH_Unlock(void)
{
// FLASH->SR = 0x0;
// FLASH->CR = 0x80;
if(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != RESET)
{
/* Authorize the FLASH Registers access */
WRITE_REG(FLASH->KEYR, FLASH_KEY1);
WRITE_REG(FLASH->KEYR, FLASH_KEY2);
/* Verify Flash is unlocked */
if(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != RESET)
{
return ERROR;
}
}
return (SUCCESS);
}
/**
* @brief LL_FLASH_Lock
* @retval ErrorStatus
*/
ErrorStatus LL_FLASH_Lock(void)
{
SET_BIT(FLASH->CR, FLASH_CR_LOCK);
return (SUCCESS);
}
/**
* @brief LL_FLASH_PageErase
* @retval None
*/
void LL_FLASH_PageErase(uint32_t PageAddress)
{
SET_BIT(FLASH->CR, FLASH_CR_PER);
WRITE_REG(FLASH->AR, PageAddress & 0xFFFFFC00); // 1K
SET_BIT(FLASH->CR, FLASH_CR_STRT);
while(FLASH->SR & FLASH_SR_BSY);
CLEAR_BIT(FLASH->CR, FLASH_CR_PER);
// CLEAR_BIT(FLASH->CR, FLASH_CR_STRT);
}
/**
* @brief LL_FLASH_Program_Word
* @retval None
*/
void LL_FLASH_Program_Word(uint32_t Address, uint32_t Data)
{
SET_BIT(FLASH->CR, FLASH_CR_PG);
*(__IO uint16_t*)Address = (uint16_t)(Data & 0xFFFF);
while(FLASH->SR & FLASH_SR_BSY);
*(__IO uint16_t*)(Address+2) = (uint16_t)(Data>>16);
while(FLASH->SR & FLASH_SR_BSY);
CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
// Verify
// if(*(__IO uint32_t*)Address != Data)
// {
// return ERROR;
// }
}
4. UserApp程序
Rom Setting
在UserApp Rom最后位置定义IAP Flag常量,用于Bootloader中对UserApp Rom的判断
const uint32_t wIAP_FLAG __attribute__((at(0x801FFFC))) = 0x33CC55AA;
重定向中断向量
修改system_stm32f1xx.c中 VECT_TAB_OFFSET 为 0x2000
// system_stm32f1xx.c
/* #define VECT_TAB_SRAM */
#define VECT_TAB_OFFSET 0x00002000U /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
升级动作
当收到需要升级的命令时,跳转到Bootloader中执行IAPHandler。跳转的方法前面都有介绍。
bin文件的生成
一般升级时需要bin文件,里面都是原始的ROM数据,可以自己写个hex转bin文件的小程序,或者使用STM32CubeProgrammer工具,先加载hex文件,然后另存为bin文件。
小结
最后写了个串口上位机,试了下升级,功能整体还是OK的,有没有bug还要再测试下,总体来说,UART IAP对于升级程序还是很方便的。
举报