下面利用板子上有的SPIFLASH和TF接口,准备实现文件读写控制。之前已经实现了spiflash的sfud库移植,可以驱动SPIFLASH读写数据了。接下来实现基于SPIFLASH的FATFS文件系统移植。
首先打开STM32CubeMX,配置中间件FATFS库参数。

然后生成KEIL代码工程,在user_diskio.c中实现文件初始化,读写接口函数。就是实现 FatFS 的底层磁盘I/O接口(diskio.c),把这些接口函数与 sfud 的API对接起来。
在 diskio.c 文件顶部,包含 sfud 的头文件,并定义一个指向你的SPI Flash设备的全局指针。再就是几个接口函数:
实现 disk_initialize (磁盘初始化)
DSTATUS USER_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
/* USER CODE BEGIN INIT */
Stat = STA_NOINIT;
switch (pdrv) {
case SPI_FLASH:
// 如果sfud还没有初始化,在这里调用 sfud_init()
sfud_init();
g_flash_ptr = (sfud_flash *)sfud_get_device_table();
// 可以在这里读取设备ID来验证是否成功
if (sfud_get_device_num() > 0) {
Stat &= ~STA_NOINIT; // 清除“未初始化”标志
}
break;
// ... 其他设备
}
return Stat;
/* USER CODE END INIT */
}
实现 disk_status (获取磁盘状态)
DSTATUS USER_status (
BYTE pdrv /* Physical drive number to identify the drive */
)
{
/* USER CODE BEGIN STATUS */
Stat = STA_NOINIT;
switch (pdrv) {
case SPI_FLASH:
// 简单实现:假设只要sfud能读到设备ID,就认为状态OK
if (sfud_get_device_num() > 0) {
Stat &= ~STA_NOINIT;
}
break;
}
return Stat;
/* USER CODE END STATUS */
}
实现 disk_read (读扇区)
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 */
switch (pdrv) {
case SPI_FLASH:
// sfud_read 的参数是 (设备, 起始地址, 数据缓冲区, 数据大小)
// 注意:FatFS的扇区地址需要转换为字节地址
if (sfud_read(g_flash_ptr, sector * 512, count * 512, buff) == SFUD_SUCCESS) {
return RES_OK;
} else {
return RES_ERROR;
}
// ... 其他设备
}
return RES_OK;
/* USER CODE END READ */
}
实现 disk_write (写扇区)
uint8_t work_buffer[4100];
#define ERASE_BLOCK_SIZE 4096 // 4KB,可根据 Flash 型号调整
int sfud_write_safe(const sfud_flash *flash, uint32_t addr, size_t len, const uint8_t *buff)
{
uint32_t start_block = addr / ERASE_BLOCK_SIZE;
uint32_t end_block = (addr + len - 1) / ERASE_BLOCK_SIZE;
uint32_t data_offset = 0; // 用户数据偏移
for (uint32_t block = start_block; block <= end_block; block++)
{
uint32_t block_addr = block * ERASE_BLOCK_SIZE;
uint32_t offset_start = (block == start_block) ? (addr - block_addr) : 0;
uint32_t offset_end = (block == end_block) ? (addr + len - block_addr) : ERASE_BLOCK_SIZE;
uint32_t block_data_len = offset_end - offset_start;
// 如果写入范围覆盖整个块,直接擦除+写
if (offset_start == 0 && offset_end == ERASE_BLOCK_SIZE) {
if (sfud_erase(flash, block_addr, ERASE_BLOCK_SIZE) != SFUD_SUCCESS) return RES_ERROR;
if (sfud_write(flash, block_addr, block_data_len, buff + data_offset) != SFUD_SUCCESS) return RES_ERROR;
} else {
// 读-修改-写
if (sfud_read(flash, block_addr, ERASE_BLOCK_SIZE, work_buffer) != SFUD_SUCCESS) return RES_ERROR;
memcpy(work_buffer + offset_start, buff + data_offset, block_data_len);
if (sfud_erase(flash, block_addr, ERASE_BLOCK_SIZE) != SFUD_SUCCESS) return RES_ERROR;
if (sfud_write(flash, block_addr, ERASE_BLOCK_SIZE, work_buffer) != SFUD_SUCCESS) return RES_ERROR;
}
data_offset += block_data_len;
}
return RES_OK;
}
#if _USE_WRITE == 1
DRESULT USER_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address in LBA */
UINT count /* Number of sectors to write */
)
{
/* USER CODE BEGIN WRITE */
/* USER CODE HERE */
switch (pdrv) {
case SPI_FLASH:
return sfud_write_safe(g_flash_ptr, sector * 512, count * 512, buff);
// ... 其他设备
}
return RES_PARERR;
/* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */
实现 disk_ioctl (控制函数)
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
/* USER CODE BEGIN IOCTL */
DRESULT res = RES_ERROR;
switch (pdrv) {
case SPI_FLASH:
switch (cmd) {
/* Make sure that no pending write process */
case CTRL_SYNC :
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = 512; // 返回扇区大小
break;
case GET_SECTOR_COUNT:
// 返回总扇区数 = 总容量 / 扇区大小
*(DWORD*)buff = g_flash_ptr->chip.capacity / 512;
break;
case GET_BLOCK_SIZE:
*(DWORD*)buff = 4096/512; // 擦除块大小
break;
default:
return RES_PARERR;
}
return RES_OK;
// ... 其他设备
}
return res;
/* USER CODE END IOCTL */
}
完成上述移植后,你就可以在应用代码中调用FatFS的标准API来操作文件系统了。
下面就是测试文件系统是否正常运行了。
FATFS fs; /* Work area (file system object) for logical drive */
char cur_path[256]; //当前路径
int disk_total_size(char* path)
{
FATFS *fs;
DWORD fre_clust;
FRESULT res;
res = f_getfree(path, &fre_clust, &fs);
if ( res==FR_OK )
{
/* Print free space in unit of MB (assuming 512 bytes/sector) */
printf("\r\nFatfs system:%u KB total drive space. %u KB available.\r\n",
(int)(((fs->n_fatent - 2) * fs->csize ) / 2 ) , (int)((fre_clust * fs->csize) / 2 ));
return ENABLE;
}else return DISABLE;
}
//====================================================
void fatfs_test_demo(void)
{
FRESULT res;
FIL fsrc; /* file objects */
UINT br;
const uint8_t textFileBuffer[] = "Thank you for using Development Board ^_^ \r\n";
res = f_mount(&fs,"0:",1);
printf("f_mount disk0 res=%d!\r\n",res);
if(res == FR_NO_FILESYSTEM)
{
sfud_chip_erase((sfud_flash *)sfud_get_device_table());
res = f_mkfs("0:",FM_FAT,4096,work_buffer,4096);
printf("f_mkfs disk0 res=%d!\r\n",res);
res = f_mount(&fs,"0:",1);
printf("f_mount disk0 res2=%d!\r\n",res);
}
if(res == 0)
{
res = f_open( &fsrc , "0:/demo.txt" , FA_OPEN_APPEND | FA_WRITE | FA_READ);
if ( res == FR_OK )
{
/* Write buffer to file */
res = f_write(&fsrc, textFileBuffer, sizeof(textFileBuffer), &br);
printf("Demo.TXT successfully created \r\n");
}else if ( res == FR_EXIST )
{
/* Write buffer to file */
res = f_write(&fsrc, textFileBuffer, sizeof(textFileBuffer), &br);
printf("Demo.TXT created in the disk1 \r\n");
}else
{
printf("Demo.TXT created in the disk1 error %d.\r\n",res);
}
/*close file */
f_close(&fsrc);
cur_path[0]='0';
cur_path[1]=':';
cur_path[2]='/';
cur_path[3]= 0;
disk_total_size(cur_path);
}
}
在mian函数调用测试函数 void fatfs_test_demo(void) 了。

编译烧写之后就可以看到打印日志记录了。
