1. SD卡基础信息
1.1 SD简介
1.1.1 SD卡基础分类
容量 | 命名 | 简称 |
---|---|---|
0~2G | Standard Capacity SD Memory Card | SDSC 或 SD |
2G~32G | High Capacity SD Memory Card | SDHC |
32G~2T | Extended Capacity SD Memory Card | SDXC |
1.1.2 总线协议
- SD总线协议
- SPI总线协议
- UHS-II总线协议/PCIe/NVMe总线协议(压下不提)
1.2 SD卡物理结构
1.3 SD卡接口图
引脚编号 | SD mode | SPI mode | |||||
---|---|---|---|---|---|---|---|
名字 | 类型 | 说明 | 名字 | 类型 | 说明 | MCU引脚模式 | |
1 | CD/Date3 | I/O/PP | 卡检测/数据线[Bit 3] | CS | I | 片选 | 复用推挽输出 |
2 | CMD | I/O/PP | 命令/响应 | DI | I | 数据输入 | 复用推挽输出 |
3 | VSS | S | 电源电压地 | VSS | S | 电源电压地 | |
4 | VDD | S | 电源电压 | VDD | S | 电源电压 | |
5 | CLK | I | 时钟 | SCLK | I | 时钟 | 复用推挽输出 |
6 | VSS | S | 电源电压地 | VSS | S | 电源电压地 | |
7 | Date0 | I/O/PP | 数据线[Bit 0] | DO | O/PP | 数据输出 | 浮空输入 |
8 | Date1 | I/O/PP | 数据线[Bit 1] | NC | |||
9 | Date2 | I/O/PP | 数据线[Bit 2] | NC |
注:
- S电源;I输入;O使用推拉驱动器输出;PP推拉驱动输出
- 拓展的DAT线路(DAT1-DAT3)在通电时输入。在执行SET_BUS_WIDTH命令后,它们开始作为DAT行操作。主机也应保持自己的DAT1-DAT3线处于输入模式,而不使用。
1.4 寄存器
SD拥有8个寄存器,分别为:
名称 | 位宽 | 描述 |
---|---|---|
CID | 128 | 卡识别号(Card identifcation number): 用来识别的卡的个体号码(唯一的) |
RCA | 16 | 相对地址 (Relative card address): 卡的本地系统地址,初始化时,动态地由卡建议,主机核准。 |
DSR | 16 | 驱动级寄存器 (Driver Stage Register): 配置卡的输出驱动 |
CSD | 128 | 卡的特定数据(Card Specific Data): 卡的操作条件信息 |
SCR | 64 | SD 配置寄存器(SD Configuration Register):SD 卡特殊特性信息 |
OCR | 32 | 操作条件寄存器(Operation conditions register) |
SSR | 512 | SD 状态(SD Status):SD 卡专有特征的信息 |
CSR | 32 | 卡状态(Card Status): 卡状态信息 |
1.4.1 CID寄存器
卡标识(CID)寄存器为128位宽。它包含在卡识别阶段使用的卡识别信息。每张读写(RW)卡都应具有唯一的识别号。CID寄存器的结构定义如下:
名称 | 含义 | 位宽 | CID位 |
---|---|---|---|
Manufacturer ID/制造商ID | MID | 8 | [127:120] |
OEM/Application ID/制造商或用途ID | OID | 16 | [119:104] |
product name/生产名 | PNM | 40 | [103:64] |
Product revision/产品修订 | PRV | 8 | [63:56] |
Product serial number/序列号 | PSN | 32 | [55:24] |
reserved | — | 4 | [23:20] |
Manufacturing date/制造日期 | MDT | 12 | [19:8] |
CRC7 checkSum | CRC | 7 | [7:1] |
not used, always 1 | — | 1 | [0:0] |
- 唯一性标识:CID寄存器中的序列号是每张SD卡的唯一标识,这对于开发需要跟踪和识别大量SD卡的应用很有用,比如物联网设备或嵌入式系统。
- 验证:CID寄存器中的制造商ID和其他信息可用于验证SD卡的真实性。这对于确保SD卡是由可信制造商生产的,而不是仿造品或低质量产品非常重要。
- 版本控制:CID寄存器中可能包含关于SD卡规范和版本的信息,这对于确保设备与SD卡的兼容性很有帮助。
- 安全性:通过检查CID寄存器中的信息,开发者可以实施安全策略,例如只允许特定制造商的SD卡与设备通信,或限制特定批次或日期范围内的SD卡访问设备。
1.4.2 RCA寄存器
RCA寄存器是一个16位寄存器,存储了SD卡在总线上的相对地址。当多张SD卡连接到同一个总线上时,RCA寄存器的值能够帮助主控制器区分和选择特定的SD卡进行操作和通信。(UHS-II模式下,请参阅UHS-II附录中的SD-TRAN章节、在SPI模式下不使用RCA寄存器)
1.4.3 DSR寄存器
- 驱动器阶段(Driver Stage):指示SD卡当前所处的驱动器阶段,以便调整电信号的驱动能力和速率。
- 速率调整信息:用于指示SD卡的通信速率,这有助于设备和SD卡之间进行合适速率的通信,确保数据的可靠传输。
DSR寄存器中的信息对于确保设备与SD卡之间的通信协调和数据传输的稳定性非常重要。然而,具体的DSR寄存器结构和内容通常是由SD卡制造商定义的,并且这些细节可能是私有的或者保密的。因此,访问和解释DSR寄存器的内容通常需要特定的文档或者SD卡制造商提供的技术支持。
1.4.4 CSD寄存器
CSD寄存器中的这些数据对于设备与SD卡之间的通信和操作非常重要。例如,SD卡容量和传输速率的信息能够帮助设备优化数据传输,并确保数据的稳定性和可靠性。写保护信息则可以告知设备是否能够向SD卡写入数据。
CSD寄存器的内容是由SD卡协议定义的,但具体的格式和数据解释可能因SD卡类型(比如SDSC、SDHC、SDXC等)而有所不同。设备在与SD卡通信时会读取CSD寄存器中的信息,以便正确地配置和管理SD卡操作。
CSD寄存器的现场结构因物理规格版本和卡容量而不同。CSD寄存器中的CSD_STRUCTURE字段表示其结构版本。下表显示了相关CSD结构的版本号。
csd_structure | CSD结构版本 | 对SD存储卡物理规范版本/卡容量有效 |
---|---|---|
0 | CSD Version 1.0 | version 1.01 ~ 1.10/version 2.0 / Standard Capacity |
1 | CSD Version 2.0 | version 2.0 / High Capacity /Extended Capacity |
2 | CSD Version 3.0 | Ultra Capacity(SDUC) |
3 | 保留 |
1.4.4.1 CSD寄存器(CSD1.0版)
1.4.4.2 CSD寄存器(CSD2.0版)
1.4.4.3 CSD寄存器(CSD3.0版)
详见SD简易规格文件《第1部分物理层简化规范_Ver2.00》或《第1部分物理层简化规范版本9.10Fin_20231201》5.3节
1.4.5 SCR寄存器
SCR寄存器(SD Card Configuration Register)是SD卡中的一个重要寄存器,用于存储SD卡的特定配置和支持的功能信息,此寄存器应由SD存储卡制造商在工厂内设置。下表描述了SCR寄存器的内容。。
详见SD简易规格文件《第1部分物理层简化规范_Ver2.00》或《第1部分物理层简化规范版本9.10Fin_20231201》5.6节
1.4.6 OCR寄存器
这个寄存器包含了SD卡支持的电压范围信息,并提供了设备供电时SD卡所需的电压等级信息。OCR寄存器中的数据是用于设备与SD卡通信时进行电压匹配的重要指示。
- 此位仅在设置了SD卡通电状态位时有效。
- 如果卡未完成通电程序,则此位设置为低。
- 只有UHS-I卡支持这一点。
- 只有SDUC卡支持这一点。
具体来说,OCR寄存器包含了以下信息:
- 电压范围支持:OCR寄存器描述了SD卡支持的电压范围。这个信息对于设备提供合适的电压以供SD卡稳定运行非常重要。SD卡可以支持多个电压等级,而OCR寄存器中的位表示每个支持的电压等级。
- 供电电压模式:OCR寄存器也可以包含一些位,指示SD卡的供电电压模式。这有助于设备了解SD卡所需的电压类型和特性,以正确地配置电源。
1.4.7 其他寄存器
1.4.7.1 SSR寄存器
SDSSR(SD Status Register)是SD卡中的一个寄存器,用于存储SD卡的状态信息,比如SD卡的错误状态、写保护状态等。
SDSSR寄存器包含了一些关键的信息,包括但不限于:
- 错误状态:指示SD卡是否出现了错误。这可能是由于通信故障、数据传输错误或其他问题引起的错误状态。
- 写保护状态:指示SD卡是否处于写保护模式。当SD卡处于写保护模式时,它将禁止写入操作,以保护存储的数据不被意外修改。
- 电源状态:提供了有关SD卡的电源状态信息。这可以包括SD卡是否处于低功耗模式或正在进行电源管理等信息。
SSR寄存器中的这些信息对于设备诊断SD卡状态并进行合适的操作非常重要。设备在与SD卡通信时可以读取SDSSR寄存器的内容,以了解SD卡的当前状态,并根据这些信息来采取适当的操作。
1.4.7.2 CSR寄存器
卡状态信息寄存器
2. SDIO总线
2.1 SDIO总线拓扑
SD 卡总线拓扑参考图 35‑3。虽然可以共用总线,但不推荐多卡槽共用总线信号(实际中STM32也能且只能有一个SDIO总线设备),要求一个单独SD总线应该连接一个单独的 SD 卡。
SD 卡使用 9-pin 接口通信,其中 3 根电源线、1 根时钟线、1 根命令线和 4 根数据线,具体说明如下:
• CLK:时钟线,由 SDIO 主机产生,即由 STM32 控制器输出;
• CMD:命令控制线,SDIO 主机通过该线发送命令控制 SD 卡,如果命令要求 SD 卡提供应答 (响应),SD 卡也是通过该线传输应答信息;
• D0-3:数据线,传输读写数据;SD 卡可将 D0 拉低表示忙状态;
• VDD、VSS1、VSS2:电源和地信号。
SDIO 的通信时序简单许多,SDIO 不管是从主机控制器向 SD 卡传输,还是 SD 卡向主机控制器传输都只以 CLK 时钟线的上升沿为有效。SD 卡操作过程会使用两种不同频率的时钟同步数据,一个是识别卡阶段时钟频率 FOD,最高为 400kHz,另外一个是数据传输模式下时钟频率 FPP,默认最高为 25MHz,如果通过相关寄存器配置使 SDIO 工作在高速模式,此时数据传输模式最高频率为 50MHz。
2.2 总线协议
SD总线上的通信基于由开始位”0″启动并由停止位”1″终止的命令和数据位流。
- 命令:一个命令是一个启动一个操作的令牌。命令从主机发送到一个卡(寻址命令)或所有连接的卡(广播命令)。一个命令在CMD行上连续传输。
- 响应:响应是从寻址卡或(同步)从所有连接的卡发送到主机的令牌,作为对先前接收到的命令的响应。一个响应在CMD线上连续传输。
- 数据:数据可以从板卡传输到主机,反之亦然。数据通过数据线进行传输。
2.2.1 命令与响应交互
SD 数据是以块 (Black) 形式传输的,SDHC 卡数据块长度一般为 512 字节,数据可以从主机到卡,也可以是从卡到主机。数据块需要 CRC 位来保证数据传输成功。CRC 位由 SD 卡系统硬件生成。STM32 控制器可以控制使用单线或 4 线传输
2.2.2 多块写入操作
SD 数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。数据写入前需要检测 SD 卡忙状态,因为 SD 卡在接收到数据后编程到存储区过程需要一定操作时间。SD 卡忙状态通过把 D0 线拉低表示。
读取与写入类似,缺少”忙”消息
2.2.3 8 位宽数据包传输
使用 4 数据线传输时,每次传输 4bit 数据,每根数据线都必须有起始位、终止位以及 CRC 位,CRC 位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过 D0 线反馈给主机。
SD 卡数据包有两种格式,一种是常规数据 (8bit 宽),它先发低字节再发高字节,而每个字节则是先发高位再发低位.4 线同步发送,每根线发送一个字节的其中两个位,数据位在四线顺序排列发送,DAT3 数据线发较高位,DAT0 数据线发较低位。
另外一种数据包发送格式是宽位数据包格式,对 SD 卡而言宽位数据包发送方式是针对 SD 卡SSR(SD 状态) 寄存器内容发送的,SSR 寄存器总共有 512bit,在主机发出 ACMD13 命令后 SD 卡将 SSR 寄存器内容通过 DAT 线发送给主机。
3. 命令
SD 命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与 SD 主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。
3.1 命令格式
SD命令格式固定为48bit
SD 命令的组成如下:
- 起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为 0,终止位为 1。
- 传输标志:用于区分传输方向,该位为 1 时表示命令,方向为主机传输到 SD 卡,该位为 0时表示响应,方向为 SD 卡传输到主机。
命令主体内容包括命令、地址信息/参数和 CRC 校验三个部分。
- 命令号:它固定占用 6bit,所以总共有 64 个命令 (代号:CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于 SD 卡操作,只是专门用于 MMC 卡或者 SD I/O 卡。
- 地址/参数:每个命令有 32bit 地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这 32bit 用于指定参数,而寻址命令这 32bit 用于指定目标 SD 卡的地址。
- CRC7 校验:长度为 7bit 的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD 卡不执行命令。
3.2 命令类型
SD 命令有 4 种类型:
- 无响应广播命令 (bc),发送到所有卡,不返回任务响应;
- 带响应广播命令 (bcr),发送到所有卡,同时接收来自所有卡响应;
- 寻址命令 (ac),发送到选定卡,DAT 线无数据传输;
- 寻址数据传输命令 (adtc),发送到选定卡,DAT 线有数据传输。
在此环境中,需要有特定的客户/应用程序功能。为实现这些功能,在标准中定义了两种类型的通用命令:特定应用命令 (ACMD) 和常规命令 (GEN_CMD)。
3.3 命令描述(部分命令不支持SPI)
3.4 命令响应
响应由SD卡向主机发出,部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。基本特性如下:
3.4.1 SDIO模式下响应
- SDIO总共有7个响应类型(代号:R1~R7),其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6。
- 与命令一样,SD卡的响应也是通过CMD线连续传输的。
- 根据响应内容大小可以分为短响应和长响应。短响应是48bit长度,只有R2类型是长响应,其长度为136bit。
3.5 SD卡的操作及切换
3.5.1 SD卡的操作模式
SD卡系统(包括主机和SD卡)定义了两种操作模式:卡识别模式和数据传输模式。在系统复位后,主机处于卡识别模式,寻找总线上可用的SDIO设备;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡接收到SEND_RCA(CMD3)命令后,SD卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。
在每个操作模式下,SD卡都有几种状态,通过命令控制实现卡状态的切换:
3.5.2 卡识别模式
在卡识别模式下,主机会复位所有处于“卡识别模式”的SD卡,确认其工作电压范围,识别SD卡类型,并且获取SD卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中,要求SD卡工作在识别时钟频率FOD的状态下。
- 上电后,所有卡处于空闲状态,可发送GO_IDLE_STATE(CMD0)让所有卡软复位从而进入空闲状态。
- 使用SEND_IF_COND(CMD8)命令根据响应确定卡的电压支持范围。CMD8是SD卡标准V2.0版本才有的新命令,所以如果主机有接收到响应,可以判断卡为V2.0或更高版本SD卡(非MMC卡)。
- 使用SD_SEND_OP_COND(ACMD41)命令识别或拒绝不匹配它的电压范围的卡。并通过HCS位及其响应判断是SDSC还是SDHC卡。
- 使用ALL_SEND_CID(CMD2) 来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。
- 发送SEND_RELATIVE_ADDR(CMD3)命令,让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA是16bit地址,而CID是128bit地址,使用RCA简化通信。
- 卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。
3.5.3 数据传输模式
只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz,频率切换可以通过CMD4命令来实现。
- CMD7用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。同时通过CMD7命令也可以让已经被选择的目标卡返回到待机状态。
- 数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以使用块的读写以及擦除命令对卡进行数据读写、擦除。
- CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0和CMD15会中止任何数据编程操作,返回卡识别模式,这可能导致卡数据被损坏。
4. stm32的SDIO功能框图
STM32控制器有一个SDIO,由两部分组成:SDIO适配器和总线接口,SDIO适配器提供SDIO主机功能,可以提供SD时钟、发送命令和进行数据传输。总线接口用于控制器访问SDIO适配器,并且可以产生中断和DMA请求信号。
SDIO使用两个时钟信号,一个是SDIO适配器时钟,另外一个是总线时钟。STM32控制器的SDIO是针对MMC卡和SD卡的主设备,所以预留有8根数据线,对于SD卡最多用四根数据线。
4.1 SDIO适配器
SDIO适配器是SD卡系统主机部分,是STM32控制器与SD卡数据通信中间设备。SDIO适配器由五个单元组成,分别是控制单元、命令路径单元、数据路径单元、寄存器单元以及FIFO。
4.1.1 命令路径
命令路径控制命令发送,并接收卡的响应,当SD卡处于某一状态时,SDIO适配器必然处于特定状态与之对应。STM32控制器以命令路径状态机(CPSM)来描述SDIO适配器状态变化,并加入了等待超时检测功能,以便退出永久等待的情况。
- 命令路径状态机
4.1.2 数据路径
数据路径部件负责与SD卡相互数据传输,SDIO适配器以数据路径状态机(DPSM)来描述SDIO适配器状态变化情况。并加入了等待超时检测功能,以便退出永久等待情况。发送数据时,DPSM处于等待发送(Wait_S)状态,如果数据FIFO不为空,DPSM变成发送状态并且数据路径部件启动向卡发送数据。接收数据时,DPSM处于等待接收状态,当DPSM收到起始位时变成接收状态,并且数据路径部件开始从卡接收数据。
- DPSM数据路径状态机
4.1.3 数据FIFO
- 数据FIFO(先进先出)部件是一个数据缓冲器,带发送和接收单元。控制器的FIFO包含宽度为32bit、深度为16/32字的数据缓冲器和发送/接收逻辑。
- SDIO状态寄存器(SDIO_STA)的TXACT位用于指示当前正在发送数据,RXACT位指示当前正在接收数据,这两个位不可能同时为1。
- 当TXACT为1时,可以通过总线接口将数据写入到传输FIFO。
- 当RXACT为1时,接收FIFO存放从数据路径部件接收到的数据。
- 根据FIFO空或满状态会把SDIO_STA寄存器位值1,并可以产生中断和DMA请求。
4.1.4 适配器寄存器
适配器寄存器包含了控制SDIO外设的各种控制寄存器及状态寄存器,内容较多,可以通过SDIO提供的各种结构体来了解,这些寄存器的功能都被整合到了结构体或ST提供的库之中。
5. STM32Cube的SDIO配置
5.1 SDIO时钟配置
5.2 SDIO参数配置
- 模式:4bitSDIO模式
- Clock transition on which the bit capture is made:SDIO控制器与sd卡通信时,数据是上升沿有效还是下降沿有效(默认上升沿有效)。
- SDlO Clock divider bypass:是否关闭旁路时钟分频器,disable为根据CLKDIV进行分频(默认状态分频)。
- SDlO Clock output enable when the bus is idle:总线空闲时是否使能SDIO时钟输出(默认为关闭)。
- SDlO hardware flow control:SDIO的硬件流控制(默认关闭)。
- SDIOCLK clock divide factor:SDIOCLK时钟分频系数 SDIOCLK时钟分频系数SDIOCLK时钟分频因子必须介于0和255之间。SDIO CK=SDIOCLK/(CLKDIV+2)。输出时钟频率可以在187KHz和24MHz之间变化。建议保持默认的ClockDiv值(0),以使SDIO CK的最大频率为24 MHz。
6. Hal库中一些相关的SD卡操作API
6.1 SD 卡信息结构定义
/**
* @brief SD Card Information Structure definition
*/
typedef struct
{
uint32_t CardType; /*!< Specifies the card Type */
uint32_t CardVersion; /*!< Specifies the card version */
uint32_t Class; /*!< Specifies the class of the card class */
uint32_t RelCardAdd; /*!< Specifies the Relative Card Address */
uint32_t BlockNbr; /*!< Specifies the Card Capacity in blocks */
uint32_t BlockSize; /*!< Specifies one block size in bytes */
uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks */
uint32_t LogBlockSize; /*!< Specifies logical block size in bytes */
}HAL_SD_CardInfoTypeDef;
字段名 | 类型 | 描述 |
---|---|---|
CardType | uint32_t | SD 卡的类型(如 SDHC、SDXC 等) |
CardVersion | uint32_t | SD 卡的版本(如 2.0, 3.0 等) |
Class | uint32_t | SD 卡的速度等级(如 Class 10, Class 4 等) |
RelCardAdd | uint16_t | 相对卡地址 (RCA) |
BlockNbr | uint32_t | 卡中的数据块总数 |
BlockSize | uint32_t | 每个数据块的大小(通常为 512 字节) |
LogBlockNbr | uint32_t | 卡中的逻辑块总数 |
LogBlockSize | uint32_t | 每个逻辑块的大小 |
- CardType
/** @defgroup SD_Exported_Constansts_Group3 SD Supported Memory Cards
* @{
*/
#define CARD_SDSC 0x00000000U /*!< SD Standard Capacity <2Go */
#define CARD_SDHC_SDXC 0x00000001U /*!< SD High Capacity <32Go, SD Extended Capacity <2To */
#define CARD_SECURED 0x00000003U
- CardVersion
/** @defgroup SD_Exported_Constansts_Group4 SD Supported Version
* @{
*/
#define CARD_V1_X 0x00000000U
#define CARD_V2_X 0x00000001U
以下为读取手中16G SDHC存储卡的信息
SD_CardInfo.BlockNbr = 30535680 SD_CardInfo.BlockSize = 512 SD_CardInfo.CardType = 1 SD_CardInfo.CardVersion = 1 SD_CardInfo.Class = 1461 SD_CardInfo.LogBlockNbr = 30535680 SD_CardInfo.LogBlockSize = 512 SD_CardInfo.RelCardAdd = 1
6.2 SD卡的读操作
函数仅支持512字节的块长度(块大小应定义为512字节)。
- 轮询
/**
* @brief 从卡中的指定地址读取块(block)。数据传输由轮询模式管理。
* @note 在调用此 API 后,应通过 HAL_SD_GetCardState() 检查卡的状态。
* @param hsd: SD 句柄的指针
* @param pData: 指向将包含接收数据的缓冲区的指针
* @param BlockAdd: 要读取数据的块地址
* @param NumberOfBlocks: 要读取的 SD 块数
* @param Timeout: 指定超时时间值
* @retval HAL 状态
*/
HAL_StatusTypeDef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);
HAL_SD_ReadBlocks(&hsd,RBuff,0x0,1,0xffff);
- DMA
/**
* @brief 从卡中的指定地址读取块(block)。数据传输由 DMA 模式管理。
* @note 在调用此 API 后,应通过 HAL_SD_GetCardState() 检查卡的状态。
* @note 您还可以通过 SD 接收中断事件检查 DMA 传输过程。
* @param hsd: SD 句柄的指针
* @param pData: 指向将包含接收数据的缓冲区的指针
* @param BlockAdd: 要读取数据的块地址
* @param NumberOfBlocks: 要读取的块数
* @retval HAL 状态
*/
HAL_StatusTypeDef HAL_SD_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);
HAL_SD_ReadBlocks_DMA(&hsd,RBuff,0x01,1);
- 中断
/**
* @brief 从卡中的指定地址读取块(block)。数据传输由中断模式管理。
* @note 在调用此 API 后,应通过 HAL_SD_GetCardState() 检查卡的状态。
* @note 您还可以通过 SD 接收中断事件检查中断传输过程。
* @param hsd: SD 句柄的指针
* @param pData: 指向将包含接收数据的缓冲区的指针
* @param BlockAdd: 要读取数据的块地址
* @param NumberOfBlocks: 要读取的块数
* @retval HAL 状态
*/
HAL_StatusTypeDef HAL_SD_ReadBlocks_IT(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);
HAL_SD_ReadBlocks_IT(&hsd,RBuff,0x01,1);
6.3 SD卡的写操作
- 轮询
/**
* @brief 允许向卡中的指定地址写入块(block)。数据传输由轮询模式管理。
* @note 在调用此 API 后,应通过 HAL_SD_GetCardState() 检查卡的状态。
* @param hsd: SD 句柄的指针
* @param pData: 指向包含要传输数据的缓冲区的指针
* @param BlockAdd: 要写入数据的块地址
* @param NumberOfBlocks: 要写入的 SD 块数
* @param Timeout: 指定超时时间值
* @retval HAL 状态
*/
HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);
HAL_SD_WriteBlocks(&hsd,WBuff,0x0,1,0xffff);
- DMA
/**
* @brief 向卡中的指定地址写入块(block)。数据传输由 DMA 模式管理。
* @note 在调用此 API 后,应通过 HAL_SD_GetCardState() 检查卡的状态。
* @note 您还可以通过 SD 发送中断事件检查 DMA 传输过程。
* @param hsd: SD 句柄的指针
* @param pData: 指向包含要传输数据的缓冲区的指针
* @param BlockAdd: 要写入数据的块地址
* @param NumberOfBlocks: 要写入的块数
* @retval HAL 状态
*/
HAL_StatusTypeDef HAL_SD_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);
HAL_SD_WriteBlocks_DMA(&hsd,WBuff,0x01,1);
- 中断
/**
* @brief 向卡中的指定地址写入块(block)。数据传输由中断模式管理。
* @note 在调用此 API 后,应通过 HAL_SD_GetCardState() 检查卡的状态。
* @note 您还可以通过 SD 发送中断事件检查中断传输过程。
* @param hsd: SD 句柄的指针
* @param pData: 指向包含要传输数据的缓冲区的指针
* @param BlockAdd: 要写入数据的块地址
* @param NumberOfBlocks: 要写入的块数
* @retval HAL 状态
*/
HAL_StatusTypeDef HAL_SD_WriteBlocks_IT(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);
HAL_SD_WriteBlocks_IT(&hsd,WBuff,0x01,1);
6.4 SD卡的状态查询
- SD卡的当前状态
HAL_SD_GetCardState
函数用于获取 SD 卡的当前状态,适合在执行读写操作之前检查卡的状态。
/**
* @brief 获取 SD 卡的当前状态。
* @param hsd: SD 句柄的指针
* @retval SD 卡的当前状态
*/
HAL_SD_CardStateTypeDef HAL_SD_GetCardState(SD_HandleTypeDef *hsd);
- SD 卡的详细状态
HAL_SD_GetCardStatus
函数用于获取 SD 卡的详细状态信息,提供更为详细的卡状态信息,包括错误状态和制造商信息等。
/**
* @brief 获取 SD 卡的详细状态信息。
* @param hsd: SD 句柄的指针
* @param pCardStatus: 指向存储 SD 卡状态信息的缓冲区的指针
* @retval HAL 状态
*/
HAL_StatusTypeDef HAL_SD_GetCardStatus(SD_HandleTypeDef *hsd, HAL_SD_CardStatusTypeDef *pCardStatus);
7. stm32 SDIO 例程
7.1 关于部分型号的STM32SDIO部分DMA使用
stm32f1部分型号单片机仅有唯一通道,则此时如果需要进行DMA处理相关数据,则需要在使用时对DMA方向进行重新绑定.(记得开SDIO中断)
7.1.1 DMA写操作
核心内容就是去初始化后对DMA重新初始化绑定方向并调用HAL_SD_WriteBlocks_DMA进行发送
HAL_StatusTypeDef SDIO_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
HAL_StatusTypeDef Return_Status;
HAL_SD_CardStateTypeDef SD_Card_Status;
do{
SD_Card_Status = HAL_SD_GetCardState(hsd);
} while(SD_Card_Status != HAL_SD_CARD_TRANSFER);
/* SDIO DMA DeInit */
/* SDIO DeInit */
HAL_DMA_DeInit(&hdma_sdio);
/* SDIO DMA Init */
/* SDIO Init */
hdma_sdio.Instance = DMA2_Channel4;
hdma_sdio.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;
hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_sdio.Init.Mode = DMA_NORMAL;
hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;
if(HAL_DMA_Init(&hdma_sdio) != HAL_OK){
Error_Handler();
}
__HAL_LINKDMA(hsd, hdmatx, hdma_sdio);
Return_Status = HAL_SD_WriteBlocks_DMA(hsd, pData, BlockAdd, NumberOfBlocks);
return Return_Status;
}
7.1.2 DMA读操作
核心内容与7.1.1相同,调用HAL_SD_ReadBlocks_DMA进行读取
HAL_StatusTypeDef SDIO_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
HAL_StatusTypeDef Return_Status;
HAL_SD_CardStateTypeDef SD_Card_Status;
do
{
SD_Card_Status = HAL_SD_GetCardState(hsd);
} while(SD_Card_Status != HAL_SD_CARD_TRANSFER);
/* SDIO DMA DeInit */
/* SDIO DeInit */
HAL_DMA_DeInit(&hdma_sdio);
/* SDIO DMA Init */
/* SDIO Init */
hdma_sdio.Instance = DMA2_Channel4;
hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;
hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_sdio.Init.Mode = DMA_NORMAL;
hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;
if(HAL_DMA_Init(&hdma_sdio) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hsd, hdmarx, hdma_sdio);
Return_Status = HAL_SD_ReadBlocks_DMA(hsd, pData, BlockAdd, NumberOfBlocks);
return Return_Status;
}
7.1.3 使用例程
uint8_t sdcard_status = SDIO_WriteBlocks_DMA(&hsd, (uint8_t *)WBuf, 0, 1);
if(sdcard_status == HAL_OK){
while(HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
}
sdcard_status = SDIO_ReadBlocks_DMA(&hsd, (uint8_t *)RBuf, 0, 1);
if(sdcard_status == HAL_OK){
while(HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
}
else
while(1);
- 对SD卡的块地址为0的1块数据进行写入,内容为WBuf(此时调用7.1.1的写操作)
- 如果写入成功,则等待SD卡的写入完成(退出编程状态)
- 从SD卡的块地址为0的1块数据进行读取,内容存至RBuf(此时调用7.1.2的读操作)
- 如果成功则等待SD卡操作完成(退出编程状态),失败则进入死循环。
7.2 SDIO中的轮询模式示例
- 读SD卡指定数量的块数据
/*
Name:uint8_t SdGetCardInfo
----------------------
Des: 读SD卡指定数量的块数据
Ref:
Paras: buf: 数据保存的起始地址;addr: 块地址;count: 块数量;
Author: zx
Date: 2023年11月09日
*/
uint8_t SdReadDisk(uint8_t *u8pbuf, uint32_t u32addr, uint32_t u32count){
uint32_t u32timeout = SD_DATATIMEOUT;
if (HAL_SD_ReadBlocks(&sd_handle, u8pbuf, u32addr, u32count, SD_DATATIMEOUT) != HAL_OK){
return 1;
}
while ((HAL_SD_GetCardState(&sd_handle) != HAL_SD_CARD_TRANSFER) && (--u32timeout != 0));
if (u32timeout == 0){
return 1;
}
return 0;
}
- 写SD卡指定数量的块数据
/*
Name:uint8_t SdWriteDisk
----------------------
Des: 写SD卡指定数量的块数据
Ref:
Paras: buf: 数据保存的起始地址;addr: 块地址;count: 块数量;
Author: zx
Date: 2023年11月09日
*/
uint8_t SdWriteDisk(uint8_t *u8pbuf, uint32_t u32addr, uint32_t u32count){
uint32_t u32timeout = SD_DATATIMEOUT;
if (HAL_SD_WriteBlocks(&sd_handle, u8pbuf, u32addr, u32count, SD_DATATIMEOUT) != HAL_OK){
return 1;
}
while ((HAL_SD_GetCardState(&sd_handle) != HAL_SD_CARD_TRANSFER) && (--u32timeout != 0));
if (u32timeout == 0){
return 1;
}
return 0;
}
7.3 中断模式尚未成功
奇怪
8. 踩坑记录
8.1 f407 stm32CUBE生成代码错误 初始化不成功
配置总线操作失败,超时
修改此处数据,将SDIO_BUS_WIDE_4B
改为SDIO_BUS_WIDE_1B
参考博客:https://blog.csdn.net/justsure91/article/details/138273155
8.2 关于SD卡的工作时钟频率
- 参数设置
- 时钟配置
SD卡的初始化时时钟频率应为400KHz,由于设置时钟为48MHz,则分频时应该48M/400K=120(0x78) 如果不是400KHz,则SD卡在初始化时将会频繁失败。
- 初始化成功后,应该将400KHz改为工作频率。这里改为24MHz
/* USER CODE END SDIO_Init 1 */
hsd.Instance = SDIO;
hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
hsd.Init.ClockDiv = 0x78;
if (HAL_SD_Init(&hsd) != HAL_OK)
{
Error_Handler();
}
if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SDIO_Init 2 */
__HAL_SD_DISABLE(&hsd);
hsd.Init.ClockDiv = 0;
HAL_SD_Init(&hsd);
__HAL_SD_ENABLE(&hsd);