在大型的存储器中,没有文件系统是万万不可行的,你不可能每次要打开一个文件都要从头到尾扫描一遍存储器,几兆的小存储器还好,几G甚至几T的存储器就根本没办法这么做了。而且有了文件系统也可以方便的管理使用各类文件。
这一次使用Cubemx生成FatFs的初始化代码,然后做最后的移植工作。本人使用的是stm32f767的野火的板子。
介绍一下FafFs
系统架构
FatFs是一种中间层,可以屏蔽硬件的差异,移植起来非常方便
移植需要注意的地方
你需要提供FatFs需要的底层I/O函数,需要的函数如下表,但是并不是全部函数都需要,你只需要提供必需的disk_status disk_initialize disk_read 和你需要的就可以。
开始移植
配置Cubemx
因为我准备的是W25Q128上的FatFs,CubeMX上面没有,所以要选择user-defined
接下来是在Configuration里面配置FatFs,因为需要读取的文件是中文的,所以要配置为中文,然后为以后可能还有别的存储器预留空位,所以配置VOLUMES为3
还有就是在project setting里面把stack设置的大点
此外就是W25Q128的基本配置,这些就不提供了,网上有,可以参考微雪课堂里的配置。
接下来就是生成代码了
补完底层I/O函数
打开user_disko.c这个文件,可以看到CubeMX自动生成的函数原型。由于我需要读写磁盘,所以我需要补全这些所有的函数USER_initialize USER_status USER_read USER_write USER_ioctl
遇到问题和解决问题
f_mount返回FR_DISK_ERR
这个问题困扰了我好几个小时,我从f_mount->get_ldnumber用printf找了差不多1个小时的BUG,发现找不到问题,突然间发现我f_mount还调用了一个函数find_volume,而且就算这里出现了问题,就换方向一路追踪find_volume->check_fs->move_window->disk_read,发现是disk_read返回的错误,看到这我就傻眼了,觉得不可能啊,因为我之前读取写入flash都是没有问题的。
第二天脑袋比较清醒了,首先先测试初始化是否成功,在读取成功flash id后确定了初始化成功。
然后再来看看disk_read,它使用的是我提供的一个全局的disk io驱动来读取disk,结构体类型如下
typedef struct
{
uint8_t is_initialized[_VOLUMES];
const Diskio_drvTypeDef *drv[_VOLUMES];
uint8_t lun[_VOLUMES];
volatile uint8_t nbr;
}Disk_drvTypeDef;
所以我回到了userdiskio.c查看我的USERread函数
DRESULT USER_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address in LBA */
UINT count /* Number of sectors to read */
)
{
/* USER CODE BEGIN READ */
FLASH_DEBUG_FUNC();
DRESULT res = RES_ERROR;
if ((Stat & STA_NOINIT))
{
res = RES_NOTRDY;
}
else
{
sector+=1536;//扇区偏移,外部Flash文件系统空间放在外部Flash后面6M空间
res = BSP_QSPI_Read(buff, sector <<12, count<<12);
}
return res;
/* USER CODE END READ */
}
我在USER_read的else括号中加了一个printf,结果发现调用f_mount时,居然没有进入这个else语句,然后我开始怀疑这个Stat有问题,所以我把if的条件去掉了,直接执行sector+=1536;res = BSP_QSPI_Read(buff, sector <<12, count<<12);这条语句,发现FatFs挂载成功。
很激动,原来问题在于Stat的状态没有更新,因为Stat一开始定义的时候是定义为STA_NOINIT。
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;
接下来就是查找disk的状态没有更新的原因。回到一开始找到返回错误信息的函数find_volume,它的执行过程相当于get_ldnumber->disk_initialize->check_fs...一开始就是到了check_fs这一步的时候报错,停止程序的,我们可以发现,这个过程并没有调用到用户定义的USER_status或FatFs定义的disk_status来检查disk的状态更新。
所以我在初始化中加入了检查disk的状态的代码
DSTATUS USER_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
/* USER CODE BEGIN INIT */
Stat = USER_status(pdrv);
return BSP_QSPI_Init(); /* Flash的初始化 */
/* USER CODE END INIT */
}
效果很好,disk的状态更新成功,f_mount挂载正常。
f_open返回FR_INVALID_NAME
好不容易挂载成功了,但是读写还是失败,第一反应是可能在编码上出错了,因为我的工程没有包括cc936.c这个文件,而野火官方的工程却包括了,我怀疑是不是这一点导致无法读取中文,从而FR_INVALID_NAME。
但是我现在还不知道FatFs是怎么调用cc936.c这个文件的,只能先从f_open跟踪进去看看。
f_open->follow_path,打LOG发现是follow_path中的create_name返回的FR_INVALID_NAME
进入create_name找到返回FR_INVALID_NAME的语句
...
mem_set(sfn, ' ', 11);
...
for (;;;) {
...
if (c == '.' || i >= ni) { /* End of body or over size? */
if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Over size or invalid dot */
i = 8; ni = 11; /* Goto extension */
continue;
}
...
}
由于对于文件名一开始只分配了11个空间,但是我的文件名却超过了11个空间,所以导致了这个问题,在我将我的文件名减少后,问题解决。
文件名长度才11有点短,但是可以通过修改支持长文件名来支持。
在支持长文件名后,读写长文件名没有问题了
可以改进的地方
本工程没有加入RTC的支持,所以没有去实现get_fattime,因此不支持多磁盘,想支持多磁盘需要实现get_fattime
本工程已上传github
https://github.com/greedyhao/stm32
———— / END / ————