对于很多嵌入式终端设备来讲,升级几乎成为了一个必备的功能项,升级的方式很多,大概包括远程升级,蓝牙升级,串口xmodem升级,静态WEB升级,U盘升级,本章单独以STM32为基础讲解,基于FAT16文件系统虚拟U盘作为系统升级!
该系统抛弃了大多数使用的 FATFS,而直接采用虚拟FAT16文件系统结构,这里借鉴了 电脑圈圈"我的假U盘"中的部分代码。(这里也可以自己找一个2G的U盘,在WIN下面格式化成FAT16,然后提取其扇区0,扇区1,根目录扇区,来实现。)所以使得整个工程ROM,RAM的使用得到严格的控制,而整个文件升级的过程也变得非常简单,只需要将升级文件拖拽到U盘里面即可完成系统升级。
其主要思路是:
新格式化的系统,在存储文件的时候扇区操作是连续的,所以在每次使用的时候重新加载 FAT16表到内存(包括引导扇区,FAT表,根目录扇区),把系统对FAT16表的操作都映射到内存,而把对文件的操作映射到ROM即可。
整个工程编译后的情况如下:
Total RO Size (Code + RO Data) 12764 ( 12.46kB)
Total RW Size (RW Data + ZI Data) 3104 ( 3.03kB)
Total ROM Size (Code + RO Data + RW Data) 12964 ( 12.66kB)
模拟U盘结果如下:
打开U盘情况如下:
打开README文件:
既然是模拟U盘升级bootloader,那么该系统肯定要满足以下几个条件:
1,具备U盘功能
a,USB 大容量设备初始化,该部分代码可以在官方的 USB-FS-Device_Driver库中找到
Fat_Init();
Set_USBClock();
USB_Interrupts_Config();
USB_Init();
while (bDeviceState != CONFIGURED);
USB_Configured_LED();
while (1)
{
if(Fat_BulidingFile())
{
Led_RW_OFF();
}
}
b,构建FAT16虚拟文件系统
//DBR(DOS引导记录)
const u8 Dbr[512]=
{
0xeb, 0x3e, 0x90, //跳转指令,不能改为0,否则提示未格式化
'M','S','D','O','S','5','.','0', //文件系统及版本信息"MSDOS5.0"
0x00, 0x02, //每扇区字节数,为512字节
0x20, //每簇扇区数,为32扇区
0x01, 0x00, //保留扇区数,为1
0x02, //该分区的FAT份数,为2
0x00, 0x02, //根目录项数,为512项
0x00, 0x00, //小扇区数,这里不用,为0
0xF8, //媒体描述符,0xF8表示硬盘
0x20, 0x00, //每FAT扇区数,为32个
0x20, 0x00, //每道扇区数,为32
0x40, 0x00, //磁头数为64
0x00, 0x00, 0x00, 0x00, //隐藏扇区数这里没有隐藏扇区,为0
0x00, 0x04, 0x00, 0x00, //大扇区数,扇区的总数,5M
0x80, //磁盘驱动器参数,80表示硬盘
0x00, //保留
0x29, //扩展引导标记,0x29表示接下来的三个域可用
0x88, 0x09, 0x71, 0x20, //标卷序列号
//磁盘标卷:圈圈的假U盘
'U','I','A','P','_','H','e','l','p','e','r',
//文件系统类型信息,为字符串"FAT16 "
'F', 'A', 'T', '1', '6', 0x20,0x20, 0x20,
//以下为引导代码,直接从其它U盘复制而来
0xf1, 0x7d,
0xfa, 0x33, 0xc9, 0x8e, 0xd1, 0xbc, 0xfc, 0x7b, 0x16, 0x07, 0xbd, 0x78, 0x00, 0xc5, 0x76, 0x00,
0x1e, 0x56, 0x16, 0x55, 0xbf, 0x22, 0x05, 0x89, 0x7e, 0x00, 0x89, 0x4e, 0x02, 0xb1, 0x0b, 0xfc,
0xf3, 0xa4, 0x06, 0x1f, 0xbd, 0x00, 0x7c, 0xc6, 0x45, 0xfe, 0x0f, 0x8b, 0x46, 0x18, 0x88, 0x45,
0xf9, 0xfb, 0x38, 0x66, 0x24, 0x7c, 0x04, 0xcd, 0x13, 0x72, 0x3c, 0x8a, 0x46, 0x10, 0x98, 0xf7,
0x66, 0x16, 0x03, 0x46, 0x1c, 0x13, 0x56, 0x1e, 0x03, 0x46, 0x0e, 0x13, 0xd1, 0x50, 0x52, 0x89,
0x46, 0xfc, 0x89, 0x56, 0xfe, 0xb8, 0x20, 0x00, 0x8b, 0x76, 0x11, 0xf7, 0xe6, 0x8b, 0x5e, 0x0b,
0x03, 0xc3, 0x48, 0xf7, 0xf3, 0x01, 0x46, 0xfc, 0x11, 0x4e, 0xfe, 0x5a, 0x58, 0xbb, 0x00, 0x07,
0x8b, 0xfb, 0xb1, 0x01, 0xe8, 0x94, 0x00, 0x72, 0x47, 0x38, 0x2d, 0x74, 0x19, 0xb1, 0x0b, 0x56,
0x8b, 0x76, 0x3e, 0xf3, 0xa6, 0x5e, 0x74, 0x4a, 0x4e, 0x74, 0x0b, 0x03, 0xf9, 0x83, 0xc7, 0x15,
0x3b, 0xfb, 0x72, 0xe5, 0xeb, 0xd7, 0x2b, 0xc9, 0xb8, 0xd8, 0x7d, 0x87, 0x46, 0x3e, 0x3c, 0xd8,
0x75, 0x99, 0xbe, 0x80, 0x7d, 0xac, 0x98, 0x03, 0xf0, 0xac, 0x84, 0xc0, 0x74, 0x17, 0x3c, 0xff,
0x74, 0x09, 0xb4, 0x0e, 0xbb, 0x07, 0x00, 0xcd, 0x10, 0xeb, 0xee, 0xbe, 0x83, 0x7d, 0xeb, 0xe5,
0xbe, 0x81, 0x7d, 0xeb, 0xe0, 0x33, 0xc0, 0xcd, 0x16, 0x5e, 0x1f, 0x8f, 0x04, 0x8f, 0x44, 0x02,
0xcd, 0x19, 0xbe, 0x82, 0x7d, 0x8b, 0x7d, 0x0f, 0x83, 0xff, 0x02, 0x72, 0xc8, 0x8b, 0xc7, 0x48,
0x48, 0x8a, 0x4e, 0x0d, 0xf7, 0xe1, 0x03, 0x46, 0xfc, 0x13, 0x56, 0xfe, 0xbb, 0x00, 0x07, 0x53,
0xb1, 0x04, 0xe8, 0x16, 0x00, 0x5b, 0x72, 0xc8, 0x81, 0x3f, 0x4d, 0x5a, 0x75, 0xa7, 0x81, 0xbf,
0x00, 0x02, 0x42, 0x4a, 0x75, 0x9f, 0xea, 0x00, 0x02, 0x70, 0x00, 0x50, 0x52, 0x51, 0x91, 0x92,
0x33, 0xd2, 0xf7, 0x76, 0x18, 0x91, 0xf7, 0x76, 0x18, 0x42, 0x87, 0xca, 0xf7, 0x76, 0x1a, 0x8a,
0xf2, 0x8a, 0x56, 0x24, 0x8a, 0xe8, 0xd0, 0xcc, 0xd0, 0xcc, 0x0a, 0xcc, 0xb8, 0x01, 0x02, 0xcd,
0x13, 0x59, 0x5a, 0x58, 0x72, 0x09, 0x40, 0x75, 0x01, 0x42, 0x03, 0x5e, 0x0b, 0xe2, 0xcc, 0xc3,
0x03, 0x18, 0x01, 0x27, 0x0d, 0x0a, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x73, 0x79,
0x73, 0x74, 0x65, 0x6d, 0x20, 0x64, 0x69, 0x73, 0x6b, 0xff, 0x0d, 0x0a, 0x44, 0x69, 0x73, 0x6b,
0x20, 0x49, 0x2f, 0x4f, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0xff, 0x0d, 0x0a, 0x52, 0x65, 0x70,
0x6c, 0x61, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x2c, 0x20, 0x61,
0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6e,
0x79, 0x20, 0x6b, 0x65, 0x79, 0x0d, 0x0a, 0x00, 0x49, 0x4f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x53, 0x59, 0x53, 0x4d, 0x53, 0x44, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x53, 0x59, 0x53, 0x80, 0x01,
0x00, 0x57, 0x49, 0x4e, 0x42, 0x4f, 0x4f, 0x54, 0x20, 0x53, 0x59, 0x53, 0x00, 0x00, 0x55, 0xaa,
};
//模拟的文件分配表
//其中项0为0xFFF8,项1为0xFFFF,表示已经使用。
//项2为0xFFFF,表示文件结束。其余项为0,表示未使用
const u8 Fat[64]=
{
0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 ,0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
//根目录
const u8 RootDir[64]=
{
//磁盘标卷:圈圈的假U盘
'U','I','A','P','_','H','e','l','p','e','r',
0x08, //文件属性,表示磁盘标卷
0x00, //保留
0x00, //创建时间毫秒时间戳
//文件创建时间,15点27分35秒
TIME_LB(13,14,0), TIME_HB(13,14,0),
//文件创建日期,2008年8月19日
DATE_LB(2016,5,20), DATE_HB(2016,5,20),
//最后访问日期,2008年8月20日
DATE_LB(2016,5,20), DATE_HB(2016,5,20),
0x00, 0x00, //起始簇号高位字节,FAT12/16必须为0
//最后修改时间,15点36分47秒
TIME_LB(13,14,20), TIME_HB(13,14,20),
//最后修改日期,2008年8月19日
DATE_LB(2016,5,20), DATE_HB(2016,5,20),
0x00, 0x00, //起始簇低字
0x00, 0x00, 0x00, 0x00, //文件长度
//根目录下的测试文件
//文件名“TEST.TXT”
'R', 'E', 'A', 'D', 'M', 'E', ' ', ' ', 'T', 'X', 'T',
0x01, //文件属性,表示只读文件
0x00, //保留
0x00, //创建时间毫秒时间戳
//文件创建时间,15点48分26秒
//文件创建时间,15点27分35秒
TIME_LB(13,14,0), TIME_HB(13,14,0),
//文件创建日期,2008年8月19日
DATE_LB(2016,5,20), DATE_HB(2016,5,20),
//最后访问日期,2008年8月20日
DATE_LB(2016,5,20), DATE_HB(2016,5,20),
0x00, 0x00, //起始簇号高位字节,FAT12/16必须为0
//最后修改时间,15点36分47秒
TIME_LB(13,14,20), TIME_HB(13,14,20),
//最后修改日期,2008年8月19日
DATE_LB(2016,5,20), DATE_HB(2016,5,20),
0x02, 0x00, //起始簇低字,簇2。
//文件长度
(sizeof(TestFileData)-1),((sizeof(TestFileData)-1)>>8), 0x00, 0x00,
};
void Fat_Init(void)
{
memcpy(MemDbr,Dbr,512);
memcpy(MemFat,Fat,64);
memcpy(MemRootDir,RootDir,64);
}
c,提供FAT16读写的接口
u8 Fat_ReadWrite(u8 IsRead,u32 Offset,u8 *RWbuff,u16 ReadLen)
{
if(Fat_DbrReadWrite(IsRead,Offset,RWbuff,ReadLen)) return 1;
if(Fat_FatReadWrite(IsRead,Offset,RWbuff,ReadLen)) return 1;
if(Fat_RootDirReadWrite(IsRead,Offset,RWbuff,ReadLen)) return 1;
if(Fat_FileReadWrite(IsRead,Offset,RWbuff,ReadLen)) return 1;
return 0;
}
u8 Fat_DbrReadWrite(u8 IsRead,u32 Offset,u8 *RWbuff,u16 ReadLen)
{
if(Offset == DbrSector)
{
if(IsRead) memcpy(RWbuff,MemDbr,512);
else memcpy(MemDbr,RWbuff,512);
return 1;
}
return 0;
}
u8 Fat_FatReadWrite(u8 IsRead,u32 Offset,u8 *RWbuff,u16 ReadLen)
{
if((Offset >= Fat1Sector)&&(Offset < (Fat1Sector + 512))) //FAT1
{
Offset -=Fat1Sector;
if(IsRead) memcpy(RWbuff,MemFat + Offset ,128);
else memcpy(MemFat + Offset ,RWbuff,128);
return 1;
}
if((Offset >= Fat2Sector)&&(Offset < (Fat2Sector + 512)))//FAT2
{
Offset -=Fat2Sector;
if(IsRead) memcpy(RWbuff,MemFat + Offset ,128);
else memcpy(MemFat + Offset ,RWbuff,128);
return 1;
}
return 0;
}
u8 Fat_RootDirReadWrite(u8 IsRead,u32 Offset,u8 *RWbuff,u16 ReadLen)
{
if((Offset >= RootDirSector)&&(Offset < (RootDirSector + 512)))
{
if(IsRead) memcpy(RWbuff,MemRootDir,sizeof(MemRootDir));
else memcpy(MemRootDir,RWbuff,sizeof(MemRootDir));
return 1;
}
return 0;
}
u8 Fat_FileReadWrite(u8 IsRead,u32 Offset,u8 *RWbuff,u16 ReadLen)
{
if(Offset == FileSecotr)
{
if(IsRead) memcpy(RWbuff,TestFileData,sizeof(TestFileData));
return 1;
}
if(Offset >= (FileSecotr + 512*16))
{
Offset -=(FileSecotr + 512*16);
if(IsRead)
{
CpuRom_Read(Offset + RomAppAddr ,RWbuff,ReadLen);
}
else
{
if(Offset == 0)
{
CpuRom_Erase(Offset + RomFlgAddr);
MemoryEraseFlag =0X01;
}
CpuRom_Erase(Offset + RomAppAddr);
CpuRom_Write(Offset + RomAppAddr ,RWbuff,ReadLen);
}
}
return 0;
}
d,为mass_mal.c 链接上 读写 虚拟 扇区的接口
uint16_t MAL_Write(uint8_t lun, uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{
switch (lun)
{
case 0:
Fat_ReadWrite(0,Memory_Offset,(u8 *)Writebuff,Transfer_Length);
break;
default:
return MAL_FAIL;
}
return MAL_OK;
}
uint16_t MAL_Read(uint8_t lun, uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{
if (lun == 0)
{
memset(Readbuff,0,Transfer_Length);
Fat_ReadWrite(1,Memory_Offset,(u8 *)Readbuff,Transfer_Length);
return MAL_OK;
}
return MAL_FAIL;
}
2,具备擦写读ROM的功能
u8 CpuRom_Erase(u32 Offset)
{
FLASH_Status fw_status;
if((Offset % PageSize) == 0)
{
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_BSY | FLASH_FLAG_PGERR |FLASH_FLAG_WRPRTERR | FLASH_FLAG_OPTERR);
fw_status =FLASH_ErasePage(Offset);
if(fw_status!=FLASH_COMPLETE)
{
FLASH_Lock();
return 0;
}
FLASH_Lock();
}
return 1;
}
u8 CpuRom_Write(u32 Offset,u8 *pbuff,u16 len)
{
u16 i,Temp;
FLASH_Status fState;
FLASH_Unlock();
for(i=0;i<len;i+=2)
{
Temp =pbuff[1] << 8;
Temp |=pbuff[0];
fState=FLASH_ProgramHalfWord(Offset, Temp);
if(fState != FLASH_COMPLETE)
{
FLASH_Lock();
return 0;
}
Offset +=2;
pbuff +=2;
}
FLASH_Lock();
return 1;
}
u8 CpuRom_Read(u32 Offset,u8 *pbuff,u16 len)
{
u16 i,Temp;
for(i=0;i<len;i+=2)
{
Temp =*(__IO u16 *)Offset;
pbuff[1] =Temp >> 8;
pbuff[0] =Temp;
Offset +=2;
pbuff +=2;
}
return 1;
}
3,具备跳转功能
a,检查IO外部事件是否需要升级(根据个人硬件电路配置,实例代码默认运行虚拟U盘)
b,检查应用程序是否存在 (根据个人硬件电路配置,实例代码默认运行虚拟U盘)
c,跳转到应用程序(根据个人硬件电路配置,实例代码默认运行虚拟U盘)
void GoToApp(void)
{
u32 JumpAddress;
pFunction Jump_To_Application;
NVIC_SetVectorTable(NVIC_VectTab_FLASH, APP_Address);
JumpAddress = *(__IO uint32_t*) (APP_Address + 4);
Jump_To_Application = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) APP_Address);
Jump_To_Application();
}
该段代码实现了boot向APP的跳转,其中基本操作是:
(1)禁止CPU中断,这里没有使用,因为bootloader跳转判断前,并没有开启过任何中断
(2)修改中断向量表地址 NVIC_SetVectorTable(NVIC_VectTab_FLASH, APP_Address);
(3)设置MSP指针的地址
(4)使用函数指针强制跳转到指定位置去运行
工程下载链接:https://download.youkuaiyun.com/download/seeker_zeroone/10739069