本帖最后由 正点原子运营 于 2026-1-20 12:11 编辑
第三十八章 SD卡实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有U盘,FLASH芯片,SD卡等。他们各有优点,综合比较,最适合单片机系统的莫过于SD卡了,它不仅容量可以做到很大(32GB以上),支持SPI/SDIO驱动,而且有多种体积的尺寸可供选择(标准的SD卡尺寸及micro SD卡尺寸等),能满足不同应用的要求。
只需要少数几个IO口即可外扩一个高达32GB或以上的外部存储器,容量从几十M到几十G选择范围很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。
ESP32-P4开发板使用的接口是micro SD卡接口,卡座带自锁功能,使用ESP32-P4自带的SDMMC接口驱动。
在本章中,我们将向大家介绍,如何在正点原子ESP32-P4上实现micro SD卡的读取。
本章分为如下几个小节:
38.1 SD卡及ESP32-P4的SD/MMC主机控制器介绍
38.2 硬件设计
38.3 程序设计
38.4 下载验证
38.1 SD卡及SD/MMC主机控制器介绍
38.1.1 SD卡介绍
在讲解SDMMC外设前,先从SD卡物理结构、命令和响应、卡模式以及数据模式方面介绍一下SD卡。
SD物理结构
SD卡的规范由SD卡协会明确,可以访问https://www.sdcard.org查阅更多标准。SD卡主要有SD、mini SD和microSD(原名TF卡,2004年正式更名为Micro SD Card,为方便本文用microSD表示)三种类型,mini SD已经被microSD取代,使用得不多,根据最新的SD卡规格列出的参数如表38.1.1.1所示:
表38.1.1.1 SD卡的主要规格参数
上述表格的“脚位数”,对应于实卡上的“金手指”数,不同类型的卡的触点数量不同,访问的速度也不相同。SD卡允许了不同的接口来访问它的内部存储单元。最常见的是SDIO模式和SPI模式,根据这两种接口模式,我们也列出SD卡引脚对应于这两种不同的电路模式的引脚功能定义,如表38.1.1.2 所示。
表38.1.1.2 SD卡引脚编号(注:S:电源 I:输入 O:推挽输出 PP:推挽)
对比来看microSD引脚,可见只比SD卡少了一个电源引脚VSS2,其它的引脚功能类似。
表38.1.1.3 microSD卡引脚编号(注:S:电源;I:输入;O:推挽输出;PP:推挽)
SD卡和micro SD只有引脚和形状大小不同,内部结构类似,操作时序完全相同,可以使用完全相同的代码驱动,下面以9’Pin SD卡的内部结构为为例,展示SD卡的存储结构。
图38.1.1.1 SD卡内部物理结构(RCA 寄存器在SPI模式下不可访问)
SD卡有自己的寄存器,但它不能直接进行读写操作,需要通过命令来控制,SDIO协议定义了一些命令用于实现某一特定功能,SD卡根据收到的命令要求对内部寄存器进行修改。表38.1.1.4中描述的SD卡的寄存器是我们和SD卡进行数据通讯的主要通道,如下:
表38.1.1.4 SD卡寄存器信息
关于SD卡的更多信息和硬件设计规范可以参考SD卡协议《Physical Layer Simplified Specification Version 2.00》的第五章节内容。
命令和响应
一个完整的SD卡操作过程是:主机(单片机等)发起“命令”,SD卡根据命令的内容决定是否发送响应信息及数据等,如果是数据读/写操作,主机还需要发送停止读/写数据的命令来结束本次操作,这意味着主机发起命令指令后,SD卡可以没有响应、数据等过程,这取决于命令的含义。这一过程如图38.1.1.2所示。
图38.1.1.2 SD卡命令格式
命令:应用相关命令(ACMD)和通用命令(CMD),通过命令线CMD传输,固定长度48位。
响应:SD卡接收到命令,会有一个响应,用来反应SD卡状态。有两种响应类型:短响应(48位,格式与命令一样)和长响应(136位)。
数据:主机发送的数据/SD发送的数据。SD数据是以块(block)形式传输,SDHC卡数据块长度一般为512字节。数据块需要CRC保证数据传输成功。
SD卡有多种命令和响应,它们的格式定义及含义在《SD卡协议V2.0》的第三和第四章有详细介绍,发送命令时主机只能通过CMD引脚发送给SD卡,串行逐位发送时先发送最高位(MSB),然后是次高位这样类推……。
接下来,我们看看SD卡的命令格式,如表 38.1.1.5所示:
表38.1.1.5 SD卡控制命令格式
SD卡的命令固定为48位,由6个字节组成,字节1的最高2位固定为01,低6位为命令号(比如CMD16,为10000B即16进制的0X10,完整的CMD16,第一个字节为01010000,即0X10+0X40)。字节2~5为命令参数,有些命令是没有参数的。字节6的高七位为CRC值,最低位恒定为1。
注意:使用SDIO接口驱动,CRC7校验值必须正确;而SPI接口驱动,CRC7校验默认关闭,即伪CRC。
SD卡的命令总共有12类,分为Class0~Class11,本章,我们仅介绍几个比较重要的命令,如表38.1.1.5所示:
表38.1.1.6 SD卡部分命令
上表中,大部分的命令是初始化的时候用的。表中的R1、R3和R7等是SD卡的应答信号,每个响应也有规定好的格式,如图38.1.1.3所示:
图38.1.1.3 SD卡命令传输过程
在规定为有响应的命令下,每发送一个命令,SD卡都会给出一个应答,以告知主机该命令的执行情况,或者返回主机需要获取的数据,应答可以是R1~R7,R1的应答,各位描述如表38.1.1.3所示:
表38.1.1.7 R1响应
R2~R7的响应,限于篇幅,我们就不介绍了,但需要注意的是除了R2响应是136位外,其它的响应都是48位,请大家参考SD卡2.0协议。
卡模式
SD卡系统(包括主机和SD卡)定义了SD卡的工作模式,在每个操作模式下,SD卡都有几种状态,参考表38.1.1.8,状态之间通过命令控制实现卡状态的切换。
表38.1.1.8 SD卡状态与操作模式
对于我们来说两种有效操作模式:卡识别模式和数据传输模式。
在系统复位后,主机处于卡识别模式,寻找总线上可用的设备,对SD卡进行数据读写之前需要识别卡的种类:V1.0标准卡、V2.0标准卡、V2.0高容量卡或者不被识别卡;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡在卡识别状态接收到CMD3 (SEND_RCA)命令后,SD卡就进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。
在卡识别模式下,主机会复位所有处于“卡识别模式”的SD卡,确认其工作电压范围,识别SD卡类型,并且获取SD卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中,要求SD卡工作在识别时钟频率FOD的状态下。卡识别模式下SD卡状态转换如图38.1.1.4。
图38.1.1.4 卡识别模式状态转换图
主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送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。
ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。之后主机就发送SEND_RELATIVE_ADDR(CMD3)命令,让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA是16bit地址,而CID是128bit地址,使用RCA简化通信。卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。
数据模式
在数据模式下我们可以对SD卡的存储块进行读写访问操作。SD卡上电后默认以一位数据总线访问,可以通过指令设置为宽总线模式,可以同时使有4位总线并行读写数据,这样对于支持宽总线模式的接口(如:SDIO和QSPI等)都能加快数据操作速度。
SD卡有两种数据模式,一种是常规的8位宽,即一次按一字节传输,另一种是一次按512字节传输,我们只介绍前面一种。当按8-bit连续传输时,每次传输从最低字节开始,每字节从最高位(MSB)开始发送,当使用一条数据线时,只能通过DAT0进行数据传输,那它的数据传输结构如图38.1.1.5 所示。
图38.1.1.5 1位数据线传输8bit的数据流格式
当使用4线模式传输8-bit结构的数据时,数据仍按MSB先发送的原则,DAT[3:0]的高位发送高数据位,低位发送低数据位。硬件支持的情况下,使用4线传输可以提升传输速率。四线数据传输结构如图38.1.1.6 所示。
图38.1.1.6 4位数据线传输8bit格式的数据流格式
只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz,频率切换可以通过CMD4命令来实现。
数据传输模式下,SD卡状态转换过程见图38.1.1.7。
图38.1.1.7 数据传输模式卡状态转换
CMD7用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。同时通过CMD7命令也可以让已经被选择的目标卡返回到待机状态。
数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以通过命令对卡进行数据读写、擦除。CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0和CMD15会中止任何数据编程操作,返回卡识别模式,注意谨慎使用,不当操作可能导致卡数据被损坏。
至此,我们已经介绍了SD卡操作的一些知识,并知道了SD卡操作的命令、响应和数据传输等状态。
38.1.2 SD/MMC主机控制器介绍
ESP32-P4存储卡接口控制器提供APB与外部存储设备之间的硬件接口。存储卡接口允许ESP32-P4连接到安全数字输入输出卡(SDIO)、多媒体卡(MMC)和带有CE-ATA(消费电子高级传输架构)接口的设备。它支持两个外部卡(Card0和Card1)即该外设有两卡槽。所有SD/MMC模块接口信号仅通过GPIO矩阵连接到GPIO引脚。
该模块支持以下特性:
●支持两个外部卡
●支持3.0和3.01版本SD存储卡标准
●支持4.41、4.5、4.51版本MMC
●支持1.1版本CE-ATA
●支持1bit、4bit和8bit位宽模式
接下来,看一下SD/MMC主机控制器的结构框图,如下图所示。
图38.1.2.1 SD/MMC主机控制器结构框图
SD/MMC主机控制器主要由总线接口单元(BIU)和卡接口单元(CIU)组成。
总线接口单元BIU提供了通过主机接口单元Host Interface Unit访问寄存器和RAM数据的方式。除此之外,它通过DMA接口提供独立的RAM数据访问。它通过DMA接口访问存储器中的数据。简单来说,BIU支持主机接口、DMA接口、中断功能、寄存器访问、FIFO访问和上电/上拉控制和卡检测功能。
卡接口单元CIU实现卡专用接口协议。在CIU中,命令通路(Cmd Path)控制单元和数据通路(Data Path)控制单元将控制器分别连接到SD/MMC/CE-ATA卡的命令接口和数据接口。CIU还提供时钟控制。简单来说,CIU由命令通道、数据通路、SDIO中断控制、时钟控制和Mux/De-Mux单元等功能模块组成。
SD/MMC主机控制器跟外部器件通信的接口信号主要包括:时钟信号、命令信号、数据信号以及一些其他信号(卡中断信号、卡检测信号和写保护信号),这些信号如下图所示。
图38.1.2.2 SD/MMC控制器外接接口信号
为了更清晰了解这些接口信号,通过下表进行了解。
表38.1.2.1 SD/MMC主机控制器外部接口信号描述
SD/MMC主机控制器与SD卡的数据通信主要就是通过命令通路和数据通路完成。通过命令通道配置时钟参数、配置卡命令参数、向卡总线发送命令(sdhost_ccmd_out)、向卡总线接收响应(sdhost_ccmd_in)、向BIU发送响应和在命令线上发送P-bit。
发送时,数据通路模块会取出RAM中的数据,并将其发送至sdhost_cdata_out;接收时,数据通路会接收sdhost_cdata_in上的数据,并将其导入RAM中。数据发送命令未运行时,数据通路将加载新的数据参数,即预期发送的数据、读/写数据发送、流/块发送、块大小、字节数、卡类型、超时寄存器等。
38.2 硬件设计
38.2.1 例程功能
开机的时候先初始化SD卡,如果SD卡初始化完成,则通过LCD提示初始化成功。在程序中还会打印出SD卡的信息以及LCD显示SD卡的容量信息。LED0用来指示程序正在运行。
38.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)RGBLCD / MIPILCD(引脚太多,不罗列出来)
3)SD卡
SD1_CMD - IO44
SD1_CLK - IO43
SD1_D0 - IO39
SD1_D1 - IO40
SD1_D2 - IO41
SD1_D3 - IO42
38.2.3 原理图
SD卡相关原理图,如下图所示。
图38.2.3.1 SD卡原理图
38.3 程序设计
38.3.1 SDMMC的IDF驱动
使用SDMMC相关功能函数,必须先导入以下头文件:
- <font size="3">#include "driver/sdmmc_host.h" /* SDMMC主机控制器底层驱动接口 */</font>
- <font size="3">#include "esp_vfs_fat.h" /* 文件系统驱动接口 */</font>
- <font size="3">#include "sdmmc_cmd.h" /* SDMMC主机控制器上层驱动接口 */</font>
复制代码 接下来,作者将介绍一些常用的函数,这些函数的描述及其作用如下:
1,初始化SDMMC主机外设函数sdmmc_host_init
该函数用于初始化SDMMC主机外设,其函数原型如下:
- esp_err_t sdmmc_host_init(void);
复制代码 无函数形参
函数返回值:
ESP_OK表示初始化成功。
ESP_ERR_NO_MEM表示内存不足。
2,初始化SDMMC主机的卡槽函数sdmmc_host_init_slot
该函数用于初始化SDMMC主机的卡槽,其函数原型如下:
- <font size="3">esp_err_t sdmmc_host_init_slot( int slot, </font>
- <font size="3">const sdmmc_slot_config_t* slot_config);</font>
复制代码 函数形参:
表38.3.1.1 sdmmc_host_init_slot函数形参描述
函数返回值:
ESP_OK表示配置成功。
ESP_ERR_INVALID_STATE表示SDMMC主机未被初始化。
ESP_ERR_INVALID_ARG表示slot_config结构体的GPIO不合法。
slot为卡槽编号,可选为SDMMC_HOST_SLOT_0或SDMMC_HOST_SLOT_1。卡槽0和1都支持1、4、8线的SD接口,卡槽0专用于UHS-I模式,但驱动程序未支持该模式,相比之下,卡槽1比较通用,所以选择SDMMC_HOST_SLOT_1即可。
slot_config为指向卡槽的额外配置结构体指针。接下来,介绍sdmmc_slot_config_t结构体中各个成员,如下代码所示:
- typedef struct {
- #ifdef SOC_SDMMC_USE_GPIO_MATRIX
- gpio_num_t clk; /* SDMMC的时钟信号引脚 */
- gpio_num_t cmd; /* SDMMC的命令信号引脚 */
- gpio_num_t d0; /* SDMMC的数据线0信号引脚 */
- gpio_num_t d1; /* SDMMC的数据线1信号引脚 */
- gpio_num_t d2; /* SDMMC的数据线2信号引脚 */
- gpio_num_t d3; /* SDMMC的数据线3信号引脚 */
- gpio_num_t d4; /* SDMMC的数据线4信号引脚(1/4线模式下忽略) */
- gpio_num_t d5; /* SDMMC的数据线5信号引脚(1/4线模式下忽略) */
- gpio_num_t d6; /* SDMMC的数据线6信号引脚(1/4线模式下忽略) */
- gpio_num_t d7; /* SDMMC的数据线7信号引脚(1/4线模式下忽略) */
- #endif
- union {
- gpio_num_t gpio_cd; /* SDMMC的卡检测信号引脚 */
- gpio_num_t cd; /* SDMMC的卡检测信号引脚,短的名字 */
- };
- union {
- gpio_num_t gpio_wp; /* SDMMC的写保护信号引脚 */
- gpio_num_t wp; /* SDMMC的写保护信号引脚,短的名字 */
- };
- uint8_t width; /* 卡槽的总线宽度 */
- uint32_t flags; /* 卡槽使用的特性 */
- #define SDMMC_SLOT_FLAG_INTERNAL_PULLUP BIT(0)
- #define SDMMC_SLOT_FLAG_WP_ACTIVE_HIGH BIT(1)
- } sdmmc_slot_config_t;
复制代码 sdmmc_slot_config_t结构体用于设置卡槽的相关通信引脚以及卡槽总线宽度等。卡槽通过GPIO交换矩阵连接到ESP32-P4的GPIO,每个SD卡信号都可以使用任意GPIO连接。若没有用到的引脚,直接对其设置为GPIO_NUM_NC即可。在SDMMC的IDF驱动中提供了初始化宏SDMMC_SLOT_CONFIG_DEFAULT,可以直接使用,然后根据实际情况对某些成员进行赋值即可。特别注意:数据线4~7在width成员为1或4情况下忽略。
3,初始化卡函数sdmmc_card_init
该函数用于初始化卡,其过程可与前面38.1.1介绍的流程对应上,其函数原型如下:
- esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card);
复制代码 函数形参:
表38.3.1.2 sdmmc_card_init函数形参描述
函数返回值:
ESP_OK表示初始化成功。
其他表示请查看SDMMC主机控制器错误码。
config为指向SDMMC主机控制器初始化配置结构体指针,描述了SDMMC主机驱动。接下来介绍sdmmc_host_t结构体中各个成员,如下代码所示:
- typedef struct {
- uint32_t flags; /* 主机属性标志 */
- #define SDMMC_HOST_FLAG_1BIT BIT(0) /* 主机支持1线SDMMC协议 */
- #define SDMMC_HOST_FLAG_4BIT BIT(1) /* 主机支持4线SDMMC协议 */
- #define SDMMC_HOST_FLAG_8BIT BIT(2) /* 主机支持8线MMC协议 */
- #define SDMMC_HOST_FLAG_SPI BIT(3) /* 主机支持SPI协议 */
- #define SDMMC_HOST_FLAG_DDR BIT(4) /* 主机支持DDR模式SDMMC */
- #define SDMMC_HOST_FLAG_DEINIT_ARG BIT(5) /* slot参数会调用 */
- #define SDMMC_HOST_FLAG_ALLOC_ALIGNED_BUF BIT(6) /* 分配512字节的缓冲区 */
- int slot; /* 卡槽编号 */
- int max_freq_khz; /* 主机支持的最大频率 */
- #define SDMMC_FREQ_DEFAULT 20000 /* SDMMC默认速度(受时钟分频器限制) */
- #define SDMMC_FREQ_HIGHSPEED 40000 /* SD高速速度(受时钟分频器限制) */
- #define SDMMC_FREQ_PROBING 400 /* SDMMC初始化速度 */
- #define SDMMC_FREQ_52M 52000 /* MMC 52MHz速度 */
- #define SDMMC_FREQ_26M 26000 /* MMC 26MHz速度 */
- float io_voltage; /* 控制器使用的IO电压(不支持电压切换) */
- esp_err_t (*init)(void); /* SDMMC主机控制器初始化函数 */
- esp_err_t (*set_bus_width)(int slot, size_t width); /* 设置总线位宽函数 */
- size_t (*get_bus_width)(int slot); /* 获得总线位宽函数 */
- esp_err_t (*set_bus_ddr_mode)(int slot, bool ddr_enable); /* 设DDR模式函数 */
- esp_err_t (*set_card_clk)(int slot, uint32_t freq_khz); /* 设CLK频率函数 */
- /* 设置时钟是否一直开启 */
- esp_err_t (*set_cclk_always_on)(int slot, bool cclk_always_on);
- /* 执行事务函数 */
- esp_err_t (*do_transaction)(int slot, sdmmc_command_t* cmdinfo);
- union {
- esp_err_t (*deinit)(void); /* 默认初始化函数 */
- esp_err_t (*deinit_p)(int slot); /* 默认卡槽初始化函数 */
- };
- esp_err_t (*io_int_enable)(int slot); /* 启用SDIO中断线的函数 */
- /* 等待SDIO中断线激活函数 */
- esp_err_t (*io_int_wait)(int slot, TickType_t timeout_ticks);
- int command_timeout_ms; /* 单个命令超时函数 */
- esp_err_t (*get_real_freq)(int slot, int* real_freq); /* 获得真实工作频率函数 */
- sdmmc_delay_phase_t input_delay_phase; /* 输入延迟相位 */
- /* 设置输入延迟相位 */
- esp_err_t (*set_input_delay)(int slot, sdmmc_delay_phase_t delay_phase);
- void* dma_aligned_buffer; /* 保存为NULL */
- sd_pwr_ctrl_handle_t pwr_ctrl_handle; /* 电源控制句柄 */
- /* 获取DMA缓冲区信息函数 */
- esp_err_t (*get_dma_info)(int slot, esp_dma_mem_info_t *dma_mem_info);
- } sdmmc_host_t;
复制代码 sdmmc_host_t结构体用于存放SDMMC主机控制器的初始化配置。IDF驱动中提供了一个初始化宏SDMMC_HOST_DEFAULT,使用该宏时,SDMMC主机驱动会尝试以当前卡所支持的4线总线宽度进行通信,并使用20MHz的通信频率。对于成员比较多的结构体,若有初始化宏提供,避免问题的出现,直接使用,根据实际情况再对某些成员进行调整。sdmmc_host_t成员比较多,这里介绍两个重要的成员。
1)slot
SDMMC主机控制器卡槽编号,选择SDMMC_HOST_SLOT_1即可。
2)max_freq_khz
使用SDMMC接口时,max_freq_khz为频率上限,因此最终的频率值始终低于或等于该上限。在支持40MHz频率通信的设计中,可以调整该成员的值为SDMMC_FREQ_HIGHSPEED,以提升总线频率。
注意:
SD模式速度还跟电压电平有关。ESP32-P4 SDMMC主机通过VDDPST_5(SD_VREF)管脚从外部提供IO电压。如果设计不需要更高速度的SD模式,将此管脚连接到3.3V即可。
如果设计需要更高速度的SD模式(仅在1.8V IO电平下工作),有两种可选方案:
① 使用片上可编程LDO。将所需的LDO输出通道连接到VDDPST_5(SD_VREF)管脚上,并调用sd_pwr_ctrl_new_on_chip_ldo()来初始化SD电源控制驱动。最后,将pwr_ctl_handle设置为生成的句柄。
② 使用外部可编程LDO。同样,将LDO输出连接到VDDPST_5(SD_VREF)管脚,并自定义sd_pwr_ctrl驱动程序来控制LDO。最后,将pwr_ctrl_handle分配给驱动程序实例句柄。
sdmmc_card_t为指向卡信息结构体指针。接下来,介绍一下sdmmc_card_t结构体中各个成员,如下代码所示:
- typedef struct {
- sdmmc_host_t host; /* 主机配置结构体 */
- uint32_t ocr; /* OCR寄存器的值 */
- union {
- sdmmc_cid_t cid; /* CID寄存器的值 */
- sdmmc_response_t raw_cid; /* MMC的CID值 */
- };
- sdmmc_csd_t csd; /* CSD寄存器的值 */
- sdmmc_scr_t scr; /* SCR寄存器的值 */
- sdmmc_ssr_t ssr; /* SSR寄存器的值 */
- sdmmc_ext_csd_t ext_csd; /* 扩展卡特定数据寄存器 */
- uint16_t rca; /* RCA寄存器的值 */
- uint16_t max_freq_khz; /* 卡支持的最大频率 */
- int real_freq_khz; /* 卡实际工作频率 */
- uint32_t is_mem : 1; /* 是否为存储卡 */
- uint32_t is_sdio : 1; /* 是否为IO卡 */
- uint32_t is_mmc : 1; /* 是否为MMC */
- uint32_t num_io_functions : 3; /* 卡的IO数量 */
- uint32_t log_bus_width : 2; /* 卡支持的总线宽度 */
- uint32_t is_ddr : 1; /* 卡支持DDR模式 */
- uint32_t reserved : 23; /* 保留 */
- } sdmmc_card_t;
复制代码 sdmmc_card_t结构体主要存放卡的相关寄存器内容以及一些配置卡的信息,可以通过sdmmc_card_print_info函数可把对应信息打印出来。
注意:通过sdmmc_host_init、sdmmc_host_init_slot和sdmmc_card_init函数便可以完成SD卡初始化了。IDF驱动还提供了更便捷,更高层的一个函数esp_vfs_fat_sdmmc_mount去完成SD卡初始化。
4,在VFS中注册带有FAT文件系统的SD卡函数esp_vfs_fat_sdmmc_mount
该函数用于在虚拟文件系统VFS中注册使用带有FAT文件系统的SD卡,其中会对SD卡进行初始化,其函数原型如下:
- esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
- const sdmmc_host_t* host_config,
- const void* slot_config,
- const esp_vfs_fat_mount_config_t* mount_config,
- sdmmc_card_t** out_card);
复制代码 函数形参:
表38.3.1.3 esp_vfs_fat_sdmmc_mount函数形参描述
函数返回值:
ESP_OK表示配置成功。
ESP_ERR_INVALID_STATE表示函数已经被调用。
ESP_ERR_NO_MEM表示内存不足。
ESP_FAIL表示分区不能挂载。
其他错误编码表示SDMMC协议或FATFS驱动有问题。
base_path为注册分区的路径,这里好比磁盘符“/0:”,不过需要注意不要重复使用磁盘符,以及需要注意FATFS支持的磁盘数量。
host_config为指向SDMMC主机控制器初始化配置结构体指针,在前面已经介绍过,这里就不再展开了。
slot_config为指向卡槽的额外配置结构体指针。在前面已经介绍过,这里就不再展开了。
mount_config为指向用于挂载FATFS的额外参数结构体指针。接下来,介绍一下esp_vfs_fat_mount_config_t结构体中各个成员,如下代码所示:
- typedef struct {
- bool format_if_mount_failed; /* 挂载失败,true就创建分区表并格式化FAT */
- int max_files; /* 打开文件的最大数目 */
- size_t allocation_unit_size; /* 挂载失败后,设定分配单元的大小以格式化卡 */
- bool disk_status_check_enable; /* 为SD卡启用真正的ff_disk_status功能 */
- bool use_one_fat; /* 使用1个FAT */
- } esp_vfs_fat_mount_config_t;
复制代码 该结构体也有提供一个初始化宏VFS_FAT_MOUNT_CONFIG,当然也可以根据实际情况去选择配置。假如存在内存不足的情况,可以对use_one_fat成员设置为true,这样子就会减少一个扇区的大小。
sdmmc_card_t为指向卡信息结构体指针。在前面已经介绍过,这里就不再展开了。
当使用esp_vfs_fat_sdcard_mount函数成功对SD卡进行初始化并挂载成功后,便可以通过文件系统的API函数对SD卡进行操作。
与esp_vfs_fat_sdcard_mount函数对应的是esp_vfs_fat_sdcard_unmount,取消挂载SD卡,调用esp_vfs_fat_sdcard_unmount函数即可。
5,打印卡信息函数sdmmc_card_print_info
该函数用于打印卡信息,其函数原型如下:
- void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card);
复制代码 函数形参:
表38.3.1.4 sdmmc_card_print_info函数形参描述
无函数返回值。
如果要把SD卡当成一个纯粹的大容量存储器,那对存储器的读写操作就可以通过sdmmc_read_sectors()和sdmmc_write_sectors()函数执行,只不过在这里的SD卡已经有文件系统了,尽可能避免使用sdmmc_write_sectors函数,不然可能破坏了文件系统内容,需要重新格式化SD卡。
38.3.2 程序流程图
图38.3.2.1 SD卡实验程序流程图
38.3.3 程序解析
在29_sdcard例程中,作者在29_sdcard \components\BSP路径下新增1个文件夹SDMMC,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. SDMMC驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。SDMMC驱动源码包括两个文件:sdmmc.c和sdmmc.h。
在sdmmc.h中,定义了SD卡相关引脚宏以及盘符宏,代码如下:
- <font size="3">/* SDMMC外设相关硬件引脚 */</font>
- <font size="3">#define SDMMC_PIN_CMD GPIO_NUM_44</font>
- <font size="3">#define SDMMC_PIN_CLK GPIO_NUM_43</font>
- <font size="3">#define SDMMC_PIN_D0 GPIO_NUM_39</font>
- <font size="3">#define SDMMC_PIN_D1 GPIO_NUM_40</font>
- <font size="3">#define SDMMC_PIN_D2 GPIO_NUM_41</font>
- <font size="3">#define SDMMC_PIN_D3 GPIO_NUM_42</font>
- <font size="3">/* 挂载名称 */</font>
- <font size="3">#define MOUNT_POINT "/0:"</font>
复制代码 SD卡相关引脚,对照原理图查看即可,分配给SD卡的磁盘符为“/0:”。
下面我们再解析sdmmc.c的程序,主要讲解初始化函数sdmmc_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief SD卡初始化并挂载SD卡</font>
- <font size="3"> * @param 无</font>
- <font size="3"> * @retval ESP_OK:初始化成功</font>
- <font size="3"> */</font>
- <font size="3">esp_err_t sdmmc_init(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret = ESP_OK;</font>
- <font size="3"> </font>
- <font size="3"> /* 防止多次调用这个函数初始化 */</font>
- <font size="3"> if (sdmmc_mount_flag == 0x01 && card != NULL)</font>
- <font size="3"> {</font>
- <font size="3"> sdmmc_unmount(); /* 取消挂载 */</font>
- <font size="3"> }</font>
- <font size="3"> </font>
- <font size="3"> esp_vfs_fat_sdmmc_mount_config_t mount_config = {</font>
- <font size="3"> .format_if_mount_failed = false, /* 挂载FAT分区 */</font>
- <font size="3"> .max_files = 5, /* 打开分文件最大数量 */</font>
- <font size="3"> .allocation_unit_size = 16 * 1024 /* 分配单位大小 */</font>
- <font size="3"> };</font>
- <font size="3"> sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT(); /* SDMMC默认设置 */</font>
- <font size="3"> sdmmc_host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; /* 设置速度最大值为40MHz */</font>
- <font size="3"> sdmmc_slot_config_t sdmmc_config = SDMMC_SLOT_CONFIG_DEFAULT(); /* 卡槽配置 */</font>
- <font size="3"> sdmmc_config.width = 4; /* SDMMC总线宽度为4 */</font>
- <font size="3"> sdmmc_config.clk = SDMMC_PIN_CLK; /* SDMMC的时钟引脚 */</font>
- <font size="3"> sdmmc_config.cmd = SDMMC_PIN_CMD; /* SDMMC的命令引脚 */</font>
- <font size="3"> sdmmc_config.d0 = SDMMC_PIN_D0; /* SDMMC的数据0引脚 */</font>
- <font size="3"> sdmmc_config.d1 = SDMMC_PIN_D1; /* SDMMC的数据1引脚 */</font>
- <font size="3"> sdmmc_config.d2 = SDMMC_PIN_D2; /* SDMMC的数据2引脚 */</font>
- <font size="3"> sdmmc_config.d3 = SDMMC_PIN_D3; /* SDMMC的数据3引脚 */</font>
- <font size="3"> sdmmc_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; /* 内部弱上拉 */</font>
- <font size="3"> /* 挂载文件系统(内部会调用sdmmc_host_init_slot初始化SDMMC) */</font>
- <font size="3">ret = esp_vfs_fat_sdmmc_mount( mount_point, &sdmmc_host, &sdmmc_config,</font>
- <font size="3"> &mount_config, &card); </font>
- <font size="3"> if (ret != ESP_OK)</font>
- <font size="3"> {</font>
- <font size="3"> if (ret == ESP_FAIL)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_LOGE(sdmmc_tag, "Failed to mount filesystem. "</font>
- <font size="3"> "If you want the card to be formatted, set the </font>
- <font size="3">EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");</font>
- <font size="3"> }</font>
- <font size="3"> else</font>
- <font size="3"> {</font>
- <font size="3"> ESP_LOGE(sdmmc_tag, "Failed to initialize the card (%s). "</font>
- <font size="3"> "Make sure SD card lines have pull-up resistors in place.",</font>
- <font size="3"> esp_err_to_name(ret));</font>
- <font size="3"> }</font>
- <font size="3"> sdmmc_mount_flag = 0xFF;</font>
- <font size="3"> return ESP_FAIL;</font>
- <font size="3"> }</font>
- <font size="3"> sdmmc_card_print_info(stdout, card); /* 打印当前TF卡相关信息 */</font>
- <font size="3"> sdmmc_mount_flag = 0x01;</font>
- <font size="3"> return ESP_OK;</font>
- <font size="3">}</font>
复制代码 在SDMMC初始化函数中,选择使用SDMMC_HOST_DEFAULT初始化宏对sdmmc_host结构体进行赋值,然后对其max_freq_khz成员修改赋值,通过SDMMC_FREQ_HIGHSPEED设置为40MHz。然后选择使用SDMMC_SLOT_CONFIG_DEFAULT初始化宏对sdmmc_config结构体进行赋值,然后对其位宽、引脚成员根据原理图电路情况重新做调整设置。还需要定义mount_config结构体变量并对其进行赋值以及定义sdmmc_card_t结构体指针类型的card变量。后续,便通过esp_vfs_fat_sdmmc_mount函数对SD卡进行初始化并挂载SD卡。最后,通过调用sdmmc_card_print_info函数对卡信息进行打印。
2. CMakeLists.txt文件
本例程的功能实现主要依靠SDMMC驱动。要在main函数中,成功调用SDMMC文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- <font size="3">set(src_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> LCD</font>
- <font size="3"> SDMMC)</font>
- <font size="3">set(include_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> LCD</font>
- <font size="3"> SDMMC)</font>
- <font size="3">set(requires</font>
- <font size="3"> driver</font>
- <font size="3"> esp_lcd</font>
- <font size="3"> esp_common</font>
- <font size="3"> fatfs)</font>
- <font size="3">idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})</font>
- <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码 3. main.c驱动代码
在main.c里面编写如下代码。
- <font size="3">void app_main(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret;</font>
- <font size="3"> uint32_t size = 0;</font>
- <font size="3"> ret = nvs_flash_init(); /* 初始化NVS */</font>
- <font size="3"> if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_erase());</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_init());</font>
- <font size="3"> }</font>
- <font size="3"> led_init(); /* LED初始化 */</font>
- <font size="3"> lcd_init(); /* LCD屏初始化 */</font>
- <font size="3"> </font>
- <font size="3"> lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);</font>
- <font size="3"> lcd_show_string(30, 70, 200, 16, 16, "SD TEST", RED);</font>
- <font size="3"> lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);</font>
- <font size="3"> while (sdmmc_init()) /* 检测不到SD卡 */</font>
- <font size="3"> {</font>
- <font size="3"> lcd_show_string(30, 110, 200, 16, 16, "SD Card Error!", RED);</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(200));</font>
- <font size="3"> lcd_fill(30, 110, 239, 126, WHITE);</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(200));</font>
- <font size="3"> }</font>
- <font size="3"> lcd_show_string(30, 110, 200, 16, 16, "SD Card OK!", RED);</font>
- <font size="3"> lcd_show_string(30, 130, 200, 16, 16, "Total: MB", RED);</font>
- <font size="3"> </font>
- <font size="3"> size = ((uint64_t)card->csd.capacity) * card->csd.sector_size/(1024 * 1024);</font>
- <font size="3"> lcd_show_num(80, 130, size, 5, 16, BLUE);</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> LED0_TOGGLE();</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(500));</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 在app_main函数中,设置一个while循环去初始化SD卡,若SD卡成功被初始化,程序便可往下执行。SD卡初始化成功时,也会把SD卡的信息通过串口打印出来,LCD也会把SD卡容量显示出来。
38.4 下载验证
先把SD卡插入到ESP32-P4开发板的SD卡槽中,测试用的卡是32GB的。将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如下图所示:
图38.4.1 SD卡实验程序运行效果图
屏幕上显示了“SD Card OK!”证明SD卡已经初始化成功,显示SD卡的容量为29580MB。可能比较多小伙伴会疑惑,29580MB跟32GB对不上,这是因为SD卡上标称的32GB是以10进制表示的,即32GB SD卡实际容量为 32 * 10003 / (1024)3 ≈ 29GB 。
接下来,继续看一下SD卡初始化成功时,串口打印的内容,如下图所示。
图38.4.2串口打印卡信息 |