用stm32移植FATFS的过程中,我弄了几天始终搞不懂2个问题。关于底层引脚和f_read()的问题。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用stm32移植FATFS的过程中,我弄了几天始终搞不懂2个问题。关于底层引脚和f_read()的问题。相关的知识,希望对你有一定的参考价值。

我发现网上大部分的例程都是在"sdcard.c"中直接 /* Configure PC.08, PC.09, PC.10, PC.11, PC.12 pin: D0, D1, D2, D3, CLK pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure PD.02 CMD line */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitStructure);
问题是,我可以更改它的引脚吗?我找不到他们与SD卡的联系!

问题二:f_read()中,发现只能读取到该文件的前512个字节,然后就读不下去了(我设置char buf[512];)。有时候遇到读几个MB时,没那么大的内容怎么办,为什么不会借着往下读。换下一个簇读写,应该可以的,我看SD读写是可以的。我用f_lseek(),发现更改指针不行啊!
我想到了一种比较笨的方法!但是能连续地读内容来,希望跟我有一样疑问的人,有个参考!
for(i=0;i<20;i++)

result = f_mount(0, &fs);
result = f_opendir(&DirInf, "/");
result = f_open(&file, "123.txt", FA_OPEN_EXISTING | FA_READ);
result = f_lseek(&file,i*512);
result = f_read(&file, &buf, sizeof(buf) - 1, &bw);
printf("\r\narmfly.txt content : \r\n%s\r\n", buf);
f_close(&file);
f_mount(0, NULL);

    关于第一个问题你需要读一下STM32的硬件参考手册,在STM32中这几个引脚就是可以被配置为四线制SD卡的那几个引脚的(clk cmd d0 d1 d2 d3)。引脚不能更改,除非你改变读写SD卡的方式,比如使用SPI口读写SD卡;或者愿意软件模拟IO读写SD卡

    第二个问题不太清楚你的描述,是不是你的底层函数没有写好

参考技术A 楼主,请问你的FATFS读取文件没有问题吗??
我也移植了,但是出现的问题就是FATFS读取不对,,在f_open()里面运行时候,读出fmt=2,然后就结束了FATFS文件系统运行。。很郁闷……

我是STM32+SDIO+FATFS操作,SD工作没有问题,因为我调用disk_read()读取出来了SD卡的内容,就是FATFS不行。。能否请楼主给予以下你的工程参考一下呢?/
还有就是网上有人说SDIO读取fatfs时候会出现DMA报错,所以使用有问题。。不知道为何你的没有问题……

我邮箱是371403578@qq.com
参考技术B SD卡的引脚配置可以改吧,那要看硬件连接,一般用SPI接口或者使用SDIO接口,就看在哪个引脚上,改变就行 参考技术C 我也遇到了第二种情况,直接STCUBEMX生成的,USB MSC FATfs 读的时候只能读512以内的,超过的就会出错,只能分多次读,估计官方的USB驱动有问题

STM32CubeMX学习笔记(27)——FatFs文件系统使用(操作SD卡)

一、FatFs简介

FatFs 是面向小型嵌入式系统的一种通用的 FAT 文件系统。它完全是由 ANSI C 语言编写并且完全独立于底层的 I/O 介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如 8051、PIC、AVR、SH、Z80、H8、ARM 等。FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我们利用前面写好的 SPI Flash 芯片驱动,把 FatFs 文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对 SPI Flash 芯片以“文件”格式进行读写操作了。

FatFs 文件系统的源码可以从 fatfs 官网下载:
http://elm-chan.org/fsw/ff/00index_e.html

1.1 FatFs文件系统布局

簇是文件存储的最小单元,FAT32分区大小与对应簇空间大小关系如下表示:

分区空间大小簇空间大小每个簇包含的扇区数
< 8GB4KB8
[ 8GB, 16GB )8KB16
[ 16GB, 32GB )16KB32
>= 32GB32KB64

例如:创建一个50字节的test.txt文件,文件大小是50字节,但是占用磁盘空间为4096字节(一个簇)

1.2 FatFs层次结构

  • 最顶层是应用层:使用者只需要调用FATFS模块提供给用户的一系列应用接口函数(如f_open, f_read, f_write和f_close等),就可以像在PC上读写文件那样简单

  • 中间层FATFS模块:实现了FAT文件读写协议;它提供了ff.c和ff.h文件,一般情况下不用修改,使用时将头文件包含进去即可

  • 最底层是FATFS模块的底层接口:包括存储媒介读写接口和供给文件创建修改时间的实时时钟,需要在移植时编写对应的代码

FATFS源码相关文件介绍如下表示;移植FATFS模块时,一般只需要修改2个文件(即ffconf.hdiskio.c

与平台无关:

文件说明
ffconf.hFATFS模块配置文件
ff.hFATFS和应用模块公用的包含文件
ff.cFATFS模块
diskio.hFATFS和disk I/O模块公用的包含文件
interger.h数据类型定义
option可选的外部功能(比如支持中文)

与平台相关:

文件说明
diskio.cFATFS和disk I/O模块接口层文件

1.3 FatFs API

1.3.1 f_mount

功能在FatFs模块上注册、注销一个工作区(文件系统对象)
函数定义FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt)
参数fs:工作区(文件系统对象)指针
path:注册/注销工作区的逻辑驱动器号
opt:注册或注销选项
返回操作结果

1.3.2 f_open

功能创建/打开一个文件对象
函数定义FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode)
参数fp:将被创建的文件对象结构的指针
path:文件名指针,指定将创建或打开的文件名
mode:访问类型和打开方法,由一下标准的一个组合指定的
返回操作结果
模式描述
FA_READ指定读访问对象。可以从文件中读取数据。 与FA_WRITE 结 合可以进行读写访问。
FA_WRITE指定写访问对象。可以向文件中写入数据。与FA_READ 结合 可以进行读写访问。
FA_OPEN_EXISTING打开文件。如果文件不存在,则打开失败。(默认)
FA_OPEN_ALWAYS如果文件存在,则打开;否则,创建一个新文件。
FA_CREATE_NEW创建一个新文件。如果文件已存在,则创建失败。
FA_CREATE_ALWAYS创建一个新文件。如果文件已存在,则它将被截断并覆盖。

1.3.3 f_close

功能关闭一个打开的文件
函数定义FRESULT f_close(FIL* fp)
参数fp:指向将被关闭的已打开的文件对象结构的指针
返回操作结果

1.3.4 f_read

功能从一个打开的文件中读取数据
函数定义FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br)
参数fp:指向将被读取的已打开的文件对象结构的指针
buff:指向存储读取数据的缓冲区的指针
btr:要读取的字节数
br:指向返回已读取字节数的UINT变量的指针,返回为实际读取的字节数
返回操作结果

1.3.5 f_write

功能写入数据到一个已打开的文件
函数定义FRESULT f_write(FIL* fp, void* buff, UINT btw, UINT* bw)
参数fp:指向将被写入的已打开的文件对象结构的指针
buff:指向存储写入数据的缓冲区的指针
btw:要写入的字节数
bw:指向返回已写入字节数的UINT变量的指针,返回为实际写入的字节数
返回操作结果

另外FatFs还有很多API操作函数,在这里不再作详细的介绍,详细信息请查看FatFs文件系统官网。

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

三、SDIO

STM32 控制器可以控制使用单线或 4 线传输,本开发板设计使用 4 线传输。

3.1 参数配置

Connetivity 中选择 SDIO 设置,并选择 SD 4 bits Wide bus 四线SD模式

此时 SDIO 对应的管脚也被选中。

Parameter Settings 进行具体参数配置。

Clock transition on which the bit capture is made: Rising transition。主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿,它设定 SDIO 时钟控制寄存器(SDIO_CLKCR)的 NEGEDGE 位的值,一般选择设置为上升沿

SDIO Clock divider bypass: Disable。时钟分频旁路使用,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驱动 CLK 线输出时钟;如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路

SDIO Clock output enable when the bus is idle: Disable the power save for the clock。节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。

SDIO hardware flow control: The hardware control flow is disabled。硬件流控制选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。

SDIOCLK clock divide factor: 6。时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数:CLK 线时钟频率=SDIOCLK/([CLKDIV+2])。

SDIO_CK 引脚的时钟信号在卡识别模式时要求不超过 400KHz,而在识别后的数据传输模式时则希望有更高的速度(最大不超过 25MHz),所以会针对这两种模式配置 SDIOCLK 的时钟。

这里参数描述建议将SDIOCLK clock divede factor 参数使用默认值为0,SDIOCLK为72MHz,可以得到最大频率36MHz,但请注意,有些型号的SD卡可能不支持36MHz这么高的频率,所以还是要以实际情况而定。

3.2 配置DMA

SDIO 外设支持生成 DMA 请求,使用 DMA 传输可以提高数据传输效率,因此在 SDIO 的控制代码中,可以把它设置为 DMA 传输模式或轮询模式,ST 标准库提供 SDIO 示例中针对这两个模式做了区分处理。应用中一般都使用DMA 传输模式。

点击 DMA Settings 添加 SDIO 对应 DMA2 的通道4。DMA模式选择循环模式,方向选为内存到外设。

  • Priority
    当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通 道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
  • Mode
    Normal 表示单次传输,传输一次后终止传输。
    Circular 表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。
  • Increment Address
    Peripheral 表示外设地址自增。
    Memory 表示内存地址自增。
  • Data Width
    Byte 一个字节。
    Half Word 半个字,等于两字节。
    Word 一个字,等于四字节。

3.3 配置NVIC

DMA及SDIO中断设置,原则是全局中断优先级高于DMA中断

四、FATFS

4.1 参数配置

Middleware 中选择 FATFS 设置,并勾选 SD Card 配置为SD卡

  • Function Parameters 跳过

  • Locale and Namespace Parameters:

    • CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows) 支持简体中文编码
    • USE_LFN(Use Long Filename): Enabled with dynamic working buffer on the STACK 支持长文件名,并指定使用栈空间为缓冲区

缓存工作区为什么放在栈?其实fatfs提供了三个选项:BSS,STACK , HEAP,根据个人情况选一个。
在BSS上启用带有静态工作缓冲区的LFN,不能动态分配。
如果选择了HEAP(堆)且自己有属于自己的malloc就去重写ff_memalloc  ff_memfree函数。如果是库的malloc就不需要。
一般都选择使用STACK(栈),能动态分配。
当使用堆栈作为工作缓冲区时,请注意堆栈溢出。

  • Physical Drive Parameters:
    • VOLUMES(Logical drivers): 2 指定物理设备数量,这里设置为 2,包括预留 SD 卡和 SPI Flash 芯片
    • MAX_SS(Maximum Sector Size): 512 指定扇区大小的最大值。SD 卡扇区大小一般都为 512 字节,SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把 _MAX_SS 改为 512
    • MIN_SS(Minimum Sector Size): 512 指定扇区大小的最小值

4.2 配置SD卡检测引脚

SD卡插入检测引脚,如果不配置一个引脚生成文件时会报错,所以这里即使没有硬件连接,也可以任意设置一引脚使用,生成工程后注释代码。

4.3 增大栈空间

将最小栈空间改到 0x1000

注意:由于刚才设置长文件名动态缓存存储在堆中,故需要增大堆大小,如果不修改则程序运行时堆会生成溢出,程序进入硬件错误中断(HardFault),死循环。

4.4 生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

五、屏蔽SD卡插入检测代码

由于没有检测SD卡插入的硬件引脚,打开 bsp_driver_sd.c 文件,修改 BSP_SD_IsDetected() 函数,始终返回SD_PRESENT:

六、修改main函数

定义相关变量:

FATFS fs;                       /* FatFs 文件系统对象 */
FIL file;                       /* 文件对象 */
FRESULT f_res;                  /* 文件操作结果 */
UINT fnum;                      /* 文件成功读写数量 */
BYTE ReadBuffer[1024] = 0;    /* 读缓冲区 */
BYTE WriteBuffer[] =            /* 写缓冲区 */
    "This is STM32 working with FatFs\\r\\n";

修改main函数:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_SDIO_SD_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
    printf("\\r\\n****** FatFs Example ******\\r\\n\\r\\n");
    
    //在外部 SD 卡挂载文件系统,文件系统挂载时会对 SD 卡初始化
    f_res = f_mount(&fs, "0:", 1);
    
    /*----------------------- 格式化测试 ---------------------------*/
    printf("\\r\\n****** Register the file system object to the FatFs module ******\\r\\n");
    /* 如果没有文件系统就格式化创建创建文件系统 */
    if(f_res == FR_NO_FILESYSTEM)
    
        printf("The SD card does not yet have a file system and is about to be formatted...\\r\\n");
        /* 格式化 */
        f_res = f_mkfs("0:", 0, 0);
        if(f_res == FR_OK)
        
            printf("The SD card successfully formatted the file system\\r\\n");
            /* 格式化后,先取消挂载 */
            f_res = f_mount(NULL, "0:", 1);
            /* 重新挂载 */
            f_res = f_mount(&fs, "0:", 1);
        
        else
        
            printf("The format failed\\r\\n");
            while(1);
               
    
    else if(f_res != FR_OK)
    
        printf(" mount error : %d \\r\\n", f_res);
        while(1);
    
    else
    
        printf(" mount sucess!!! \\r\\n");
    
    
    /*----------------------- 文件系统测试:写测试 -----------------------------*/
    /* 打开文件,如果文件不存在则创建它 */
    printf("\\r\\n****** Create and Open new text file objects with write access ******\\r\\n");
    f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_CREATE_ALWAYS | FA_WRITE);
    if(f_res == FR_OK)
    
        printf(" open file sucess!!! \\r\\n");
        /* 将指定存储区内容写入到文件内 */
        printf("\\r\\n****** Write data to the text files ******\\r\\n");
        f_res = f_write(&file, WriteBuffer, sizeof(WriteBuffer), &fnum);
        if(f_res == FR_OK)
        
            printf(" write file sucess!!! (%d)\\n", fnum);
            printf(" write Data : %s\\r\\n", WriteBuffer);
        
        else
        
            printf(" write file error : %d\\r\\n", f_res);
        
        /* 不再读写,关闭文件 */
        f_close(&file);
    
    else
    
        printf(" open file error : %d\\r\\n", f_res);
    
    
    /*------------------- 文件系统测试:读测试 ------------------------------------*/
    printf("\\r\\n****** Read data from the text files ******\\r\\n");
    f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_OPEN_EXISTING | FA_READ);
    if(f_res == FR_OK)
    
        printf(" open file sucess!!! \\r\\n");
        f_res = f_read(&file, ReadBuffer, sizeof(ReadBuffer), &fnum);
        if(f_res == FR_OK)
        
            printf("read sucess!!! (%d)\\n", fnum);
            printf("read Data : %s\\r\\n", ReadBuffer);
        
        else
        
            printf(" read error!!! %d\\r\\n", f_res);
        
    
    else
    
        printf(" open file error : %d\\r\\n", f_res);
    
    /* 不再读写,关闭文件 */
    f_close(&file);
    /* 不再使用文件系统,取消挂载文件系统 */
    f_mount(NULL, "0:", 1);
    /* 操作完成,停机 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  
  /* USER CODE END 3 */

七、查看打印

串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用

八、工程代码

链接:https://pan.baidu.com/s/1kgJ7AmbnW89yHcrgBzD_1w 提取码:kg5a

九、注意事项

f_open、f_write、f_read如果偶尔有问题;f_mkfs报错 FS_DISK_ERR,可以加上了SDIO硬件流使能试试。

用户代码要加在 USER CODE BEGIN NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。


• 由 Leung 写于 2021 年 12 月 7 日

• 参考:STM32CubeMX系列教程18:文件系统FATFS
    STM32CubeMX系列|FATFS文件系统
    1.6 Cubemx_STM32F103_NOOS SDIO_DMA_FATFS基于SD卡的FATFS测试(一)
    【STM32Cube_20】在SD卡上移植FATFS文件系统
    STM32CubeMX之SD卡+FatFs

以上是关于用stm32移植FATFS的过程中,我弄了几天始终搞不懂2个问题。关于底层引脚和f_read()的问题。的主要内容,如果未能解决你的问题,请参考以下文章

STM32+雷龙SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

STM32+雷龙SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

STM32之独立版USB(Host)驱动+MSC+Fatfs移植

STM32 下 FatFs的移植,实现了擦写均衡,坏块管理,硬件 ECC,ECC纠错

基于STM32完成FATFS文件系统移植与运用--这是完全免费开源的FAT文件系统

stm32移植fatfs系统,已实现往sd卡中写txt,但只能存64k的数据,怎么能存更多的数据呢