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;
SDRAM_GPIOConfig();
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE);
FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2;
FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7;
FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4;
FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 7;
FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2;
FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2;
FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2;
FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank2_SDRAM;
FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b;
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_SDRAMInit(&FMC_SDRAMInitStructure);
SDRAM_InitSequence();
}
void SDRAM_InitSequence(void)
{
FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStructure;
uint32_t tmpr = 0;
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;
while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
{
}
FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
__Delay(10);
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;
while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
{
}
FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
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;
while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
{
}
FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
{
}
FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
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;
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;
while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
{
}
FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
FMC_SetRefreshCount(1386);
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)
{
SPI1_ReadWriteByte((u8)((ReadAddr)>>24));
}
SPI1_ReadWriteByte((u8)((ReadAddr)>>16));
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;
if(NumByteToWrite<=secremain)secremain=NumByteToWrite;
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++;
secoff=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:
status &= ~STA_NOINIT;
break;
case 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:
if(SD_Init()==SD_OK)
{
status &= ~STA_NOINIT;
}
else
{
status = STA_NOINIT;
}
break;
case 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,
BYTE *buff,
DWORD sector,
UINT count
)
{
DRESULT status = RES_PARERR;
SD_Error SD_state = SD_OK;
BYTE usb_status = USBH_MSC_OK;
switch (pdrv) {
case ATA:
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)
{
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;
W25QXX_Read(buff,sector <<12, count<<12);
status=RES_OK;
break;
case USB:
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,
const BYTE *buff,
DWORD sector,
UINT count
)
{
DRESULT status = RES_PARERR;
SD_Error SD_state = SD_OK;
BYTE usb_status = USBH_MSC_OK;
if (!count) {
return RES_PARERR;
}
switch (pdrv) {
case ATA:
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)
{
SD_state=SD_WaitWriteOperation();
while(SD_GetStatus() != SD_TRANSFER_OK);
}
if(SD_state!=SD_OK)
status = RES_PARERR;
else
status = RES_OK;
break;
case SPI_FLASH:
sector +=45;
W25QXX_Write((u8*)buff, sector<<12,count<<12);
status = RES_OK;
break;
case USB:
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:
switch (cmd)
{
case GET_SECTOR_SIZE :
*(WORD * )buff = SD_BLOCKSIZE;
break;
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;
status = RES_OK;
break;
default:
status = RES_PARERR;
break;
}
break;
case USB:
switch (cmd)
{
case CTRL_SYNC :
status = RES_OK;
break;
case GET_SECTOR_COUNT :
*(DWORD*)buff = (DWORD) USBH_MSC_Param.MSCapacity;
status = RES_OK;
break;
case GET_SECTOR_SIZE :
*(WORD*)buff = SECTOR_SIZE;
status = RES_OK;
break;
case GET_BLOCK_SIZE : \
*(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)
| ((DWORD)RTC_Date.RTC_Month << 21)
| ((DWORD)RTC_Date.RTC_Date << 16)
| ((DWORD)RTC_Time.RTC_Hours << 11)
| ((DWORD)RTC_Time.RTC_Minutes << 5)
| ((DWORD)RTC_Time.RTC_Seconds >> 1);
}
void* ff_memalloc (UINT msize)
{
return (void *)mymalloc(msize);
}
void ff_memfree (void* mblock)
{
myfree(mblock);
}
ffconf.h文件配置如下所示
#define _FFCONF 64180
#define _FS_READONLY 0
#define _FS_MINIMIZE 0
#define _USE_STRFUNC 2
#define _USE_FIND 0
#define _USE_MKFS 1
#define _USE_FASTSEEK 0
#define _USE_LABEL 1
#define _USE_FORWARD 0
#define _CODE_PAGE 936
#define _USE_LFN 3
#define _MAX_LFN 255
#define _LFN_UNICODE 0
#define _STRF_ENCODE 3
#define _FS_RPATH 0
#define _VOLUMES 3
#define _STR_VOLUME_ID 0
#define _VOLUME_STRS "RAM","NAND","CF","SD1","SD2","USB1","USB2","USB3"
#define _MULTI_PARTITION 0
#define _MIN_SS 512
#define _MAX_SS 4096
#define _USE_TRIM 0
#define _FS_NOFSINFO 0
#define _FS_TINY 0
#define _FS_NORTC 0
#define _NORTC_MON 1
#define _NORTC_MDAY 1
#define _NORTC_YEAR 2015
#define _FS_LOCK 0
#define _FS_REENTRANT 0
#define _FS_TIMEOUT 1000
#define _SYNC_t HANDLE
#define _WORD_ACCESS 0
#define uni2oem 0
#define oem2uni 87172
WCHAR ff_convert (
WCHAR chr,
UINT dir
)
{
uint32_t offset;
WCHAR p;
WCHAR c;
int i, n, li, hi;
if (chr < 0x80) {
c = chr;
} else {
if (dir) {
offset = oem2uni;
} else {
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储存那一张)。我们只需要编写一个转换函数即可。如下所示。
#define ADDR_FLASH_SECTOR_0 ((u32)0x08000000)
#define ADDR_FLASH_SECTOR_1 ((u32)0x08004000)
#define ADDR_FLASH_SECTOR_2 ((u32)0x08008000)
#define ADDR_FLASH_SECTOR_3 ((u32)0x0800C000)
#define ADDR_FLASH_SECTOR_4 ((u32)0x08010000)
#define ADDR_FLASH_SECTOR_5 ((u32)0x08020000)
#define ADDR_FLASH_SECTOR_6 ((u32)0x08040000)
#define ADDR_FLASH_SECTOR_7 ((u32)0x08060000)
#define ADDR_FLASH_SECTOR_8 ((u32)0x08080000)
#define ADDR_FLASH_SECTOR_9 ((u32)0x080A0000)
#define ADDR_FLASH_SECTOR_10 ((u32)0x080C0000)
#define ADDR_FLASH_SECTOR_11 ((u32)0x080E0000)
#define ADDR_FLASH_SECTOR_12 ((u32)0x08100000)
#define ADDR_FLASH_SECTOR_13 ((u32)0x08104000)
#define ADDR_FLASH_SECTOR_14 ((u32)0x08108000)
#define ADDR_FLASH_SECTOR_15 ((u32)0x0810C000)
#define ADDR_FLASH_SECTOR_16 ((u32)0x08110000)
#define ADDR_FLASH_SECTOR_17 ((u32)0x08120000)
#define ADDR_FLASH_SECTOR_18 ((u32)0x08140000)
#define ADDR_FLASH_SECTOR_19 ((u32)0x08160000)
#define ADDR_FLASH_SECTOR_20 ((u32)0x08180000)
#define ADDR_FLASH_SECTOR_21 ((u32)0x081A0000)
#define ADDR_FLASH_SECTOR_22 ((u32)0x081C0000)
#define ADDR_FLASH_SECTOR_23 ((u32)0x081E0000)
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;
}
当读写擦除函数准备完全后就可以进行组装了,代码如下所示:
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)
{
if(STMFLASH_ReadWord(addrx)!=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;
iapbuf[i++]=temp;
if(i==512)
{
i=0;
STMFLASH_Write(fwaddr,iapbuf,512);
*Offset_Address+=2048;
}
}
if(i)
{
STMFLASH_Write(fwaddr,iapbuf,i);
*Offset_Address +=i*4;
}
}
该函数时一次写入2k的字节,如果不到2k的在下面进行了处理。 下面就可以读U盘中的数据进行写入了,代码如下所示:
u8 USB_Read_Write_flash(char *file_name)
{
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);
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;
}
}
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,所以我们要跳到这个地址上。代码如下所示。
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)
{
printf("开始执行FLASH用户代码!!\r\n");
Clear_Peripheral();
if(((*(vu32*)appxaddr)&0x2FF00000)==0x20000000)
{
jump2app=(iapfun)*(vu32*)(appxaddr+4);
__set_MSP(*(vu32*)appxaddr);
jump2app();
}
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 声明 本人才疏学浅,语言表达不到位,在以上叙述中,如有错误,请指出,不胜感激。
|