使用Cubemx移植FatFs到stm32

在大型的存储器中,没有文件系统是万万不可行的,你不可能每次要打开一个文件都要从头到尾扫描一遍存储器,几兆的小存储器还好,几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,结构体类型如下

1
2
3
4
5
6
7
typedef struct
{
uint8_t is_initialized[_VOLUMES];
const Diskio_drvTypeDef *drv[_VOLUMES];
uint8_t lun[_VOLUMES];
volatile uint8_t nbr;
}Disk_drvTypeDef;

所以我回到了user_diskio.c查看我的USER_read函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

1
2
3
/* 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的状态更新。

1
2
3
4
5
6
7
8
9
10
11
所以我在初始化中加入了检查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的语句

1
2
3
4
5
6
7
8
9
10
11
12
...
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

1
https://github.com/greedyhao/stm32