我们进行嵌入式开发时,常会遇到SD卡驱动或者扩展SDIO模块,ST提供的SD卡HAL标准库中常出现SDMMC,为什么SD经常与MMC一块出现,SD与SDIO又有什么关系呢?
一、SD/MMC/SDIO概念区分
MMC(MultiMediaCard)从本质上看,是一种用于固态非易失性存储的内存卡(memory card)规范,定义了诸如卡的形态、尺寸、容量、电气信号、和主机之间的通信协议等方方面面的内容。
从1997年MMC规范发布至今,基于不同的考量(物理尺寸、电压范围、管脚数量、最大容量、数据位宽、clock频率、安全特性、是否支持SPI mode、是否支持DDR mode、等等),进化出了MMC、SD、microSD、SDIO、eMMC等不同的规范,如下图所示(图片取自博客:MMC/SD/SDIO介绍):
SD卡(Secure Digital Memory Card)是在MMC的基础上发展而来的,相比MMC增加了两个主要特色:SD卡强调数据的安全,可以设定所储存的使用权限,防止数据被他人复制;SD卡的传输速度比MMC卡快。在数据传输和物理规范上,SD卡向前兼容了MMC卡,所有支持SD卡的设备也支持MMC卡,这也是我们在SD卡驱动文件中常见到SDMMC的原因。
SDIO(Secure Digital Input Output)是从SD规范演化来的,强调的是接口 I/O功能,不再关注另一端的具体形态(可以是Wi-Fi card、Bluetooth card、GPS card、GSM/GPRS card 、Camera card、Radio/TV card等)。SDIO和SD卡规范间的一个重要区别是增加了低速标准,低速卡的目标应用是以最小的硬件开销支持低速IO能力。因此,SDIO接口兼容SD卡,或者说SD卡就是其中一种具有安全存储功能的SDIO Card,SDIO Card也可以是兼具多种功能的组合卡Combo Card(比如 I/O + Memory)。
二、SDIO Bus简介
SDIO/SD协议规范包含三部分:Host Controller、SDIO Bus、SDIO Card,三者的结构框图如下所示:
SDIO总线和USB总线类似,都是采用主从模式通信,其中一端是主机端(Host Controller / Adapter),另一端是设备端(SDIO Card),采用 Host - Device 这样的设计是为了简化 Device 的设计,所有的通信都是由 Host 端发出命令开始的。在 Device 端只要能解析 Host 的命令,就可以同 Host 进行通信了,SDIO 的 Host 可以连接多个 Device。
2.1 SDIO Bus物理层
SDIO Bus的物理信号有 CK、CMD、DATA三类(SDIO适配器时钟SDIOCLK用于驱动SDIO Host,并用于产生SDIO_CK时钟):
- SDIO_CK:Host给Device的时钟信号,SDIO卡时钟频率全速模式可达0-25MHZ(低速模式为0-400KHZ),SD卡或MMC卡的高速版本时钟频率可达上百兆赫兹;
- SDIO_CMD:双向的命令、响应信号线,Command由Host发送给Device,Response由Device 返回给Host;
- SDIO_D[7:0]: 双向的数据传输线,数据位宽允许动态配置为 1-bit(默认)、4-bit、8-bit模式(数据线与命令线通常要保持上拉状态,以保护数据免受总线浮动影响);
- VCC/VDD:按照工作电压的不同,SD卡可分为高电压版(3.3V)和双电压版(1.8V和3.3V)。
SDIO的信号传输模式有SD 4-bit、SD 1-bit、SPI三种(8-bit模式一般用于MMC),以最常见的SD卡9 pin引脚(3根电源线 + 6根信号线)为例,分别工作在这三种模式下的引脚功能定义如下表所示:
依据 SD 标准,所有的 SD(记忆卡)与 SDIO(外设)都必须支持 SPI mode,因此 SPI mode 是「required」。SDIO新增的低速标准本身就为了减少硬件开支,因此不支持上面的SD 4-bit模式。
2.2 SDIO Bus协议层
SDIO 总线的通信是基于命令和数据流的,由一个起始位开始,由一个停止位终止。
- 命令(Command):就是一个标记,用于发起(或结束)一个操作,由主机发送到单个卡(寻址命令)或者所有卡(广播命令);
- 响应(Response):也是一个标记,从寻址的卡或者所有卡(同步)发送给主机,作为向先前接收到的命令的回答;
- 数据(Data):可以从主机到卡,也可以从卡到主机,数据线的个数可以是 1、4、8。
SDIO总线上的基本交互是命令/响应交互,这种总线交互直接在命令或者响应的结构里面传输它们的信息。SDIO的每次操作都是由Host 在CMD线上发起一个命令,对于有的Command,Device 需要返回 Response,有的则不需要。
SDIO总线上传输的数据可以是数据块(比如SD memory card 这类块设备),也可以是数据流(比如 I/O function card 这类字符设备),数据块需附加CRC(Cyclic Redundancy Check)位来保证数据准确传输。主机端可以配置单线或者多线传输,数据块可以单块操作或多块操作,数据块写操作使用busy来表示DAT0数据线上的持续写操作,不管使用几线传输。SDIO总线上命令、响应、数据块、数据流的交互示意图如下:
2.2.1 SDIO Command
Command是一次操作开始的令牌,从主机发送到一个卡设备(寻址命令)或者连接到主机的所有卡设备(广播命令)。每个命令都有一个起始位和结束位,总长度为48 bits,并且每个命令都有 7 bits 的CRC 校验码。命令只能通过CMD线传输,并且MSB(Most Significant Bit)为先(也即最高有效位在前),Command的编码格式如下图所示:
命令的开始位(始终为0)、传输位(始终为1,表示Host–>Device的传输方向)、 CRC7 和结束位(始终为1)由 SDIO 硬件控制,我们需要设置的就只有命令索引和参数部分。其中命令索引(CMD0 ~ CMD63共64个命令)在 SDIO_CMD 寄存器里面设置,命令参数则由寄存器 SDIO_ARG 设置。
SDIO的命令分为应用相关命令ACMD 和通用命令CMD 两部分,应用相关命令ACMD 的发送,必须先发送通用命令 CMD55 ,然后才能发送应用相关命令 ACMD 。 SDIO的命令根据发送范围和是否带响应可以分为下面四种不同的类型:
- Broadcast Commands(BC):发送到所有卡,没有响应返回;
- Broadcast Commands with Response(BCR):发送到所有卡,同时收到从所有卡返回的响应;
- Addressed(point-to-point) Commands(AC):由Host 发送到指定的卡设备,没有数据的传输;
- Address(point-to-point) Data Transfer Commands(ADTC):由Host 发送到指定的卡设备且伴随有数据传输。
SD卡与SDIO卡支持的命令共分为12类(class 0 ~ class 11),下面按命令类别class和命令类型type,将SD/SDIO支持的常用命令及其功能描述汇总如下(保留命令、SPI模式命令等没有列出):
上面的命令中出现的CID、CSD、OCR、SCR等都是卡设备寄存器(SD/SDIO Card寄存器信息下文再单独介绍),实际上Host 与Card Device 之间的命令/响应信息交互,都是通过Host 读写Card Device 的寄存器信息实现的。类似于,CPU控制SDIO Host 发送命令、解析响应、传输数据等操作,都是通过CPU 读写 SDIO Host 的寄存器信息实现的。
2.2.2 SDIO Response
Response从卡设备发回数据的令牌,回应主机之前发送的命令。每个响应也都有一个起始位(始终为0)、传输位(始终为0,表示Device --> Host 方向)和结束位(始终为1),总长度为48 bits(短响应)或者136 bits(长响应),有相应的 CRC 校验码(除了R3/R4没有CRC)。响应信号也只在 CMD 线上传输,并且 MSB(Most Significant Bit) 为先,Response的编码格式如下图所示:
SD存储卡有5种类型的Response:R1/R1b、R2、R3、R6、R7,SDIO卡还支持另外两种Response 类型:R4、R5。只有R2 为136 bits的长响应(为了返回 CID 或 CSD 这类长达 128 bits的数据),其余的响应均为48 bits的短响应。从前面SD/SDIO命令列表中可以看到,每个命令对应哪种响应格式,这七种Response的内容及描述如下:
上面这些响应返回的内容,多数也是卡设备自身的寄存器信息,比如CID、CSD、OCR、CSR等,这些寄存器在下文单独介绍。
2.2.3 SDIO Data
Data可以从主机Host 到卡设备,也可以从卡设备到主机,通过数据线传输,SDIO总线定义了3种数据模式——1bit、4bit、8bit,其中SD/SDIO卡只用到1bit和4bit模式(MMC卡会使用8-bit 模式),SD卡传输数据时使用的是4bit模式。
在SDIO_DATA线上传输的数据包格式跟Command和Response类似的是,在每根数据线上传输的每个数据包也都包含一个起始位和一个结束位,而且每个数据包内也都包含CRC校验位。CRC状态反馈和busy 标识只会通过DATA0 从卡设备Device 发送到主机Host,跟DATA1 ~ DATA3无关。SD有两种数据包格式:
- Usual data(8-bit width):常规数据是先发送低字节,再发送高字节,但每个字节则是先高位后低位的顺序发送;
- Wide width data(SD memory register):宽位数据从高位开始传输。
从上面两幅图的对比可以看出,宽位数据包主要用于传输SD Status和SCR(SD Configuration Register)等信息,正常的数据传输主要还是使用常规数据包格式。
三、SDIO Card简介
3.1 SD memory card简介
前面介绍SDIO bus传输模式时,拿SD存储卡作为SDIO Card示例,我们都见过SD卡长啥样,但你知道SD存储卡的内部结构吗?
3.1.1 SD卡寄存器简介
SD卡内部跟SDIO bus直接相连的是Card interface controller,与Card interface controller相连的除了memory core interface用于访问存储颗粒外,就是SD卡的各个寄存器了。从上图也可以看到SD卡主要的寄存器位宽,各个寄存器的功能描述如下:
限于篇幅,SD卡各个寄存器内部每一位的作用及设置就不展开介绍了,感兴趣可以到网站:https://www.sdcard.org/downloads/pls/archive/index.html下载相关文档查阅。
3.1.2 SD卡操作模式简介
SD卡系统(Host & Card)定义了两种操作模式:
- 卡识别模式:卡在复位后会处于“识别模式”,直到收到 SEND_RCA(CMD3)命令(主机复位所有卡片后将进入该模式);
- 数据传输模式:当 RCA 第一次发布后,卡会处于“数据传输模式”(主机会在总线上所有的卡都被识别后进入该模式)。
- SD卡识别模式
当主机复位所有卡片后将进入卡片识别模式,在识别模式下将会确认卡片的操作电压并要求卡片发布自己的相对地址(默认地址为0x0000),在此操作模式下只会用到 CMD 线并且工作在专门的时钟频率 F-od(400 KHz)。SD卡识别模式的流程图如下:
- 上电后发送命令CMD0(GO_IDLE_STATE),使所有的卡设备进入“Idle”状态;
- 发送命令CMD8(只有V2.0以上版本的SD卡才支持该命令,MMC卡和V1.x的SD卡不支持该命令),如果卡设备有Response,说明该卡为SD V2.0以上版本;
- 发送命令CMD55+ACMD41(在发送ACMD41这类应用指令之前需要先发送CMD55指令),探测卡设备的工作电压是否符合host 端的要求,并通过HCS位告诉SD卡,主机Host 是否支持高容量卡SDHC(HCS置1 表示Host 支持SDHC),ACMD41的命令与响应格式如下:
命令ACMD41的响应类型是R3,R3主要是向Host 返回OCR寄存器的内容,除了告知Host 自己支持的工作电压外,还会通过CCS位告诉Host 自己是否为高容量卡(CCS为1 表示自己是高容量SD卡)。OCR的bit 31(busy)用来告知Host 自己的上电状态,ACMD41命令是Host 向卡设备循环发送的,当SD卡按照ACMD41命令里面的工作电压完成上电初始化流程后,busy 会置 1,通知Host 已ready,可以继续接收命令了。不能支持Host 指定电压范围的卡设备在收到ACMD41命令后会进入“Inactive”状态。 - 当卡设备按照Host 要求的工作电压,上电初始化完成后,Host 发送CMD2命令,获取卡设备的CID信息;
- 发送CMD3命令,要求卡设备发布自己的相对地址RCA,一旦主机接收到相对地址,卡片就进入数据传输模式的待机状态(这时候主机可以通过 CMD3 命令重复要求卡片发布相对地址)。
上面是SD V2.0的卡初始化与识别流程,并不包括SD I/O卡,在Host 收到ACMD41命令的响应后,就能识别出卡设备是MMC卡、SD V1.0标准容量卡(V1.0不支持高容量卡)、SD V2.0标准容量卡、SD V2.0高容量卡 这四类中的哪一类。在SD更新的标准中,又新增了SDXC和SDUC等更高容量的SD卡或UHS卡,这类卡的初始化与识别流程可以参考更新的标准。
- SD卡数据传输模式
当相对卡地址(RCA)第一次发布后,卡会处于“数据传输模式”,主机会在总线上所有的卡都被识别后进入这个模式,数据传输模式下可以读写SD卡的相关数据。当没有数据传输时,DAT线被拉高。当有数据传输时,主机通过命令CMD7先选中卡,然后发送CMD9命令获得卡设备的CSD信息,发送ACMD6命令设置总线位宽,此时可以对SD卡进行读写操作。SD卡数据传输模式的流程图如下:
上图中SD卡的Sending-data状态对于Host 来说就是从SD卡读取数据的过程,SD卡的Receiving-data状态对于Host 来说则是向SD卡写入数据的过程,SD卡的Programming状态则是SD卡将接收到的数据写入内部存储介质的过程。
数据传输时,发送方先拉低所有数据线,发送起始位;然后发送连续的数据流,数据流包含了有效数据;最后拉高所有数据线发送结束位。数据传输是和时钟信号同时进行的,数据传输的有效性通过CRC校验值验证。主机Host 读取、写入、擦除数据的过程,都是通过前面介绍的Command/Response完成的,上图中出现的每个命令的功能可以参考前面的命令列表。
3.2 SD I/O Card简介
3.2.1 SD I/O Card寄存器简介
SDIO本身只是一种接口技术,类似于SPI接口,通过 I/O 引脚来连接外部设备,并且与外围设备之间传输数据,所以这些外围设备又被称为SD I/O卡。SD I/O Card与SD Card提供的功能不同,卡内部的寄存器自然也不一样(支持的Command/ Response也有差异,详见前面的命令与响应类型列表),比如单纯的SD I/O Card不支持CID、CSD、SCR、SSR、DSR等寄存器(Combo组合卡中的memory card依然支持SD卡的寄存器,这里只讨论单纯的 I/O function card)。SD I/O Card有哪些寄存器来支持Command/Response呢?
SD I/O Card访问与SD Memory Card访问的不同之处在于:I/O Card无需FAT文件结构或块的概念,就可以基于数据字节数直接写入或读取寄存器( I/O Card 可以看作字符设备);SD Memory Card 则依赖于固定块长度的概念,命令读取或写入这些固定大小块的倍数( Memory Card 可以看作块设备)。
每个SDIO卡可能具有1到7个功能以及一个内置的存储功能(Combo Card),功能是自包含的 I/O 设备,I/O 功能可能彼此相同或完全不同。所有 I/O 功能都组织为寄存器的集合,每个 I/O 功能最多可以有131072(即217)个寄存器,这些寄存器在 I/O 卡内的宽度可以为8、16或32位,所有寻址都基于字节访问。单个寄存器读写访问通常用于初始化 I/O 功能或读取单个状态或数据值(CMD52命令),对固定地址的多次访问用于从卡中的数据 FIFO 寄存器读取或写入数据(CMD53命令),读到递增地址用于从卡内部的RAM区域读取数据或向其中写入数据集合。
SDIO卡具有固定的内部寄存器空间和功能专有区域,这些固定区域包含有关卡的信息以及某些强制和可选寄存器的信息,允许任何主机访问。功能专有区域是每个功能专属的区域,由应用程序定义标准SDIO功能的规范,由供应商定义非标准功能的规范,具有多种 I/O 功能的SDIO卡内部CIA(Common I/O Area)与可选CSA(Code Storage Area)映射图如下:
通用 I/O 区域(CIA)必须在所有SDIO卡上实现,主机通过对功能 0 的 I/O 读取和写入来访问CIA。CIA中的寄存器提供了启用或禁用 I/O 功能、控制中断的产生以及选择性加载支持 I/O 功能的软件等操作,CIA中的寄存器还提供有关 I/O 功能的能力和需求信息。 CIA支持三种不同的寄存器结构:
- Card Common Control Registers (CCCR):可用于主机快速检查并控制每个 I/O 功能的启用和中断;
- Function Basic Registers (FBR):每个受支持的 I/O 功能都有一个256字节的区域,用于主机快速确定每个功能的能力和需求,并启用软件加载以支持 I/O 功能;
- Card Information Structure (CIS):提供有关卡和各个功能的更完整信息。
为了支持SDIO卡的“即插即用”概念,I/O 卡中包含的每个功能可能都需要包含一块内存,用于存储驱动程序或应用程序。另外,由于相同的SDIO卡可能会在多个不同的主机平台上使用,因此可能需要为每个功能提供几个不同版本的代码。一种选择是将这些程序存储在组合卡(Combo card)的标准SD memory部分中。另一种选择是,将这些代码加载到 I/O card 可选的CSA(Code Storage Area)中,CSA是一个独立的16MB内存区域,可使用CSA地址指针或包含在FBR中的CSA窗口寄存器来访问它。
3.2.2 SD I/O Card初始化流程
SD I/O Card与SD Memory Card类似,也支持两种操作模式:卡识别模式与数据传输模式。SD I/O Card数据传输模式与SD Memory Card类似,只是交互的Command/Response不一样,可对照SD Memory Card数据传输模式图和SDIO卡支持的Command/Response列表理解,这里就不再赘述了。
SD I/O Card的卡识别模式与SD Memory Card有较大差异,主要也是因为两者在卡识别与初始化过程中交互的Command/Response不一样。SD I/O Card的卡识别模式不仅包括单纯 I/O Card 或Memory Card 的识别,还包括组合卡Combo Card的识别,因此SD I/O Card的识别与初始化流程比较复杂:
- 上电或重新初始化时,memory card发送CMD0命令使卡进入“idle”状态,I/O card则发送CMD52 (write to I/O Reset in CCCR)命令使卡进入“idle”状态;
- Memory card发送CMD8命令识别卡支持的版本,I/O card则不需要分辨卡的版本;
- Memory card发送ACMD41命令探测卡的工作电压是否符合Host 端的要求,并通过HCS和CCS交换所支持的容量信息;I/O card则发送CMD5命令探测卡的工作电压是否符合Host 端的要求,单纯的 I/O card没有容量区分,响应也应包含 I/O 功能设备的信息,CMD5命令及响应格式如下:
命令CMD5的响应除了包含 I/O OCR(只有24位,没有CCS与busy位)信息外,还包含 I/O 上电状态位(当 I/O card按照CMD5命令里面的工作电压完成上电初始化后,该C位置1)、I/O 功能数量、是否附带memory功能(也即是单纯 I/O卡还是Combo卡)等信息。主机Host 会向 I/O card循环发送CMD5命令,直到收到的 R4 中 C 位置 1,不能支持Host 知道电压的卡设备在收到CMD5命令后会进入“inactive”状态; - 当memory card或combo card按照Host 要求的工作电压,上电初始化完成后,Host 发送CMD2命令,获取卡设备的CID信息,I/O card没有CID寄存器不需要该操作(会读取CIA获得 I/O card信息);
- 所有完成上电初始化后的卡设备发送CMD3命令,要求卡设备发布自己的相对地址RCA,一旦主机接收到相对地址,卡片就进入数据传输模式的待机状态(这时候主机可以通过 CMD3 命令重复要求卡片发布相对地址)。
上面是SDIO V2.0 SD mode的卡初始化与识别流程,并不包括SPI mode,SPI mode的Command/Response格式与SD mode有所区别,SPI mode下的SDIO卡初始化流程自然也跟SD mode有所区别,想了解更多SPI mode的信息,可以参考《SDIO Simplified Specification》。
四、SDIO Host 简介
我们在外设驱动开发中常接触的是SDIO Host controller(Adapter)端,我们编写的驱动程序或应用程序被CPU取指、译码、执行,访问外设实际是通过操作 Host Adapter 的寄存器,控制Host 端通过外设总线接口向Device 端发送符合协议规范的命令实现的。前面已经介绍了 Host 端访问或控制 Device 端也是通过读写 Device 端的寄存器实现的,在对Device 端进行识别、初始化、数据传输时,也要按照 Device 的要求进行。下面介绍 CPU 如何通过系统总线AHB访问或控制 Host 端,HAL 标准库(芯片厂商ST提供的)为我们提供了哪些访问Host 端的接口,供我们进行驱动或应用开发呢?
前面介绍了SDIO结构框图,包含了Host Controller、SDIO Bus、SDIO Card三部分,下面展示SDIO Host Controller(Adapter)的内部结构:
SDIO适配器主要包含四个模块:寄存器、控制单元、命令通道、数据通道,HAL标准库则为这四个模块分别提供了数据描述结构:
- SDIO适配器寄存器
前面介绍SDIO Command时提到过,命令索引在 SDIO_CMD 寄存器里面设置,命令参数则由寄存器 SDIO_ARG 设置,我们以STM32L475芯片为例,看下SDIO Host 总共有哪些寄存器:
// libraries\STM32L4xx_HAL\CMSIS\Device\ST\STM32L4xx\Include\stm32l475xx.h
/* @brief Secure digital input/output Interface */
typedef struct
{
__IO uint32_t POWER; /*!< SDMMC power control register, Address offset: 0x00 */
__IO uint32_t CLKCR; /*!< SDMMC clock control register, Address offset: 0x04 */
__IO uint32_t ARG; /*!< SDMMC argument register, Address offset: 0x08 */
__IO uint32_t CMD; /*!< SDMMC command register, Address offset: 0x0C */
__I uint32_t RESPCMD; /*!< SDMMC command response register, Address offset: 0x10 */
__I uint32_t RESP1; /*!< SDMMC response 1 register, Address offset: 0x14 */
__I uint32_t RESP2; /*!< SDMMC response 2 register, Address offset: 0x18 */
__I uint32_t RESP3; /*!< SDMMC response 3 register, Address offset: 0x1C */
__I uint32_t RESP4; /*!< SDMMC response 4 register, Address offset: 0x20 */
__IO uint32_t DTIMER; /*!< SDMMC data timer register, Address offset: 0x24 */
__IO uint32_t DLEN; /*!< SDMMC data length register, Address offset: 0x28 */
__IO uint32_t DCTRL; /*!< SDMMC data control register, Address offset: 0x2C */
__I uint32_t DCOUNT; /*!< SDMMC data counter register, Address offset: 0x30 */
__I uint32_t STA; /*!< SDMMC status register, Address offset: 0x34 */
__IO uint32_t ICR; /*!< SDMMC interrupt clear register, Address offset: 0x38 */
__IO uint32_t MASK; /*!< SDMMC mask register, Address offset: 0x3C */
uint32_t RESERVED0[2]; /*!< Reserved, 0x40-0x44 */
__I uint32_t FIFOCNT; /*!< SDMMC FIFO counter register, Address offset: 0x48 */
uint32_t RESERVED1[13]; /*!< Reserved, 0x4C-0x7C */
__IO uint32_t FIFO; /*!< SDMMC data FIFO register, Address offset: 0x80 */
} SDMMC_TypeDef;
上面的CMD寄存器设置命令索引,ARG寄存器设置命令参数,RESPCMD与RESP1 ~ RESP4 则是用来处理响应的。其余的寄存器有负责电源管理、时钟控制、数据传输、状态查询、中断管理等的,想有更详细的了解,可以参阅STM32L475参考手册。
- SDIO控制单元
SDIO控制单元包括电源管理与时钟管理两部分,控制SDIO_CK引脚的时钟输出和数据总线宽度,HAL标准库提供了初始化配置结构体来实现控制单元的配置:
// libraries\STM32L4xx_HAL\STM32L4xx_HAL_Driver\Inc\stm32l4xx_ll_sdmmc.h
/* @brief SDMMC Configuration Structure definition */
typedef struct
{
uint32_t ClockEdge; /*!< Specifies the clock transition on which the bit capture is made.
This parameter can be a value of @ref SDMMC_LL_Clock_Edge */
#if !defined(STM32L4R5xx) && !defined(STM32L4R7xx) && !defined(STM32L4R9xx) && !defined(STM32L4S5xx) && !defined(STM32L4S7xx) && !defined(STM32L4S9xx)
uint32_t ClockBypass; /*!< Specifies whether the SDMMC Clock divider bypass is
enabled or disabled.
This parameter can be a value of @ref SDMMC_LL_Clock_Bypass */
#endif /* !STM32L4R5xx && !STM32L4R7xx && !STM32L4R9xx && !STM32L4S5xx && !STM32L4S7xx && !STM32L4S9xx */
uint32_t ClockPowerSave; /*!< Specifies whether SDMMC Clock output is enabled or
disabled when the bus is idle.
This parameter can be a value of @ref SDMMC_LL_Clock_Power_Save */
uint32_t BusWide; /*!< Specifies the SDMMC bus width.
This parameter can be a value of @ref SDMMC_LL_Bus_Wide */
uint32_t HardwareFlowControl; /*!< Specifies whether the SDMMC hardware flow control is enabled or disabled.
This parameter can be a value of @ref SDMMC_LL_Hardware_Flow_Control */
uint32_t ClockDiv; /*!< Specifies the clock frequency of the SDMMC controller.
This parameter can be a value between Min_Data = 0 and Max_Data = 1023 */
}SDMMC_InitTypeDef;
/* @defgroup SDMMC_LL_Clock_Edge Clock Edge */
#define SDMMC_CLOCK_EDGE_RISING ((uint32_t)0x00000000U)
#define SDMMC_CLOCK_EDGE_FALLING SDMMC_CLKCR_NEGEDGE
/* @defgroup SDMMC_LL_Clock_Bypass Clock Bypass */
#define SDMMC_CLOCK_BYPASS_DISABLE ((uint32_t)0x00000000U)
#define SDMMC_CLOCK_BYPASS_ENABLE SDMMC_CLKCR_BYPASS
/* @defgroup SDMMC_LL_Clock_Power_Save Clock Power Saving */
#define SDMMC_CLOCK_POWER_SAVE_DISABLE ((uint32_t)0x00000000U)
#define SDMMC_CLOCK_POWER_SAVE_ENABLE SDMMC_CLKCR_PWRSAV
/* @defgroup SDMMC_LL_Bus_Wide Bus Width */
#define SDMMC_BUS_WIDE_1B ((uint32_t)0x00000000U)
#define SDMMC_BUS_WIDE_4B SDMMC_CLKCR_WIDBUS_0
#define SDMMC_BUS_WIDE_8B SDMMC_CLKCR_WIDBUS_1
/* @defgroup SDMMC_LL_Hardware_Flow_Control Hardware Flow Control */
#define SDMMC_HARDWARE_FLOW_CONTROL_DISABLE ((uint32_t)0x00000000U)
#define SDMMC_HARDWARE_FLOW_CONTROL_ENABLE SDMMC_CLKCR_HWFC_EN
- SDIO命令通道
SDIO命令通道控制命令发送与响应接收,HAL标准库提供了命令初始化结构体来实现命令通道的配置:
// libraries\STM32L4xx_HAL\STM32L4xx_HAL_Driver\Inc\stm32l4xx_ll_sdmmc.h
/* @brief SDMMC Command Control structure */
typedef struct
{
uint32_t Argument; /*!< Specifies the SDMMC command argument which is sent
to a card as part of a command message. If a command
contains an argument, it must be loaded into this register
before writing the command to the command register. */
uint32_t CmdIndex; /*!< Specifies the SDMMC command index. It must be Min_Data = 0 and
Max_Data = 64 */
uint32_t Response; /*!< Specifies the SDMMC response type.
This parameter can be a value of @ref SDMMC_LL_Response_Type */
uint32_t WaitForInterrupt; /*!< Specifies whether SDMMC wait for interrupt request is
enabled or disabled.
This parameter can be a value of @ref SDMMC_LL_Wait_Interrupt_State */
uint32_t CPSM; /*!< Specifies whether SDMMC Command path state machine (CPSM)
is enabled or disabled.
This parameter can be a value of @ref SDMMC_LL_CPSM_State */
}SDMMC_CmdInitTypeDef;
/* @defgroup SDMMC_LL_Response_Type Response Type */
#define SDMMC_RESPONSE_NO ((uint32_t)0x00000000U)
#define SDMMC_RESPONSE_SHORT SDMMC_CMD_WAITRESP_0
#define SDMMC_RESPONSE_LONG SDMMC_CMD_WAITRESP
/* @defgroup SDMMC_LL_Wait_Interrupt_State Wait Interrupt */
#define SDMMC_WAIT_NO ((uint32_t)0x00000000U)
#define SDMMC_WAIT_IT SDMMC_CMD_WAITINT
#define SDMMC_WAIT_PEND SDMMC_CMD_WAITPEND
/* @defgroup SDMMC_LL_CPSM_State CPSM State */
#define SDMMC_CPSM_DISABLE ((uint32_t)0x00000000U)
#define SDMMC_CPSM_ENABLE SDMMC_CMD_CPSMEN
结构体SDMMC_CmdInitTypeDef 中的CPSM(Command path state machine)是由命令通道的状态标志和控制逻辑共同组成的一个控制中心,它能够根据控制信号按照预先设定的状态进行状态转移,协调相关信号动作,完成特定操作,命令通道状态机的状态迁移图如下:
当写入命令寄存器并设置了CPSM使能位,CPSM 进入Send 状态,开始发送命令。命令发送完成时,CPSM 设置状态标志并在不需要响应时进入 Idle 状态,当需要收到响应时则进入 Wait 状态,命令定时器开始运行。待Device开始返回响应,则CPSM从Wait状态进入Receive状态,响应接收完成后进入Idle状态,当CPSM进入 Receive 状态之前产生了超时,则设置超时标志并进入 Idle 状态。一个正常的需要返回响应的命令传输状态迁移为:Idle --> Send --> Wait --> Receive --> Idle。
- SDIO数据通道
SDIO数据通道控制数据的发送与接收,FIFO(first in first out)是一个先进先出数据缓冲区,内部按照32位对齐,共128 Bytes,同时具有发送和接收单元,HAL标准库提供了数据初始化结构体来实现数据通道的配置:
// libraries\STM32L4xx_HAL\STM32L4xx_HAL_Driver\Inc\stm32l4xx_ll_sdmmc.h
/* @brief SDMMC Data Control structure */
typedef struct
{
uint32_t DataTimeOut; /*!< Specifies the data timeout period in card bus clock periods. */
uint32_t DataLength; /*!< Specifies the number of data bytes to be transferred. */
uint32_t DataBlockSize; /*!< Specifies the data block size for block transfer.
This parameter can be a value of @ref SDMMC_LL_Data_Block_Size */
uint32_t TransferDir; /*!< Specifies the data transfer direction, whether the transfer
is a read or write.
This parameter can be a value of @ref SDMMC_LL_Transfer_Direction */
uint32_t TransferMode; /*!< Specifies whether data transfer is in stream or block mode.
This parameter can be a value of @ref SDMMC_LL_Transfer_Type */
uint32_t DPSM; /*!< Specifies whether SDMMC Data path state machine (DPSM)
is enabled or disabled.
This parameter can be a value of @ref SDMMC_LL_DPSM_State */
}SDMMC_DataInitTypeDef;
/* @defgroup SDMMC_LL_Data_Block_Size Data Block Size */
#define SDMMC_DATABLOCK_SIZE_1B ((uint32_t)0x00000000U)
#define SDMMC_DATABLOCK_SIZE_2B SDMMC_DCTRL_DBLOCKSIZE_0
#define SDMMC_DATABLOCK_SIZE_4B SDMMC_DCTRL_DBLOCKSIZE_1
#define SDMMC_DATABLOCK_SIZE_8B (SDMMC_DCTRL_DBLOCKSIZE_0|SDMMC_DCTRL_DBLOCKSIZE_1)
#define SDMMC_DATABLOCK_SIZE_16B SDMMC_DCTRL_DBLOCKSIZE_2
#define SDMMC_DATABLOCK_SIZE_32B (SDMMC_DCTRL_DBLOCKSIZE_0|SDMMC_DCTRL_DBLOCKSIZE_2)
#define SDMMC_DATABLOCK_SIZE_64B (SDMMC_DCTRL_DBLOCKSIZE_1|SDMMC_DCTRL_DBLOCKSIZE_2)
#define SDMMC_DATABLOCK_SIZE_128B (SDMMC_DCTRL_DBLOCKSIZE_0|SDMMC_DCTRL_DBLOCKSIZE_1|SDMMC_DCTRL_DBLOCKSIZE_2)
#define SDMMC_DATABLOCK_SIZE_256B SDMMC_DCTRL_DBLOCKSIZE_3
#define SDMMC_DATABLOCK_SIZE_512B (SDMMC_DCTRL_DBLOCKSIZE_0|SDMMC_DCTRL_DBLOCKSIZE_3)
#define SDMMC_DATABLOCK_SIZE_1024B (SDMMC_DCTRL_DBLOCKSIZE_1|SDMMC_DCTRL_DBLOCKSIZE_3)
#define SDMMC_DATABLOCK_SIZE_2048B (SDMMC_DCTRL_DBLOCKSIZE_0|SDMMC_DCTRL_DBLOCKSIZE_1|SDMMC_DCTRL_DBLOCKSIZE_3)
#define SDMMC_DATABLOCK_SIZE_4096B (SDMMC_DCTRL_DBLOCKSIZE_2|SDMMC_DCTRL_DBLOCKSIZE_3)
#define SDMMC_DATABLOCK_SIZE_8192B (SDMMC_DCTRL_DBLOCKSIZE_0|SDMMC_DCTRL_DBLOCKSIZE_2|SDMMC_DCTRL_DBLOCKSIZE_3)
#define SDMMC_DATABLOCK_SIZE_16384B (SDMMC_DCTRL_DBLOCKSIZE_1|SDMMC_DCTRL_DBLOCKSIZE_2|SDMMC_DCTRL_DBLOCKSIZE_3)
/* @defgroup SDMMC_LL_Transfer_Direction Transfer Direction */
#define SDMMC_TRANSFER_DIR_TO_CARD ((uint32_t)0x00000000U)
#define SDMMC_TRANSFER_DIR_TO_SDMMC SDMMC_DCTRL_DTDIR
/* @defgroup SDMMC_LL_Transfer_Type Transfer Type */
#define SDMMC_TRANSFER_MODE_BLOCK ((uint32_t)0x00000000U)
#define SDMMC_TRANSFER_MODE_STREAM SDMMC_DCTRL_DTMODE
/* @defgroup SDMMC_LL_DPSM_State DPSM State */
#define SDMMC_DPSM_DISABLE ((uint32_t)0x00000000U)
#define SDMMC_DPSM_ENABLE SDMMC_DCTRL_DTEN
结构体SDMMC_DataInitTypeDef 中的DPSM(Data path state machine)可以控制数据的发送、接收流程,数据通道状态机的状态迁移图如下:
发送流程:设置数据控制寄存器的数据使能位以及数据方向为“Host to Card”,此时DPSM 进入Wait_S 状态,一旦FIFO中有数据写入时,DPSM 进入 Send 状态,开始发送数据到卡设备。根据数据控制寄存器中传输模式位的设置,可以是数据块传输或数据流传输:在块模式下,当数据块计数器为0,DPSM发送CRC校验码,然后发送结束位,并进入 Busy 状态;在流模式下,当使能位为高同时数据计数器不为0,DPSM向卡发送数据,然后进入 Idle 状态。
接收流程:设置数据控制寄存器的数据使能位以及数据方向为“Card to Host”,此时DPSM 进入Wait_R 状态并等待开始位,当收到开始位时, DPSM进入 Receive 状态,同时数据通道子单元开始从卡接收数据,接收到的串行数据被组合为字节并写入数据FIFO。根据数据控制寄存器中传输模式位的设置,也可以是数据块传输或数据流传输:在块模式下,当数据块计数器为0,DPSM等待接收CRC码,如果接收到的代码与内部产生的CRC码匹配,则DPSM进入Wait_R 状态,否则设置CRC失败状态标志同时DPSM进入到 Idle 状态;在流模式下,当数据计数器不为0,DPSM接收数据,当计数器为0,将移位寄存器中的剩余数据写入数据FIFO,同时DPSM进入Wait_R 状态。
- HAL提供的SDIO配置接口函数
前面HAL标准库为我们提供了四个结构体,方便我们对SDIO Host controller 的四个模块分别进行配置,既然有结构体自然有接口函数。我们在进行程序开发时,只需要调用HAL提供的接口函数就可以控制这几个模块实现我们想要的功能,HAL为我们提供的接口函数如下:
// libraries\STM32L4xx_HAL\STM32L4xx_HAL_Driver\Inc\stm32l4xx_ll_sdmmc.h
/* Initialization/de-initialization functions **********************************/
HAL_StatusTypeDef SDMMC_Init(SDMMC_TypeDef *SDMMCx, SDMMC_InitTypeDef Init);
/* I/O operation functions *****************************************************/
uint32_t SDMMC_ReadFIFO(SDMMC_TypeDef *SDMMCx);
HAL_StatusTypeDef SDMMC_WriteFIFO(SDMMC_TypeDef *SDMMCx, uint32_t *pWriteData);
/* Peripheral Control functions ************************************************/
HAL_StatusTypeDef SDMMC_PowerState_ON(SDMMC_TypeDef *SDMMCx);
HAL_StatusTypeDef SDMMC_PowerState_OFF(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_GetPowerState(SDMMC_TypeDef *SDMMCx);
/* Command path state machine (CPSM) management functions */
HAL_StatusTypeDef SDMMC_SendCommand(SDMMC_TypeDef *SDMMCx, SDMMC_CmdInitTypeDef *Command);
uint8_t SDMMC_GetCommandResponse(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_GetResponse(SDMMC_TypeDef *SDMMCx, uint32_t Response);
/* Data path state machine (DPSM) management functions */
HAL_StatusTypeDef SDMMC_ConfigData(SDMMC_TypeDef *SDMMCx, SDMMC_DataInitTypeDef* Data);
uint32_t SDMMC_GetDataCounter(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_GetFIFOCount(SDMMC_TypeDef *SDMMCx);
/* SDMMC Cards mode management functions */
HAL_StatusTypeDef SDMMC_SetSDMMCReadWaitMode(SDMMC_TypeDef *SDMMCx, uint32_t SDMMC_ReadWaitMode);
/* SDMMC Commands management functions */
uint32_t SDMMC_CmdBlockLength(SDMMC_TypeDef *SDMMCx, uint32_t BlockSize);
uint32_t SDMMC_CmdReadSingleBlock(SDMMC_TypeDef *SDMMCx, uint32_t ReadAdd);
uint32_t SDMMC_CmdReadMultiBlock(SDMMC_TypeDef *SDMMCx, uint32_t ReadAdd);
uint32_t SDMMC_CmdWriteSingleBlock(SDMMC_TypeDef *SDMMCx, uint32_t WriteAdd);
uint32_t SDMMC_CmdWriteMultiBlock(SDMMC_TypeDef *SDMMCx, uint32_t WriteAdd);
uint32_t SDMMC_CmdEraseStartAdd(SDMMC_TypeDef *SDMMCx, uint32_t StartAdd);
uint32_t SDMMC_CmdSDEraseStartAdd(SDMMC_TypeDef *SDMMCx, uint32_t StartAdd);
uint32_t SDMMC_CmdEraseEndAdd(SDMMC_TypeDef *SDMMCx, uint32_t EndAdd);
uint32_t SDMMC_CmdSDEraseEndAdd(SDMMC_TypeDef *SDMMCx, uint32_t EndAdd);
uint32_t SDMMC_CmdErase(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_CmdStopTransfer(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_CmdSelDesel(SDMMC_TypeDef *SDMMCx, uint64_t Addr);
uint32_t SDMMC_CmdGoIdleState(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_CmdOperCond(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_CmdAppCommand(SDMMC_TypeDef *SDMMCx, uint32_t Argument);
uint32_t SDMMC_CmdAppOperCommand(SDMMC_TypeDef *SDMMCx, uint32_t Argument);
uint32_t SDMMC_CmdBusWidth(SDMMC_TypeDef *SDMMCx, uint32_t BusWidth);
uint32_t SDMMC_CmdSendSCR(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_CmdSendCID(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_CmdSendCSD(SDMMC_TypeDef *SDMMCx, uint32_t Argument);
uint32_t SDMMC_CmdSetRelAdd(SDMMC_TypeDef *SDMMCx, uint16_t *pRCA);
uint32_t SDMMC_CmdSendStatus(SDMMC_TypeDef *SDMMCx, uint32_t Argument);
uint32_t SDMMC_CmdStatusRegister(SDMMC_TypeDef *SDMMCx);
uint32_t SDMMC_CmdOpCondition(SDMMC_TypeDef *SDMMCx, uint32_t Argument);
uint32_t SDMMC_CmdSwitch(SDMMC_TypeDef *SDMMCx, uint32_t Argument);
想了解SDIO总线设备驱动模型或SDIO WIFI 驱动开发的读者,可以参考博客:SDIO设备对象管理 + AP6181(BCM43362) WiFi模块。