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 声明
本人才疏学浅,语言表达不到位,在以上叙述中,如有错误,请指出,不胜感激。