本帖最后由 ZhuMX 于 2017-9-15 17:27 编辑
之前做的一个小项目,因为刚开始没有考虑到以后会经常升级,而每次升级都要旋开4颗螺丝拆壳,然后烧程序,再装壳,如果只要更新几个倒没啥感觉,但是一下更新几百个,那工作量。。。。,正好板子上有SD卡,就想着写个Bootloader程序,通过读取SD卡中的Bin文件进行IAP升级,这样可以大大简化以后的升级。IAP升级原理就不多说了,网上相关的资料和帖子一大堆,这里简单介绍我做的IAP方案,欢迎大家批评指正!
一、Bootloader程序设计
bootloader程序的设计思路很简单,流程图如下:
bootloader流程图
初始化程序就不介绍了,比较简单。主要介绍下Bin文件检测以及IAP过程。我将IAP过程分为5个步骤,如下:
Step1:检查是否存在升级文件,若存在,打开后跳至Step2,若不存在或者打开失败,跳至Step5
Step2:擦除App程序对应的扇区,擦除成功后跳至Step3,若擦除失败,跳至Step5
Step3:使用f_read()函数读取Bin文件,每次读取2048个字节,并写入Flash。当文件全部被写入flash后跳至Step4,若中间出现写入错误,跳至Step5
Step4:检查栈顶地址,跳转至App程序。若栈顶地址非法,跳至Step5
Step5:此步表示本次升级失败,死循环,同时LED提示升级失败,等待重新上电
查找升级文件时我固定从Update文件夹查找,所以只要将Bin文件拷贝至Update文件夹就行了。
五个步骤的转换是通过switch函数实现的。代码如下:
[mw_shl_code=c,true] while(1)
{
switch(iap_step)
{
/* Step1:检查是否存在升级文件 */
case 1:
{
/* 查找升级文件 */
result = f_findfirst(&dj, &fno, "0:/Update", "FDR_update*.bin");
/* 存在升级文件 */
if(result==FR_OK && fno.fname[0])
{
/* 获取文件名字符串 */
#if _USE_LFN
fn_str = *fno.lfname ? fno.lfname : fno.fname;
#else
fn_str = fno.fname;
#endif
/* 得到完整的文件名路径 */
sprintf(fname_path,"/Update/%s",fn_str);
/* 打开升级文件 */
result = f_open(&file_fdr,fname_path,FA_OPEN_EXISTING|FA_READ);
if(result==FR_OK)
{
/* 打开成功,准备升级 */
iap_step = 2;
}
else
{
/* 打开失败 */
f_close(&file_fdr);
f_closedir(&dj);
iap_step = 5;
}
}
else
{
/* 不存在升级文件,直接跳转 */
f_closedir(&dj);
iap_step = 4;
}
break;
}
/* Step2:存在升级文件,先擦除扇区 */
case 2:
{
FLASH_Unlock();
res = IAP_FLASH_Erase(APPLICATION_ADDRESS);
FLASH_Lock();
if( res )
{
iap_step = 3;
}
else
{
f_close(&file_fdr);
f_closedir(&dj);
iap_step = 5;
}
break;
}
/* Step3:扇区擦除成功,准备依次读取并写入 */
case 3:
{
memset(appbuf,0xFF,2052);
f_read(&file_fdr,appbuf,2048,&br);
FLASH_Unlock();
res = IAP_FLASH_Write((u32*)appbuf,(u16)ceil(br/4.0f));
FLASH_Lock();
Toggle_LED_AP();
if(res == 0)
{
f_close(&file_fdr);
f_closedir(&dj);
iap_step = 5;
}
else
{
/* 文件读完了 */
if(br<2048)
{
f_close(&file_fdr);
f_closedir(&dj);
f_unlink(fname_path);
iap_step = 4;
}
}
break;
}
/* Step4:跳转至App程序 */
case 4:
{
/* 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 */
Jump_To_Application();
}
else
{
iap_step = 5;
}
break;
}
/* Step5:升级失败,等待重新上电 */
case 5:
{
if(GetFreqFlag(FREQ_0_5HZ))
{
Toggle_LED_AP();
}
break;
}
default:
{
iap_step = 1;
break;
}
}//iap step switch
}//end of bootloader while(1)[/mw_shl_code]
其中的Flash擦除函数是参考的官方例程:
[mw_shl_code=c,true]uint32_t IAP_FLASH_Erase(uint32_t StartSector)
{
uint32_t flashaddress;
flashaddress = StartSector;
while (flashaddress <= (uint32_t) USER_FLASH_LAST_PAGE_ADDRESS)
{
if (FLASH_ErasePage(flashaddress) == FLASH_COMPLETE)
{
flashaddress += FLASH_PAGE_SIZE;
}
else
{
/* Error occurred while page erase */
return (0);
}
}
return (1);
}[/mw_shl_code]
写Flash函数是我在例程的基础上修改的,将flash地址定义为局部静态变量,这样每次写完flash后地址会自增。
[mw_shl_code=c,true]uint32_t IAP_FLASH_Write(uint32_t* Data ,uint16_t DataLength)
{
uint32_t i = 0;
volatile static uint32_t wr_addr = APPLICATION_ADDRESS;
for (i = 0; (i < DataLength) && (wr_addr <= (USER_FLASH_END_ADDRESS-4)); i++)
{
/* the operation will be done by word */
if (FLASH_ProgramWord(wr_addr, *(uint32_t*)(Data+i)) == FLASH_COMPLETE)
{
/* Check the written value */
if (*(uint32_t*)wr_addr != *(uint32_t*)(Data+i))
{
/* Flash content doesn't match SRAM content */
return 0;
}
/* Increment FLASH destination address */
wr_addr += 4;
}
else
{
/* Error occurred while writing data in Flash memory */
return (0);
}
}
return (1);
}[/mw_shl_code]
特别要注意形参uint16_t Datalength是指的字数,就是uint32_t类型变量的数量,而f_read读取的是字节数,要除以4进行转换,刚开始就是没有转换导致写的flash数据不正常,跳转后死机。
跳转程序也是参考的官方例程。我设置的App程序起始地址为:0x0800 A000
此外bootloader程序的IAR工程配置如图,flash地址范围:0x0800 0000 - 0x0800 9FFF,占用40K
bootloader-IAR工程配置
二、App程序设计
1、App程序主要在原来的程序基础上修改flash起始和结束地址,以及中断向量偏移。Flash地址范围我设为:0x0800 A000 – 0x0801 FFFF,占用88K,IAR配置如下:
App-IAR1
2、由于STM32F0没有像F1,F4那样的中断向量偏移寄存器,需要通过进行内存地址映射来实现,具体实现原理参见点击打开链接http://www.51hei.com/bbs/dpj-40938-1.html
所以在App程序main函数开始的地方加如下代码:(参考官方例程修改)
[mw_shl_code=c,true]#define APPLICATION_ADDRESS (uint32_t)0x0800A000
#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
/*========================================= Main Function ============================================*/
void main(void)
{
uint32_t i = 0;
//float sdsize;
/* Relocate by software the vector table to the internal SRAM at 0x20000000 ***/
/* Copy the vector table from the Flash (mapped at the base of the application
load address 0x0800A000) to the base address of the SRAM at 0x20000000. */
for(i = 0; i < 48; i++)
{
VectorTable = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
}
/* Enable the SYSCFG peripheral clock*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* Remap SRAM at 0x00000000 */
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM); [/mw_shl_code]
其实在官方例程中为 RCC_APB2PeriphResetCmd(RCC_APB2Periph_SYSCFG, ENABLE);这并没有打开系统配置时钟,应该改为RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);我也是看到网上其他帖子才发现并改正过来的,在这里感谢网友们的分享!
以上就是我做的STM32F0的IAP升级方案,实际测试感觉速度很快,可能我的App程序不大,50K左右,升级过程基本在3秒以内。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
主要是其他模块不断增加新的数据和功能,所以这个模块也要跟着变,产品本身还在改善中
一周热门 更多>