一、需要移植的接口
我们通过看官网的手册,可以看到我们只要完成下面函数的实现,就可以完成移植。我们这里只移植前5个函数,获取时间的函数我们不在这里移植。
二、移植接口函数
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
switch (pdrv) {
case DEV_FALSH :
stat = EN25QXX_ReadSR();
return stat;
return stat;
}
return STA_NOINIT;
}
//获取存储器状态的函数
uint8_t EN25QXX_ReadSR(void)
{
uint8_t byte = 0;
FLASH_CS = 0; //使能器件
SPI1_ReadWriteByte(EN25X_ReadStatusReg); //发送读取状态寄存器命令
byte = SPI1_ReadWriteByte(0Xff); //读取一个字节
FLASH_CS = 1; //取消片选
return byte;
}
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat = 0;;
if (pdrv == DEV_FALSH) //由于我只里关于FLASH的初始化,在main函数里面已经完成了,这里直接返回就行
{
return stat;
}
return STA_NOINIT;
}
需要注意的是读和写的函数里面的LBA_t sector这个程序内部代表的是扇区号,而不是扇区地址,但是我们的FLASH写和读函数里面传的参数是地址,因此一定要先将扇区号转化成对应的地址然后才可以进行传参。count代表的是扇区的个数而不是我们要写的字节数。而我们的读函数一次是读一个扇区,因此count是多少就代表我们要读多少个扇区。读完一个扇区后一定要将地址和数据进行更新,虽然在FALSH层面上读是不会限制大小的,但是由于我们是给FATFS文件系统使用,因此我们就要写成按一个扇区来读。
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA 这个参数是扇区的个数 而不是扇区的地址*/
UINT count /* Number of sectors to read */
)
{
uint32_t i = 0;
uint32_t addr = sector*SECTOR_SIZE;
if (pdrv == DEV_FALSH)
{
for (i=0; i<count; i++)
{
EN25QXX_Read((BYTE *)buff, addr, SECTOR_SIZE); //每运行一次就读一个扇区
addr += SECTOR_SIZE;
buff += SECTOR_SIZE;
}
return RES_OK;
}
return RES_PARERR;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void EN25QXX_Read(uint8_t *pBuffer,uint32_t ReadAddr, uint16_t NumByteToRead)
{
uint16_t i;
FLASH_CS = 0; //使能器件
SPI1_ReadWriteByte(EN25X_ReadData); //发送读取命令
SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>16)); //发送24bit地址
SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>8));
SPI1_ReadWriteByte((uint8_t)ReadAddr);
for(i=0; i<NumByteToRead; i++)
{
pBuffer[i] = SPI1_ReadWriteByte(0XFF); //循环读数
}
FLASH_CS = 1;
}
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
uint8_t Rxdata;
HAL_SPI_TransmitReceive_DMA(&hspi1, &TxData, &Rxdata, 1);
delay_us(1); //必须的需要这个延时,不然速度太快了
return Rxdata; //返回收到的数据
}
这里需要注意的是FLASH在每次写之前一定要先进行擦除,才可以写,因此我们这里还需要一个按扇区擦除的函数。
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
uint32_t i = 0;
uint32_t addr = sector*SECTOR_SIZE;
if (pdrv == DEV_FALSH)
{
for (i=0; i<count; i++)
{
EN25QXX_Erase_Sector(addr);
EN25QXX_Write_Sector((BYTE *)buff, addr, SECTOR_SIZE);
addr += SECTOR_SIZE;
buff += SECTOR_SIZE;
}
return RES_OK;
}
return RES_PARERR;
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void EN25QXX_Erase_Sector(uint32_t Dst_Addr)
{
//监视falsh擦除情况,测试用
// printf("fe:%x\r\n",Dst_Addr);
EN25QXX_Write_Enable(); //SET WEL
EN25QXX_Wait_Busy();
FLASH_CS = 0; //使能器件
SPI1_ReadWriteByte(EN25X_SectorErase); //发送扇区擦除指令
SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>16)); //发送24bit地址
SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>8));
SPI1_ReadWriteByte((uint8_t)Dst_Addr);
FLASH_CS = 1; //取消片选
EN25QXX_Wait_Busy(); //等待擦除完成
}
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
if (pdrv == DEV_FALSH)
{
switch (cmd){
case CTRL_SYNC: //确保设备已完成挂起的写入过程。如果磁盘I/O层或存储设备具有回写式缓存,则脏缓存数据必须立即提交到介质。如果对介质的每个写操作都在以下时间内完成,则此命令不执行任何操作 disk_write 功能。
return RES_OK;
case GET_SECTOR_COUNT:{
*(DWORD *)buff = 4096; //表示扇区的个数
return RES_OK;
}
case GET_SECTOR_SIZE:{
*(WORD *)buff = SECTOR_SIZE; //表示每个扇区的大小
return RES_OK;
}
}
return RES_PARERR;
}
return RES_PARERR;
}
三、注意事项
移植完上面的接口函数后,因为我们无法手动给FLASH里面格式化成FAT32文件系统,因此我们需要使用f_mkfs()函数,来完成格式化。当你使用这个函数的时候,你会发现会报错,那是因为在ffconf.h里面的相关配置没有打开。
如果你在编译的过程中发现下面的错误,那也是因为配置里面没有关闭使用时间这个选项
如果发现运行过程中,程序老是死在某个地方,他不是死循环,而是直接程序就不动了。那有可能是因为扇区的大小设置的不对,因为FATFS默认情况将512个字节作为一个簇,但是我们的FLASH里面的最小擦出的单元是一个扇区,而我们的一个扇区大小是4KB也就是4096个字节。因此我们要将扇区范围的上限提高一下。
res = f_mkfs("0:/", 0, work, 4096);
这里传的参数也是一个扇区的大小,如果不对格式化会有问题。