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的管理,并提供了测试功能。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/892154.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaWeb学习(1)

目录 一、什么是JavaWeb 二、静态web和动态web 三、Web服务器&#xff08;Tomcat&#xff09; 四、Http 4.1 是什么 4.2 两个时代 4.3 Http请求 4.4 Http响应 五、Maven 六、Servlet 七、HttpServletResponse 7.1 常见应用 7.1.1 向浏览器输出消息 7.1.2 下载文件 …

为您的人工智能数据提供类似 Git 的版本管理功能

您过去肯定有过版本控制代码。但是&#xff0c;您是否对数据进行了版本控制&#xff1f;您是否曾经想过与不同的团队协作处理大量数据&#xff0c;而无需提交大量数据&#xff1f;想象一下&#xff0c;使用类似 git 的命令来运行类似存储库的生态系统&#xff0c;在该生态系统中…

Unity实现自定义图集(三)

以下内容是根据Unity 2020.1.0f1版本进行编写的   1、实现编辑器模式下进游戏前Pack全部自定义图集 同Unity的图集一样,Unity的编辑器模式会在进游戏前把全部的SpriteAtlas都打一次图集,如图: 我们也实现这样的效果。 首先需要获取全部的图集路径。因为目前使用的是以.…

RISC-V笔记——RVWMO基本体

1. 前言 RISC-V使用的内存模型是RVWMO(RISC-V Weak Memory Ordering)&#xff0c;它是Release Consistency的扩展&#xff0c;因此&#xff0c;RVWMO的基本特性类似于RC模型。 2. RC模型 Release consistency(RC)的提出是基于一个观察&#xff1a;将所有同步操作用FENCE围在一…

全国职业技能大赛——信息安全管理与评估第一阶段BC、FW、WAF题目详细解析过程

💗需要职业技能大赛环境+WP,请联系我!🍬 博主介绍 👨‍🎓 博主介绍:大家好,我是 一个想当文人的黑客 ,很高兴认识大家~ ✨主攻领域:【渗透领域】【应急响应】 【edusrc漏洞挖掘】 【VulnHub靶场复现】【面试分析】 🎉欢迎关注💗一起学习👍一起讨论⭐️一起…

【WPF】中ListBox的ListBox选项的选中状态在弹出MessageBox后失效的解决办法

1.问题描述 1.1 ListBox选项的样式 在WPF中&#xff0c;可以通过定义ListBoxItem的样式来改变ListBox选项的选中状态。这通常涉及到使用ControlTemplate和Trigger来指定当ListBoxItem处于不同状态时&#xff08;如被选中、鼠标悬停等&#xff09;的外观。ListBoxItem设置不同…

TikTok零播放的原因及解决方法

TikTok作为一个月活跃用户数已经超过15亿的社媒平台&#xff0c;巨大的流量不断吸引着用户加入&#xff0c;其中不乏需要推广获客的卖家。在运营推广工作中&#xff0c;视频播放量是重要的评估维度&#xff0c;如果出现零播放的情况&#xff0c;需要卖家找出原因并尽快解决。 一…

『Mysql集群』Mysql高可用集群之主从复制 (一)

Mysql主从复制模式 主从复制有一主一从、主主复制、一主多从、多主一从等多种模式. 我们可以根据它们的优缺点选择适合自身企业情况的主从复制模式进行搭建 . 一主一从 主主复制 (互为主从模式): 实现Mysql多活部署 一主多从: 提高整个集群的读能力 多主一从: 提高整个集群的…

vulnhub靶场之digitalworld.local: MERCY v2

一.环境搭建 1.靶场描述 MERCY is a machine dedicated to Offensive Security for the PWK course, and to a great friend of mine who was there to share my sufferance with me. :-) MERCY is a name-play on some aspects of the PWK course. It is NOT a hint for the …

快速排序-加餐

1.快排性能的关键点分析 决定快排性能的关键点是每次单趟排序后&#xff0c;key对数组的分割&#xff0c;如果每次选的key基本都二分居中&#xff0c;那么快排的递归树就是一棵均匀的满二叉树&#xff0c;性能达到最佳。 但是在实践中虽然不可能每次都是二分居中&#xff0c;…

[CTF夺旗赛] CTFshow Web13-14 详细过程保姆级教程~

前言 ​ CTFShow通常是指网络安全领域中的“Capture The Flag”(夺旗赛)展示工具或平台。这是一种用于分享、学习和展示信息安全竞赛中获取的信息、漏洞利用技巧以及解题思路的在线社区或软件。参与者会在比赛中收集“flag”&#xff0c;通常是隐藏在网络环境中的数据或密码形…

面向对象--继承

文章目录 1. 继承概念及定义&#xff1a;继承的定义&#xff1a;继承关系和访问限定符&#xff1a;继承基类成员访问方式的变化 &#xff08;在派生类中访问方式&#xff09; 2. 基类和派生类对象赋值转换3 .继承中的作用域4. 派生类的默认成员函数5. 继承与友元6. 继承与静态成…

《Python爬虫逆向实战》内存漫游

所谓内存漫游&#xff0c;就是说我们可以在浏览器内存中随意检索任何想要的数据。在JS逆向过程中&#xff0c;最麻烦和最浪费时间的步骤就是跟值。本篇文章介绍内存漫游工具能够帮助我们快速定位某个加密值的生成位置&#xff0c;即可以直接搜索变量的值(value)&#xff0c;而不…

【Linux】Linux进程基础

1.进程介绍与概念 进程的本质是在计算机内存中运⾏的程序&#xff0c;但是这⼀个概念太过于⼴泛 每个应用程序运行于现代操作系统之上时&#xff0c;操作系统会提供一种抽象&#xff0c;好像系统上只有这个程序在运行&#xff0c;所有的硬件资源都被这个程序在使用。这种假象…

jenkins 插件Publish Over SSH (sskey) 同步文件夹

一、安装插件 Publish Over SSH SSH Pipeline Steps 二、添加sshkey 将ssh免密登录的私钥新建到 二、准备目录 源&#xff1a;images 目标&#xff1a;/root/images2 流水线脚本 pipeline {agent anystages {stage(Dest) {steps {script{def remote [:]remote.name tstr…

Go 语言应用开发:从入门到实战

Go 语言应用开发&#xff1a;从入门到实战 引言 Go&#xff08;Golang&#xff09;是由 Google 开发的一种开源编程语言&#xff0c;设计初衷是提高编程效率&#xff0c;尤其是在高并发场景下表现出色。Go 语言以其简洁、易学、高效并发的特性&#xff0c;逐渐成为开发者的首…

【LeetCode每日一题】——1588.所有奇数长度子数组的和

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【题目进阶】八【解题思路】九【时间频度】十【代码实现】十一【提交结果】 一【题目类别】 前缀和 二【题目难度】 简单 三【题目编号】 1588.所有奇数长度子数组的和 …

【fisco学习记录】搭建第一个单群组联盟链

前提&#xff1a;操作系统Windows11&#xff0c;安装wsl&#xff1a;Windows11安装wsl并迁移记录_adduser: please enter a username matching the regu-CSDN博客 一、 安装依赖 安装ubuntu依赖 sudo apt install -y openssl curl 二、创建操作目录, 下载安装脚本 ## 创建操…

一文介绍SQL标准1986~2023的演变

SQL标准1986年制定第一版&#xff0c;到最新的2023版&#xff0c;已经有38年的历史&#xff0c;现在依然是计算机非常活跃的语言&#xff0c;50%的程序员都能掌握SQL&#xff0c;数据分析师也是SQL的主要使用人员之一。 从早期的基本语法&#xff0c;到融合了XML、JSON等复杂数…

Qt- JSONXML

1. JSON概述 JSON&#xff08;JavaScript Object Notation, JS 对象简谱&#xff09;是一种轻量级的数据交换格式。 JSON 采用 key-value 的结构来组织和管理数据。 JSON 支持的数据类型&#xff1a; 数值型、字符串、布尔值、数组、对象等 JSON 来源于 JavaScript JSON应用…