一、SD卡简介
SD 卡的规范由 SD 卡协会明确,可以访问 https://www.sdcard.org 查阅更多标准。SD 卡主要有 SD、Mini SD 和 microSD(原名 TF 卡,2004 年正式更名为 Micro SD Card)三种类型,Mini SD 已经被 microSD 取代。
上述表格的 “脚位数”,对应于实卡上的 “金手指” 数,不同类型的卡的触点数量不同,访问的速度也不相同。SD 卡允许了不同的接口来访问它的内部存储单元。最常见的是 SDIO 模式和 SPI 模式。
我们对比着来看一下 microSD 引脚,可见只比 SD 卡少了一个电源引脚 VSS2,其它的引脚功能类似。SD 卡和 Micro SD 只有引脚和形状大小不同,内部结构类似,操作时序完全相同,可以使用完全相同的代码驱动。
二、SD卡的存储结构
SD 卡有自己的寄存器,但它不能直接进行读写操作,需要通过命令来控制,SDIO 协议定义了一些命令用于实现某一特定功能,SD 卡根据收到的命令要求对内部寄存器进行修改。
下表中描述的 SD 卡的寄存器是我们和 SD 卡进行数据通讯的主要通道,如下:
三、命令和响应
一个完整的 SD卡操作过程是:主机(单片机等)发起 “命令”,SD 卡根据命令的内容决定是否发送响应信息及数据等,如果是数据读/写操作,主机还需要发送停止读/写数据的命令来结束本次操作,这意味着主机发起命令指令后,SD 卡可以没有响应、数据等过程,这取决于命令的含义。
SD 卡有多种命令和响应,它们的格式定义及含义在《SD 卡协议 V2.0》的第三和第四章有详细介绍,发送命令时主机只能通过 CMD 引脚发送给 SD 卡,串行逐位发送时先发送最高位(MSB),然后是次高位这样类推。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,常用的命令如下所示:
上表中,大部分的命令是初始化的时候用的。表中的 R1、R3 和 R7 等是 SD 卡的应答信号,每个响应也有规定好的格式。
在规定为有响应的命令下,每发送一个命令,SD 卡都会给出一个应答,以告知主机该命令的执行情况,或者返回主机需要获取的数据,应答可以是 R1 ~ R7,R1 的应答,各位描述如下表所示:
除了 R2 响应是 128 位外,其它的响应都是 48 位。
四、卡模式
SD 卡系统(包括主机和 SD 卡)定义了 SD 卡的工作模式,在每个操作模式下,SD 卡都有几种状态,状态之间通过命令控制实现卡状态的切换。
在系统复位后,主机处于卡识别模式,寻找总线上可用的 SDIO 设备,对 SD 卡进行数据读写之前需要识别卡的种类:V1.0 标准卡、V2.0 标准卡、V2.0 高容量卡或者不被识别卡;同时,SD 卡也处于 卡识别模式,直到被主机识别到,即当 SD 卡在卡识别状态接收到 CMD3(SEND_RCA)
命令后,SD 卡就进入 数据传输模式,而主机在总线上所有卡被识别后也进入 数据传输模式。
在 卡识别模式 下,主机会复位所有处于 “卡识别模式” 的 SD 卡,确认其工作电压范围,识别 SD 卡类型,并且获取 SD 卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中,要求 SD 卡工作在识别时钟频率 FOD 的状态下。
主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送 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 进行数据传输,那它的数据传输结构如下所示:
当使用 4 线模式传输 8-bit 结构的数据时,数据仍按 MSB 先发送的原则,DAT[3:0] 的高位发送高数据位,低位发送低数据位。硬件支持的情况下,使用 4 线传输可以提升传输速率。
只有 SD 卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机 SD 时钟频率设置为 FPP,默认最高为 25MHz,频率切换可以通过 CMD4 命令来实现。数据传 SD 时钟频率设置为 FPP,默认最高为 25MHz,频率切换可以通过 CMD4 命令来实现。数据传输模式下,SD 卡状态转换过程如下所示:
CMD7 用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个 RCA 地址目标卡使其进入传输状态才可以进行数据通信。同时通过 CMD7 命令也可以让已经被选择的目标卡返回到待机状态。
数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以通过命令对卡进行数据读写、擦除。CMD12 可以中断正在进行的数据通信,让卡返回到传输状态。CMD0 和 CMD15 会中止任何数据编程操作,返回卡识别模式,注意谨慎使用,不当操作可能导致卡数据被损坏。
六、SD卡初始化流程
6.1、SDIO模式下的SD卡初始化流程
从图中,我们看到,不管什么卡(这里我们将卡分为 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 的格式如下所示:
这里,我们需要在发送 CMD8 的时候,通过其带的参数我们可以设置 VHS 位,以告诉 SD卡,主机的供电情况,VHS 位定义如下所示:
这里我们使用参数 0X1AA,即告诉 SD 卡,主机供电为 2.7 ~ 3.6V 之间,如果 SD 卡支持 CMD8,且支持该电压范围,则会通过 CMD8 的响应(R7)将参数部分原本返回给主机,如果不支持 CMD8,或者不支持这个电压范围,则不响应。
在发送 CMD8 后,发送 ACMD41(注意发送 ACMD41 之前要先发送 CMD55),来进一步确认卡的操作电压范围,并通过 HCS 位来告诉 SD 卡,主机是不是支持高容量卡(SDHC)。ACMD41 的命令格式如下所示:
ACMD41 得到的响应(R3)包含 SD 卡 OCR 寄存器内容,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 卡的类型区分,最后发送了 CMD2 和 CMD3 命令,用于获得卡 CID 寄存器数据和卡相对地址(RCA)。
CMD2,用于获得 CID 寄存器的数据,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 卡的容量和扇区大小等十分重要的信息。至此,我们的 SD 卡初始化基本就结束了,最后通过 CMD7 命令,选中我们要操作的 SD 卡,即可开始对 SD 卡的读写操作。
6.2、SPI模式下的SD卡初始化流程
ESP32 的 SDIO 驱动模式和 SPI模式不兼容,二者使用时需要区分开来。
要使用 SPI 模式驱动 SD 卡,先得让 SD 卡进入 SPI 模式。方法如下:在 SD 卡收到复位命令(CMD0)时,CS 为有效电平(低电平)则 SPI 模式被启用。不过在发送 CMD0 之前,要发送 >74 个时钟,这是因为 SD 卡内部有个供电电压上升时间,大概为 64 个 CLK,剩下的 10 个CLK 用于 SD 卡同步,之后才能开始 CMD0 的操作,在卡初始化的时候,CLK 时钟最大不能超过 400KHz。
SD 卡是先发送数据高位的,SD 卡的典型初始化过程如下:
- 初始化与 SD 卡连接的硬件条件(MCU 的 SPI 配置,IO 口配置)。
- 拉低片选信号,上电延时(>74 个 CLK)。
- 复位卡(CMD0),进入 IDLE 状态。
- 发送 CMD8,检查是否支持 2.0 协议。
- 根据不同协议检查 SD 卡(命令包括:CMD55、ACMD41、CMD58 和 CMD1 等)。
- 取消片选,发多 8 个 CLK,结束初始化。
这样我们就完成了对 SD 卡的初始化,注意末尾发送的 8 个 CLK 是提供 SD 卡额外的时钟,完成某些操作。通过 SD 卡初始化,我们可以知道 SD卡的类型(V1、V2、V2HC 或者 MMC),在完成了初始化之后,就可以开始读写数据了。
SD 卡单扇区读取数据,这里通过 CMD17 来实现,具体过程如下:
- 发送 CMD17。
- 接收卡响应 R1。
- 接收数据起始令牌 0XFE。
- 接收数据。
- 接收 2 个字节的 CRC,如果不使用 CRC,这两个字节在读取后可以丢掉。
- 禁止片选之后,发多 8 个 CLK。
以上就是一个典型的读取 SD卡数据过程,SD卡的写于读数据差不多,写数据通过 CMD24 来实现,具体过程如下:
- 发送 CMD24。
- 接收卡响应 R1。
- 发送写数据起始令牌 0XFE。
- 发送数据。
- 发送 2 字节的伪 CRC。
- 禁止片选之后,发多 8 个 CLK。
七、SD/MMC概述
ESP32 S3 存储卡接口控制器提供了一个访问安全数字输入输出卡、MMC 卡以及 CD-ATA 设备的硬件接口,用于连接高级外设总线和外部存储设备。该控制器支持两个外部卡,分别为:卡 0 和卡 1。所有 SD/MMC 模块接口信号都必须通过 GPIO 矩阵传输至 GPIO pad。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 控制器可通过这些外部接口信号与外部设备通信。其它信号还包括卡中断信号、卡检测信号和写保护信号等。
八、SD卡常用函数
ESP IDF 提供了一套 API 来配置 SD 卡。要使用此功能,我们需要在 CMakeLists.txt 文件中导入 fatfs
依赖库,然后还需要导入必要的头文件:
# 注册组件到构建系统的函数
idf_component_register(# 依赖库的路径REQUIRES fatfs
)
#include "driver/sdspi_host.h"
#include "driver/spi_common.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
8.1、挂载SD卡
使用 SDIO 方式挂载 SD 卡:
/*** @brief 使用SDIO方式挂载SD卡* * @param base_path 应该注册分区的路径* @param host_config_input 指向描述SDMMC主机的结构的指针* @param slot_config 指向具有插槽配置的结构的指针* @param mount_config 指向具有用于安装FATFS的额外参数的结构的指针* @param out_card 指向卡片信息结构的指针* @return esp_err_t ESP_OK挂载成功,其它配置失败*/
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);
使用 SPI 方式挂载 SD 卡:
/*** @brief 使用SPI方式挂载SD卡* * @param base_path 应该注册分区的路径* @param host_config_input 指向描述SDMMC主机的结构的指针* @param slot_config 指向具有插槽配置的结构的指针* @param mount_config 指向具有用于安装FATFS的额外参数的结构的指针* @param out_card 指向卡片信息结构的指针* @return esp_err_t ESP_OK挂载成功,其它配置失败*/
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);
形参 host_config
和 host_config_input
是 指向描述 SDMMC 主机的结构的指针。此结构可以使用 SDSPI_HOST_DEFAULT
宏初始化。
typedef struct
{uint32_t flags; // 定义主机属性的标志
#define SDMMC_HOST_FLAG_1BIT BIT(0) // 主机支持1线SD和MMC协议
#define SDMMC_HOST_FLAG_4BIT BIT(1) // 主机支持4线SD和MMC协议
#define SDMMC_HOST_FLAG_8BIT BIT(2) // 主机支持8行MMC协议
#define SDMMC_HOST_FLAG_SPI BIT(3) // 主机支持SPI协议
#define SDMMC_HOST_FLAG_DDR BIT(4) // 主机支持SD/MMC的DDR模式
#define SDMMC_HOST_FLAG_DEINIT_ARG BIT(5) // 使用slot参数调用主机deinit函数
#define SDMMC_HOST_FLAG_ALLOC_ALIGNED_BUF BIT(6) // 分配满足DMA要求的512字节的内部缓冲区。目前这只被SDIO驱动程序使用。int slot; // 槽位号,要传递给主机函数int max_freq_khz; // 主机支持的最大频率
#define SDMMC_FREQ_DEFAULT 20000 // SD/MMC默认速度
#define SDMMC_FREQ_HIGHSPEED 40000 // 高速
#define SDMMC_FREQ_PROBING 400 // SD/MMC探测速度
#define SDMMC_FREQ_52M 52000 // MMC 52MHz速度
#define SDMMC_FREQ_26M 26000 // MMC 26MHz速度
#define SDMMC_FREQ_DDR50 50000 // MMC 50MHz速度
#define SDMMC_FREQ_SDR50 100000 // MMC 100MHz速度float io_voltage; // 控制器使用的I/O电压(不支持电压切换)sdmmc_driver_strength_t driver_strength; // 驱动器强度sdmmc_current_limit_t current_limit; // 当前的限制esp_err_t (*init)(void); // 初始化驱动程序的主机函数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); // 主机功能设置卡时钟频率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); // 初始化驱动程序的主机函数,用slot调用};esp_err_t (*io_int_enable)(int slot); // 主机功能启用SDIO中断线esp_err_t (*io_int_wait)(int slot, TickType_t timeout_ticks); // 主机函数等待SDIO中断线激活int command_timeout_ms; // 单个命令的超时时间,以毫秒为单位。设置为0使用默认值esp_err_t (*get_real_freq)(int slot, int* real_freq); // 主机功能提供真实的工作频率,基于SDMMC控制器设置sdmmc_delay_phase_t input_delay_phase; // 输入延迟阶段,这只会在主机工作在SDMMC_FREQ_HIGHSPEED或SDMMC_FREQ_52M时生效。esp_err_t (*set_input_delay)(int slot, sdmmc_delay_phase_t delay_phase); // 设置输入延迟相位void* dma_aligned_buffer; // 将其保留为NULL。为SDIO模式的缓存对齐缓冲区保留sd_pwr_ctrl_handle_t pwr_ctrl_handle; // 电源控制手柄esp_err_t (*get_dma_info)(int slot, esp_dma_mem_info_t *dma_mem_info); // 主机功能dma内存信息esp_err_t (*is_slot_set_to_uhs1)(int slot, bool *is_uhs1); // 主机槽位是否设置为uhs1
} sdmmc_host_t;
形参 mount_config
是 指向具有用于安装 FATFS 的额外参数的结构的指针,它的定义如下:
typedef struct
{// 如果不能挂载FAT分区,且该参数为true,则创建分区表并格式化文件系统。bool format_if_mount_failed;// 打开文件的最大数目int max_files;// 如果设置了format_if_mount_failed,并且挂载失败,则使用给定的分配单元大小格式化卡。// 必须是扇区大小和128,扇区大小之间的2的幂。// 对于SD卡,扇区大小总是512字节。size_t allocation_unit_size;// 为SD卡(ff_sdmmc_status)启用真正的ff_disk_status功能。bool disk_status_check_enable;// 使用1个FAT(文件分配表)而不是2个。// 注意,这个选项只有在文件系统被格式化时才有效。// 当挂载一个已经格式化的分区时,实际的fat数量可能会有所不同。bool use_one_fat;
} esp_vfs_fat_mount_config_t;typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
8.2、取消挂载SD卡
我们可以使用 函数用于 取消挂载 SD 卡,该函数原型如下所示:
/*** @brief 取消挂载 SD 卡* * @param base_path 应该注册分区的路径* @param card 指向卡片信息结构的指针* @return esp_err_t ESP_OK取消挂载成功,其它取消挂载失败*/
esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card);
8.3、打印SD卡信息
我们挂载好 SD 卡后,可以使用 sdmmc_card_print_info()
函数打印 SD 卡信息,该函数的原型如下:
/*** @brief 打印SD卡信息* * @param stream 输出流* @param card SD卡句柄*/
void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card);
8.4、读写数据
挂载好 SD 卡后,我们就可以使用 C 标准操作文件的函数向 SD 卡中读写数据。
【1】、打开文件
/*** @brief 打开文件函数* * @param _name 打开的文件的文件名* @param _type 打开模式* @return FILE* 指向文件类型的指针*/
FILE * fopen(const char *__restrict _name, const char *__restrict _type);
【2】、关闭文件
/*** @brief 关闭文件* * @param stream 文件指针* @return int 正常操作时返回值为 0,否则返回 EOF*/
int fclose(FILE *stream );
【3】、读写一个字符
/*** @brief 写入一个字符到文件中* * @param ch 要写入的字符* @param stream 文件指针* @return int 写入成功返回值写入的字符;如果写入失败,就返回 EOF*/
int fputc( int ch, FILE *stream );
/*** @brief 从文件中读取一个字符* * @param stream 文件指针* @return int 读取的字符*/
int fgetc( FILE *stream );
【4】、读写字符串
/*** @brief 写入一个字符串到文件中* * @param str 字符串* @param stream 文件指针* @return int 写入成功返回写入的字符串的长度,失败返回EOF*/
int fputs( const char *restrict str, FILE *restrict stream );
/*** @brief 从文件中读取字符串* * @param str 保存字符串的缓冲区* @param count 要读取的字符串长度* @param stream 文件指针* @return char* 如果成功则返回字符串的地址,如果没读入任何字符则返回NULL*/
char *fgets( char *restrict str, int count, FILE *restrict stream );
【5】、格式化读写数据
/*** @brief 格式化写入数据到文件中* * @param stream 文件指针* @param format 格式化字符串* @param ... 格式控制符* @return int 成功匹配的参数个数*/
int fprintf( FILE *restrict stream, const char *restrict format, ... );
/*** @brief 格式化从文件中读取数据* * @param stream 文件指针* @param format 格式化字符串* @param ... 格式控制符* @return int 成功匹配的参数个数*/
int fscanf( FILE *restrict stream, const char *restrict format, ... );
九、实验例程
我们在【components】文件夹下的【device】文件夹中新增了一个 【sdcard】 文件夹,用于存放 sdcard.c 和 sdcard.h 这两个文件。
#ifndef __SDCARD_H__
#define __SDCARD_H__#include "esp_vfs_fat.h"
#include "driver/sdspi_host.h"
#include "driver/spi_common.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "esp_mac.h"#include "bsp_spi.h"#define SD_MOUNT_POING "/sdcard"extern sdmmc_card_t *g_sdmmc_card;
extern spi_device_handle_t g_sdcard_spi_device_handle;void sdcard_init(sdmmc_card_t **card, spi_host_device_t host_id, gpio_num_t sdcard_cs_gpio_num, char *mount_point, bool format_if_mount_failed);#endif // !__SDCARD_H__
#include "sdcard.h"sdmmc_card_t *g_sdmmc_card;
spi_device_handle_t g_sdcard_spi_device_handle;void sdcard_init(sdmmc_card_t **card, spi_host_device_t host_id, gpio_num_t sdcard_cs_gpio_num, char *mount_point, bool format_if_mount_failed)
{sdmmc_host_t host = SDSPI_HOST_DEFAULT();sdspi_device_config_t sdspi_device_config = SDSPI_DEVICE_CONFIG_DEFAULT();esp_vfs_fat_sdmmc_mount_config_t mount_config = {0};// SD卡参数配置host.slot = host_id; // 使用SPI端口// SD卡引脚配置sdspi_device_config.host_id = host_id;sdspi_device_config.gpio_cs = sdcard_cs_gpio_num;sdspi_device_config.gpio_cd = SDSPI_SLOT_NO_CD;sdspi_device_config.gpio_wp = SDSPI_SLOT_NO_WP;sdspi_device_config.gpio_int = SDSPI_SLOT_NO_INT;// 文件系统挂载配置mount_config.format_if_mount_failed = format_if_mount_failed; // 如果挂载失败:true会重新分区和格式化/false不会重新分区和格式化mount_config.max_files = 5; // 打开文件最大数量mount_config.allocation_unit_size = 16 * 1024; // 硬盘分区簇的大小// 挂载SD卡esp_vfs_fat_sdspi_mount(mount_point, &host, &sdspi_device_config, &mount_config, card);
}
然后,我们修改【components】文件夹下的【device】文件夹下的 CMakeLists.txt
文件。
# 源文件路径
set(src_dirssdcard
)# 头文件路径
set(include_dirssdcard# ${CMAKE_SOURCE_DIR}表示顶级 CMakeLists.txt 文件所在的目录的绝对路径${CMAKE_SOURCE_DIR}/components/peripheral/inc${CMAKE_SOURCE_DIR}/components/toolkit
)# 设置依赖库
set(requiresdriver
)# 注册组件到构建系统的函数
idf_component_register(# 源文件路径SRC_DIRS ${src_dirs}# 自定义头文件的路径INCLUDE_DIRS ${include_dirs}# 依赖库的路径REQUIRES ${requires}
)# 设置特定组件编译选项的函数
# -ffast-math: 允许编译器进行某些可能减少数学运算精度的优化,以提高性能。
# -O3: 这是一个优化级别选项,指示编译器尽可能地进行高级优化以生成更高效的代码。
# -Wno-error=format: 这将编译器关于格式字符串不匹配的警告从错误降级为警告。
# -Wno-format: 这将完全禁用关于格式字符串的警告。
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
修改【main】文件夹下的 main.c
文件。
#include <stdio.h>
#include <string.h>#include "freertos/FreeRTOS.h"#include "bsp_spi.h"#include "sdcard.h"// app_main()函数是ESP32的入口函数,它是FreRTOS的一个任务,任务优先级是1
// main()函数是C语言入口函数,它会在编译过程中插入到二进制文件中的
void app_main(void)
{FILE *fp = NULL;char write_chr = 'S', read_chr = 0;char write_str[32] = "hello world!", read_str[32];bsp_spi_init(SPI2_HOST, GPIO_NUM_5, GPIO_NUM_6, GPIO_NUM_7);sdcard_init(&g_sdmmc_card, SPI2_HOST, GPIO_NUM_4, SD_MOUNT_POING, false);sdmmc_card_print_info(stdout, g_sdmmc_card); // 打印SD卡信息fp = fopen(SD_MOUNT_POING"/test.txt", "w");fputc(write_chr, fp);fclose(fp);fp = fopen(SD_MOUNT_POING"/test.txt", "r");read_chr = fgetc(fp);fclose(fp);printf("chr: %c\n", read_chr);fp = fopen(SD_MOUNT_POING"/test2.txt", "w");fputs(write_str, fp);fclose(fp);fp = fopen(SD_MOUNT_POING"/test2.txt", "r");fgets(read_str, strlen(write_str), fp);fclose(fp);printf("str: %s\n", read_str);fp = fopen(SD_MOUNT_POING"/test3.txt", "w");fprintf(fp, "%s", write_str);fclose(fp);memset(read_str, 0, sizeof(read_str));fp = fopen(SD_MOUNT_POING"/test3.txt", "r");fscanf(fp, "%s", read_str);fclose(fp);printf("str: %s\n", read_str);esp_vfs_fat_sdcard_unmount(SD_MOUNT_POING, g_sdmmc_card); // 取消挂载SD卡while (1){// 将一个任务延迟给定的滴答数,IDF中提供pdMS_TO_TICKS可以将指定的ms转换为对应的tick数vTaskDelay(pdMS_TO_TICKS(100));}
}