本帖最后由 正点原子 于 2024-7-29 16:54 编辑
1)实验平台:正点原子ESP32S3开发板
2)购买链接:https://detail.tmall.com/item.htm?id=768499342659
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-347618-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子手把手教你学ESP32S3快速入门视频教程:https://www.bilibili.com/video/BV1sH4y1W7Tc
6)正点原子FPGA交流群:132780729
第三十七章 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卡进行数据通讯的主要通道,如下:
表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所示:
表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所示:
表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所示:
表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,模式下的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),SDv2.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所示:
表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所示:
表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卡的介绍,我们就介绍到这里,更详细的关于SPI操作SD卡方法可以参考我们《ESP32F1 Mini板》关于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控制器外部接口信号
图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);
复制代码 该函数的形参描述如下表所示:
表37.3.2.1 esp_vfs_fat_sdspi_mount()函数形参描述
该函数的返回值描述,如下表所示:
表37.3.2.2 函数esp_vfs_fat_sdspi_mount ()返回值描述
该函数使用sdmmc_host_t类型的结构体变量传入,该结构体的定义如下所示:
表37.3.2.3 sdmmc_host_t结构体参数值描述
完成上述结构体参数配置之后,可以将结构传递给 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);
复制代码 该函数的形参描述如下表所示:
表37.3.2.4 esp_vfs_fat_sdcard_unmount ()函数形参描述
该函数的返回值描述,如下表所示:
表37.3.2.5 函数esp_vfs_fat_sdcard_unmount ()返回值描述
37.3.3 SD卡驱动解析
在IDF版的26_sd例程中,作者在26_sd \components\BSP路径下新增了一个SDIO文件夹,用于存放spi_sdcard.c、spi_sdcard.h这两个文件。其中,spi_sdcard.h文件负责声明SDIO相关的函数和变量,而spi_sdcard.c文件则实现了SDIO的驱动代码。下面,我们将详细解析这两个文件的实现内容。
1,spi_sdcard.h文件
- /* 引脚定义 */
- #define SD_NUM_CS GPIO_NUM_2
- #define MOUNT_POINT "/0:"
复制代码 1,spi_sdcard.c文件
sd_pi_init的设计就比较简单了,我们只需要填充SPI结构体的控制句柄,然后添加SPI总线设备并配置文件系统挂载即可,根据外设的情况,我们可以设置SPI的使用端口为SPI2端口。
- spi_device_handle_t MY_SD_Handle;
- /**
- * @brief SD卡初始化
- * @param 无
- * @retval esp_err_t
- */
- esp_err_t sd_spi_init(void)
- {
- esp_err_t ret = ESP_OK;
-
- /* SD / MMC卡结构 */
- sdmmc_card_t *card;
-
- /* 挂载点/根目录 */
- const char mount_point[] = MOUNT_POINT;
- /* SPI驱动接口配置,SPISD卡时钟是20-25MHz */
- spi_device_interface_config_t devcfg = {
-
- /* SPI时钟 */
- .clock_speed_hz = 20 * 1000 * 1000,
-
- /* SPI模式0 */
- .mode = 0,
-
- /* 片选引脚 */
- .spics_io_num = SD_NUM_CS,
-
- /* 事务队列尺寸 7个 */
- .queue_size = 7,
- };
- /* 添加SPI总线设备 */
- ret = spi_bus_add_device(SPI2_HOST, &devcfg, &MY_SD_Handle);
- /* 文件系统挂载配置 */
- esp_vfs_fat_sdmmc_mount_config_t mount_config = {
-
- /* 如果挂载失败:true会重新分区和格式化/false不会重新分区和格式化 */
- .format_if_mount_failed = false,
-
- /* 打开文件最大数量 */
- .max_files = 5,
-
- /* 硬盘分区簇的大小 */
- .allocation_unit_size = 16 * 1024,
- };
- /* SD卡参数配置 */
- sdmmc_host_t host = {0};
-
- /* 定义主机属性的标志:SPI协议且可调用deinit函数 */
- host.flags = SDMMC_HOST_FLAG_SPI | SDMMC_HOST_FLAG_DEINIT_ARG;
-
- /* 使用SPI2端口 */
- host.slot = SPI2_HOST;
-
- /* 主机支持的最大频率:20000 */
- host.max_freq_khz = SDMMC_FREQ_DEFAULT;
-
- /* 控制器使用的I/O电压 */
- host.io_voltage = 3.3f;
-
- /* 用于初始化驱动程序的主机函数 */
- host.init = &sdspi_host_init;
-
- /* 设置总线宽度的主机功能 */
- host.set_bus_width = NULL;
-
- /* 取总线宽度的主机函数 */
- host.get_bus_width = NULL;
-
- /* 设置DDR模式的主机功能 */
- host.set_bus_ddr_mode = NULL;
-
- /* 设置板卡时钟频率的主机函数 */
- host.set_card_clk = &sdspi_host_set_card_clk;
-
- /* 执行事务的主机函数 */
- host.do_transaction = &sdspi_host_do_transaction;
-
- /* 用于取消初始化驱动程序的主机函数 */
- host.deinit_p = &sdspi_host_remove_device;
-
- /* 启用SDIO中断线的主机功能 */
- host.io_int_enable = &sdspi_host_io_int_enable;
-
- /* 等待SDIO中断线路激活的主机功能 */
- host.io_int_wait = &sdspi_host_io_int_wait;
-
- /* 超时,默认为0*/
- host.command_timeout_ms = 0;
- /* 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;
- /* 挂载文件系统 */
- ret = esp_vfs_fat_sdspi_mount(mount_point,
- &host,
- &slot_config,
- &mount_config,
- &card);
- if (ret != ESP_OK)
- {
- spi_bus_remove_device(MY_SD_Handle); /* 移除SPI上的SD卡设备 */
- return ret;
- }
- return ret;
- }
复制代码 接下来这个函数用于获取SD卡相关信息,比如容量大小,以及剩余容量,如下所示:
- /**
- * @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;
- size_t free_clusters;
- int res = f_getfree("0:", (size_t *)&free_clusters, &fs);
- assert(res == FR_OK);
- size_t total_sectors = (fs->n_fatent - 2) * fs->csize;
- size_t free_sectors = free_clusters * fs->csize;
- size_t sd_total = total_sectors / 1024;
- size_t sd_total_KB = sd_total * fs->ssize;
- size_t sd_free = free_sectors / 1024;
- size_t sd_free_KB = sd_free*fs->ssize;
- /* 假设总大小小于4GiB,对于SPI Flash应该为true */
- if (out_total_bytes != NULL)
- {
- *out_total_bytes = sd_total_KB;
- }
-
- if (out_free_bytes != NULL)
- {
- *out_free_bytes = sd_free_KB;
- }
- }
复制代码
37.3.4 CMakeLists.txt文件
打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:
- set(src_dirs
- IIC
- LCD
- LED
- SDIO
- SPI
- XL9555)
- set(include_dirs
- IIC
- LCD
- LED
- SDIO
- SPI
- XL9555)
- set(requires
- driver
- fatfs)
- idf_component_register(SRC_DIRS ${src_dirs}
- INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
- component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码 上述的红色SDIO驱动以及fatfs依赖库需要由开发者自行添加,以确保SDIO驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了SDIO驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。
37.3.5 实验应用代码
打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。
- i2c_obj_t i2c0_master;
- /**
- * @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());
- ret = nvs_flash_init();
- }
- led_init(); /* 初始化LED */
- i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */
- spi2_init(); /* 初始化SPI */
- xl9555_init(i2c0_master); /* 初始化IO扩展芯片 */
- lcd_init(); /* 初始化LCD */
- lcd_show_string(30, 50, 200, 16, 16, "ESP32-S3", RED);
- lcd_show_string(30, 70, 200, 16, 16, "FATFS TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- while (sd_spi_init()) /* 检测不到SD卡 */
- {
- lcd_show_string(30, 110, 200, 16, 16, "SD Card Error!", RED);
- vTaskDelay(500);
- lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);
- vTaskDelay(500);
- }
- lcd_show_string(30, 110, 200, 16, 16, "SD Card OK!", RED);
- lcd_show_string(30, 130, 200, 16, 16, "Total: MB", RED);
- lcd_show_string(30, 150, 200, 16, 16, "Free : MB", RED);
- sd_get_fatfs_usage(&bytes_total, &bytes_free);
- lcd_show_num(80, 130,(int)bytes_total / 1024,5,16,BLUE);
- lcd_show_num(80, 150,(int)bytes_free / 1024,5,16,BLUE);
- while (1)
- {
- LED_TOGGLE();
- vTaskDelay(500);
- }
- }
复制代码 可以看到,本实验的应用代码中,通过初始化SD卡判断与SD卡的连接是否有误,SD卡初始化成功后便通过函数sd_get_fatfs_usage()获取容量等信息,同时也在LCD上显示了SD的容量信息。
37.4 下载验证
程序下载到开发板后,可以看到SPILCD显示SD卡目录下的test.txt文件的内容,如下图所示。如图37.4.1:
图37.4.1 程序运行效果图 |