1、理论分析
理论依据很重要,这里稍微介绍一下原理和数据的操作过程。实际实现U盘的IAP升级一共分为两个部分,一部分是bootloader程序,这部分需要使用烧录器或者串口烧写进芯片。另一部分是用户的APP程序,这部分是真正的设备需要执行的程序。实际操作过程就是芯片连接U盘后从U盘读取一部分bin文件的数据到RAM再写入到芯片的flash用户app区,然后循环这个写入过程直到将整个bin文件写入到芯片flash中。然后执行一条指令跳转到用户app程序区开始执行。另外用户app区执行时在systemInit初始化完成后需要将中断表偏移一个地址。实现U盘升级程序需要移植两部分代码,一个是U盘驱动代码,一个是FAT文件系统代码。同时将U盘格式化为FAT32格式,4k对齐。
2、bootloader关键代码
bootloader部分的关键代码如下,U盘的状态机和读取文件并写入到芯片flash的过程。IAP部分代码参考了原子哥的串口IAP代码,但发现直接使用跳转时无法执行。经过测试跳转之前必须要增加__ASM(“CPSID I”);这句关闭中断。但是很奇怪我曾经做过一个串口IAP的bootloader,不加这句是完全没问题的。这个很是让人费解啊。
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
{
__ASM("CPSID I");
jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
__set_MSP(*(__IO uint32_t*) appxaddr);//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到APP.
}
}
int USBH_USR_Application(void)
{
UINT Len = 0;
u16 FramesLen = 0;//最后帧长度
char FilePath[] = "0:/PROGRAM.bin";
if(HCD_IsDeviceConnected(&USB_OTG_Core) != 0)//判断U盘连接
{
switch(UDiskState)
{
case FileSysInit: //文件系统初始化
{
if(f_mount(&fatfs, "", 0)!=FR_OK)//判断是否挂载
{
return 1;
}
UDiskState = OpenFile;
break;
}
case OpenFile: //打开文件
{
f_mount(&fatfs, "", 0);//挂载
if(f_open(&file,FilePath,FA_OPEN_EXISTING|FA_READ) != FR_OK )//打开文件,如果不存在则失败
{
f_close(&file); //关闭文件
f_mount(NULL, "", 0); //卸载
USBDiskSig = USBDiskBreak;
RunNumber = 0;
return 1;
}
USBDiskSig = USBDiskLink;//U盘已连接,确认能够打开文件以后才认为U盘有效连接
FileSize = f_size(&file);//获取文件大小
WriteInteger = FileSize/WriteUSBLen;
WriteRemSIG = FileSize%WriteUSBLen;
Write_Count = 0;//清空次数
UDiskState = UDiskWriteData;
break;
}
case UDiskWriteData://读取数据并执行IAP
{
if((WriteInteger > 0)&&(Write_Count < WriteInteger))
{
if(f_read(&file,FileReadBuf,WriteUSBLen,&Len) != FR_OK)//读取指定长度的数据到RAM
{
f_close(&file);
f_mount(NULL, "", 0);
USBDiskSig = USBDiskBreak;
RunNumber = 0;
return 1;
}
iap_write_appbin(((WriteUSBLen*Write_Count)+FLASH_APP1_ADDR),FileReadBuf,WriteUSBLen);//更新FLASH代码
Write_Count++;
}else
{
if(WriteRemSIG != 0)//有剩余数据
{
FramesLen = FileSize-(Write_Count*WriteUSBLen);//得到最后一次写入的长度
if(f_read(&file,FileReadBuf,FramesLen,&Len) != FR_OK)//读取指定长度的数据到RAM
{
f_close(&file);
f_mount(NULL, "", 0);
USBDiskSig = USBDiskBreak;
RunNumber = 0;
return 1;
}
iap_write_appbin(((WriteUSBLen*Write_Count)+FLASH_APP1_ADDR),FileReadBuf,FramesLen);//更新FLASH代码
}
f_close(&file);//关闭文件
f_mount(NULL, "", 0);//断开连接
Write_Count = 0;
UDiskState = FileSysInit;
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{
// RCC_DeInit();//关闭外设
// __set_PRIMASK(1); //关闭总中断
iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
}else //非FLASH应用程序,无法执行!
{
USBDiskSig = USBDiskBreak;
RunNumber = 0;
}
}
}
default:break;
}
}else
{
f_close(&file);
f_mount(NULL, "", 0);
return 1;
}
return 0;
}
3、用户APP程序关键代码
此部分是用户APP的初始化代码,代码中必须要添加中断向量表偏移和打开中断。偏移量需要根据自己定义的用户APP地址修改。本人程序使用IAR编译器编译,所以用户APP程序配置如下图:
void ALL_Init(void)
{
SystemInit(); //系统时钟源配置
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x6400);//设置中断向量表偏移
__ASM("CPSIE I");//打开中断
ChipDeInit();//芯片外设复位
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
}
实际这里配置就是修改了stm32f105xC.icf文件中的以下部分:
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x08006400;
/*-Memory Regions-*/
define symbol __ICFEDIT_region_ROM_start__ = 0x08006400;
define symbol __ICFEDIT_region_ROM_end__ = 0x0803FFFF;
define symbol __ICFEDIT_region_RAM_start__ = 0x20000000;
define symbol __ICFEDIT_region_RAM_end__ = 0x2000FFFF;
整个程序的源码在我的资源中可以找到地址如下。
https://download.csdn.net/download/u010552215/12016457