当前位置: 首页 > news >正文

第三十七章 ESP32S3 SPI_SDCARD 实验

        很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有 U 盘、 FLASH 芯片、SD 卡等。各有优点,综合比较,最适合单片机系统的莫过于 SD 卡,它不仅容量可以做到很大(32GB 以上),支持 SPI/SDIO 驱动,而且有多种体积的尺寸可供选择(标准的 SD 卡尺寸及 Micro SD 卡尺寸等),能满足不同应用的要求。
只需要少数几个 IO 口即可外扩一个高达 32GB 或以上的外部存储器,容量从几十 M 到几十G 选择范围很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。
原子 ESP32-S3 开发板使用的接口是 Micro SD 卡接口, 卡座带自锁功能, SD SPI 主机驱动程序基于 SPI Master Driver 实现。借助 SPI 主控驱动程序, SD 卡及其他 SPI 设备可以共享同一 SPI 总线。 SPI 主机驱动程序将处理来自不同任务的独占访问。 在本章中,将学习如何在原子 ESP32-S3 开发板上实现 Micro SD 卡的读取。本章分为如下几个部分:
本章分为如下几个小节:
37.1 SD 卡简介
37.2 硬件设计
37.3 程序设计
37.4 下载验证

37.1 SD 卡简介
37.1.1 SD 物理结构

        SD 卡的规范由 SD 卡协会明确, 可以访问 https://www.sdcard.org 查阅更多标准。 SD 卡主要有 SD、 Mini SD 和 microSD(原名 TF 卡, 2004 年正式更名为 Micro SD Card, 为方便本文用microSD 表示)三种类型, Mini SD 已经被 microSD 取代,使用得不多,根据最新的 SD 卡规格列出的参数如表 37.1.1.1 所示:

表 37.1.1.1 SD 卡的主要规格参数

        上述表格的“脚位数”,对应于实卡上的“金手指”数,不同类型的卡的触点数量不同, 访问的速度也不相同。 SD 卡允许了不同的接口来访问它的内部存储单元。 最常见的是 SDIO 模式和 SPI 模式,根据这两种接口模式, 我们也列出 SD 卡引脚对应于这两种不同的电路模式的引脚功能定义,如表 37.1.1.2 所示。

表 37.1.1.2 SD 卡引脚编号(注: S:电源 I:输入 O: 推挽输出 PP: 推挽)

        对比着来看一下 microSD 引脚, 可见只比 SD 卡少了一个电源引脚 VSS2, 其它的引脚功能类似。

表 37.1.1.3 microSD 卡引脚编号(注: S:电源 I:输入 O: 推挽输出 PP: 推挽)

        SD 卡和 Micro SD 只有引脚和形状大小不同, 内部结构类似,操作时序完全相同, 可以使用完全相同的代码驱动, 下面以 9’Pin SD 卡的内部结构为为例,展示 SD 卡的存储结构。

图 37.1.1.1 SD 卡内部物理结构(RCA 寄存器在 SPI 模式下不可访问)

        SD 卡有自己的寄存器,但它不能直接进行读写操作,需要通过命令来控制, SDIO 协议定义了一些命令用于实现某一特定功能, SD 卡根据收到的命令要求对内部寄存器进行修改。表37.1.1.4 中描述的 SD 卡的寄存器是我们和 SD 卡进行数据通讯的主要通道,如下:

名称位宽描述
CID128卡标识(Card identification):每个卡都是唯一的
RCA16相对地址(Relative card address):卡的本地系统地址,初始
化时,动态地由卡建议,经主机核准。
DSR16驱动级寄存器(Driver Stage Register):配置卡的输出驱动
CSD128卡的特定数据(Card Specific Data):卡的操作条件信息
SCR64SD 配置寄存器(SD Configuration Register):SD 卡特殊特性
信息
OCR32操作条件寄存器(Operation conditions register):卡电源和
状态标识
SSR512SD 状态(SD Status):SD 卡专有特征的信息
CSR32卡状态(Card Status):卡状态信息

表 37.1.1.4 SD 卡寄存器信息

        关于 SD 卡的更多信息和硬件设计规范可以参考 SD 卡协议《Physical Layer Simplified Specification Version 2.00》 的相关章节。

37.1.2 命令和响应

        一个完整的 SD卡操作过程是:主机(单片机等)发起“命令”, SD卡根据命令的内容决定是否发送响应信息及数据等,如果是数据读/写操作,主机还需要发送停止读/写数据的命令来结束本次操作,这意味着主机发起命令指令后, SD 卡可以没有响应、数据等过程,这取决于命令的含义。 这一过程如图 37.1.2.1 所示。

图 37.1.2.1 SD 卡命令格式

        SD 卡有多种命令和响应,它们的格式定义及含义在《SD 卡协议 V2.0》 的第三和第四章有详细介绍,发送命令时主机只能通过 CMD 引脚发送给 SD 卡,串行逐位发送时先发送最高位(MSB),然后是次高位这样类推……接下来,我们看看 SD 卡的命令格式,如表 37.1.2.1 所示:

字节字节1字节2--5字节6
474645:4039:87:10
描述01command命令参数CRC71

表 37.1.2.1 SD 卡控制命令格式

        SD 卡的命令固定为 48 位,由 6 个字节组成,字节 1 的最高 2 位固定为 01,低 6 位为命令号(比如 CMD16,为 10000B即 16进制的 0X10,完整的 CMD16,第一个字节为 01010000,即0X10+0X40)。字节 2~5 为命令参数,有些命令是没有参数的。字节 6 的高七位为 CRC 值,最低位恒定为 1。
SD 卡的命令总共有 12 类,分为 Class0~Class11,本章,仅介绍几个比较重要的命令,如表 37.1.2.2 所示:

命令参数响应描述
CMD0(0X00)NONER1复位SD卡
CMD8(0X08)VHS+Checkpatte
rn
R7发送接口状态命令
CMD9(0X09)NONER1读取卡特定数据寄存器
CMD10(0X0A)NONER1读取卡标志数据寄存器
CMD16(0X10)块大小R1设置块大小(字节数)
CMD17(0X11)地址R1读取一个块的数据
CMD24(0X18)地址R1写入一个块的数据
CMD41(0X29)NONER3发送给主机容量支持信息和激活卡
初始化过程
CMD55(0X37)NONER1告诉SD卡,下一个是特定应用命令
CMD58(0X3A)NONER3读取OCR寄存器

表 37.1.2.2 SD 卡部分命令

        上表中,大部分的命令是初始化的时候用的。表中的R1、 R3和R7等是SD卡的应答信号,每个响应也有规定好的格式,如图 37.1.2.2 所示:

图 37.1.2.2 SD 卡命令传输过程

        在规定为有响应的命令下,每发送一个命令, SD 卡都会给出一个应答,以告知主机该命令的执行情况,或者返回主机需要获取的数据,应答可以是 R1~R7, R1 的应答,各位描述如表37.1.2.3 所示:

R1响应描述起始位传输位命令号卡状态CRC7终止位
Bit4746[45:40][39:8][7:1]0
位宽1163271
"0""0"xxx"1"

表 37.1.2.3 R1 响应

        R2~R7 的响应, 暂不介绍, 需要注意的是除了 R2 响应是 128 位外,其它的响应都是 48位,参考 SD 卡 2.0 协议。

37.1.3 卡模式

        SD 卡系统(包括主机和 SD 卡)定义了 SD 卡的工作模式,在每个操作模式下, SD 卡都有几种状态,参考表 37.1.3.1, 状态之间通过命令控制实现卡状态的切换。

表 37.1.3.1 SD 卡状态与操作模式

        对于我们来说两种有效操作模式:卡识别模式和数据传输模式。在系统复位后,主机处于卡识别模式,寻找总线上可用的 SDIO 设备, 对 SD 卡进行数据读写之前需要识别卡的种类:V1.0 标准卡、 V2.0 标准卡、 V2.0 高容量卡或者不被识别卡;同时, SD 卡也处于卡识别模式,直到被主机识别到,即当 SD 卡在卡识别状态接收到 CMD3 (SEND_RCA)命令后, SD 卡就进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。
在卡识别模式下,主机会复位所有处于“卡识别模式”的 SD 卡,确认其工作电压范围,识别SD 卡类型,并且获取 SD 卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中,要求SD 卡工作在识别时钟频率 FOD 的状态下。卡识别模式下 SD 卡状态转换如图 37.1.3.1。
主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送GO_IDLE_STATE(CMD0)让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不会复位。
主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。 SD 卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。 SEND_IF_COND(CMD8)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。CMD8 是 SD 卡标准 V2.0 版本才有的新命令,所以如果主机有接收到响应,可以判断卡为 V2.0或更高版本 SD 卡。
SD_SEND_OP_COND(ACMD41)命令可以识别或拒绝不匹配它的电压范围的卡。 ACMD41
命令的 VDD 电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。对于对CMD8 有响应的卡,把 ACMD41 命令的 HCS 位设置为 1,可以测试卡的容量类型,如果卡响应的 CCS 位为 1 说明为高容量 SD 卡,否则为标准卡。卡在响应 ACMD41 之后进入准备状态,不响应 ACMD41的卡为不可用卡,进入无效状态。 ACMD41是应用特定命令,发送该命令之前必须先发 CMD55。

图 37.1.3.1 卡识别模式状态转换图

        ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送 CID 之后就进入识别状态。之后主机就发送 SEND_RELATIVE_ADDR(CMD3)命令,让卡自己推荐一个相对地址(RCA)并响应命令。这个 RCA 是 16bit 地址,而 CID 是 128bit 地址,使用 RCA简化通信。卡在接收到 CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡 RCA 之后也进入数据传输模式。

37.1.4 数据模式

        在数据模式下我们可以对 SD 卡的存储块进行读写访问操作。 SD 卡上电后默认以一位数据总线访问,可以通过指令设置为宽总线模式,可以同时使有 4 位总线并行读写数据,这样对于支持宽总线模式的接口(如: SDIO 和 QSPI 等) 都能加快数据操作速度。

图 37.1.4.1 1 位数据线传输 8bit 的数据流格式

        SD 卡有两种数据模式, 一种是常规的 8 位宽, 即一次按一字节传输, 另一种是一次按 512字节传输,我们只介绍前面一种。当按 8-bit 连续传输时,每次传输从最低字节开始,每字节从最高位(MSB)开始发送,当使用一条数据线时,只能通过 DAT0进行数据传输, 那它的数据传输结构如图 37.1.4.1 所示。
当使用 4线模式传输 8-bit结构的数据时,数据仍按 MSB 先发送的原则, DAT[3:0]的高位发送高数据位, 低位发送低数据位。 硬件支持的情况下,使用 4 线传输可以提升传输速率。

图 37.1.4.2 4 位数据线传输 8bit 格式的数据流格式

        只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD 时钟频率设置为 FPP,默认最高为 25MHz,频率切换可以通过 CMD4 命令来实现。数据传输模式下, SD 卡状态转换过程见图 37.1.4.3。

图 37.1.4.3 数据传输模式卡状态转换

        CMD7 用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个 RCA 地址目标卡使其进入传输状态才可以进行数据通信。同时通过 CMD7 命令也可以让已经被选择的目标卡返回到待机状态。
数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以通过命令对卡进行数据读写、擦除。 CMD12 可以中断正在进行的数据通信,让卡返回到传输状态。 CMD0 和 CMD15 会中止任何数据编程操作,返回卡识别模式, 注意谨慎使用, 不当操作可能导致卡数据被损坏。
至此, 已经学习了 SD 卡操作的一些知识, 并知道了 SD 卡操作的命令、响应和数据传输等状态,接下来我们来分析实际的硬件接口如何向 SD 卡发送我们需要的数据。

37.1.5 SD 卡初始化流程

(1)SDIO模式下的 SD 卡初始化

        这一节,学习 SD卡的初始化流程,要实现 SDIO 驱动 SD 卡,最重要的步骤就是 SD卡的初始化,只要SD卡初始化完成了,那么剩下的(读写操作) 就简单了,所以这里重点介绍 SD 卡的初始化。从《SD 卡 2.0 协议》(见光盘资料)文档,我们得到 SD 卡初始化流程图如图 37.1.5.1 所示:

图 37.1.5.1 SD 卡初始化流程(Card Initialization and Identification Flow (SD mode))

        从图中,可以看到不管什么卡(这里将卡分为 4 类: SD2.0高容量卡(SDHC,最大32G), SD v2.0 标准容量卡(SDSC,最大 2G), SD1.x 卡和 MMC 卡),首先要执行的是卡上电(需要设置 SDIO_POWER[1:0]=11),上电后发送 CMD0,对卡进行软复位,之后发送CMD8 命令,用于区分 SD 卡 2.0,只有 2.0 及以后的卡才支持 CMD8 命令, MMC 卡和 V1.x 的卡,是不支持该命令的。 CMD8 的格式如表 37.1.5.1 所示:

位序4746[45:40][39:20][19:16][15:8][7:1]0
占用位116204871
命令值‘0’‘1’‘001000’‘00000h’xxx‘1’
描述起始位传输位命令索引保留位电源(VHS)校验CRC7结束位

表 37.1.5.1 CMD8 命令格式

        这里,需要在发送 CMD8 的时候,通过其带的参数我们可以设置 VHS 位,以告诉 SD卡,主机的供电情况, VHS 位定义如表 37.1.5.2 所示:

表 37.1.5.2 VHS 位定义

        这里使用参数 0X1AA,即告诉 SD 卡,主机供电为 2.7~3.6V 之间,如果 SD 卡支持CMD8,且支持该电压范围,则会通过 CMD8 的响应(R7)将参数部分原本返回给主机,如果不支持CMD8,或者不支持这个电压范围,则不响应。
在发送 CMD8 后,发送 ACMD41(注意发送 ACMD41 之前要先发送 CMD55),来进一步确认卡的操作电压范围,并通过 HCS 位来告诉 SD 卡,主机是不是支持高容量卡(SDHC)。ACMD41 的命令格式如表 37.1.5.3 所示:

表 37.1.5.3 ACMD41 命令格式

        ACMD41 得到的响应(R3)包含 SD 卡 OCR 寄存器内容, OCR 寄存器内容定义如表37.1.5.4 所示:

表 37.1.5.4 OCR 寄存器定义

        对于支持 CMD8 指令的卡,主机通过 ACMD41 的参数设置 HCS 位为 1,来告诉 SD 卡主机支 SDHC 卡,如果设置为 0,则表示主机不支持 SDHC 卡, SDHC 卡如果接收到 HCS 为 0,则永远不会反回卡就绪状态。对于不支持 CMD8 的卡, HCS 位设置为 0 即可。
SD 卡在接收到 ACMD41 后,返回 OCR 寄存器内容,如果是 2.0 的卡,主机可以通过判断OCR 的 CCS 位来判断是 SDHC 还是 SDSC;如果是 1.x 的卡,则忽略该位。 OCR 寄存器的最后一个位用于告诉主机 SD 卡是否上电完成,如果上电完成,该位将会被置 1。
对于 MMC 卡, 则不支持 ACMD41,不响应 CMD55,对 MMC 卡,我们只需要在发送CMD0 后,在发送 CMD1(作用同 ACMD41),检查 MMC 卡的 OCR 寄存器,实现 MMC 卡的初始化。

        至此,便实现了对 SD 卡的类型区分,图 37.1.5.1 中,最后发送了 CMD2 和 CMD3 命令,用于获得卡 CID 寄存器数据和卡相对地址(RCA)。
CMD2,用于获得 CID 寄存器的数据, CID 寄存器数据各位定义如表 37.1.5.5 所示:

NameFieldWidthCID-slice
Manufacturer IDMID8[127:120]
OEM/Application IDOID16[119:104]
Product namePNM40[103:64]
Product revisionPRV8[63:56]
Product serial numberPSN32[55:24]
reserved--4[23:20]
Manufacturing dateMDT12[19:8]
CRC7 checksumCRC7[7:1]
not used, always 1-1[0:0]

表 37.1.5.5 卡 CID 寄存器位定义

        SD 卡在收到 CMD2 后,将返回 R2 长响应(136 位),其中包含 128 位有效数据(CID 寄存器内容),存放在 SDIO_RESP1~4 等 4 个寄存器里面。通过读取这四个寄存器,就可以获得 SD卡的 CID 信息。
CMD3,用于设置卡相对地址(RCA,必须为非 0),对于 SD 卡(非 MMC 卡),在收到CMD3 后,将返回一个新的 RCA 给主机,方便主机寻址。 RCA 的存在允许一个 SDIO 接口挂多个 SD 卡,通过 RCA来区分主机要操作的是哪个卡。而对于 MMC 卡,则不是由 SD卡自动返回RCA,而是主机主动设置 MMC 卡的 RCA,即通过 CMD3 带参数(高 16 位用于 RCA 设置),实现 RCA 设置。同样 MMC 卡也支持一个 SDIO 接口挂多个 MMC 卡,不同于 SD 卡的是所有的RCA 都是由主机主动设置的,而 SD 卡的 RCA 则是 SD 卡发给主机的。
在获得卡 RCA 之后,我们便可以发送 CMD9(带 RCA 参数),获得 SD 卡的 CSD 寄存器内容,从 CSD 寄存器,我们可以得到 SD 卡的容量和扇区大小等十分重要的信息。 CSD 寄存器在这里就不详细介绍,关于 CSD 寄存器的详细介绍,请大家参考《SD 卡 2.0 协议.pdf》。
至此,我们的 SD 卡初始化基本就结束了,最后通过 CMD7 命令,选中我们要操作的 SD卡,即可开始对 SD 卡的读写操作了, SD 卡的其他命令和参数,我们这里就不再介绍了,参考《SD 卡 2.0 协议.pdf》,里面有非常详细的介绍。

(2)SPI 模式下的 SD 卡初始化

        ESP32 的 SDIO 驱动模式和 SPI模式不兼容,二者使用时需要区分开来。《SD 卡 2.0 协议.pdf》中提供了 SD卡的 SPI初始化时序, 我们可以按它建议的流程进行 SD卡的初始化,如图 37.1.5.2所示。

图 37.1.5.2 SD 卡的 SPI 初始化流程(SPI Mode Initialization Flow)

        要使用 SPI 模式驱动 SD 卡,先得让 SD 卡进入 SPI 模式。方法如下:在 SD 卡收到复位命令(CMD0)时, CS 为有效电平(低电平)则 SPI 模式被启用。不过在发送 CMD0 之前,要发送>74 个时钟,这是因为 SD 卡内部有个供电电压上升时间,大概为 64 个 CLK,剩下的 10 个CLK 用于 SD 卡同步,之后才能开始 CMD0 的操作,在卡初始化的时候, CLK 时钟最大不能超
过 400Khz!
接着看 SD 卡的初始化, 由前面 SD 卡的基本介绍,我们知道 SD 卡是先发送数据高位的, SD 卡的典型初始化过程如下:
1)初始化与 SD 卡连接的硬件条件(MCU 的 SPI 配置, IO 口配置);
2) 拉低片选信号,上电延时(>74 个 CLK);
3)复位卡(CMD0),进入 IDLE 状态;
4)发送 CMD8,检查是否支持 2.0 协议;
5)根据不同协议检查 SD 卡(命令包括: CMD55、 ACMD41、 CMD58 和 CMD1 等);
6)取消片选,发多 8 个 CLK,结束初始化
这样就完成了对 SD卡的初始化,注意末尾发送的 8 个 CLK 是提供 SD卡额外的时钟,完成某些操作。通过 SD卡初始化,我们可以知道 SD卡的类型(V1、 V2、 V2HC或者 MMC),在完成了初始化之后,就可以开始读写数据了。
SD 卡单扇区读取数据,这里通过 CMD17 来实现,具体过程如下:
1)发送 CMD17;
2)接收卡响应 R1;
3)接收数据起始令牌 0XFE;
4)接收数据;
5)接收 2 个字节的 CRC,如果不使用 CRC,这两个字节在读取后可以丢掉。
6)禁止片选之后,发多 8 个 CLK;
以上就是一个典型的读取 SD 卡数据过程, SD 卡的写于读数据差不多,写数据通过 CMD24来实现,具体过程如下:
1)发送 CMD24;
2)接收卡响应 R1;
3)发送写数据起始令牌 0XFE;
4)发送数据;
5)发送 2 字节的伪 CRC;
6)禁止片选之后,发多 8 个 CLK;
以上就是一个典型的写 SD 卡过程。关于 SD 卡的介绍,就介绍到这里。

(3)32-S3 的 SD/MMC 概述

        ESP32-S3 存储卡接口控制器提供了一个访问安全数字输入输出卡、 MMC 卡以及 CD-ATA设备的硬件接口,用于连接高级外设总线和外部存储设备。该控制器支持两个外部卡,分别为:卡 0 和卡 1。所有 SD/MMC 模块接口信号都必须通过 GPIO 矩阵传输至 GPIO pad。 SD/MMC 控制器支持两组外设工作,但不支持同时工作,其连接的拓扑结构如下所示:

图 37.1.5.3 ESP32-S3 的 SD/MMC 控制器连接的拓扑结构

        SD/MMC的外部接口信号主要为时钟信号(sdhost_cclk_out_1.eg:card1)、命令信号(sdhost_ccmd_out_1)、数据信号(sdhost_cdata_in_1[7:0]/sdhost_cdata_out_1[7:0]),SD/MMC控制器可通过这些外部接口信号与外部设备通信。其它信号还包括卡中断信号、卡检测信号和写保护信号等。

图 37.1.5.4 P32-S3 的 SD/MMC 控制器外部接口信号

管脚方向描述
sdhost_cclk_out输出向从机发送信号
sdhost_ccmd双向双向命令/响应线
sdhost_cdata双向双向数据读/写线
sdhost_card_detect_n输入卡检测输入线
sdhost_card_write_prt输入卡写保护状态输入

图 37.1.5.6 ESP32-S3 的 SD/MMC 信号描述

37.2 硬件设计
37.2.1 例程功能

        本章实验功能简介:经过一系列初始化之后,通过一个 while 循环以 SD 卡初始化为条件,以检测 SD 卡是否初始化成功,若初始化 SD 卡成功,则会通过串口或者 VSCode 终端输出 SD卡的相关参数,并在 LCD 上显示 SD 卡的总容量以及剩余容量。此时 LED 闪烁,表示程序正在运行。

37.2.2 硬件资源

        1. LED 灯
LED -IO0
2. XL9555
IIC_SDA-IO41
IIC_SCL-IO42
3. SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
4. SD
CS-IO2
SCK-IO12
MOSI-IO11
MISO-IO13

37.2.3 原理图

        本章实验使用 SPI 接口与 SD 卡进行连接,开发板板载了一个 Micro SD 卡座用于连接 SD卡, SD 卡与 ESP32-S3 的连接原理图,如下图所示:

图 37.2.3.1 SD 卡接口与 ESP32-S3 的连接电路图

37.3 程序设计
37.3.1 程序流程图

        本实验的程序流程图:

图 37.3.1.1 SD 卡实验程序流程图

37.3.2 SD 卡函数解析

        ESP-IDF 提供了一套 API 来配置 SD 卡。要使用此功能,需要导入必要的头文件:

#include "driver/sdspi_host.h"
#include "driver/spi_common.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "spi.h"

        接下来,介绍一些常用的 ESP32-S3 中的 SD 卡函数,这些函数的描述及其作用如下:
(1)挂载 SD 卡
该函数用给定的配置,挂载 SD 卡,该函数原型如下所示:

esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,const sdmmc_host_t* host_config_input,const sdspi_device_config_t* slot_config,const esp_vfs_fat_mount_config_t*mount_config,sdmmc_card_t** out_card);

        该函数的形参描述如下表所示:

参数描述
base_path应该注册分区的路径(例如“/sdcard”
host_config_input指向描述SDMMC主机的结构的指针。此结构可以使用
SDSPI_HOST_DEFAULT宏初始化。
slot_config指向具有插槽配置的结构的指针,对于SPI外设,将指针传递到使用sdspi_device_config_DEFAULT初始化的sdspi_device_config_t结构。
mount_config指向具有用于安装 FATFS 的额外参数的结构的指针。
out_card如果不是 NULL,指向卡片信息结构的指针将通过此参数返回。

表 37.3.2.1 esp_vfs_fat_sdspi_mount()函数形参描述

        该函数的返回值描述,如下表所示:

返回值描述
ESP_OK返回: 0,配置成功
ESP_ERR_INVALID_STATE如果已经调用了 esp_vfs_fat_sdmmc_mount
ESP_ERR_NO_MEM如果无法分配内存
ESP_FAIL如果分区无法安装,则来自 SDMMC 或 SPI 驱动程序、
SDMMC 协议或 FATFS 驱动程序的其他错误代码

表 37.3.2.2 函数 esp_vfs_fat_sdspi_mount ()返回值描述

        该函数使用 sdmmc_host_t 类型的结构体变量传入,该结构体的定义如下所示:

结构体成员变量可选参数
sdmmc_host_tflags定义主机属性的标志: SPI 协议且可调用
deinit 函数,有如下值可以配置:
SDMMC_HOST_FLAG_1BIT
SDMMC_HOST_FLAG_4BIT
SDMMC_HOST_FLAG_8BIT
SDMMC_HOST_FLAG_SPI
SDMMC_HOST_FLAG_DDR
SDMMC_HOST_FLAG_DEINIT_ARG
slot使用 SPI2 端口 ,有如下值可以配置:
SPI1_HOST
SPI2_HOST
max_freq_khz主机支持的最大频率: 20000
io_voltage控制器使用的 I/O 电压
init用于初始化驱动程序的主机函数
set_bus_width设置总线宽度的主机功能 ,例程设置为NULL
get_bus_width取总线宽度的主机函数,例程设置为NULL
set_bus_ddr_mode设置 DDR 模式的主机功能,例程设置为NULL
set_card_clk设置板卡时钟频率的主机函数
do_transaction执行事务的主机函数
deinit_p用于取消初始化驱动程序的主机函数
io_int_enable启用 SDIO 中断线的主机功能
io_int_wait等待 SDIO 中断线路激活的主机功能
command_timeout_ms超时,默认为 0

        完成上述结构体参数配置之后,可以将结构传递给 esp_vfs_fat_sdspi_mount () 函数,用以实例化 SD 卡,挂载文件系统。

(2)取消挂载 SD 卡
该函数用于取消挂载 SD 卡,该函数原型如下所示:

esp_err_t esp_vfs_fat_sdcard_unmount(const char* base_path, sdmmc_card_t *card);

        该函数的形参描述如下表所示:

参数描述
base_path应该注册分区的路径(例如“/sdcard”)
cardSD / MMC 卡结构

表 37.3.2.4 esp_vfs_fat_sdcard_unmount ()函数形参描述

        该函数的返回值描述,如下表所示:

返回值描述
ESP_OK返回: 0,配置成功
ESP_ERR_INVALID_ARG如果 card 参数未注册
ESP_ERR_INVALID_STATE如果尚未调用 esp_vfs_fat_sdmmc_mount

表 37.3.2.5 函数 esp_vfs_fat_sdcard_unmount ()返回值描述

37.3.3 SD 卡驱动解析

        在 IDF 版的 StandardExampleIDF(v5.3.x)\25_sd在 26_sd \components\BSP路径下新增了一个 SPI_SD 文件夹,用于存放 spi_sd.c、 spi_sd.h 这两个文件。其中, spi_sd.h 文件负责声明SD相关的函数和变量,而 spi_sdcard.c 文件则实现了 SDIO 的驱动代码。下面,我们将详细解析这两个文件的实现内容。
(1)spi_sd.h 文件

/* 引脚定义 */
#define SD_NUM_CS       GPIO_NUM_2
#define MOUNT_POINT     "/0:"/* 函数声明 */
esp_err_t sd_spi_init(void);    /* SD卡初始化 */
void sd_get_fatfs_usage(size_t *out_total_bytes, size_t *out_free_bytes);

(2) spi_sd.c 文件
sd_pi_init 的设计就比较简单了,我们只需要填充 SPI 结构体的控制句柄, 然后添加 SPI 总
线设备并配置文件系统挂载即可, 根据外设的情况,设置 SPI 的使用端口为 SPI2 端口。sd_get_fatfs_usage()函数用于获取 SD 卡相关信息,比如容量大小,以及剩余容量,如下所示:

sdmmc_card_t *card;                                                 /* SD / MMC卡结构 */
const char mount_point[] = MOUNT_POINT;                             /* 挂载点/根目录 */
esp_err_t ret = ESP_OK;
esp_err_t mount_ret = ESP_FAIL;/*** @brief       SD卡初始化* @param       无* @retval      esp_err_t*/
esp_err_t sd_spi_init(void)
{   ret = ESP_OK;if (MY_SD_Handle != NULL)                                       /* 再一次挂载或者初始化SD卡 */{if (mount_ret == ESP_OK){esp_vfs_fat_sdcard_unmount(mount_point, card);          /* 取消挂载 */mount_ret = ESP_FAIL;}}else if (MY_SD_Handle == NULL)                                  /* 未初始化SPI驱动 */{my_spi_init();                                              /* 初始化SPI驱动 */}/* 文件系统挂载配置 */esp_vfs_fat_sdmmc_mount_config_t mount_config = {.format_if_mount_failed = false,                            /* 如果挂载失败:true会重新分区和格式化/false不会重新分区和格式化 */.max_files = 5,                                             /* 打开文件最大数量 */.allocation_unit_size = 4 * 1024 * sizeof(uint8_t)          /* 硬盘分区簇的大小 */};/* SD卡参数配置 */sdmmc_host_t host = SDSPI_HOST_DEFAULT();/* SD卡引脚配置 */sdspi_device_config_t slot_config = {0};slot_config.host_id   = host.slot;slot_config.gpio_cs   = SD_NUM_CS;slot_config.gpio_cd   = GPIO_NUM_NC;slot_config.gpio_wp   = GPIO_NUM_NC;slot_config.gpio_int  = GPIO_NUM_NC;mount_ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);      /* 挂载文件系统 */ret |= mount_ret;vTaskDelay(pdMS_TO_TICKS(10));return ret;
}/*** @brief       获取SD卡相关信息* @param       out_total_bytes:总大小* @param       out_free_bytes:剩余大小* @retval      无*/
void sd_get_fatfs_usage(size_t *out_total_bytes, size_t *out_free_bytes)
{FATFS *fs;DWORD free_clusters; // 使用与f_getfree参数匹配的类型// 1. 调用f_getfree获取文件系统信息FRESULT res = f_getfree("0:", &free_clusters, &fs);assert(res == FR_OK);// 2. 计算总簇数和每簇扇区数DWORD total_clusters = fs->n_fatent - 2; // 总数据簇数DWORD sectors_per_cluster = fs->csize;   // 每簇扇区数// 3. 计算总扇区数和空闲扇区数DWORD total_sectors = total_clusters * sectors_per_cluster;DWORD free_sectors = free_clusters * sectors_per_cluster;// 4. CORRECT: 正确计算总字节数和空闲字节数// 注意:fs->ssize是每个扇区的大小(通常是512字节)uint64_t total_bytes = (uint64_t)total_sectors * fs->ssize; // 防止溢出uint64_t free_bytes = (uint64_t)free_sectors * fs->ssize;size_t total_bytes_kb = total_bytes/1024;size_t free_bytes_kb = free_bytes/1024;/* 将结果通过指针参数返回给调用者 */if (out_total_bytes != NULL) {*out_total_bytes = total_bytes_kb; // 返回总字节数}if (out_free_bytes != NULL) {*out_free_bytes = free_bytes_kb; // 返回空闲字节数}
}

        几点说明:

  • 调用 f_getfree​​:

       "0:":指定要查询的驱动器号;
 (DWORD *)&free_clusters:获取​​可用簇的数量​​。DWORD是 FatFS 中定义的无符号长整型,与 size_t在嵌入式环境下通常都是 4 字节,但强制转换是良好的做法,确保类型匹配;
 &fs:这个参数是 ​​输出参数​​。函数会在内部查找 "0:"对应的 FATFS工作区,并将其地址填充到 fs指针中。这样,后续代码就可以通过 fs访问文件系统的元数据。

  • 计算总扇区数和空闲扇区数​​:

        fs->n_fatent:文件分配表(FAT)中的总表项数,基本上等于文件系统中的​​总簇数 + 2​​。因为 FAT 的前两个表项是保留的;
fs->csize:每个簇包含的​​扇区数​​(sectors per cluster)。这是一个关键参数。所以,(fs->n_fatent - 2)得到​​总簇数​​。

37.3.4 CMakeLists.txt 文件

        打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:

set(src_dirsLEDMYIICXL9555MYSPISPILCDSPI_SD)set(include_dirsLEDMYIICXL9555MYSPISPILCDSPI_SD)set(requiresdriveresp_lcdfatfs)idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
37.3.5 实验应用代码

        打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。

extern sdmmc_card_t *card;  /* SD / MMC卡结构 *//*** @brief       程序入口* @param       无* @retval      无*/
void app_main(void)
{esp_err_t ret;size_t bytes_total, bytes_free;                     /* SD卡的总空间与剩余空间 */ret = nvs_flash_init();     /* 初始化NVS */if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ESP_ERROR_CHECK(nvs_flash_init());}led_init();                 /* LED初始化 */my_spi_init();              /* SPI初始化 */myiic_init();               /* MYIIC初始化 */xl9555_init();              /* XL9555初始化 */spilcd_init();              /* SPILCD初始化 */spilcd_show_string(30, 50, 200, 16, 16, "ESP32-S3", RED);spilcd_show_string(30, 70, 200, 16, 16, "SD TEST", RED);spilcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);while (sd_spi_init())       /* 检测不到SD卡 */{spilcd_show_string(30, 110, 200, 16, 16, "SD Card Error!", RED);vTaskDelay(500);spilcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);vTaskDelay(500);}spilcd_show_string(30, 110, 200, 16, 16, "SD Card OK!", RED);spilcd_show_string(30, 130, 200, 16, 16, "Total:      MB", RED);spilcd_show_string(30, 150, 200, 16, 16, "Free :      MB", RED);sd_get_fatfs_usage(&bytes_total, &bytes_free);spilcd_show_num(80, 130,(int)bytes_total / 1024,5,16,BLUE);spilcd_show_num(80, 150,(int)bytes_free / 1024,5,16,BLUE);while(1){LED0_TOGGLE();vTaskDelay(pdMS_TO_TICKS(500));}
}

        可以看到,本实验的应用代码中,通过初始化 SD 卡判断与 SD 卡的连接是否有误, SD 卡初始化成功后便通过函数 sd_get_fatfs_usage()获取容量等信息,同时也在 LCD 上显示了 SD的容量信息。

37.4 下载验证

        本次实验使用的是SanDisk Micro SD 16G,程序下载到开发板可以看到 SPILCD显示,如下图 37.4.1所示:

图 37.4.1 程序运行效果图

http://www.dtcms.com/a/441766.html

相关文章:

  • 企业营销型网站特点企业信息查询系统官网山东省
  • docker-compose 安装MySQL8.0.39
  • Go语言入门(18)-指针(上)
  • Django ORM - 聚合查询
  • 【STM32项目开源】基于STM32的智能老人拐杖
  • YOLO入门教程(番外):卷积神经网络—汇聚层
  • 网站改版一般需要多久智慧团建学生登录入口
  • Dotnet接入AI通过Response创建一个简单控制台案例
  • 【论文笔记】2025年图像处理顶会论文
  • 用 Maven 配置 Flink 从初始化到可部署的完整实践
  • 做职业规划的网站seo学院
  • 怎么建优惠券网站太原seo排名外包
  • jmeter中java.net.ConnectException: Connection refused: connect
  • “十四五”科技冲锋:迈向科技强国的壮阔征程
  • 使用 Python 进行自然语言处理的完整初学者指南
  • 框架系统的多维赋能——论其对自然语言处理深层语义分析的影响与启示
  • HCIP 和 HCIE到底是报班还是自学好?
  • 网站建设要多少钱国外服装设计网站
  • Spring配置文件XML验证错误全面解决指南:从cvc-elt.1.a到找不到‘beans‘元素声明
  • 做美食视频网站有哪些网架公司招聘施工队伍
  • Qwen3-Coder 实战:从 0 到 1 开发商业级 API 平台,过程开源!
  • 知识点-红帽Linux入门
  • 《C++进阶之C++11》【异常】
  • 电商网站怎么推广福州短视频seo获客
  • Java 高并发多线程 “ThreadLocal” 面试清单(含超通俗生活案例与深度理解)
  • Linux网络部分—网络层
  • 30.渗透-.Kali Linux下载和安装
  • 浪浪山 iOS 奇遇记:给 APP 裹上 Liquid Glass “琉璃罩”(上集)
  • 博主自创项目:专属秘密表白源码(C语言版)(可自定义表白对象)
  • 网站建设的软硬件平台西宁做手机网站的公司