1 Xiliffs库
Xilinx 的 Xilffs 库是一个用于嵌入式系统的FAT文件系统库。它支持FAT12、FAT16和FAT32乃至exFAT系统文件系统,适用于SD卡、eMMC等存储设备。Xilffs库与Xilinx的硬件IP核(如SD/SDIO控制器)紧密集成,提供文件读写、目录管理等基本操作。该库具有轻量级、可配置性强等特点,适合资源受限的嵌入式环境,广泛应用于数据记录、固件升级等场景。
2 BSP配置
与其它常用的库(如 Lwip)一样,要使用 Xiliffs 库,需要先在BSP中添加对其的支持。步骤如下:
1,打开需要使用 Xiliffs 库的核的工程 BSP。
使用多核的需要注意,每个核之间的 BSP 是不互通的,像我上面示例图片中,就是把SD卡的保存放在了第三个核(CPU2)上,这时,其它两个核还是无法使用 Xiliffs 库,如有跨核使用的需求,需要分别进行配置。
2,在点开的BSP设置中,勾选对于 Xiliffs 库的支持。
3,点进左侧的 Xiliffs 库设置,进行相关参数的设置。
这里面,需要重点关注以下几个参数:
enable_exfat:是否使能exFAT文件系统?
enable_multi_partition:是否使用多个分区?
fs_interface:给SD卡还是RAM使用?
read_only:是否让文件系统设置为只读模式?(不能写)
use_lfn:是否使能长文件名?(如果保存的文件字符数比较多(看别人说是6个)如果不开这个保存文件会失败)
3 文件IO操作
1,挂载文件系统:
FATFS fs;
fresult = f_mount(&fs, "1:/", 0);
if (fresult != FR_OK)
{
xil_printf("mount file system failed : %d\r\n", fresult);
return 0;
}
2,打开文件:
FIL file;
fresult = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE); //FA_READ
if (fresult != FR_OK)
{
xil_printf("open file failed : %d\r\n", fresult);
return 0;
}
这里注意 f_open 的第三个参数,这里用到了两个宏,一个是 FA_CREATE_ALWAYS ,表示不管以前文件系统中是否存在名为 filename 的文件,都是创建一个新的,如果原来有,则会被覆盖掉;第二个宏为 FA_WRITE,表示为以写入模式打开文件,与之对应的,还有 FA_READ 表示以读取模式打开文件。
3,写入文件
UINT bytesWritten, bytesRead;
char data_buffer[10000]; // 数据缓冲区
fresult = f_write(&file, data_buffer, sizeof(data_buffer), &bytesWritten);
if ((fresult != FR_OK) || (bytesWritten != DATA_BUFFER_SIZE))
{
xil_printf("write file failed : %d, %d\r\n", fresult, bytesWritten);
}
这里执行 f_write 传入了一个指针 &bytesWritten,最终可以通过读取 bytesWritten 来获取写入的数据字节数。这也引出了另一个问题,即 f_write 如果由于种种原因只是写入了部分数据,没有完全写入,fresult 不会表现出错误,所以在判断错误时,需要加入 bytesWritten 加以判断。
4,读取文件
fresult = f_read(&file, read_data_buffer, sizeof(read_data_buffer), &bytesRead);
if (fresult != FR_OK)
{
xil_printf("read file failed : %d\r\n", fresult);
return 0;
}
与上面写入文件同理,执行后,可以通过 bytesRead 获取读到的字节数。
5,关闭文件
fresult = f_close(&file);
if (fresult != FR_OK)
{
xil_printf("close file failed : %d\r\n", fresult);
}
特别注意,不要省略了这个步骤,否则文件可能保存不下来!
6,卸载文件系统
// 卸载文件系统
//fresult = f_mount(NULL, "1:/", 0); //第一个参数为NULL就是卸载
fresult = f_unmount("1:/");
有两种写法,但本质上是一样的,因为 f_unmout 是一个宏,它与上面使用 f_mount 是完全等价的:
#define f_unmount(path) f_mount(NULL, path, 0)
这里需要说明,不是说不卸载文件系统,最后文件就保存不下来,毕竟大部分运用场景,掉电是不确定性的。
4 一些重要的细节
1,上面的 f_mount 的第二个参数是 “1:/”,这类似于 Windows 下的盘符的概念,如果不想设置,也可以直接传入 “”。但需要注意的是写入、读取文件与卸载文件系统时,也会涉及它,所有地方必须保持一致,否则一些操作会无效。
2,如果要对文件进行读写操作,第三个参数可以继续或上,对应的宏,不是说只能读或只能写:
fresult = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
3,有时候,我们并不希望一直在文件的尾部进行内容的追加,也可能会涉及到对于中间部分的操作,这时候,可以使用以下函数来修改表示文件当前所在位置的指针,来改变读写位置:
fresult = f_lseek(&file, 0);
if (fresult != FR_OK)
{
xil_printf("f_lseek 0 failed : %d\r\n", fresult);
return 0;
}
其中第二个参数就表示文件指针的目标位置(偏移量),从文件开头开始计算,单位为字节。
4,要想实现文件名的递增操作,可以使用 snprintf 函数进行辅助:
snprintf(filename, sizeof(filename), "1:/f_%d.bin", file_cnt);
file_cnt++; // 递增文件名计数器
fresult = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE);
if (fresult != FR_OK)
{
xil_printf("open file failed : %d\r\n", fresult);
return 0;
}
效果如下:
5,如果对文件的一条龙操作(打开,写入,关闭)比较频繁,需要注意一下保存速度能否满足数据的带宽,我自己测试的SD卡,使用一个核单纯用来保存文件,最终的写入速度只有 10MB/s +,如果对速度有更高要求,需要换更高速的SD卡,甚至换硬盘。并且,经过测试,随着保存的文件的增多,这一条龙的操作时间有缓慢增加的趋势,对于实际应用需要考虑进去。
6,可以操作的文件格式很多,比如 txt,bin,甚至是 csv等,但需要根据不同的文件格式进行对应的写入策略修改,如果求简单就用 txt 或者 bin 即可。
5 总体测试代码
需要注意,这里的测试代码试图打开在SD卡中存在的一个名为 1.bmp 图片,读取其数据,大家可以根据自己情况灵活更改。
#include "string.h"
#include "ff.h"
#include <stdio.h>
#include "xil_printf.h"
#define SD_SAVE_BAG_NUM_ONCE 256 //256*(11*72)=202752Byte=198KB 0x31800
#define DATA_BUFFER_SIZE (256 * 11 * 72)
FATFS fs;
FIL file;
UINT bytesWritten, bytesRead;
FRESULT fresult;
char filename[20];
// 数据缓冲区(200KB)
char data_buffer[DATA_BUFFER_SIZE]; // 数据缓冲区
char read_data_buffer[2000000000]; // 数据缓冲区
// 全局变量,用于文件名递增
static int file_cnt = 0;
int sd_init()
{
// 初始化数据
for (int i = 0; i < DATA_BUFFER_SIZE; i++) {
data_buffer[i] = (char)(i % 256); // 填充0-255的循环数据
}
// 挂载文件系统
fresult = f_mount(&fs, "1:/", 0);
if (fresult != FR_OK)
{
xil_printf("mount file system failed : %d\r\n", fresult);
return 0;
}
xil_printf("sd init is successful\r\n");
return 0;
}
void sd_open_write_close()
{
snprintf(filename, sizeof(filename), "1:/f_%d.bin", file_cnt); // 格式化文件名,开始时,是file_0.bin
file_cnt++; // 递增文件名计数器
fresult = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE);
if (fresult != FR_OK)
{
xil_printf("open file failed : %d\r\n", fresult);
return 0;
}
// 写入数据
fresult = f_write(&file, data_buffer, sizeof(data_buffer), &bytesWritten);
if ((fresult != FR_OK) || (bytesWritten != DATA_BUFFER_SIZE))
{
xil_printf("write file failed : %d, %d\r\n", fresult, bytesWritten);
}
// 关闭文件
fresult = f_close(&file);
if (fresult != FR_OK)
{
xil_printf("close file failed : %d\r\n", fresult);
}
}
void sd_ffs_end()
{
// 卸载文件系统
//fresult = f_mount(NULL, "1:/", 0); //第一个参数为NULL就是卸载
fresult = f_unmount("1:/");
if (fresult != FR_OK)
{
xil_printf("unmount file failed : %d\r\n", fresult);
}
}
void sd_ffs_read()
{
fresult = f_open(&file, "1:/1.bmp", FA_OPEN_EXISTING | FA_READ);
if (fresult != FR_OK)
{
xil_printf("open 1.bmp failed : %d\r\n", fresult);
}
fresult = f_read(&file, read_data_buffer, sizeof(read_data_buffer), &bytesRead);
xil_printf("read file num : %d\r\n", bytesRead);
if (fresult != FR_OK)
{
xil_printf("read file failed : %d\r\n", fresult);
}
}
int main()
{
int Status;
xil_printf("CPU2 start!\r\n");
Status = sd_init();
sd_open_write_close();
sd_open_write_close();
sd_open_write_close();
sd_open_write_close();
sd_open_write_close();
sd_ffs_read();
sd_ffs_end();
xil_printf("CPU2 end!\r\n");
return 0;
}