虚拟U盘升级 bootloader

本文介绍了一种基于STM32的虚拟U盘Bootloader系统升级方案,利用FAT16文件系统实现简单易用的固件更新。通过直接采用FAT16结构而非FATFS,有效控制了ROM和RAM资源使用,只需将升级文件拖拽至U盘即可完成系统升级。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        对于很多嵌入式终端设备来讲,升级几乎成为了一个必备的功能项,升级的方式很多,大概包括远程升级,蓝牙升级,串口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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值