使用串口二次加载程序即:不需要拆机就能够对产品进行升级,通过Bootloader就可以完成这项工作。该BootLoader的主要功能就是接受串口发送过来的应用程序并存放在固定的内存地址上,程序指针跳转到该地址上,程序就加载成功。
1、STM32在线升级 (IAP)
IAP(In-Application Programming) 指MCU可以在系统中获取新代码并对自己重新编程,即可用程序来改变程序。
1.1、IAP编写流程:
由Bootloader负责检测SD卡中是否有固件更新所需的BIN文件,或者通过SPI、CAN、以太网、串口等方式获取BIN文件。
如果获取到所需要的BIN文件,则开始复制文件更新固件,更新结束后跳转到指定的地址开始执行最新的程序。
1.2、STM32内置FLASH
STM32内部FLASH的起始地址为0X08000000,Bootloader程序文件就从此地址开始写入,存放APP程序的首地址设置在紧跟Bootloader之后。当程序开始执行时,首先运行的是Bootloader程序,然后Bootloader收到BIN文件并将其复制到APP区域使固件得以更新,固件更新结束后还需要跳转到APP程序开始执行新的程序,完成这最后这一步要了解Cortex-M3的中断向量表。
程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,当复位中断程序运行完成后才跳转到main函数。由此可见,在最后一步的设计中需要根据存放APP程序的起始地址以及中断向量表来设置栈顶地址,并获取复位中断地址跳转到复位中断程序。
内置Flash的分配情况大致如下:
在只有一个程序的情况下应该是如下所示:
STM32Fx有一个中断向量表,这个中断向量表存放代码开始部分的后4个字节处(即0x08000004),代码开始的4个字节存放的是栈顶地址。
当发生中断后程序通过查找该表得到相应的中断服务程序入口,然后跳到相应的中断服务程序中执行。
上电后从0x08000004处取出复位中断向量的地址,然后跳转到复位中断程序入口(标号1所示),执行结束后跳转到main函数。
在执行main函数的过程中发生中断,则STM32强制将PC指针指回中断向量表处(标号3所示),从中断向量表中找到相应的中断函数入口地址,跳转到相应的中断服务函数,执行完中断服务函数后再返回到main函数中来。
在内置的Flash里面添加一个BootLoader程序,BootLoader程序和user application各有一个中断向量表,
假设BootLoader程序占用的空间为N+M字节,则程序的走向应该如下图所示(借用网友的原图并做改动,其中虚线部分为原图步骤④⑤的走向,本人改为指向灰色部分)。
上电初始程序依然从0x08000004处取出复位中断向量地址,执行复位中断函数后跳转到IAP的main(标号①所示),
在IAP的main函数执行完成后强制跳转到0x08000004+N+M处(标号②所示),最后跳转到新的main函数中来(标号③所示),
当发生中断请求后,程序跳转到新的中断向量表中取出新的中断函数入口地址,再跳转到新的中断服务函数中执行(标号④⑤所示),执行完中断函数后再返回到main函数中来(标号⑥所示)。
对于步骤④⑤我认为的是,在main函数的执行过程中,如果CPU得到一个中断请求,PC指针本来应该跳转到0x08000004处的中断向量表,由于我们设置了中断向量表偏移量为N+M,因此PC指针被强制跳转到0x08000004+N+M处的中断向量表中得到相应的中断函数地址,再跳转到相应新的中断服务函数,执行结束后返回到main函数中来。
2、代码
主函数代码如下所示。主函数主要功能包括有:配置系统时钟、初始化升级引脚(未用到)、串口初始化。如果5s内超级终端敲下回车按键,则进入串口将会接收通过超级终端发过来的等级程序,负责将会直接跳转到应用程序。
int main(void)
{
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable Prefetch Buffer */
FLASH_PrefetchBufferCmd(ENABLE);
/* Flash 2 wait state */
FLASH_SetLatency(FLASH_Latency_2);
/* HCLK = SYSCLK */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config(RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config(RCC_HCLK_Div2);
/* PLLCLK = 16MHz / 16 * 128 / 2 = 64 MHz */
RCC_PLLConfig(RCC_PLLSource_HSI, 16, 128, 2, 4);
/* Enable PLL */
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while(RCC_GetSYSCLKSource() != 0x08)
{
}
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA|
RCC_AHB1Periph_GPIOB|
RCC_AHB1Periph_GPIOC|
RCC_AHB1Periph_GPIOD|
RCC_AHB1Periph_GPIOE, ENABLE);
Chip_InitUpgradeGpio();
Chip_LedInit();
Serial_Init();
SerialPutString("\n\rHit Key to upgrade the program... (press 'Enter' to abort)\n\r");
bCountDownNum=5;
vulRunTime=0;
SerialPutChar('0'+bCountDownNum);
SerialPutString("\n\r");
while(1)
{
SYS_TIME++;
if(SYS_TIME >= 1400)
{
SYS_TIME=0;
SYS_TICKS ++;
}
if( 0 == bUpFlag && 0 == bJumpFlag)
{
u8 key;
SerialKeyPressed((u8*)&key);
if(13 == key) //回车键
{
bUpFlag=1;
}
if( 0 == bUpFlag && 1000 <= ( SYS_TICKS-vulRunTime ))
{
bCountDownNum--;
SerialPutChar('0'+bCountDownNum);
SerialPutString("\n\r");
vulRunTime=SYS_TICKS;
if( 0 == bCountDownNum )
{
bJumpFlag=1;
bCountDownNum=10;
}
}
}
if( 1 == bUpFlag )
{
/* Unlock the Flash to enable the flash control register access *************/
FLASH_Unlock();
/* Erase the user Flash area
(area defined by FLASH_USER_START_ADDR and FLASH_USER_END_ADDR) ***********/
/* Clear pending flags (if any) */
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
Main_Menu();
}
else if(1 == bJumpFlag)
{
if (((*(vu32*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)
{
u32 JumpAddress;
pFunction Jump_To_Application;
JumpAddress = *(vu32*) (ApplicationAddress + 4);
Jump_To_Application = (pFunction) JumpAddress;
//初始化用户程序的堆栈指针
__set_MSP(*(vu32*)ApplicationAddress);
Jump_To_Application();
}
while(1);
}
}
}
如果按下回车按键或者升级GPIO引脚为低(未用),将会进入接收升级程序模式。
/*******************************************************************************
* Function Name : Main_Menu
* Description : Display the Main Menu on to HyperTerminal
* Input : None
* Output : None
* Return : None
*******************************************************************************/
static void Main_Menu(void)
{
GPIO_WriteBit( LED_1_GPIO, LED_1_PIN, Bit_RESET );
while(1)
{
s32 Size = 0;
SerialPutString("Waiting for the file to be sent ... (press 'a' to abort)\n\r");
Size = Ymodem_Receive();
if (Size > 0)
{
u32 AppStartValue = *(u32 *)ApplicationAddress;
if( 0xFFFFFFFF == AppStartValue )
{
SerialPutString("\n\n\r Upgrate Failed, Please resend...\n\r");
}
else
{
SerialPutString("\n\n\r Upgrate Successfully!\n\r");
while(1);
}
}
else if (Size == -1)
{
SerialPutString("\n\n\rThe file size is higher than the allowed space memory!\n\r");
}
else if (Size == -2)
{
SerialPutString("\n\n\rVerification failed!\n\r");
}
else if (Size == -3)
{
SerialPutString("\r\n\nAborted by user.\n\r");
}
else
{
SerialPutString("\n\rFailed to receive the file!\n\r");
}
}
}
注意:此处地址为应用程序起始地址
#define ApplicationAddress 0x080C0000