STM32F103ZET6移植FATFS文件体系教程(W25Q32)

[复制链接]
发表于 2025-9-23 04:46:09 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
一、FATFS核心特性

跨平台支持‌
支持FAT12/FAT16/FAT32格式,兼容Windows文件体系‌;
接纳标准C语言编写,代码量小且支持RTOS‌。
设置灵活性‌
通过宏界说实现功能裁剪,比方:
FF_FS_READONLY:设为1时禁用写使用相干函数(如f_write()、f_unlink())‌;
FF_FS_MINIMIZE:设置差异精简级别(0-3),逐步移除非须要API(如目次使用、文件统计等)‌。

二、移植关键步调


存储介质初始化‌
需初始化底层存储装备(如SD卡、SPI Flash),涉及存储地址映射与读写函数实现‌;
在user_diskio.c中实现底层接口:disk_initialize(初始化)、disk_read(读)、disk_write(写)‌。
文件体系挂载‌
调用f_mount()函数挂载存储装备(如f_mount(fs, "0:", 1)),分配逻辑驱动器号(如"0:")‌。
三、应用层使用

文件读写流程‌
创建文件‌:f_open(&file, "0:/file.txt", FA_CREATE_ALWAYS | FA_WRITE)‌;
数据写入‌:使用f_write()时需将非字符范例数据(如int/float)转换为字符格式‌;
资源开释‌:使用完成后需调用f_close()关闭文件‌。
辅助功能设置‌
支持长文件名需启用FF_USE_LFN并选择编码方式(如UTF-8)‌;
多卷管理需设置FF_VOLUMES参数‌。

四、注意事项



存储介质格式化‌:初次使用前需通过工具或代码格式化,以创建文件分配表和目次布局‌;
内存管理‌:合理分配缓冲区以避免内存溢出,尤其在动态内存模式下‌;
实时性优化‌:在RTOS中需确保文件体系使用与使命调理兼容‌。

开始移植

我们先使用STM32CubeMX创建一个纯净的工程

设置好SPI1

然后在初始化一个片选引脚(PC13)

之后在中心件里设置好FATFS(选择User-defind就好,其他的可以不消改)

末了选择MDK-ARM,生成工程

底层驱动兼容SFUD


一、通用驱动兼容性



广泛硬件支持‌:兼容市面主流SPI Flash芯片(如W25Q64、SST25VF等),支持SPI/QSPI接口的底子读写擦除使用,无需针对差异芯片重复开辟驱动‌。
接口标准化‌:通过同一API(如sfud_read、sfud_erase)屏蔽底层硬件差异,低沉代码与硬件的耦合度‌。

二、参数自动检测



动态识别本领‌:运行时自动读取Flash的厂商ID、容量、擦除粒度等参数,镌汰因芯片停产或型号变动导致的维护风险‌。
SFDP协议支持‌:基于JEDEC标准的SFDP(Serial Flash Discoverable Parameters)规范,自动剖析装备特性表,避免手动设置‌。

三、轻量级设计



低资源占用‌:标准模式代码量约5.5KB ROM/0.2KB RAM,最小模式可压缩至3.6KB ROM/0.1KB RAM,实用于STM32等资源受限的MCU‌。
可裁剪性‌:通过宏界说关闭非须要功能(如QSPI支持),进一步优化存储和运行服从‌。

四、扩展性与易用性



多装备管理‌:支持通过注册机制同时驱动多个Flash装备,实用于多存储介质的复杂场景(如主控+备份存储)‌。
分层架构设计‌:与FAL(Flash抽象层)组件无缝集成,提供同一的Flash访问接口,简化文件体系(如FATFS)或OTA升级功能的实现‌。

五、开辟服从提拔



快速移植‌:仅需实现底层SPI接口函数(如spi_send、spi_recv),收缩从零开辟驱动的时间‌。
动态适配上风‌:新增Flash型号时无需修改驱动代码,仅需更新装备参数表或依赖SFDP自动识别‌。
场景‌SFUD作用‌ 
外部存储扩展(如W25Q32)提供同一接口,简化文件体系(FATFS)或日志日志存储的实现流程 
多Flash装备管理通过注册机制区分差异存储介质,支持并行使用    ‌
特性‌嵌入式开辟价值‌
兼容性‌覆盖90%以上主流SPI Flash型号,低沉硬件选型限定
低资源斲丧‌适配低端MCU(如STM32F103),节流ROM/RAM资源
维护本钱低‌动态参数检测镌汰代码修改频率,提拔长期项目可持续性

移植SFUD


SFUD会检测单片机上的Flash芯片,以是我们这里选择SFUD驱动做一个兼容,就算更换芯片后也不需要修改底层代码。
SFUD: https://github.com/armink/SFUD.git - Gitee.com
把代码克隆到本地,然后把sfud文件夹复制出来,放到工程目次下,内里共有3个目次(inc,port,src)

将port和src内的C文件加到Keil工程内 

然后添加inc路径

我们只需要实现底层的函数接口
进入到sfud_port.c文件内,实现spi_write_read函数
  1. #include <sfud.h>
  2. #include <stdarg.h>
  3. #include "main.h"
  4. static char log_buf[256];
  5. extern SPI_HandleTypeDef hspi1;
  6. #define SPI_Start() (HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET))
  7. #define SPI_Stop() (HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET))
  8. void sfud_log_debug(const char* file, const long line, const char* format, ...);
  9. /**
  10. * SPI write data then read data
  11. */
  12. static sfud_err spi_write_read(const sfud_spi* spi, const uint8_t* write_buf, size_t write_size, uint8_t* read_buf, size_t read_size) {
  13.     sfud_err result = SFUD_SUCCESS;
  14.     uint8_t send_data, read_data;
  15.     /**
  16.      * add your spi write and read code
  17.      */
  18.     SPI_Start();
  19.     if (write_size > 0) {
  20.         if (HAL_OK != HAL_SPI_Transmit(&hspi1, (uint8_t*)write_buf, write_size, 1000))
  21.             result = SFUD_ERR_TIMEOUT;
  22.     }
  23.     if (read_size > 0) {
  24.         if (HAL_OK != HAL_SPI_Receive(&hspi1, (uint8_t*)read_buf, read_size, 1000))
  25.             result = SFUD_ERR_TIMEOUT;
  26.     }
  27.     SPI_Stop();
  28.     return result;
  29. }
复制代码
实现sfud_spi_port_init函数,表明反面提示Required的都是需要设置的
关于delay的设置,就恣意用软件的方式延时一下就好了
  1. void delay(void) {
  2.     uint16_t count = 10000;
  3.     while (count--) {
  4.         __NOP();
  5.     }
  6. }
  7. sfud_err sfud_spi_port_init(sfud_flash* flash) {
  8.     sfud_err result = SFUD_SUCCESS;
  9.     /**
  10.      * add your port spi bus and device object initialize code like this:
  11.      * 1. rcc initialize
  12.      * 2. gpio initialize
  13.      * 3. spi device initialize
  14.      * 4. flash->spi and flash->retry item initialize
  15.      *    flash->spi.wr = spi_write_read; //Required
  16.      *    flash->spi.qspi_read = qspi_read; //Required when QSPI mode enable
  17.      *    flash->spi.lock = spi_lock;
  18.      *    flash->spi.unlock = spi_unlock;
  19.      *    flash->spi.user_data = &spix;
  20.      *    flash->retry.delay = null;
  21.      *    flash->retry.times = 10000; //Required
  22.      */
  23.     flash->spi.wr = spi_write_read;
  24.     flash->retry.delay = delay;
  25.     flash->retry.times = 10000;
  26.     return result;
  27. }
复制代码
测试SFUD和芯片是否移植乐成
在main.c内引入 sfud.h文件

  1. int fputc(int ch, FILE* f) {
  2.     HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 1000);
  3.     return ch;
  4. }
  5. // sfud测试
  6. void es(void) {
  7.     printf("擦除数据\r\n");
  8.     const sfud_flash* flash = sfud_get_device(0);
  9.     sfud_erase(flash, 0, 100);
  10. }
  11. void ws(void) {
  12.     //获取到设备Flash
  13.     const sfud_flash* flash = sfud_get_device(0);
  14.     uint8_t buff[300] = {0};
  15.     for (uint16_t i = 0; i < 300; i++) {
  16.         buff[i] = i;
  17.     }
  18.     //先擦除在写入
  19.     sfud_erase_write(flash, 0, 300, buff);
  20. }
  21. void rs(void) {
  22.     const sfud_flash* flash = sfud_get_device(0);
  23.     uint8_t buff[300] = {0};
  24.     sfud_read(flash, 0, 300, buff);
  25.     for (uint16_t i = 0; i < 300; i++) {
  26.         printf("%d ", buff[i]);
  27.         if (i % 10 == 0) {
  28.             printf("\r\n");
  29.         }
  30.     }
  31. }
  32. // sfud测试
  33. int main(void) {
  34.     /* USER CODE BEGIN 1 */
  35.     /* USER CODE END 1 */
  36.     /* MCU Configuration--------------------------------------------------------*/
  37.     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  38.     HAL_Init();
  39.     /* USER CODE BEGIN Init */
  40.     /* USER CODE END Init */
  41.     /* Configure the system clock */
  42.     SystemClock_Config();
  43.     /* USER CODE BEGIN SysInit */
  44.     /* USER CODE END SysInit */
  45.     /* Initialize all configured peripherals */
  46.     MX_GPIO_Init();
  47.     MX_SPI1_Init();
  48.     //MX_FATFS_Init();
  49.     MX_USART1_UART_Init();
  50.     /* USER CODE BEGIN 2 */
  51.     sfud_init();
  52.     rs();
  53.     HAL_Delay(1000);
  54.     ws();
  55.     HAL_Delay(1000);
  56.     rs();
  57.     /* USER CODE END 2 */
  58.     /* Infinite loop */
  59.     /* USER CODE BEGIN WHILE */
  60.     while (1) {
  61.         /* USER CODE END WHILE */
  62.         /* USER CODE BEGIN 3 */
  63.     }
  64.     /* USER CODE END 3 */
  65. }
复制代码
先读取数据在写入数据,再读出来,看看数据是否同等,读取数据就是把1变0,擦除就是把0变1,以是默认都是1,以是读出来都是255

设置FATFS

打开user_diskio.c 文件,修改USER_initialize和USER_status函数
  1. /**
  2. * @brief  Initializes a Drive
  3. * @param  pdrv: Physical drive number (0..)
  4. * @retval DSTATUS: Operation status
  5. */
  6. DSTATUS USER_initialize(
  7.     BYTE pdrv /* Physical drive nmuber to identify the drive */
  8. ) {
  9.     /* USER CODE BEGIN INIT */
  10.     Stat &= ~STA_NOINIT;
  11.     return Stat;
  12.     /* USER CODE END INIT */
  13. }
  14. /**
  15. * @brief  Gets Disk Status
  16. * @param  pdrv: Physical drive number (0..)
  17. * @retval DSTATUS: Operation status
  18. */
  19. DSTATUS USER_status(
  20.     BYTE pdrv /* Physical drive number to identify the drive */
  21. ) {
  22.     /* USER CODE BEGIN STATUS */
  23.     Stat &= ~STA_NOINIT;
  24.     return Stat;
  25.     /* USER CODE END STATUS */
  26. }
复制代码
实现USER_read函数
  1. DRESULT USER_read(
  2.     BYTE pdrv,    /* Physical drive nmuber to identify the drive */
  3.     BYTE* buff,   /* Data buffer to store read data */
  4.     DWORD sector, /* Sector address in LBA */
  5.     UINT count    /* Number of sectors to read */
  6. ) {
  7.     /* USER CODE BEGIN READ */
  8.     const sfud_flash* flash = sfud_get_device(0);
  9.     uint32_t read_addr = sector * 4096;
  10.     sfud_read(flash, read_addr, count * 4096, buff);
  11.     return RES_OK;
  12.     /* USER CODE END READ */
  13. }
复制代码
 实现USER_write函数
  1. DRESULT USER_write(
  2.     BYTE pdrv,        /* Physical drive nmuber to identify the drive */
  3.     const BYTE* buff, /* Data to be written */
  4.     DWORD sector,     /* Sector address in LBA */
  5.     UINT count        /* Number of sectors to write */
  6. ) {
  7.     /* USER CODE BEGIN WRITE */
  8.     const sfud_flash* flash = sfud_get_device(0);
  9.     uint32_t write_addr = sector * 4096;
  10.     sfud_erase_write(flash, write_addr, count * 4096, buff);
  11.     /* USER CODE HERE */
  12.     return RES_OK;
  13.     /* USER CODE END WRITE */
  14. }
复制代码
实现USER_ioctl函数
  1. DRESULT USER_ioctl(
  2.     BYTE pdrv, /* Physical drive nmuber (0..) */
  3.     BYTE cmd,  /* Control code */
  4.     void* buff /* Buffer to send/receive control data */
  5. ) {
  6.     /* USER CODE BEGIN IOCTL */
  7.     DRESULT res = RES_OK;
  8.     switch (cmd) {
  9.         case CTRL_SYNC:
  10.             break;
  11.         case GET_SECTOR_COUNT:
  12.             *((DWORD*)buff) = 1024;
  13.             break;
  14.         case GET_SECTOR_SIZE:
  15.             *((WORD*)buff) = 4096;
  16.             break;
  17.         case GET_BLOCK_SIZE:
  18.             *((DWORD*)buff) = 1;
  19.             break;
  20.         default:
  21.             res = RES_PARERR;
  22.     }
  23.     return res;
  24.     /* USER CODE END IOCTL */
  25. }
复制代码
 打开ffconf.h文件,修改__MAX_SS为4096

进去到fatfs.c文件,USERPath是文件的起始路径,这里使用 \,前面在转义一下

如果我们这里设置起始路径的话,在ff_gen_drv.c文件内需要表明掉path代码,如果不设置的话会给我们一个默认的起始路径,这里就不需要表明掉了 

 回到main.c文件内,取消表明FATFS初始化函数

 测试FATFS文件体系

  1. // fatfs测试
  2. void mkfs(void) {
  3.     uint8_t res = f_mkfs(USERPath, 1, _MAX_SS);
  4.     printf("mkfs res: %d\r\n", res);
  5. }
  6. FATFS fs;
  7. void mnt(void) {
  8.     uint8_t res = f_mount(&fs, USERPath, 1);
  9.     printf("mnt res: %d\r\n", res);
  10. }
  11. FIL file;
  12. void create_file(char* file_name, char* content) {
  13.     int res = 0;
  14.     int bwritten = 0;
  15.     printf("Create file :%s\r\n", file_name);
  16.     // 创建一个a.txt文件
  17.     res = f_open(&file, file_name, FA_WRITE | FA_CREATE_ALWAYS);
  18.     printf("Create file res:%d\r\n", res);
  19.     // 写入数据到文件中
  20.     res = f_write(&file, content, strlen(content), (UINT*)&bwritten);
  21.     printf("write file res:%d\r\n", res);
  22.     // 关闭文件
  23.     f_close(&file);
  24. }
  25. void list_files(char* path) {
  26.     printf("file list:\r\n");
  27.     FILINFO fno;
  28.     DIR dir;
  29.     FRESULT res;
  30.     res = f_opendir(&dir, path);
  31.     if (res == FR_OK) {
  32.         while (1) {
  33.             res = f_readdir(&dir, &fno);
  34.             if (res != FR_OK || fno.fname[0] == 0)
  35.                 break;
  36.             printf("%s\r\n", fno.fname);
  37.         }
  38.         f_closedir(&dir);
  39.     }
  40. }
  41. void read_file(char* file_name, char* content) {
  42.     int res = 0;
  43.     int bread = 0;
  44.     printf("Show File Content:%s\r\n", file_name);
  45.     res = f_open(&file, file_name, FA_READ);
  46.     printf("Open file res : %d\r\n", res);
  47.     res = f_read(&file, content, 100, (UINT*)&bread);
  48.     printf("Read file res : %d\r\n", res);
  49.     f_close(&file);
  50. }
  51. // fatfs测试
  52. int main(void) {
  53.     /* USER CODE BEGIN 1 */
  54.     /* USER CODE END 1 */
  55.     /* MCU Configuration--------------------------------------------------------*/
  56.     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  57.     HAL_Init();
  58.     /* USER CODE BEGIN Init */
  59.     /* USER CODE END Init */
  60.     /* Configure the system clock */
  61.     SystemClock_Config();
  62.     /* USER CODE BEGIN SysInit */
  63.     /* USER CODE END SysInit */
  64.     /* Initialize all configured peripherals */
  65.     MX_GPIO_Init();
  66.     MX_SPI1_Init();
  67.     MX_FATFS_Init();
  68.     MX_USART1_UART_Init();
  69.     /* USER CODE BEGIN 2 */
  70.     sfud_init();
  71.     mnt();
  72.     mkfs();
  73.     char content_a[100] = "hello world\r\nhello fatfs\r\nhello violet\r\n";
  74.     create_file("a.txt", content_a);
  75.     char content_b[100] = "37193719731831300\r\n";
  76.     create_file("b.txt", content_b);
  77.     char content_c[100] = "dadajdlajldjajajflajf\r\n";
  78.     create_file("c.txt", content_c);
  79.     list_files(USERPath);
  80.     char read_content[100] = {0};
  81.     read_file("a.txt", read_content);
  82.     printf("%s\r\n", read_content);
  83.     memset(read_content, 0, sizeof(read_content));
  84.     read_file("b.txt", read_content);
  85.     printf("%s\r\n", read_content);
  86.     memset(read_content, 0, sizeof(read_content));
  87.     read_file("c.txt", read_content);
  88.     printf("%s\r\n", read_content);
  89.     // rs();
  90.     // HAL_Delay(1000);
  91.     // ws();
  92.     // HAL_Delay(1000);
  93.     // rs();
  94.     /* USER CODE END 2 */
  95.     /* Infinite loop */
  96.     /* USER CODE BEGIN WHILE */
  97.     while (1) {
  98.         /* USER CODE END WHILE */
  99.         /* USER CODE BEGIN 3 */
  100.     }
  101.     /* USER CODE END 3 */
  102. }
复制代码
效果


测试乐成,完成了对FATFS的移植了,喜好的话点个关注吧。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表