IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32F429实现USB通过IAP在线升级 -> 正文阅读

[嵌入式]STM32F429实现USB通过IAP在线升级

1、目标
1.实现STM32对U盘文件的读取。
2.实现STM32拓展外部SDRAM。
3.实现STM32拓展外部Flash。
4.实现内存管理。
5.实现Fatfs文件系统,读写U盘和外部Flash。
6.实现IAP在线升级。

2、硬件设备
1.采用STM32F429IGT6单片机作为本次项目的主控。
2.SDRAM的型号为IS42S16400J(1M164Banks)。
3.外部Flash芯片型号为W25Q128(16M)。

3、硬件作用和软件移植
本文主要是实现STM32读取U盘中的bin文件,对STM32进行在线升级(IAP技术)。其中SDRAM只是为了扩展STM32的RAM(其实我只是为了学习一下)。W25Q128外部Flsh芯片的主要作用是为了存储升级文件的文件名和大小,避免重复升级,为了文件系统支持中文,外部Flash还存储了FatFS的中文字库。
在软件上,因为要实现IAP在线升级,所以程序应该分为两部分,第一部分为bootloader(读取升级文件,将升级文件写入Flash的程序),第二部分则是APP(需要正常执行的程序)。

4、功能实现
4.1 SDRAM实现
在STM32F429IGT6上是支持可变存储控制器 (FMC)的(仅适用于 STM32F42xxx 和 STM32F43xxx),可以使用FMC直接和SDRAM芯片进行通信。可变存储控制器 (FMC) 包括以下三个存储控制器,分别为:NOR/PSRAM 存储控制器、NAND/PC 卡存储控制器、同步 DRAM (SDRAM/Mobile LPSDR SDRAM) 控制器。在本项目中使用同步 DRAM (SDRAM/Mobile LPSDR SDRAM) 控制器实现IS42S16400J芯片的通信。具体的FMC功能可以参考官方的中文手册。此处可以参考正点原子或野火的教程,需要注意的地方有:STM32各个外设的时钟频率一定要搞清楚、IS42S16400J芯片的行列数要知道,配置中是需要的,该芯片的行列数为12 rows 8 columns(可在芯片手册中查询)、FMC的SDRAM控制器是有两块的,分别为Bank1和Bank2两块的起始地址是不一样的,在本项目中使用的是Bank2起始地址为0XD000 0000。主要代码如下所示。当初始化后,就需要SDRAM 的内存序列进行初始化,预充电之类的操作(可以参考芯片手册,这一块我也不是很了解)。

void SDRAM_Init(void)
{
  FMC_SDRAMInitTypeDef  FMC_SDRAMInitStructure;
  FMC_SDRAMTimingInitTypeDef  FMC_SDRAMTimingInitStructure; 
  
  /* GPIO configuration for FMC SDRAM bank */
  SDRAM_GPIOConfig();
  
  /* Enable FMC clock */
  RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE);
  //(RCC->AHB3ENR |= (RCC_AHB3ENR_FMCEN));
/* FMC Configuration ---------------------------------------------------------*/
/* FMC SDRAM Bank configuration */   
  /* Timing configuration for 82 Mhz of SD clock frequency (168Mhz/2) */
  /* TMRD: 2 Clock cycles */
  FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay    = 2;      
  /* TXSR: min=70ns (7x11.11ns) */
  FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7;
  /* TRAS: min=42ns (4x11.11ns) max=120k (ns) */
  FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime      = 4;
  /* TRC:  min=70 (7x11.11ns) */        
  FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay        = 7;         
  /* TWR:  min=1+ 7ns (1+1x11.11ns) */
  FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime    = 2;      
  /* TRP:  20ns => 2x11.11ns */
  FMC_SDRAMTimingInitStructure.FMC_RPDelay              = 2;                
  /* TRCD: 20ns => 2x11.11ns */
  FMC_SDRAMTimingInitStructure.FMC_RCDDelay             = 2;

/* FMC SDRAM control configuration */
  FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank2_SDRAM;
  /* Row addressing: [7:0] */
  FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b;
  /* Column addressing: [11:0] */
  FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_12b;
  FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = SDRAM_MEMORY_WIDTH;
  FMC_SDRAMInitStructure.FMC_InternalBankNumber = FMC_InternalBank_Number_4;
  FMC_SDRAMInitStructure.FMC_CASLatency = SDRAM_CAS_LATENCY; 
  FMC_SDRAMInitStructure.FMC_WriteProtection = FMC_Write_Protection_Disable;
  FMC_SDRAMInitStructure.FMC_SDClockPeriod = SDCLOCK_PERIOD;  
  FMC_SDRAMInitStructure.FMC_ReadBurst = SDRAM_READBURST;
  FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_1;
  FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct = &FMC_SDRAMTimingInitStructure;
  
  /* FMC SDRAM bank initialization */
  FMC_SDRAMInit(&FMC_SDRAMInitStructure); 
  
  /* FMC SDRAM device initialization sequence */
  SDRAM_InitSequence(); 
  
}
void SDRAM_InitSequence(void)
{
  FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStructure;
  uint32_t tmpr = 0;
  
/* Step 3 --------------------------------------------------------------------*/
  /* Configure a clock configuration enable command */
  FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled;
  FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
  FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
  FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
  /* Wait until the SDRAM controller is ready */ 
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }
  /* Send the command */
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);  
  
/* Step 4 --------------------------------------------------------------------*/
  /* Insert 100 ms delay */
  __Delay(10);
    
/* Step 5 --------------------------------------------------------------------*/
  /* Configure a PALL (precharge all) command */ 
  FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL;
  FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
  FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
  FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
  /* Wait until the SDRAM controller is ready */ 
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }
  /* Send the command */
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
  
/* Step 6 --------------------------------------------------------------------*/
  /* Configure a Auto-Refresh command */ 
  FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh;
  FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
  FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 4;
  FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
  /* Wait until the SDRAM controller is ready */ 
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }
  /* Send the  first command */
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
  
  /* Wait until the SDRAM controller is ready */ 
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }
  /* Send the second command */
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
  
/* Step 7 --------------------------------------------------------------------*/
  /* Program the external memory mode register */
  tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2          |
                   SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL   |
                   SDRAM_MODEREG_CAS_LATENCY_3           |
                   SDRAM_MODEREG_OPERATING_MODE_STANDARD |
                   SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
  
  /* Configure a load Mode register command*/ 
  FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode;
  FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
  FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
  FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr;
  /* Wait until the SDRAM controller is ready */ 
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }
  /* Send the command */
  FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
  
/* Step 8 --------------------------------------------------------------------*/

  /* Set the refresh rate counter */
  /* (15.62 us x Freq) - 20 */
  /* Set the device refresh counter */
  FMC_SetRefreshCount(1386);
  /* Wait until the SDRAM controller is ready */ 
  while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
  {
  }
}

4.2内存管理功能的实现
在上一节,实现了SDRAM,如果不适用内存管理的情况下,可以直接使用地址直接的读写数据,但是这样非常的不方便。在C语言中有一个malloc和free函数,该函数的功能就是实现了对内存的申请和释放。在本节,就是实现一个字节的malloc函数。关于内存管理的知识请参考正点原子或野火的文档。在移植过程中,最需要关注的就是,外部SDRAM的起始地址,在上节中,已经说过使用的是Bank2,所以起始地址就是0XD000 0000,其次就是管理内存的大小(在这里,内存管理表也需要占用一定的空间,所以内存的大小不要写满,否则不能正常的使用),还有就是内存管理表的定义,内存管理表的大小和内存块的大小有关,内存块越大,管理表就越小。在移植过程中需要注意的就是内存起始地址、要管理内存的大小以及内存管理表的定义。
4.3 外部FLASH实现
在本项目中,外部Flash采用的是W25Q128,该芯片是由可编程的65536页组成,每一页256字节,每16页为一个扇区,一个扇区大小为4K,擦除内存可以是按16个页擦除(即一个Sector),128个页擦除(八个Sector),256个页擦除(16个Sector),或者整片擦除。支持标准的SPI通信,支持 SPI 的 模式 0 和 模式 3 ,也就是 CPOL=0/CPHA=0 和CPOL=1/CPHA=1 这两种模式。
在使用SPI与W25Q128通信时,只需要配置好SPI所需要的IO口,根据W25Q128芯片的要求配置好SPI即可,最重要的就是W25Q128芯片只支持标准SPI的模式 0 和 模式 3,在配置好SPI后,即可根据W25Q128的芯片手册,发送读写,擦除的命令,即可实现对芯片的读写操作和擦除操作。主要代码如下所示。

void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{ 
 	u16 i;   										    
	W25QXX_CS=0;                            //使能器件   
    SPI1_ReadWriteByte(W25X_ReadData);      //发送读取命令  
    if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
    {
        SPI1_ReadWriteByte((u8)((ReadAddr)>>24));    
    }
    SPI1_ReadWriteByte((u8)((ReadAddr)>>16));   //发送24bit地址    
    SPI1_ReadWriteByte((u8)((ReadAddr)>>8));   
    SPI1_ReadWriteByte((u8)ReadAddr);   
    for(i=0;i<NumByteToRead;i++)
	{ 
        pBuffer[i]=SPI1_ReadWriteByte(0XFF);    //循环读数  
    }
	W25QXX_CS=1;  				    	      
}  

void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{ 
	u32 secpos;
	u16 secoff;
	u16 secremain;	   
 	u16 i;    
	u8 * W25QXX_BUF;	  
   	W25QXX_BUF=W25QXX_BUFFER;	     
 	secpos=WriteAddr/4096;//扇区地址  
	secoff=WriteAddr%4096;//在扇区内的偏移
	secremain=4096-secoff;//扇区剩余空间大小   
 //	printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
 	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
	while(1) 
	{	
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			W25QXX_Erase_Sector(secpos);//擦除这个扇区
			for(i=0;i<secremain;i++)	   //复制
			{
				W25QXX_BUF[i+secoff]=pBuffer[i];	  
			}
			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区  

		}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
		if(NumByteToWrite==secremain)break;//写入结束了
		else//写入未结束
		{
			secpos++;//扇区地址增1
			secoff=0;//偏移位置为0 	 

		   	pBuffer+=secremain;  //指针偏移
			WriteAddr+=secremain;//写地址偏移	   
		   	NumByteToWrite-=secremain;				//字节数递减
			if(NumByteToWrite>4096)secremain=4096;	//下一个扇区还是写不完
			else secremain=NumByteToWrite;			//下一个扇区可以写完了
		}	 
	};	 
}

4.4 USB HOST实现
STM32F429IGT6 有两个USB外设,分别为全速 USB,另外一个为高速USB,本项目使用全速USB(OTG_FS)实现对U盘的读写功能。
USB的使用非常的复杂,作为一个萌新只能去移植代码,才能实现。ST公司给的有一个USB的库,这个库是可以直接使用的,只需要配置好,USB的引脚、时钟、定时器、USB中断后即可使用,在官方库中使用TIME2作为一个精确延时。如果时钟配置正确的话,USB库移植后就可以正常使用了(可以参考官方的例程)。USB的使用过程,就是一直在枚举,最终都会执行到USBH_USR_MSC_Application这个函数(反正我移植的库是这样的),USB相关的操作在这个函数里面写就可以了。在移植好后,官方库里面也提供了读写函数和文件系统的相关函数,但由于我需要外部flash也支持文件系统,所系就没用官方库里的文件系统。其实只需要知道读写函数,移植文件系统是非常容易的。
USB的官方库读写函数如下所示:

uint8_t USBH_MSC_Read10(USB_OTG_CORE_HANDLE *pdev,
                        uint8_t *dataBuffer,
                        uint32_t address,
                        uint32_t nbOfbytes)
uint8_t USBH_MSC_Write10(USB_OTG_CORE_HANDLE *pdev, 
                         uint8_t *dataBuffer,
                         uint32_t address,
                         uint32_t nbOfbytes)

使用上面两个函数就可以对U盘进行操作了,不过这样是很不方便的,加入一个文件系统,就可以跟我们在电脑上一样的去创建文件了。

4.5 FATFS文件系统移植
FATFS是一个完全免费开源的FAT文件系统模块,专门为小型的嵌入式系统而设计。完全用标准C语言编写,所以具有良好的硬件平台独立性。可以移植到8051、PIC、AVR、SH、Z80、H8、ARM等系列单片机上而只需做简单的修改。它支持FATl2、FATl6和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8位单片机和16位单片机做了优化。FATFS是可裁剪的文件系统。文件系统下载链接为:http://elm-chan.org/fsw/ff/00index_e.html,版本使用R0.12。
在这里插入图片描述
FATFS的所有函数如上图所示。ff.h 和ff.c 两个文件是应用层函数,在移植过程中不需要修改;interger.h 里面是数据类型的定义;option文件夹内使用的是c936.c这个文件,这个文件是支持中文的一个文件,在正常的移植中,这个文件是不需要修改的,但是这个文件中有两个很大的表,大概有170k,比较占用flash内存,所以我就把这两张表烧写在外部flash中了,所以这这函数需要稍加修改。ffconf.h文件是文件系统的配置文件,所以这个文件是需要自己的需求进行修改的。diskio.h和diskio.c文件是移植过程中需要修改的文件。分别为:disk_initialize、disk_status、disk_read、disk_write、disk_ioctl、get_fattime这6个函数,对应的功能分别为:初始化磁盘驱动器、返回当前磁盘驱动器的状态、从磁盘驱动器上读取一个/多个扇区的数据、往磁盘驱动器上写入一个/多个扇区的数据、控制设备指定特性和除了读/写外的杂项功能、获取当前时间。其中disk_status这个函数直接返回正确的状态,读和写函数只需要把对应设备的读写函数放在对应位置即可,注意函数的参数都代表什么意思。disk_ioctl函数里面有三个需要设置的参数,分别为GET_SECTOR_SIZE、GET_SECTOR_COUNT、GET_BLOCK_SIZE,分别代表的意思就是英语翻译的意思,但是GET_BLOCK_SIZE这个代表的是擦除的最小单位(是以扇区为单位的)
因为文件系统支持了中文,所以还需要两个额外的函数,void* ff_memalloc (UINT msize)和void ff_memfree (void* mblock),这两个函数的功能为从内存中申请和释放内存,我的这两个函数是从外部的SDRAM从申请的,当然也可以从内部的RAM从申请(参考正点原子的内存管理这一章节)。diskio.c代码如下所示。我这个文件系统支持了三种设备分别为SD卡、SPI Flash以及USB设备。SD卡的话不用可以去除。

/*-----------------------------------------------------------------------*/
/* 获取设备状态                                                          */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
	BYTE pdrv		/* 物理编号 */
)
{
	DSTATUS status = STA_NOINIT;
		switch (pdrv) {
		case ATA:	/* SD CARD */
			status &= ~STA_NOINIT;
			break;
    		case SPI_FLASH:        /* SPI Flash */   
					status &= ~STA_NOINIT;
			break;
		case USB:
			 status &= ~STA_NOINIT;
			break;
		default:
			status = STA_NOINIT;
	}
	return status;
}
*-----------------------------------------------------------------------*/
/* 设备初始化                                                            */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
	BYTE pdrv				/* 物理编号 */
)
{
	DSTATUS status = STA_NOINIT;	
	switch (pdrv) {
		case ATA:	         /* SD CARD */
			if(SD_Init()==SD_OK)
			{
				status &= ~STA_NOINIT;
			}
			else 
			{
				status = STA_NOINIT;
			}
		
			break;
    
		case SPI_FLASH:    /* SPI Flash */ 
		 if (W25QXX_Init() == SD_OK) 
		  {
					status &= ~STA_NOINIT;
		  } 
		 else 
		  {
					status = STA_NOINIT;
		  }
		
			break;
     case USB:
			if(HCD_IsDeviceConnected(&USB_OTG_Core))
			{
						status &= ~STA_NOINIT;
			}
			break;
		default:
			status = STA_NOINIT;
	}
	return status;
}


/*-----------------------------------------------------------------------*/
/* 读扇区:读取扇区内容到指定存储区                                              */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE pdrv,		/* 设备物理编号(0..) */
	BYTE *buff,		/* 数据缓存区 */
	DWORD sector,	/* 扇区首地址 */
	UINT count		/* 扇区个数(1..128) */
)
{
	DRESULT status = RES_PARERR;
	SD_Error SD_state = SD_OK;
	BYTE usb_status = USBH_MSC_OK;
	
	switch (pdrv) {
		case ATA:	/* SD CARD */						
		  if((DWORD)buff&3)
			{
				DRESULT res = RES_OK;
				DWORD scratch[SD_BLOCKSIZE / 4];

				while (count--) 
				{
					res = disk_read(ATA,(void *)scratch, sector++, 1);

					if (res != RES_OK) 
					{
						break;
					}
					memcpy(buff, scratch, SD_BLOCKSIZE);
					buff += SD_BLOCKSIZE;
		    }
		    return res;
			}
			
			SD_state=SD_ReadMultiBlocks(buff,sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
		  if(SD_state==SD_OK)
			{
				/* Check if the Transfer is finished */
				SD_state=SD_WaitReadOperation();
				while(SD_GetStatus() != SD_TRANSFER_OK);
			}
			if(SD_state!=SD_OK)
				status = RES_PARERR;
		  else
			  status = RES_OK;	
			break;   
			
		case SPI_FLASH:
					sector +=45; //flash的前45个扇区,一个扇区4k,总共180k的存储空间存储了文件系统的中文编码,所以此处的扇区数量需要偏移45个扇区。
					W25QXX_Read(buff,sector <<12, count<<12);
					status=RES_OK;
		break;
    
		case USB:
			  
				//if (Stat & STA_NOINIT) 	return RES_NOTRDY;

				if (HCD_IsDeviceConnected(&USB_OTG_Core))
				{
					do
					{
						usb_status = USBH_MSC_Read10(&USB_OTG_Core, (uint8_t *)buff,sector,SECTOR_SIZE * count);
            USBH_MSC_HandleBOTXfer(&USB_OTG_Core ,&USB_Host);

						if (!HCD_IsDeviceConnected(&USB_OTG_Core))
						{
							break;
						}
					}
					while (usb_status == USBH_MSC_BUSY );
				}

				if (usb_status == USBH_MSC_OK)
				{
					status = RES_OK;
				}
				else
				{
					status = RES_ERROR;
				}
			break;
				
		default:
			status = RES_PARERR;
	}
	return status;
}

/*-----------------------------------------------------------------------*/
/* 写扇区:见数据写入指定扇区空间上                                      */
/*-----------------------------------------------------------------------*/
#if _USE_WRITE
DRESULT disk_write (
	BYTE pdrv,			  /* 设备物理编号(0..) */
	const BYTE *buff,	/* 欲写入数据的缓存区 */
	DWORD sector,		  /* 扇区首地址 */
	UINT count			  /* 扇区个数(1..128) */
)
{
	DRESULT status = RES_PARERR;
	SD_Error SD_state = SD_OK;
	BYTE usb_status = USBH_MSC_OK;
	
	if (!count) {
		return RES_PARERR;		/* Check parameter */
	}

	switch (pdrv) {
		case ATA:	/* SD CARD */  
			if((DWORD)buff&3)
			{
				DRESULT res = RES_OK;
				DWORD scratch[SD_BLOCKSIZE / 4];

				while (count--) 
				{
					memcpy( scratch,buff,SD_BLOCKSIZE);
					res = disk_write(ATA,(void *)scratch, sector++, 1);
					if (res != RES_OK) 
					{
						break;
					}					
					buff += SD_BLOCKSIZE;
		    }
		    return res;
			}		
		
			SD_state=SD_WriteMultiBlocks((uint8_t *)buff,sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
			if(SD_state==SD_OK)
			{
				/* Check if the Transfer is finished */
				SD_state=SD_WaitWriteOperation();

				/* Wait until end of DMA transfer */
				while(SD_GetStatus() != SD_TRANSFER_OK);			
			}
			if(SD_state!=SD_OK)
				status = RES_PARERR;
		  else
			  status = RES_OK;	
		break;

		case SPI_FLASH:
				sector +=45;//flash的前45个扇区,一个扇区4k,总共180k的存储空间存储了文件系统的中文编码,所以此处的扇区数量需要偏移45个扇区。
				W25QXX_Write((u8*)buff, sector<<12,count<<12);
				status = RES_OK;
		break;
		
		
		case USB:
						//res = USB_disk_write(buff, sector, count);

				//if (drv || !count) return RES_PARERR;

				//if (Stat & STA_NOINIT) return RES_NOTRDY;
				//if (Stat & STA_PROTECT) return RES_WRPRT;

				if (HCD_IsDeviceConnected(&USB_OTG_Core))
				{
					do
					{
						usb_status = USBH_MSC_Write10(&USB_OTG_Core,(BYTE*)buff,sector, SECTOR_SIZE * count);
						USBH_MSC_HandleBOTXfer(&USB_OTG_Core, &USB_Host);

						if(!HCD_IsDeviceConnected(&USB_OTG_Core))
						{
							break;
						}
					}
					while(usb_status == USBH_MSC_BUSY );

				}

				if (usb_status == USBH_MSC_OK)
				{
					status = RES_OK;
				}
				else
				{
					status = RES_ERROR;
				}
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}
#endif


/*-----------------------------------------------------------------------*/
/* 其他控制                                                              */
/*-----------------------------------------------------------------------*/

#if _USE_IOCTL
DRESULT disk_ioctl (
	BYTE pdrv,		/* 物理编号 */
	BYTE cmd,		  /* 控制指令 */
	void *buff		/* 写入或者读取数据地址指针 */
)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) {
		case ATA:	/* SD CARD */
			switch (cmd) 
			{
				// Get R/W sector size (WORD) 
				case GET_SECTOR_SIZE :    
					*(WORD * )buff = SD_BLOCKSIZE;
				break;
				// Get erase block size in unit of sector (DWORD)
				case GET_BLOCK_SIZE :      
					*(DWORD * )buff = SDCardInfo.CardBlockSize;
				break;

				case GET_SECTOR_COUNT:
					*(DWORD * )buff = SDCardInfo.CardCapacity/SDCardInfo.CardBlockSize;
					break;
				case CTRL_SYNC :
				break;
			}
			status = RES_OK;
			break;
    
		case SPI_FLASH:		
	    switch(cmd)
	    {
		    case CTRL_SYNC:
						status = RES_OK; 
		        break;	 
		    case GET_SECTOR_SIZE:
		        *(WORD*)buff = 4096;
		        status = RES_OK;
		        break;	 
		    case GET_BLOCK_SIZE:
		        *(WORD*)buff = 1;
		        status = RES_OK;
		        break;	 
		    case GET_SECTOR_COUNT:
		        *(DWORD*)buff = 4096 - 45; //flash的前45个扇区,一个扇区4k,总共180k的存储空间存储了文件系统的中文编码,所以此处的扇区数量需要减少45个。
		        status = RES_OK;
		        break;
		    default:
		        status = RES_PARERR;
		        break;
	    }      
		break;
			
	case USB:	
    switch (cmd)
			{
				case CTRL_SYNC :		/* Make sure that no pending write process */
					status = RES_OK;
					break;

				case GET_SECTOR_COUNT :	/* Get number of sectors on the disk (DWORD) */
					*(DWORD*)buff = (DWORD) USBH_MSC_Param.MSCapacity;
					status = RES_OK;
					break;

				case GET_SECTOR_SIZE :	/* Get R/W sector size (WORD) */
					*(WORD*)buff = SECTOR_SIZE;
					status = RES_OK;
					break;

				case GET_BLOCK_SIZE :	/* Get erase block size in unit of sector (DWORD) */\
					*(DWORD*)buff = SECTOR_SIZE;
					status = RES_OK;
					break;

				default:
					status = RES_PARERR;
					break;
			}
	 break;		
		default:
			status = RES_PARERR;
	}
	return status;
}
#endif

__weak DWORD get_fattime(void) {
	/* 返回当前时间戳 */
	return	  ((DWORD)(2000+RTC_Date.RTC_Year - 1980) << 25)	/* Year 2015 */
			| ((DWORD)RTC_Date.RTC_Month << 21)				/* Month 1 */
			| ((DWORD)RTC_Date.RTC_Date << 16)				/* Mday 1 */
			| ((DWORD)RTC_Time.RTC_Hours << 11)				/* Hour 0 */
			| ((DWORD)RTC_Time.RTC_Minutes << 5)				  /* Min 0 */
			| ((DWORD)RTC_Time.RTC_Seconds >> 1);				/* Sec 0 */
}


void* ff_memalloc (UINT msize)
{
	return (void *)mymalloc(msize);
}

void ff_memfree (void* mblock)
{
	myfree(mblock);
}

ffconf.h文件配置如下所示

/*---------------------------------------------------------------------------/
/  FatFs - FAT file system module configuration file  R0.11a (C)ChaN, 2015
/---------------------------------------------------------------------------*/

#define _FFCONF 64180	/* Revision ID */

/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/

#define _FS_READONLY	0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/  Read-only configuration removes writing API functions, f_write(), f_sync(),
/  f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/  and optional writing functions as well. */


#define _FS_MINIMIZE	0
/* This option defines minimization level to remove some basic API functions.
/
/   0: All basic functions are enabled.
/   1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_chmod(), f_utime(),
/      f_truncate() and f_rename() function are removed.
/   2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/   3: f_lseek() function is removed in addition to 2. */


#define	_USE_STRFUNC	2   //usb Add
/* This option switches string functions, f_gets(), f_putc(), f_puts() and
/  f_printf().
/
/  0: Disable string functions.
/  1: Enable without LF-CRLF conversion.
/  2: Enable with LF-CRLF conversion. */


#define _USE_FIND		0
/* This option switches filtered directory read feature and related functions,
/  f_findfirst() and f_findnext(). (0:Disable or 1:Enable) */


#define	_USE_MKFS		1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */


#define	_USE_FASTSEEK	0
/* This option switches fast seek feature. (0:Disable or 1:Enable) */


#define _USE_LABEL		1  // usb add
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/  (0:Disable or 1:Enable) */


#define	_USE_FORWARD	0
/* This option switches f_forward() function. (0:Disable or 1:Enable)
/  To enable it, also _FS_TINY need to be set to 1. */


/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/

#define _CODE_PAGE	936
/* This option specifies the OEM code page to be used on the target system.
/  Incorrect setting of the code page can cause a file open failure.
/
/   1   - ASCII (No extended character. Non-LFN cfg. only)
/   437 - U.S.
/   720 - Arabic
/   737 - Greek
/   771 - KBL
/   775 - Baltic
/   850 - Latin 1
/   852 - Latin 2
/   855 - Cyrillic
/   857 - Turkish
/   860 - Portuguese
/   861 - Icelandic
/   862 - Hebrew
/   863 - Canadian French
/   864 - Arabic
/   865 - Nordic
/   866 - Russian
/   869 - Greek 2
/   932 - Japanese (DBCS)
/   936 - Simplified Chinese (DBCS)
/   949 - Korean (DBCS)
/   950 - Traditional Chinese (DBCS)
*/


#define	_USE_LFN	3
#define	_MAX_LFN	255
/* The _USE_LFN option switches the LFN feature.
/
/   0: Disable LFN feature. _MAX_LFN has no effect.
/   1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/   2: Enable LFN with dynamic working buffer on the STACK.
/   3: Enable LFN with dynamic working buffer on the HEAP.
/
/  When enable the LFN feature, Unicode handling functions (option/unicode.c) must
/  be added to the project. The LFN working buffer occupies (_MAX_LFN + 1) * 2 bytes.
/  When use stack for the working buffer, take care on stack overflow. When use heap
/  memory for the working buffer, memory management functions, ff_memalloc() and
/  ff_memfree(), must be added to the project. */


#define	_LFN_UNICODE	0
/* This option switches character encoding on the API. (0:ANSI/OEM or 1:Unicode)
/  To use Unicode string for the path name, enable LFN feature and set _LFN_UNICODE
/  to 1. This option also affects behavior of string I/O functions. */


#define _STRF_ENCODE	3
/* When _LFN_UNICODE is 1, this option selects the character encoding on the file to
/  be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
/
/  0: ANSI/OEM
/  1: UTF-16LE
/  2: UTF-16BE
/  3: UTF-8
/
/  When _LFN_UNICODE is 0, this option has no effect. */


#define _FS_RPATH	0
/* This option configures relative path feature.
/
/   0: Disable relative path feature and remove related functions.
/   1: Enable relative path feature. f_chdir() and f_chdrive() are available.
/   2: f_getcwd() function is available in addition to 1.
/
/  Note that directory items read via f_readdir() are affected by this option. */


/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/

#define _VOLUMES	3
/* Number of volumes (logical drives) to be used. */


#define _STR_VOLUME_ID	0
#define _VOLUME_STRS	"RAM","NAND","CF","SD1","SD2","USB1","USB2","USB3"
/* _STR_VOLUME_ID option switches string volume ID feature.
/  When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive
/  number in the path name. _VOLUME_STRS defines the drive ID strings for each
/  logical drives. Number of items must be equal to _VOLUMES. Valid characters for
/  the drive ID strings are: A-Z and 0-9. */


#define	_MULTI_PARTITION	0
/* This option switches multi-partition feature. By default (0), each logical drive
/  number is bound to the same physical drive number and only an FAT volume found on
/  the physical drive will be mounted. When multi-partition feature is enabled (1),
/  each logical drive number is bound to arbitrary physical drive and partition
/  listed in the VolToPart[]. Also f_fdisk() funciton will be available. */


#define	_MIN_SS		512
#define	_MAX_SS		4096
/* These options configure the range of sector size to be supported. (512, 1024,
/  2048 or 4096) Always set both 512 for most systems, all type of memory cards and
/  harddisk. But a larger value may be required for on-board flash memory and some
/  type of optical media. When _MAX_SS is larger than _MIN_SS, FatFs is configured
/  to variable sector size and GET_SECTOR_SIZE command must be implemented to the
/  disk_ioctl() function. */


#define	_USE_TRIM	0
/* This option switches ATA-TRIM feature. (0:Disable or 1:Enable)
/  To enable Trim feature, also CTRL_TRIM command should be implemented to the
/  disk_ioctl() function. */


#define _FS_NOFSINFO	0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/  option, and f_getfree() function at first time after volume mount will force
/  a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/  bit0=0: Use free cluster count in the FSINFO if available.
/  bit0=1: Do not trust free cluster count in the FSINFO.
/  bit1=0: Use last allocated cluster number in the FSINFO if available.
/  bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/



/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/

#define	_FS_TINY	0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/  At the tiny configuration, size of the file object (FIL) is reduced _MAX_SS
/  bytes. Instead of private sector buffer eliminated from the file object,
/  common sector buffer in the file system object (FATFS) is used for the file
/  data transfer. */


#define _FS_NORTC	0
#define _NORTC_MON	1
#define _NORTC_MDAY	1
#define _NORTC_YEAR	2015
/* The _FS_NORTC option switches timestamp feature. If the system does not have
/  an RTC function or valid timestamp is not needed, set _FS_NORTC to 1 to disable
/  the timestamp feature. All objects modified by FatFs will have a fixed timestamp
/  defined by _NORTC_MON, _NORTC_MDAY and _NORTC_YEAR.
/  When timestamp feature is enabled (_FS_NORTC == 0), get_fattime() function need
/  to be added to the project to read current time form RTC. _NORTC_MON,
/  _NORTC_MDAY and _NORTC_YEAR have no effect. 
/  These options have no effect at read-only configuration (_FS_READONLY == 1). */


#define	_FS_LOCK	0
/* The _FS_LOCK option switches file lock feature to control duplicated file open
/  and illegal operation to open objects. This option must be 0 when _FS_READONLY
/  is 1.
/
/  0:  Disable file lock feature. To avoid volume corruption, application program
/      should avoid illegal open, remove and rename to the open objects.
/  >0: Enable file lock feature. The value defines how many files/sub-directories
/      can be opened simultaneously under file lock control. Note that the file
/      lock feature is independent of re-entrancy. */


#define _FS_REENTRANT	0
#define _FS_TIMEOUT		1000
#define	_SYNC_t			HANDLE
/* The _FS_REENTRANT option switches the re-entrancy (thread safe) of the FatFs
/  module itself. Note that regardless of this option, file access to different
/  volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/  and f_fdisk() function, are always not re-entrant. Only file/directory access
/  to the same volume is under control of this feature.
/
/   0: Disable re-entrancy. _FS_TIMEOUT and _SYNC_t have no effect.
/   1: Enable re-entrancy. Also user provided synchronization handlers,
/      ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/      function, must be added to the project. Samples are available in
/      option/syscall.c.
/
/  The _FS_TIMEOUT defines timeout period in unit of time tick.
/  The _SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/  SemaphoreHandle_t and etc.. A header file for O/S definitions needs to be
/  included somewhere in the scope of ff.c. */


#define _WORD_ACCESS	0
/* The _WORD_ACCESS option is an only platform dependent option. It defines
/  which access method is used to the word data on the FAT volume.
/
/   0: Byte-by-byte access. Always compatible with all platforms.
/   1: Word access. Do not choose this unless under both the following conditions.
/
/  * Address misaligned memory access is always allowed to ALL instructions.
/  * Byte order on the memory is little-endian.
/
/  If it is the case, _WORD_ACCESS can also be set to 1 to reduce code size.
/  Following table shows allowable settings of some type of processors.
/
/  ARM7TDMI   0   *2          ColdFire   0    *1         V850E      0    *2
/  Cortex-M3  0   *3          Z80        0/1             V850ES     0/1
/  Cortex-M0  0   *2          x86        0/1             TLCS-870   0/1
/  AVR        0/1             RX600(LE)  0/1             TLCS-900   0/1
/  AVR32      0   *1          RL78       0    *2         R32C       0    *2
/  PIC18      0/1             SH-2       0    *1         M16C       0/1
/  PIC24      0   *2          H8S        0    *1         MSP430     0    *2
/  PIC32      0   *1          H8/300H    0    *1         8051       0/1
/
/  *1:Big-endian.
/  *2:Unaligned memory access is not supported.
/  *3:Some compilers generate LDM/STM for mem_cpy function.
*/


//CC939.c 修改后的ff_convert 函数,转换数组从外部flash中直接读取
#define uni2oem 0
#define oem2uni 87172
WCHAR ff_convert (	/* Converted code, 0 means conversion error */
	WCHAR	chr,	/* Character code to be converted */
	UINT	dir		/* 0: Unicode to OEM code, 1: OEM code to Unicode */
)
{
	uint32_t offset;
	 WCHAR p;
	WCHAR c;
	int i, n, li, hi;


	if (chr < 0x80) {	/* ASCII */
		c = chr;
	} else {
		if (dir) {		/* OEM code to unicode */
								offset = oem2uni;
		} else {		/* Unicode to OEM code */
			offset = uni2oem;

		}
		hi =  87172 / 4 - 1;
		li = 0;
		for (n = 16; n; n--) {
			i = li + (hi - li) / 2;
			W25QXX_Read((u8 *)&p,i*4 + offset ,2);
			if (chr == p) break;
			if (chr > p)
				li = i;
			else
				hi = i;
		}
		
		if(c != 0)
		{
			W25QXX_Read((u8 *)&p,i*4 + offset + 2 ,2);
			c = p;
		}
		else
		{
			c = 0;
		}
	}

	return c;
}

把文件系统之后移植好之后,就可以使用f_mount、f_open、f_write、f_read等函数操作储存设备的。

4.6 IAP升级的实现
IAP的功能实现其实是很简单的,用一个字表示就是“跳”。使用IAP方式升级需要有两个两个程序烧入到STM32的内部flash,其中一个是Bootloader、一个是APP,Bootloader是升级引导的程序,在本项目中的作用就是读取USB中的BIN文件,然后烧录到指定的flash地址中,然后跳转到APP程序中。APP则是我们正常运行的程序。如下图所示,IROM1就是设置工程的起始地址的,在本项目中Bootloader的起始地址是0x8000000,偏移地址为0x10000,所以bootloader最大支持的大小就为64k。app的起始地址为0x8010000,偏移地址为0xf0000,app的最大支持的大小就为960k,因为STM32F429IGT6的Flash空间为1M。(这一款不清楚的可以参考正点原子的文档)
在这里插入图片描述
IAP的核心就是读取文件,然后写入内部的Flash,所以首先要解决的就是内部Flash的读写和擦除。在标准库中,只有内部flash的写函数和擦除,但没有读函数,所以我们需要自己写一个内部的读函数,读函数的实现非常简单,就是使用指针取值的方式,如下所示。faddr就是要读取flash的地址, (vu32)faddr就是将地址中数据取出。

u32 STMFLASH_ReadWord(u32 faddr)
{
	return *(vu32*)faddr; 
}

在flash的擦除函数中,是以扇区为单位进行擦除的,所以我们要擦除某个地址时,需要知道这个地址对应的扇区,所以我们还需要有一个扇区地址起始表和扇区编号对应表,扇区对应表在STM32F4xx_FLASH.h中有提供,扇区地址起始表可以参考官方文档写出(在flash储存那一张)。我们只需要编写一个转换函数即可。如下所示。

//FLASH 扇区的起始地址
#define ADDR_FLASH_SECTOR_0     ((u32)0x08000000) 	//扇区0起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_1     ((u32)0x08004000) 	//扇区1起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_2     ((u32)0x08008000) 	//扇区2起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3     ((u32)0x0800C000) 	//扇区3起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4     ((u32)0x08010000) 	//扇区4起始地址, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5     ((u32)0x08020000) 	//扇区5起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6     ((u32)0x08040000) 	//扇区6起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_7     ((u32)0x08060000) 	//扇区7起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_8     ((u32)0x08080000) 	//扇区8起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_9     ((u32)0x080A0000) 	//扇区9起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_10    ((u32)0x080C0000) 	//扇区10起始地址,128 Kbytes  
#define ADDR_FLASH_SECTOR_11    ((u32)0x080E0000) 	//扇区11起始地址,128 Kbytes 
#define ADDR_FLASH_SECTOR_12	((u32)0x08100000) 	//扇区12起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_13	((u32)0x08104000) 	//扇区13起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_14    ((u32)0x08108000) 	//扇区14起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_15	((u32)0x0810C000) 	//扇区15起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_16    ((u32)0x08110000) 	//扇区16起始地址, 64 Kbytes  
#define ADDR_FLASH_SECTOR_17	((u32)0x08120000) 	//扇区17起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_18	((u32)0x08140000) 	//扇区18起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_19	((u32)0x08160000) 	//扇区19起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_20    ((u32)0x08180000) 	//扇区20起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_21	((u32)0x081A0000) 	//扇区21起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_22    ((u32)0x081C0000) 	//扇区22起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_23    ((u32)0x081E0000) 	//扇区23起始地址, 128 Kbytes   

u8 STMFLASH_GetFlashSector(u32 addr)
{
	if(addr<ADDR_FLASH_SECTOR_1)return FLASH_Sector_0;
	else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;
	else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;
	else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;
	else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;
	else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;
	else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;
	else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;
	else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;
	else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;
	else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_Sector_10; 
	else if(addr<ADDR_FLASH_SECTOR_12)return FLASH_Sector_11; 
	else if(addr<ADDR_FLASH_SECTOR_13)return FLASH_Sector_12; 
	else if(addr<ADDR_FLASH_SECTOR_14)return FLASH_Sector_13; 
	else if(addr<ADDR_FLASH_SECTOR_15)return FLASH_Sector_14; 
	else if(addr<ADDR_FLASH_SECTOR_16)return FLASH_Sector_15; 
	else if(addr<ADDR_FLASH_SECTOR_17)return FLASH_Sector_16; 
	else if(addr<ADDR_FLASH_SECTOR_18)return FLASH_Sector_17; 
	else if(addr<ADDR_FLASH_SECTOR_19)return FLASH_Sector_18; 
	else if(addr<ADDR_FLASH_SECTOR_20)return FLASH_Sector_19; 
	else if(addr<ADDR_FLASH_SECTOR_21)return FLASH_Sector_20; 
	else if(addr<ADDR_FLASH_SECTOR_22)return FLASH_Sector_21; 
	else if(addr<ADDR_FLASH_SECTOR_23)return FLASH_Sector_22;  
	return FLASH_Sector_23;	
}

当读写擦除函数准备完全后就可以进行组装了,代码如下所示:

//从指定地址开始写入指定长度的数据
//特别注意:因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数
//         写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
//         写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
//         没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写. 
//该函数对OTP区域也有效!可以用来写OTP区!
//OTP区域地址范围:0X1FFF7800~0X1FFF7A0F(注意:最后16字节,用于OTP数据块锁定,别乱写!!)
//WriteAddr:起始地址(此地址必须为4的倍数!!)
//pBuffer:数据指针
//NumToWrite:字(32位)数(就是要写入的32位数据的个数.) 
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)	
{ 
	FLASH_Status FlashStatus = FLASH_COMPLETE;
	u32 addrx=0;
	u32 endaddr=0;	
	if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return;	//非法地址
    
	FLASH_Unlock();             //解锁	
	addrx=WriteAddr;				//写入的起始地址
	endaddr=WriteAddr+NumToWrite*4;	//写入的结束地址
    
	if(addrx<0X1FFF0000)
	{
			while(addrx<endaddr)		//扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
			{
					if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
					{   
						  FLASH_EraseSector(STMFLASH_GetFlashSector(addrx), VoltageRange_3);
					}
					else 
					{
								addrx+=4;
					}
					FLASH_WaitForLastOperation();                //等待上次操作完成
			}
	}
	FlashStatus = FLASH_WaitForLastOperation();            //等待上次操作完成
	if(FlashStatus == FLASH_COMPLETE)
	{
		 while(WriteAddr<endaddr)//写数据
		 {
			if(FLASH_ProgramWord(WriteAddr,*pBuffer) != FLASH_COMPLETE)//写入数据
			{ 
				break;	//写入异常
			}
			WriteAddr+=4;
			pBuffer++;
		}  
	}
	FLASH_Lock();           //上锁
} 

因为一个升级文件很大,但单片机能申请的内存有限,所以我们分包进行写入,一次写入2K的数据,代码如下所示:

void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize,u32 *Offset_Address)
{
	u32 t;
	u16 i=0;
	u32 temp;
	u32 fwaddr = appxaddr + *Offset_Address;//当前写入的地址
	u8 *dfu=appbuf;
	for(t=0;t<appsize;t+=4)
	{						   
		temp=(u32)dfu[3]<<24;   
		temp|=(u32)dfu[2]<<16;    
		temp|=(u32)dfu[1]<<8;
		temp|=(u32)dfu[0];	  
		dfu+=4;//偏移4个字节
		iapbuf[i++]=temp;	    
		if(i==512)
		{
			i=0; 
			STMFLASH_Write(fwaddr,iapbuf,512);   //此处的512是代表512*4个字节
			*Offset_Address+=2048;//偏移2048  512*4=2048
		}
	} 
	if(i)
	{
		STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  
		*Offset_Address +=i*4;
		//*Offset_Address = fwaddr;
	}
}

该函数时一次写入2k的字节,如果不到2k的在下面进行了处理。
下面就可以读U盘中的数据进行写入了,代码如下所示:

u8 USB_Read_Write_flash(char *file_name) //函数的参数是U盘中文件的地址
{
	u32 file_len = 0;
	u32 *Offset_Address = 0;
	Offset_Address = (u32*)mymalloc(sizeof(u32));
	*Offset_Address = 0;
	res_sd = f_mount(fs[2],"2:",1);
	if(res_sd == FR_OK)
	{
				res_sd = f_open(USB_File,file_name,FA_READ);

				if ( res_sd == FR_OK )
				{
					file_len = USB_File->fsize;
					printf("file len %d\r\n",file_len);
					res_sd=f_read(USB_File,USB_ReadBuffer,2*1024,&br);
					if(((*(vu32*)(USB_ReadBuffer+4))&0xFF000000)==0x08000000)//判断复位向量表的地址是否正确
					{
							while(1)
							{
										if(br <= 0)
										{
											printf("Offset_Address is %d\r\n",*Offset_Address);
											if(*Offset_Address == file_len)
											{
												IAP_Write_Success_Flag = 0;
												printf("app write success\r\n");
												f_close(USB_File);
												f_mount(NULL,"2:",1);
												myfree(Offset_Address);
												return 1;
											}
										}
										else
										{
												iap_write_appbin(FLASH_APP1_ADDR,USB_ReadBuffer,br,Offset_Address);//更新FLASH代码   
												if(res_sd==FR_OK)
												{
														printf("IAP data read success %d\r\n",br);
												}
												else
												{
														printf("IAP data read error %d\r\n",res_sd);
															/* 不再读写,关闭文件 */
														f_close(USB_File);
														f_mount(NULL,"2:",1);
														myfree(Offset_Address);
													  return 0;
												}   
										}
										res_sd=f_read(USB_File,USB_ReadBuffer,2*1024,&br);
							}
					 }											
				}
				else
				{	
					printf("open %s error %d\r\n",file_name,res_sd);
						/* 不再读写,关闭文件 */
					f_close(USB_File);
					f_mount(NULL,"2:",1);
					myfree(Offset_Address);
					return 0;
					//printf("!!打开/创建文件失败。\r\n");
				}
		}
	else
	{
		printf("f_mount error %d\r\n",res_sd);
			/* 不再读写,关闭文件 */
		f_close(USB_File);
		f_mount(NULL,"2:",1);
		myfree(Offset_Address);
		return 0;
	}
	return 0;
}

此时,代码就完成了一大半了,剩下的就是从U盘中扫描出bin文件,然后使用f_open获得这个文件的大小,与SPI_flash中的数据进行比对,判断这个文件是否和上次的文件一样,如果不一样则执行上面的函数,写入新的代码,如果比对一样,则不升级。当文件写完后,就剩下一个事情了,就是跳转,就是要跳到新程序的地址上,在前面已经说过,APP的地址是0x8010000,复位向量表的地址就是0x8010000+4,所以我们要跳到这个地址上。代码如下所示。

//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{ 
		
		if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
		{	 
			  printf("开始执行FLASH用户代码!!\r\n");
				Clear_Peripheral(); //关闭所开启所有时钟以及外设
			
				if(((*(vu32*)appxaddr)&0x2FF00000)==0x20000000)	//检查栈顶地址是否合法.
				{ 
						jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
						__set_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
						jump2app();									//跳转到APP.
				}
				else
				{	
						printf("jump error\r\n");
				}
		}
		else 
	  {
			printf("非FLASH应用程序,无法执行!\r\n");   
		}	
					
}	

此段程序就是最终需要执行的,当然,在跳转之前检查app写入的flash地址上的顶栈地址和向量表地址是否合法。除此之外,跳转前要关闭所有时钟以及外设(在官方文档中没有此操作,但是没有此操作,跳转后就问题百出);关闭所有时钟以及外设代码如下所示:

void Clear_Peripheral()
{
	u8 i;
	DISABLE_INT();            //关闭全局中断
	
	/*关闭串口的时钟和串口外设*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE);
	USART_DeInit(USART1);
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,DISABLE);
	SPI_I2S_DeInit(SPI1);
	
	
	RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, DISABLE);
	FMC_SDRAMDeInit(FMC_Bank2_SDRAM);
		
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, DISABLE);
    RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS, DISABLE) ; 
	USBF_DeInit();
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, DISABLE);  

	TIM_DeInit(TIM2);
	GPIO_DeInit(GPIOA);
	GPIO_DeInit(GPIOB);
	GPIO_DeInit(GPIOC);
	GPIO_DeInit(GPIOD);
	GPIO_DeInit(GPIOE);
	GPIO_DeInit(GPIOF);
	GPIO_DeInit(GPIOG);
	GPIO_DeInit(GPIOH);
	GPIO_DeInit(GPIOI);
	
	SysTick->CTRL = 0;
	SysTick->LOAD = 0;
	SysTick->VAL = 0;
	for (i = 0; i < 8; i++)
	{
		NVIC->ICER[i]=0xFFFFFFFF;
		NVIC->ICPR[i]=0xFFFFFFFF;
	}  	

	RCC_DeInit();	
}

因为在跳转前不仅关闭了外设以及时钟,还关闭了全局中断,所以在跳转app后在main中要打开全局中断,当然在全局中断打开前,要把中断向量偏移表设置一下,不然页容易出BUG。

5 总结
本项目包括bootloader和app两部分,但由于篇幅过于长,本文中只叙述了bootloader部分。APP中的功能也具有插入U盘跳转bootloader的部分,原理与bootloader相同,就不多叙述。
最后在说明一点,跳转之前一定要关闭所打开的所有外设和时钟以及全局中断,跳转后要设置向量表偏移和打开全局中断,切记切记!!!!!!!!

6 声明
本人才疏学浅,语言表达不到位,在以上叙述中,如有错误,请指出,不胜感激。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:44:29  更:2022-09-21 00:46:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 14:55:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码