STM32F4- SD卡和 FATFS文件系统

        单片机系统常需大容量存储设备,如U盘、FLASH芯片、SD卡等。

        其中,SD卡因容量大、支持SPI/SDIO驱动、尺寸多样,成为单片机系统的优选。

        STM32F4开发板自带SD卡接口,使用SDIO接口驱动,支持高速数据传输。

1.1 SDIO 简介

1.1.1 SDIO 主要功能及框图

        STM32F4的SDIO控制器多种存储卡,包括 MMC卡、SD存储卡、SD I/O卡和 CE-ATA设备。

        它兼容多种版本的系统规格,并支持1位、4位和8位数据总线模式。

        SDIO 控制器SDIO适配器模块和 APB2总线接口组成,复位后默认使用 SDIO_D0进行数据传输,但可以通过命令改变总线宽度。

        对于 MMC卡,通常使用 1位数据线;

        而对于 SD或 SD I/O卡,则可以选择 1位或 4位数据线

        SDIO_CMD信号在初始化时采用开路模式(仅适用于旧版MMC卡),在命令传输时则采用推挽模式。

        SDIO控制器在 8位总线模式下,数据传输速率最高可达 48MHz。

STM32F4 的 SDIO控制器功能框图

1.1.2 SDIO 的时钟

        SDIO有三种时钟:

        卡时钟(SDIO_CK)SDIO适配器时钟(SDIOCLK)

        

        APB2总线接口时钟(PCLK2)

        SDIO_CK频率根据卡类型变化,范围分别是0-20MHz、0-48MHz和0-25MHz。

        SDIOCLK一般为48MHz,用于产生SDIO_CK。

        PCLK2频率为HCLK/2,一般为84MHz。

        SDIO_CK与SDIOCLK关系(时钟分频器不旁路时)为:

                SDIO_CK=SDIOCLK/(2+CLKDIV)。

                卡时钟 = 卡适配器时钟/(2+分频器)

        CLKDIV通过SDIO寄存器设置,确保SDIO_CK不超过卡的最大频率。

        注意,初始化时SDIO_CK不能超过400KHz,初始化后可设置到最大(但不能超过卡的最大频率)。 

1.1.3 SDIO 的命令与响应

        SDIO命令分为ACMD(相关命令)CMD(通用命令)发送ACMD前需先发送CMD55

        所有命令和响应通过SDIO_CMD引脚传输,命令长度固定为48位。

        STM32F4发出命令,硬件控制开始位传输位CRC7结束位

        用户只需要设置命令索引参数

SDIO命令格式

        SD卡在接收命令后回复应答,这个应答我们称之为响应,有短响应(48位)长响应(136位)两种,均带CRC错误检测。

        硬件滤除起始位、传输位、CRC7和结束位,

        短响应命令索引存放在SDIO_RESPCMD寄存器参数存放在SDIO_RESP1寄存器

        长响应CID/CSD存放在SDIO_RESP1~SDIO_RESP4寄存器

        SD存储卡有5类响应(R1,R2,R3,R6,R7),

        以R1为例,为短响应,包含起始位、传输位、命令索引、卡状态、CRC7校验位和结束位。

        R1响应为短响应,提供命令索引卡状态信息

SDIO命令格式
短响应格式
长响应命令格式

        数据在SDIO控制器与SD卡间以数据块形式传输,SDIO硬件自动处理CRC校验。

        单数据块读无需停止命令多数据块读需发送CMD12停止

        读操作包括主机发送读命令,卡响应后发送数据块;

        写操作类似,但需判断卡是否繁忙。

        多块数据读写时,SD卡一直发送/接收数据,直到主机发送STOP命令(CMD12)

        数据块均带CRC校验,确保数据完整性和准确性。

1.1.4 SDIO 相关寄存器介绍

        第一个,SDIO 电源控制寄存器(SDIO_POWER)

        要启用SDIO,需先设SDIO_POWER寄存器最低2位为1,以开启电源和卡时钟

SDIO_POWER  SDIO电源控制寄存器

        第二个,SDIO 时钟控制寄存器(SDIO_CLKCR)

        SDIO_CLKCR寄存器控制SDIO时钟,设置SDIO时钟分配系数时钟开关数据位宽

        具体为:

        WIDBUS= 1 表示4位总线;

        BYPASS= 0 禁用旁路;

        CLKEN= 1 启用时钟;

        CLKDIV= 0 设定24MHz频率。

        这些确保SDIO通信顺畅。

        时钟分频器旁路(Bypass)是指在时钟系统中,通过特定的设置或配置,使时钟信号绕过分频器直接传输,而不对其进行分频处理。

SDIO_CLKCR ,SDIO时钟控制寄存器

        第三个,SDIO 参数寄存器(SDIO_ARG),32位寄存器,存储命令参数,写命令前必须先写入此寄存器。

        第四个,SDIO 命令响应寄存器(SDIO_RESPCMD),32位寄存器,低6位有效,存储最后命令响应的命令索引

        第五个,SDIO响应寄存器组(SDIO_RESP1~SDIO_RESP4),4个32位寄存器,存放卡响应信息,短响应存于SDIO_RESP1长响应依次存放于(SDIO_RESP1~SDIO_RESP4)

响应类型和SDIO_RESPx寄存器

        第七个,SDIO命令寄存器(SDIO_CMD)的低6位[5:0]表示命令索引,位[7:6]设置等待响应位,指示CPSM(命令通道状态机)的等待需求。

SDIO命令寄存器(SDIO_CMD)

 

 第八个,SDIO数据定时器寄存器(SDIO_DTIMER)存储数据超时时间,以SDIO_CK为周期。

        计数器在DPSM进入Wait_R或繁忙状态时递减,若减为0则设置超时标志。

        DPSM(数据通道状态机)负责监控和控制SDIO接口上的数据传输,确保数据能够按照预定的协议和时序正确地进行传输。

        注意:数据传输前必须先写入SDIO_DTIMER和数据长度寄存器(SDIO_DLEN)

        第九个,SDIO数据长度寄存器(SDIO_DLEN)的低 25位用于设置传输的数据字节长度,对于块数据传输,其值需为数据块长度(由SDIO_DCTRL设置)的倍数

        第十个,SDIO数据控制寄存器(SDIO_DCTRL)用于控制DPSM,包括数据传输使能、方向、模式、DMA使能及数据块长度等设置,需根据实际情况配置实现正常数据收发。

SDIO_DCTRL寄存器

 

        第十一个,SDIO接口中的状态寄存器(SDIO_STA)清除中断寄存器(SDIO_ICR)中断屏蔽寄存器(SDIO_MASK)位定义相同,但功能各异。

        SDIO_STA反映状态,SDIO_ICR用于清除中断,SDIO_MASK则控制中断屏蔽。

SDIO_STA,SDIO状态寄存器

 

        第十二个,SDIO的数据FIFO寄存器(SDIO_FIFO)包括接收和发送FIFO,各由16个32位寄存器组成,共32个地址。

        CPU可通过FIFO进行多数据读写,

        读SD卡数据需读SDIO_FIFO,

        写数据则写SDIO_FIFO。

        每次读写最多处理8个字(32字节),且操作必须以4字节对齐,否则出错。

1.1.5 SD 卡初始化流程

  1. 电源开启:流程始于SD卡电源开启
  2. CMD0与CMD8检测:通过CMD0和CMD8命令检测SD卡是否响应
  3. 电压与版本检查
    • 若无响应,检查是否为Ver2.00或更高版本的标准容量SD卡,确认电压范围是否兼容
    • 若电压不匹配或版本不兼容,则标记为不可用卡。
  4. 高容量支持(HCS)设置:对于兼容电压范围的卡片,检查是否支持高容量,并相应设置HCS值。
  5. ACMD41响应判断
    • 使用ACMD41命令,分别设置HCS=0和HCS=1(或未设置),判断卡片是否准备好
    • 根据响应码(OSS),区分卡片为标准容量、高容量或不可用的SD卡。
  6. 不可读卡处理:对于非SD存储卡,标记为“非SD存储卡”,并提示可能为MMC卡。

        各类卡(SDHC、SDSC、SD1.x、MMC)上电后,

        首先需设置SDIO_POWER[1:0]=11,

        然后发送CMD0进行软复位

        接着发送CMD8命令以区分SD2.0卡与其他不支持该命令的卡(MMC、SD1.x)。

CMD8命令格式

        发送CMD8时,可通过参数设置VHS位,向SD卡传达主机的供电状态。 

VHS位定义

        使用参数0x1AA发送CMD8,告知SD卡主机供电为2.7~3.6V。

        若SD卡支持CMD8且电压范围兼容,则通过R7响应返回相同参数;否则,不响应。

        发送CMD8后,需先发送CMD55,再发送ACMD41,以确认卡的操作电压范围,并通过HCS位告知SD卡主机是否支持高容量卡(SDHC)。

CMD41命令格式

        ACMD41 得到的响应(R3)包含 SD 卡 OCR 寄存器内容,

OCR寄存器定义

        对于支持CMD8的卡,主机通过ACMD41的HCS位告知SD卡是否支持SDHC;不支持CMD8的卡,HCS设0。

        SD卡接收ACMD41后返回OCR内容,主机据此判断卡类型及上电状态。

        MMC卡不支持ACMD41和CMD55,需在CMD0后发送CMD1进行初始化。

        最后,发送CMD2和CMD3获取SD卡的CID和RCA,完成类型区分及初始化。

        RCA,SD卡的相对地址

        CMD2,用于获得 CID 寄存器的数据

卡CID寄存器位定义

        SD卡收到CMD2后,返回R2长响应含CID信息,存于SDIO_RESP1~4寄存器。

        CMD3用于设置RCA,SD卡自动返回,MMC卡需主机主动设置。

        获得RCA后,发CMD9带RCA参数获CSD信息,含容量、扇区大小等。

        最后,发CMD7选中SD卡开始读写。其他命令请参考《SD卡2.0协议.pdf》。

1.2 硬件设计

        可以看到,SD卡有 4根数据线,一根时钟线,一根指令线。

        分别连接到 STM32的复用引脚上。

1.2 FATFS文件系统

        FATFS是免费开源的FAT文件系统模块,专为小型嵌入式系统设计。

        它用标准C编写,具有良好的硬件平台独立性,易移植至多种单片机。

        支持FAT12/16/32,多存储媒介,独立缓冲区,优化8/16位单片机。

        特点包括 Windows兼容、平台无关、代码高效、配置多样、支持多卷、长文件名、多种代码页、RTOS、多种扇区大小及只读API等。

FATFS层次结构图

 

        应用层用户无需了解 FATFS内部和 FAT协议,只需调用接口函数如 f_open, f_read, f_write, f_close等即可操作文件

        中间层 FATFS模块实现FAT读写协议,提供 ff.c和 ff.h,用户通常无需修改。

        底层接口需用户编写移植代码,包括存储媒介读写和实时时钟。

        目前FATFS最新版本为R0.10b,包含doc和src文件夹。

        与平台无关的文件有 ffconf.h、ff.h、ff.c等,而与平台相关的代码需用户提供,主要是diskio.c。

        移植FATFS时,通常只需修改ffconf.h和diskio.c。

        ffconf.h包含所有配置项,如_FS_TINY、_FS_READONLY等,可根据需求进行配置。

        移植步骤分为三步:

        ①在 integer.h定义数据类型

        ②通过 ffconf.h配置功能

        ③编写 diskio.c中的底层驱动函数

        使用 MDK5编译器时,若数据类型与 integer.h一致,则无需改动。

1.3 底层磁盘I/O模块函数

        底层磁盘 I/O模块不属于 FATFS,必须由用户提供,以实现物理磁盘的读写和时间获取。

        底层磁盘 I/O模块涉及的函数一般有 6个,在 diskio.c里面。

1.3.1 dick_initialize()

        首先是初始化磁盘驱动器disk_initialize() 函数,

// 函数声明:初始化磁盘驱动器  
DSTATUS disk_initialize(BYTE Drive) {  
    // 备注:此处应包含具体的初始化代码,如设备选择、参数配置等  
    // 由于具体实现与硬件相关,此处仅给出函数框架  
  
    // 假设初始化成功,清零 STA_NOINIT 标志(示例值,实际应根据硬件状态确定)  
    DSTATUS status = 0; // 假设返回值为 0 表示成功,具体值依据 DSTATUS 定义  
  
    // 备注:在实际应用中,需要根据硬件的响应来设置 status 的值  
    // 例如,如果初始化失败,可能需要设置相应的错误标志位  
  
    return status; // 返回磁盘状态值  
}  
// 示例用法:初始化驱动器 0  
BYTE drive = 0; // 指定逻辑驱动器号  
DSTATUS status = disk_initialize(drive); // 调用初始化函数  
  
// 检查初始化是否成功(假设 STA_NOINIT 为 0x01,实际值应依据 DSTATUS 定义)  
if ((status & STA_NOINIT) == 0) {  
    // 初始化成功,可以进行后续操作  
} else {  
    // 初始化失败,处理错误情况  
}

1.3.2 disk_status() 

        第二个函数是返回磁盘驱动器的状态disk_status() 函数。

// 函数声明:获取指定逻辑驱动器的磁盘状态  
DSTATUS disk_status(BYTE Drive) {  
    // 备注:此处应包含与硬件交互的代码以获取磁盘状态  
    // 由于实现与具体硬件相关,以下仅给出示例返回值  
  
    // 假设磁盘已初始化且未写保护,返回值为 0(具体值依据实际情况和 DSTATUS 定义)  
    DSTATUS status = 0; // 示例返回值,表示无错误状态  
  
    // 备注:在实际应用中,需要根据硬件的响应来设置 status 的值  
    // 例如,如果磁盘未初始化,可能需要设置 STA_NOINIT 标志位  
    // 如果磁盘被写保护,可能需要设置 STA_PROTECTED 标志位  
  
    return status; // 返回磁盘状态值  
}  
// 示例代码:获取驱动器 0 的状态  
BYTE drive = 0; // 指定逻辑驱动器号  
DSTATUS status = disk_status(drive); // 调用函数获取状态  
  
// 检查磁盘状态(假设 STA_NOINIT 和 STA_PROTECTED 的值分别为 0x01 和 0x02,实际值应依据 DSTATUS 定义)  
if (status & STA_NOINIT) {  
    // 磁盘未初始化,处理相应情况  
}  
if (status & STA_PROTECTED) {  
    // 磁盘被写保护,处理相应情况  
}  
// 其他状态检查和处理...

1.3.3 disk_read() 

        第三个函数是 disk_read() 函数,用于从磁盘驱动器上读取扇区

/*读取磁盘扇区*/
DRESULT disk_read(BYTE Drive, //指定逻辑驱动器号(0-9)  
                BYTE* Buffer, //指向存储读取数据的字节数组指针,需足够大以存储所有扇区数据  
          DWORD SectorNumber, //起始扇区的逻辑块(LBA)地址 
            BYTE SectorCount) //要读取的扇区数(1-128)  
{  
    // 检查参数有效性  
    if (Drive < 0 || Drive > 9 || Buffer == NULL || SectorNumber == 0 || SectorCount < 1 || SectorCount > 128) {  
        return RES_PARERR; // 传入非法参数  
    }  
  
    // 检查磁盘驱动器是否已初始化  
    if (!disk_is_initialized(Drive)) {  
        return RES_NOTRDY; // 磁盘驱动器未初始化  
    }  
  
    // 执行读取操作(此处为伪代码,需根据具体硬件实现)  
    // 假设 disk_hardware_read 为硬件相关的读取函数  
    // 注意:需确保Buffer地址对齐或处理非对齐情况  
    if (!disk_hardware_read(Drive, Buffer, SectorNumber, SectorCount)) {  
        return RES_ERROR; // 读操作期间发生不可恢复的错误  
    }  
  
    return RES_OK; // 函数执行成功  
}

1.3.4 disk_write() 

        第四个函数是 disk_write() 函数,用于向磁盘写一个或多个扇区

/*向磁盘写入*/
DRESULT disk_write(BYTE Drive, //Drive: 指定逻辑驱动器编号(0-9)  
           const BYTE* Buffer, //指向要写入字节数组的指针,需确保数据对齐或处理非对齐情况  
           DWORD SectorNumber, //起始扇区的逻辑块(LBA)地址  
             BYTE SectorCount) //要写入的扇区数(1-128)  
 {  
    // 检查参数有效性  
    if (Drive < 0 || Drive > 9 || Buffer == NULL || SectorNumber == 0 || SectorCount < 1 || SectorCount > 128) {  
        return RES_PARERR; // 传入非法参数  
    }  
  
    // 检查磁盘驱动器是否已初始化  
    if (!disk_is_initialized(Drive)) {  
        return RES_NOTRDY; // 磁盘驱动器未初始化  
    }  
  
    // 检查写保护状态(假设 disk_is_write_protected 为检查写保护的函数)  
    if (disk_is_write_protected(Drive)) {  
        return RES_WRPRT; // 媒体被写保护  
    }  
  
    // 执行写入操作(此处为伪代码,需根据具体硬件实现)  
    // 假设 disk_hardware_write 为硬件相关的写入函数  
    // 注意:需确保Buffer地址对齐或处理非对齐情况  
    if (!disk_hardware_write(Drive, Buffer, SectorNumber, SectorCount)) {  
        return RES_ERROR; // 写入期间产生错误且无法恢复  
    }  
  
    return RES_OK; // 函数执行成功  
}
// 返回值:  
// - RES_OK(0): 函数执行成功  
// - RES_ERROR: 写入期间产生错误且无法恢复  
// - RES_WRPRT: 媒体被写保护  
// - RES_PARERR: 传入非法参数  
// - RES_NOTRDY: 磁盘驱动器未初始化  
// 所在文件:ff.c  
// 注意事项:在只读配置中不需要此函数  

1.3.5 disk_ioctl() 

        第五个函数是 disk_ioctl() 函数,用于控制设备指定特性和除了读写外的杂项功能

#include "ff.h" // 假设包含FatFs相关的头文件  

/*控制设备特性和功能*/
DRESULT disk_ioctl(BYTE Drive, //指定逻辑驱动器号(0-9)  
                 BYTE Command, //指定命令代码
                 void* Buffer) //指向参数缓冲区的指针,根据命令代码确定,不使用时指定为 NULL  
{  
    // 检查参数有效性  
    if (Drive < 0 || Drive > 9 || Buffer == NULL) {  
        // Drive参数超出范围或Buffer为NULL  
        return RES_PARERR;  
    }  
  
    // 检查磁盘驱动器是否已初始化  
    if (!disk_is_initialized(Drive)) {  
        return RES_NOTRDY; // 磁盘驱动器未初始化  
    }  
  
    // 根据命令代码执行相应操作  
    switch (Command) {  
        case CTRL_SYNC:  
            // 确保磁盘驱动器完成写处理,刷新写回缓存的扇区  
            // 假设 disk_sync 为实际执行同步操作的函数  
            if (!disk_sync(Drive)) {  
                return RES_ERROR; // 同步期间发生错误  
            }  
            break;  
  
        case GET_SECTOR_SIZE:  
            // 返回磁盘扇区大小,假设每个扇区大小为512字节  
            // 实际应用中应根据具体磁盘特性返回正确值  
            *(WORD*)Buffer = 512; // 假设扇区大小为512字节  
            break;  
  
        case GET_SECTOR_COUNT:  
            // 返回可利用的扇区数  
            // 假设 disk_get_sector_count 为获取扇区数的函数  
            *(DWORD*)Buffer = disk_get_sector_count(Drive);  
            break;  
  
        case GET_BLOCK_SIZE:  
            // 获取擦除块大小  
            // 假设每个擦除块大小为4096字节(实际应用中应根据具体磁盘特性返回正确值)  
            *(WORD*)Buffer = 4096; // 假设擦除块大小为4096字节  
            break;  
  
        case CTRL_ERASE_SECTOR:  
            // 强制擦除指定扇区  
            // 假设 disk_erase_sector 为执行擦除操作的函数  
            if (!disk_erase_sector(Drive, *(DWORD*)Buffer)) {  
                return RES_ERROR; // 擦除期间发生错误  
            }  
            break;  
  
        default:  
            // 未知命令代码  
            return RES_PARERR; // 传入非法参数  
    }  
  
    return RES_OK; // 函数执行成功  
}  
// 返回值:  
// - RES_OK(0): 函数执行成功  
// - RES_ERROR: 操作期间发生错误且无法恢复  
// - RES_PARERR: 传入非法参数  
// - RES_NOTRDY: 磁盘驱动器未初始化  
// 所在文件:ff.c  

1.3.6 get_fattime() 

        第六个函数是 get_fattime() 函数,用于获取当前时间

// 函数名称: get_fattime  
// 功能描述: 获取当前时间,并以特定格式封装返回  
// 所在文件: ff.c  
// 注意事项: 必须返回一个合法的时间值,不能为0,除非文件确实没有合法时间  
  
#include "ff.h" // 假设包含FatFs相关的头文件  
#include <time.h> // 用于获取系统时间的头文件  
  
DWORD get_fattime() {  
    // 假设系统支持实时时钟,使用time函数获取当前时间  
    time_t current_time = time(NULL);  
    struct tm* time_info = localtime(&current_time);  
  
    // 提取时间信息并封装成DWORD返回  
    // 注意:这里的秒是0~29,因为FAT文件系统的时间精度为2秒  
    DWORD fattime = 0;  
    fattime |= ((time_info->tm_year - 80) << 25); // 年份从1980年开始计算  
    fattime |= ((time_info->tm_mon + 1) << 21);   // 月份1~12  
    fattime |= (time_info->tm_mday << 16);        // 日期1~31  
    fattime |= (time_info->tm_hour << 11);        // 小时0~23  
    fattime |= (time_info->tm_min << 5);          // 分钟0~59  
    fattime |= (time_info->tm_sec / 2);           // 秒0~29,FAT时间精度为2秒  
  
    return fattime; // 返回封装好的时间值  
}  
  
// 备注:  
// 1. 如果系统不支持实时时钟,需要实现一个替代方案来生成合法的时间值。  
// 2. 在只读配置下,此函数不会被调用,但仍然需要提供一个有效的实现。  
// 3. 函数返回的时间值必须符合FAT文件系统的时间格式要求。

1.4 硬件设计

 可以看到,SD卡有 4根数据线,一根时钟线,一根指令线。

        分别连接到 STM32的复用引脚上。

1.5 软件设计

本章实验功能简介:

        开机的时候先初始化 SD 卡,

        初始化成功之后,注册两个工作区(一个给 SD 卡用,一个给 SPI FLASH 用),

        然后获取 SD 卡的容量和剩余空间,并显示在 LCD模块上,最后等待 USMART 输入指令进行各项测试。

        在工程目录下新建了FATFS文件夹,解压了FATFS R0.10b程序包,并新建了exfuns文件夹存放扩展代码。

 

1. 磁盘初始化与配置

  • diskio.c中实现了磁盘初始化、状态获取、扇区读写和控制函数。
  • 定义了SD卡和SPI FLASH的卷标和扇区大小。
  • 初始化了SPI FLASH的前12M字节供FATFS使用。
#define SD_CARD 0          // 定义SD卡设备号  
#define EX_FLASH 1         // 定义外部闪存设备号  
#define FLASH_SECTOR_SIZE 512  // 定义闪存扇区大小  
u16 FLASH_SECTOR_COUNT=2048*12;  // 定义闪存扇区数量  
  
// 磁盘初始化函数  
DSTATUS disk_initialize(BYTE pdrv) {    
    u8 res=0;  // 初始化结果变量  
    switch(pdrv) {    
        case SD_CARD:    
            res=SD_Init();  // 初始化SD卡  
            break;    
        case EX_FLASH:    
            W25QXX_Init();  // 初始化外部闪存  
            FLASH_SECTOR_COUNT=2048*12;  // 设置闪存扇区数量  
            break;    
        default:    
            res=1;  // 未知设备,设置错误状态  
    }    
    return res ? STA_NOINIT : 0;  // 返回初始化状态  
}    
  
// 磁盘读取函数  
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) {    
    u8 res=0;  // 初始化结果变量  
    if (!count) return RES_PARERR;  // 如果读取扇区数为0,返回参数错误  
    switch(pdrv) {    
        case SD_CARD:    
            res=SD_ReadDisk(buff, sector, count);  // 从SD卡读取数据  
            break;    
        case EX_FLASH:    
            for(;count>0;count--) {  // 循环读取每个扇区  
                W25QXX_Read(buff, sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);  // 从外部闪存读取数据  
                sector++;  // 增加扇区号  
                buff+=FLASH_SECTOR_SIZE;  // 移动缓冲区指针  
            }    
            res=0;  // 设置读取成功状态  
            break;    
        default:    
            res=1;  // 未知设备,设置错误状态  
    }    
    return res ? RES_ERROR : RES_OK;  // 返回读取结果状态  
}

// 类似地实现了 disk_write 和 disk_ioctl 函数

2. FATFS配置与内存管理

  • ffconf.h中根据需求修改了相关配置。
  • 实现了ff_memallocff_memfree函数用于动态内存分配。

3. 扩展功能与测试

  • exfuns文件夹中编写了扩展代码,包括全局变量定义和磁盘容量获取函数。
  • 编写了fattester.c用于测试FATFS函数,并通过USMART调用。

4. 主函数流程

  • 初始化系统、延时、串口、LED、LCD、按键和SPI FLASH。
  • 检测SD卡并初始化,若失败则提示错误。
  • 挂载SD卡和SPI FLASH,若SPI FLASH文件系统错误则格式化。
  • 显示SD卡总容量和剩余容量。
  • 进入死循环,等待USMART测试。
int main(void) {  
    // 初始化各部分  
    while(SD_Init()) {  
        // 提示SD卡错误  
    }  
    exfuns_init();  
    f_mount(fs[0],"0:",1);  
    if(f_mount(fs[1],"1:",1)==0X0D) {  
        // 格式化SPI FLASH  
    }  
    // 显示SD卡容量信息  
    while(1) {  
        // 等待USMART测试  
    }  
}

5. USMART配置

  • usmart_config.c中添加了FATFS测试函数的名称和描述,以便通过USMART调用。

        本工程通过FATFS文件系统实现了对SD卡和SPI FLASH的管理,并提供了测试功能。

打开串口助手,记得测试指令加换行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大象荒野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值