在使用SMT32F103的时候,发现STM32是可以通过串口实现在线升级的(当然也可以通过文件的形式升级,原理都是一样的),正好在使用STM32F030,所以就想能不能在STM32F030上做一个在线升级的功能,通过一天的捣腾,还是搞出来了。后面想想了,还是把整个过程写成文档的形式分享出来。因为网上的资料都是零散的。
主要的参考资料:
《AN4657-STM32Cube_IAP_using_UART》
《STM32串口IAP实验(战舰STM32开发板实验)http://www.openedv.com/thread-11494-1-1.html》
《STM32串口IAP实验(战舰STM32开发板实验)http://www.openedv.com/thread-11494-1-1.html》
《MDK STM32启动文件的详细分析(_main,map详细分析)http://www.openedv.com/thread-20164-1-1.html》
《stm32 IAP + APP ==双剑合一(HEX) https://wenku.baidu.com/view/cc93905d79563c1ec5da717a.html》
《keil .sct分散加载文件及其应用 http://blog.csdn.net/temp741852963/article/details/51668884》
平台:STM32F030F4 Flash 16K, RAM 4K
功能:IAP程序和APP可以通过按键实现相互跳转的功能。
1. IAP升级流程:
2. APP回到IAP的流程:
为了区分当前程序是IAP还是APP,我用了一个LED灯来指示,在IAP程序下,LED灯300ms闪烁一次,在APP状态下LED灯1s闪烁一次。
stm32f0系列MCU中断矢量表的定位跟STM32其它系列相比有点差异,即M0系列没有像其它M3/M4/M0+系列所具备的中断矢量表重定位寄存器,其中断矢量表不能借助矢量重定位寄存器简单修改实现。所以Stm32f0 IAP的过程会跟其它系列的STM32芯片的IAP动作有所不同。但是stm32有两种启动方式,一种是从flash启动,默认地址为0x0800000,一种是从RAM启动,默认地址为0x20000000,所以可以让IAP从Flash启动,而APP从RAM启动,通过接口SYSCFG_MemoryRemapConfig配置。由于我们是把APP代码直接拷贝到0x0800000+offset的地址上去的,其中断向量表在Flash里面,所以在APP启动的时候,需要把中断向量表拷贝到RAM的起始地址中去。
3. IAP源码和工程配置
主函数如下:
int main(void)
{
#ifdef FLASH_UPDATE_FLAG
//读取是否升级成功的标志
JTHAL_STMFLASH_Read(APP_UPDATE_FLAG_ADDR, &s_u16AppIsUpdateFlag, 1);
if (s_u16AppIsUpdateFlag != 1) //没有升级成功,进入等待升级的状态
{
s_u16AppIsUpdateFlag = 0;
iLedInit();
iKeyInit();
}
else //升级成功,直接跳转到app处执行
{
iJumpToAppStartFun(APP_START_ADDR);
}
#else
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_Flash); //设置成flash启动
iLedInit();
iKeyInit();
#endif
while (1)
{
// 通过按键去更新APP
if (s_u8KeyPress == 1)
{
s_u8KeyPress = 0;
//开始更新APP
memcpy(s_au8AppStartAddr, s_u8UartBuff+4, 4);
//防止大小端问题
// s_u32Val = 0;
// s_u32Val |= (s_u8UartBuff[7] << 24);
// s_u32Val |= (s_u8UartBuff[6] << 16);
// s_u32Val |= (s_u8UartBuff[5] << 8);
// s_u32Val |= (s_u8UartBuff[4] << 0);
s_u32Val = *((uint32 *)(s_au8AppStartAddr));
if((s_u32Val & 0xFF000000) == 0x08000000) // APP 数据有效性判断
{
while (wLen != sizeof(s_u8UartBuff))
{
if (sizeof(s_u8UartBuff) - wLen >= 1024)
wTmp = 1024;
else
wTmp = sizeof(s_u8UartBuff) - wLen;
JTHAL_STMFLASH_Write(APP_START_ADDR+wLen, (uint16 *)(s_u8UartBuff+wLen), (wTmp+1)/2);//更新FLASH代码
wLen += wTmp;
}
#ifdef FLASH_UPDATE_FLAG
s_u16AppIsUpdateFlag = 1;
JTHAL_STMFLASH_Write(APP_UPDATE_FLAG_ADDR, &s_u16AppIsUpdateFlag, 1);
NVIC_SystemReset();
#else
goto JUMP_TO_APP;
#endif
}
}
// LED 快速闪烁
iLedON();
iDelayMs(500);
iLedOff();
iDelayMs(500);
}
JUMP_TO_APP:
EXTI_DeInit(); //关闭外部中断
SYSCFG_DeInit();
RCC_DeInit();
iJumpToAppStartFun(APP_START_ADDR);
}
#define APP_START_ADDR 0x08002800 //第一个应用程序起始地址(存放在FLASH)
typedef void (*APP_START_F)(void); //定义一个函数类型的参数.
static APP_START_F s_pfnAppStart = NULL;
//跳转到应用程序段
//appxaddr:用户代码起始地址.
static void iJumpToAppStartFun(uint32 appxaddr)
{
s_u32Val = *((vu32*)appxaddr);
if((s_u32Val & 0x2FFE0000) == 0x20000000) //检查栈顶地址是否合法.
{
s_pfnAppStart =(APP_START_F)*(uint32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
__set_MSP(*(__IO uint32_t*) appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
s_pfnAppStart(); //跳转到APP.
}
}
注意:
1. 如果是通过非重启的方式跳转到APP,跳转之前一定要清除中断,RCC相关配置,因为跳转,相当于调用函数,配置的寄存器值不会改变,开启的中断也不会自动关闭,如果APP中没有编写相同的中断函数,触发中断的时候就挂掉了。
2.如果是通过非重启的方式回到IAP,那么在执行IAP之前,需要设置为Flash启动模式。
IAP工程配置:
我这里分配的是10K的flash空间给IAP,这里最好是配置一下。
4.APP源码和工程配置
int main(void)
{
iRemapIrqVector(); //拷贝中断向量到RAM,并设置为RAM启动模式
iKeyInit();
iLedInit();
while (1)
{
if (s_u8KeyPress == 1)
{
s_u8KeyPress = 0;
#ifdef FLASH_UPDATE_FLAG
//如果通过这种方式,在IAP里面不需要去设置启动模式
s_u16AppIsUpdateFlag = 0;
JTHAL_STMFLASH_Write(APP_UPDATE_FLAG_ADDR, &s_u16AppIsUpdateFlag, 1);
NVIC_SystemReset();
#else
// 如果通过这种方式去跳转到IAP,在IAP里面一定要设置启动模式为flash模式
//否则会出现死机的情况
EXTI_DeInit(); //关闭外部中断
SYSCFG_DeInit();
RCC_DeInit();
iJumpToAppStartFun(APP_START_ADDR);
#endif
}
// LED慢闪烁
iLedOn();
iDelayMs(10000);
iLedOff();
iDelayMs(10000);
}
}
#if (defined ( __CC_ARM ))
__IO uint32_t VectorTable[48] __attribute__((at(0x20000000)));
#elif (defined (__ICCARM__))
#pragma location = 0x20000000
__no_init __IO uint32_t VectorTable[48];
#elif defined ( __GNUC__ )
__IO uint32_t VectorTable[48] __attribute__((section(".RAMVectorTable")));
#elif defined ( __TASKING__ )
__IO uint32_t VectorTable[48] __at(0x20000000);
#endif
// 重映射中断向量
static void iRemapIrqVector(void)
{
// 中断向量重映射
unsigned char i = 0;
for(i = 0; i < 48; i++)
{
VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2)); //中断向量是一个指针,每个占4个字节
}
/* Enable the SYSCFG peripheral clock*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //这个一定要有
/* Remap SRAM at 0x00000000 */
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM); //设置为RAM启动模式
}
工程配置:
需要勾上Use Memory Layout from Target Dialog选项,否则会出现..\OBJ\STM32F030.axf: Error: L6985E: Unable to automatically place AT section main.o(.ARM.__AT_0x20000000) with required base address 0x20000000. Please manually place in the scatter file using the --no_autoat option.
的问题。
如果完全按照网上的资料,把APP代码通过串口全部接收到本地(RAM),然后校验,然后写入到flash,这样是行不通的,RAM太小了。所以我就把APP bin文件的内容全部拷贝到一个const数组中,然后通过按键写入到Flash升级。然后通过按键从IAP->APP->IAP切换。至于怎么用4K的RAM去升级大于4K的程序APP,我提供一个思路。
把APP.bin分成若干个包,每个包包含包编号,包大小,包检验,bin文件总大小。stm32f0收到升级命令后,开始通过包编号顺序去请求包,校验成功后,写入到对应的flash区域,然后请求下一包,直到完成。
参考代码下载地址: 点击打开链接 (http://download.csdn.net/detail/losthome/9820748)