Skip to main content

14. TF卡编程

14.1 背景知识

​ 多媒体存储卡(英语:Multimedia Card),又译MMC卡,是一种快闪记忆卡标准。在1997年由西门子及闪迪共同开发,技术基于东芝的NAND快闪记忆技术,因此较早期基于Intel NOR快闪记忆技术的存储卡(例如CF卡)更细小。MMC卡大小与一张邮票差不多,约24mm x 32mm x 1.5mm。MMC卡原本使用1bit串联接口,但较新的标准则容许同时发送4 bit或8 bits的数据。近年MMC卡技术已差不多完全被SD卡所代替;但由于MMC卡仍可被兼容SD卡的设备所读取,因此仍有其作用。这项技术一个公开标准,所有愿意改进它或者为它开发产品的公司都可使用。

​ Secure Digital,缩写为SD,全名为Secure Digital Memory Card,中文翻译为安全数字卡,为一种存储卡,被广泛地于便携式设备上使用,例如数字相机、个人数码助理和多媒体播放器等。SD卡是东芝在MMC卡技术中加入加密技术硬件而成,由于MMC卡可能会较易让用户复制数字音乐,东芝便加入这些技术希望令音乐业界安心。SD卡的技术是建基于MultiMedia卡格式上。SD卡有比较高的数据发送速度,而且不断更新标准。大部分SD卡的侧面设有写保护控制,以避免一些数据意外地写入,而少部分的SD卡甚至支持数字版权管理的技术。SD卡的大小为32mm×24mm×2.1mm,但官方标准亦有记载“薄版”1.4mm厚度,与MMC卡相同。

​ Micro SD卡原本称为TF卡(T-Flash卡或TransFlash),由摩托罗拉与闪迪共同研发,在2004年推出。不过闪迪无法自行将它推广普及化,前期仅有摩托罗拉的手机支持TransFlash。为了能将销路完全拓展,闪迪于是将TransFlash规格并入SD协会,成为SD家族产品之一,造就了当前使用最广泛的手机存储卡;而重命名为microSD的原因是因为被SD协会(SDA)采纳。TransFlash纳入SD协会后,仅有在实体规格的电性接触端(印刷电路板的金属端子,俗称金手指)缩短一点,其他部分完全沿用完全兼容。另一些被SD协会采纳的存储卡包括miniSD和SD卡。这类存储卡主要于移动电话使用,但因其拥有体积极小的优点,随着不断提升的容量,它慢慢开始于GPS设备、便携式音乐播放器和一些闪存盘中使用。microSD的体积为 15 毫米 x 11 毫米 x 1 毫米 - 约为SD卡的1/4,相当于手指甲盖的大小,它亦能够以转接器来接驳于SD卡或Memory Stick插槽中使用。

​ eMMC (Embedded Multi Media Card)是MMC协会订立、主要针对手机或平板电脑等产品的内嵌式存储器标准规格。eMMC在封装中集成了一个控制器,提供标准接口并管理闪存,使得手机厂商就能专注于产品开发的其它部分,并缩短向市场推出产品的时间。该架构标准将MMC组件(闪存加控制器)放入一个小的球栅数组封装(BGA)中,是一种主要用于印刷电路板的嵌入式非易失性存储器系统。eMMC与MMC的其他版本有明显的不同,因为eMMC不是用户可随意移动的卡,而是永久性的电路板附件。

​ SD插口的用途不止是插存储卡。还支持很多SDIO接口的外设,像GPS接收器,Wi-Fi或蓝牙适配器,调制解调器,局域网适配器,条码读取器,FM无线电,电视接收器,射频识别读取器,或者数字相机等等采用SD标准接口的设备。SDIO和SD卡规范间的一个重要区别是增加了低速标准。SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开支支持低速I/ O能力。低速卡支持类似调制解调器、条码扫描仪和GPS接受器等应用。对“组合”卡(存储器+ SDIO)而言,全速和4位操作对卡内存储器和SDIO部分都是强制要求的。

14.2 通用的概念和协议介绍

参考的协议文档:

JEDEC的JESD84-B451 或者 JESD84-B51,可以在JEDEC网站上注册一个用户进行下载。

SD Card Association 的三个文档:

Physical_Layer_Simplified_Specification_Ver6.00,

SD Host_Controller_Simplified_Specification_Ver3.00,

SDIO可以参考SDIO_Simplified_Specification_Ver3.00。

这几个文档都可以直接在SD Card Association的网站上下载。

参考资料:芯片手册《Chapter 58 : Ultra Secured Digital Host Controller (uSDHC)》。

14.2.1 系统特性

14.2.1.1 SD系统特性

  • 面向便携式和固定式的应用
  • 卡的容量

​ (1)SDSC,Standard Capacity SD Memory Card,最大到2GB

​ (2)SDHC,High Capacity SD Memory Card,2GB到32GB

​ (3)SDXC, Extended Capacity SD Memory Card ,32GB到2TB

  • 电压范围

​ (1)高电压SD卡,电压范围2.7-3.6V

​ (2)UHS-II存储卡,电压范围VDD1:2.7-3.6V,VDD2:1.70V-1.95V

  • 可以设计成只读的卡和可以读写的卡
  • 总线速度模式(使用4根并行的数据线)
  • 切换功能命令支持总线速度模式,命令体系,驱动能力和未来的功能
  • 修正内存域的错误
  • 读操作过程中拔卡不会破坏数据内容
  • 数据保护机制,兼容最高级别的SDMI标准
  • 卡的密码保护(CMD42-LOCK_UNLOCK)
  • 使用机械开关的写保护
  • 内建的保护特性
  • 卡的插拔检测
  • 特定应用的命令
  • 优雅的擦除机制
  • 通信信道的协议属性:通过时钟线,命令线和4根数据线进行通信,错误保护的数据传输,面向单块和多块的数据传输

14.2.1.2 eMMC系统特性

​ eMMC设备定义了一种非直接存储访问的机制。通过一个特别的控制器来实现存储的访问。非直接存储访问的优点是在不用主机软件的的支持下,可以在设备中执行几种存储管理任务。这样可以简化主机的闪存管理层。eMMC支持一下特性。

  • 系统电压范围

image-20220112141205032

  • eMMC4.5总线由十根线组成(时钟线,1bit命令线,8bit数据线)和硬件复位线。eMMC5.1还有一根数据选通线。

​ (1)时钟频率范围0~200MHz

​ (2)三种数据总线宽度模式:1bit(默认),4bit,8bit

  • 数据保护机制:密码,永久保护,上电保护,暂时保护
  • 不同的读写模式:单块读写,多块读写
  • 数据移除命令:Erase,Trim,Sanitize
  • 突然掉电数据保护
  • 可以使用客制化命令
  • 电源管理休眠模式
  • 增强的主机和设备通信技术以提高性能:关机通知,高优先级中断(HPI),后台操作,分区,增强的区域,实时时钟,分区属性,上下文管理,系统数据标签,Packed Commands,动态设备容量,可选易失性缓存,芯片封装表面温度
  • 使用boot模式时自动发送数据
  • 访问重放攻击保护块(RPMB)
  • 两种类型高容量设备:512B块大小和4KB块大小

14.2.2 总线模式

14.2.2.1 SD卡总线速度模式

Bus speed mode for UHS-I Card

image-20220112141322603

Default Speed Mode 3.3V signaling, Frequency up to 25MHz, up to 12.5MB/sec

High Speed Mode 3.3V signaling, Frequency up to 50MHz, up to 25MB/sec

SDR12 UHS-I 1.8V signaling, Frequency up to 25MHz, up to 12.5MB/sec

SDR25 UHS-I 1.8V signaling, Frequency up to 50MHz, up to 25MB/sec

SDR50 UHS-I 1.8V signaling, Frequency up to 100MHz, up to 50MB/sec

SDR104 UHS-I 1.8V signaling, Frequency up to 208MHz, up to 104MB/sec

DDR50 UHS-I 1.8V signaling, Frequency up to 50MHz, up to 50MB/sec

UHS156 UHS-II RCLK Frequency Range 26MHz~52MHz, up to 1.56Gbps per lane

UHS624 UHS-II RCLK Frequency Range 26MHz~52MHz, up to 6.24Gbps per lane

开发板SD卡槽的电压为3.3v,所以SD卡的模式只能设置为default speed或者High speed模式。

14.2.2.2 eMMC总线速度模式

image-20220112141353206

开发板eMMC的电压为3.3v,所以只能设置成default speed或者high speed模式。

14.2.3 SD卡和eMMC寄存器

14.2.3.1 sd卡的构成

image-20220112141429981

​ 上图显示了标准SD卡的形状和内部的构成。通过VDD、两个GND、CLK、CMD和DAT0~DAT3总共9个外部引脚供获取电源和与主机进行通信。内部由卡接口控制器、寄存器、上电检测模块、存储介质接口和存储介质等构成。卡接口控制器主要对内部的存储核心进行控制和管理,通过接收用户对其发送的命令来进行控制和设置,并根据命令做出响应,执行数据读写等操作。寄存器提供卡的状态信息和反映卡的运行状态。存储介质主要由具有存储功能的flash块组成。

14.2.3.2 sd卡的寄存器

名称 宽度 描述 实现
CID 128 Card identification number,设备识别代码 强制
RCA 16 Relative Card Address,相对设备地址 强制
DSR 16 Driver Stage Register, 可选
CSD 128 Card Specific Data,设备特定数据 强制
SCR 64 SD Configuration Register,SD配置寄存器 强制
OCR 32 Operation Condition Register,运行条件寄存器 强制
SSR 512 SD status,SD状态 强制
CSR 32 Card status,Card状态 强制

14.2.3.3 MMC的构成

image-20220112141532242

​ 从上图可以看出,eMMC是把Nand flash存储阵列和Device Controller模块封装在一起。Device Controller负责flash阵列的管理,提供标准接口与主机进行通信。这样主机软件不用处理繁杂的Nand Flash的兼容性和坏块管理问题,简化软件设计,加快产品上市时间。

14.2.3.4 eMMC的寄存器

image-20220112141550937

名称 宽度(字节) 描述 实现
CID 16 Device Identification number,设备识别代码 强制
RCA 2 Relative Device Address,相对设备地址,在初始化过程中由主机动态分配 强制
DSR 2 Driver Stage Register,用来配置设备的驱动能力 可选
CSD 16 Device Specific Data,设备特定数据,包含设备的操作条件 强制
OCR 4 Operations Conditions Register,运行条件寄存器,特定的广播命令来识别设备的电压类型 强制
EXT_CSD 512 Extended Device Specific Data,扩展的设备特定数据,包含设备的能力和选择的模式,v4.0开始存在 强制

14.2.4 命令(COMMAND)和响应(RESPONSE)

14.2.4.1 命令类型

image-20220112141618129

​ 上图是命令的格式,每个命令令牌都以一个开始位(“ 0”)开头,并以一个结束位(“ 1”)结束。总长度为48个位。每个令牌均受CRC位保护,因此可以检测到传输错误,检测到错误后可以重发命令。

SD和eMMC都是4种类型:

  • bc Broadcast commands,没有响应
  • bcr Broadcast commands,有响应
  • ac addressed(point-to-point)commands,点对点命令,DATA线上没有数据传输
  • adtc addressed(point-to-point)data transfer commands,DATA线上有数据传输

image-20220112141644353

​ 上图中前一个命令CMD线上只传输了command;后一个命令CMD线上主机给卡发送command后,卡给主机回应了一个response;这两个命令都是只用到了CMD线,没有用到DAT线。如果命令参数里没有指定RCA,那么前一个命令就是就是bc类型,后一个命令就是bcr类型。如果后一个命令的参数里指定了RCA,那么就是ac类型。

image-20220112141707356

​ 上图显示了多块读操作,属于adtc类型,这种类型既要在命令参数里指定RCA,CMD线上传输的command后会有response,并且会使用DAT线传输数据。对于读操作,数据由卡发往主机。

image-20220112141712729

​ 上图显示了多块写操作,与读操作类似,属于adtc类型,不过数据由主机发往卡。

14.2.4.2 响应类型

响应通过CMD线发送。

对于SD卡和eMMC卡都有:

  • R1 normal response command 长度48bit
  • R1b 等同于R1,一个可选的在DATA0线上传送忙信号。
  • R2(CID,CSD register)长度136bit,CID寄存器的内容作为CMD2和CMD10的响应,CSD寄存器的内容作为CMD的响应。
  • R3(OCR register)长度48bit,OCR寄存器的内容作为CMD1的响应。

image-20220112141745166

上图显示了SD卡的响应格式,对于SD卡还有:

  • R6 published RCA response 长度48bit
  • R7 Card interface condition长度48bit

image-20220112141756135

上图显示了eMMC卡的响应的格式,对于eMMC卡还有:

  • R4(fast IO)长度48bit
  • R5(Interrupt request)长度48bit

14.3 IMX6ULL的uSDHC控制器

14.3.1 uSDHC控制器特性介绍:

  • 兼容SD Host Controller Standard Specification version 3.0
  • 兼容MMC System Specification version 4.2/4.3/4.4/4.41/4.5
  • 兼容SD Memory Card Specification version 3.0,支持Extended Capacity SD Memory Card
  • 兼容SDIO Card Specification version 3.0
  • 设计成支持SD Memory, miniSD Memory, SDIO, miniSDIO, SD Combo, MMC, MMC plus, and MMC RS cards
  • 卡的时钟频率最高到208MHz
  • 支持1-bit/4-bit SD和SDIO模式,1-bti/4-bit SD/8-bit MMC模式

​ (1)4线SDR(Single Data Rate)模式SDIO卡数据传输速率最高到832Mbps

​ (2)4线DDR(Dual Data Rate)模式SDIO卡数据传输速率最高到400Mbps

​ (3)4线SDR(Single Data Rate)模式SDXC卡数据传输速率最高到832Mbps

​ (4)4线DDR(Dual Data Rate)模式SDXC卡数据传输速率最高到400Mbps

​ (5)8线SDR(Single Data Rate)模式MMC卡数据传输速率最高到416Mbps

​ (6)8线DDR(Dual Data Rate)模式MMC卡数据传输速率最高到832Mbps

  • 支持单块和多块读写
  • 支持的块大小Wie 1~4096字节
  • 对于写操作支持写保护
  • 支持同步和异步终止操作
  • 支持数据块间传输间隙暂停操作
  • 支持SDIO读等待,暂停(suspend)和恢复(resume)操作
  • 对于多块读写支持自动CMD12
  • 主机可以在数据传输的同时初始化没有数据传输的命令
  • 1-bit和4-bit SDIO模式下允许卡中断主机,同时支持中断周期
  • 嵌入可自动配置的128x32-bit FIFO,用来读写数据
  • 支持内部和外部DMA
  • 支持通过配置vendor专用寄存器用于电压选择
  • 支持ADMA进行链式内存操作

14.3.2 数据传输模式

uSDHC可以选择如下模式进行数据传输:

  • SD 1-bit
  • SD 4-bit
  • MMC 1-bit
  • MMC 4-bit
  • MMC 8-bit
  • 识别模式(最高到400 kHZ)
  • MMC全速模式(最高到26MHz)
  • MMC高速模式(最高到52MHz)
  • MMC HS200模式(最高到200MHz)
  • MMC DDR模式(52M)
  • SD/SDIO全速模式(最高到25MHz)
  • SD/SDIO高速模式(最高到50MHz)
  • SD/SDIO UHS-I模式(SDR模式最高到208M,DDR模式最高到50M)

14.3.3 引脚概述

image-20220112142021189

上图是100sdk_imx6ull开发板的SD卡原理图,使用了uSDHC1控制器。

image-20220112142025607

上图是100sdk_imx6ull开发板eMMC的原理图,接在uSDHC2控制器上。

uSDHC有14个I/O管脚,分别是

  • CLK是由内部产生的时钟用来驱动MMC,SD和SDIO卡
  • CMD I/O用来发送命令给卡或者从卡接受响应
  • 8条数据线用来在uSDHC和卡之间传输数据
  • CD和WP用来卡检测和写保护,这两个信号直接来自卡槽。这两个信号都是低有效,CD#脚低表明有卡插入,WP高表明写保护打开
  • LCTL是输出信号,用来驱动外部LED表明控制器正忙
  • RST是输出信号,用来复位MMC卡
  • VSELECT用来改变外部电源提供者的电压
  • CD, WP, LCTL, RST 和VSELECT是可选的。如果uSDHC只需要4bit传输,DATA7~DATA4也是可选的并且拉高

14.3.4 时钟

Clock name Clock Root Description
hclk ahb_clk_root AHB bus clock
ipg_clk ipg_clk_root Peripheral clock
ipg_clk_perclk usdhc_clk_root Base clock
ipg_clk_s ipg_clk_root Peripheral access clock for register accesses

​ 上表是uSDHC的时钟源,hclk提供了AHB总线的时钟,ipg_clk提供了Peripheral的时钟,ipg_clk_perclk提供了uSDHC控制器的时钟,ipg_clk_s提供了访问控制器寄存器的时钟,这些时钟如何配置呢?需要根据时钟树进行配置。

image-20220112142122012

​ 上图是时钟树的片段,来自于imx6ull reference manual的chapter18 Clock Controller Module章节的18.3 CCM Clock Tree。可以看出CCM_CSCMR1[USDHCn_CLK_SEL]选择时钟源,CSCDRn[USDHC1_PODF]来控制分频,最终产生USDHCn_CLK_ROOT时钟。寄存器说明如下:

CCM_CSCMR1寄存器片段

image-20220112142130658

CSCDR1[USDHC1_PODF]

image-20220112142135423

​ 从上图可以看出,USDHC1和USDHC2的时钟可以来自PLL2PFD0或者PLL2PFD2,由CCM_CSCMR1来控制,然后由CSCDR1[USDHCn_PODF]来控制分频(1,2,…,8)。默认设置使用PLL2PFD0,PLL2PFD0设置为396M,如果USDHCn_PODF都设置为1的话,即对PLL2PFD2进行二分频,这样USDHC1_CLK_ROOT和USDHC1_CLK_ROOT都为198M。

​ Card_clk是由peripheral source clock分成两个阶段产生的,其实就是两个通过两个分频器产生的。

image-20220112142205025

​ 第一个分频是由uSDHCx_SYS_CTRL[DVS]控制产生DIV,它的值可以等于Base,ase/2, Base/3, ..., 或者 Base/16。

​ 第二个分频是由uSDHCx_SYS_CTRL[SDCLKFS]控制产生内部工作时钟card_clk。

​ 在SDR模式和DDR模式时,CLK是不同的:SDR模式时,CLK等于内部工作时钟card_clk;DDR模式时,CLK等于card_clk/2。

​ Base是来自USDHCx_CLK_ROOT,如果USDHCx_CLK_ROOT为198M的话,就要由uSDHCx_SYS_CTRL[DVS]和uSDHCx_SYS_CTRL[SDCLKFS]分频产生400K,25M,26M,50M,52M等时钟。

14.3.5 ADMA引擎

​ SD主机控制器标准里定义了一种新的DMA传输算法,该算法被称为ADMA(Advanced DMA)。竟然是advanced,是不是之前还有个算法呢?的确是有的,称为SDMA(Simple DMA)。

​ 对于SDMA,当页边界(page boundary)到达时,会产生DMA中断,然后新的地址需要再次写入到DMA System Address (uSDHCx_DS_ADDR)寄存器。由于每个页边界都会产生中断,SDMA算法会导致性能瓶颈。比如对于一个主机控制器,页边界是512KB,那就意味着每次中断里最多传输512KB的数据,并且一次传输的数据不能跨越512KB的边界,然后需要重新对DMA System addreass进行编程。

​ 为了能达到更高的传输速度,ADMA采用了分散聚集DMA算法(scatter gather algorithm)。在ADMA传输前,主机驱动可以事先在系统内存里准备好一个描述符表(descriptor table),然后将描述符表的地址写入ADMA System Address (uSDHCx_ADMA_SYS_ADDR)寄存器。主机会根据描述符表里的内容进行传输,这减少了DMA的中断频率,在描述符表里指向的内容传输完成前就不用打断CPU,自然地提高了传输的速度。

​ ADMA也有两种,一个是ADMA1,另一个是ADMA2。ADMA1需要内存里传输数据的地址4KB对齐。ADMA2改进了这个限制,传输的数据可以在内存里任何位置,传输的大小也可以设置。主机控制标准第三个版本里对ADMA1已经不支持了,后面说到ADMA都是指ADMA2。

image-20220112142231919

(图来源于PartA2_SD Host_Controller_Simplified_Specification_Ver3.00 page14)

上图是ADMA2的框图,主机驱动在系统内存里创建描述符表。每个描述符行(也就是一个执行单元)包括三个域地址(address),长度(length)和属性(attribute)。属性指定了每个描述符行对应的操作。ADMA2不使用ADMA System Address寄存器,而是使用ADMA System Address寄存器指向描述符表。然后ADMA2会从描述表里逐行提取描述符行,依次执行。

上图是一个ADMA2编程的示例。主机驱动里描述符表每行都表示一段内存里连续的空间,分别是内存里这段区间的起始地址,长度和属性。ADMA2会逐行解析描述符,依次传输每段连续内存空间。

对描述符的编程有3个要求:

(1)最小的地址单元是4字节;

(2)每个描述符行描述的地址长度低于64KB;

(3)总长度(Length1+length2+…+lengthn)等于块大小的倍数。

Block count寄存器限制最大传输65535个块,如果ADMA2操作不大于65535个块,block count可以使用。总长度大小需要等于块大小(block size)与块数目(block count)的乘积。

image-20220112142239359

描述符表

image-20220112142245267

上图是32-bit地址描述符表,一个描述符行需要64字节空间。属性值用来控制传输,Nop表明直接跳过这行描述符,取下一个描述符。Rsv操作与NOP相同。Tran操作表明传输以这行描述里32-bit地址为起始地址,长度为16-bit length指定的内存空间。Link用来指定另一个描述符表,可见描述之间也可以向链表那样串起来。属性里的int为1的话表示操作完该描述行后产生DMA中断。End表明描述符结束了,这是最后一个要传输的描述符。Valid为1表明改行描述符是有效的,为0的话产生ADMA error,并且停止ADMA传输。

14.3.6 寄存器介绍

SD Host_Controller_Simplified_Specification_Ver3.00里的标准的SD主机控制器寄存器:

image-20220112142257497

下表示IMX6ULL的寄存器表格:

Absolute address (hex) Register name Width (in bits) Access Reset value Section/ page
219_0000 DMA System Address (uSDHC1_DS_ADDR) 32 R/W 0000_0000h 58.8.1/4014
219_0004 Block Attributes (uSDHC1_BLK_ATT) 32 R/W 0000_0000h 58.8.2/4015
219_0008 Command Argument (uSDHC1_CMD_ARG) 32 R/W 0000_0000h 58.8.3/4017
219_000C Command Transfer Type (uSDHC1_CMD_XFR_TYP) 32 R/W 0000_0000h 58.8.4/4017
219_0010 Command Response0 (uSDHC1_CMD_RSP0) 32 R 0000_0000h 58.8.5/4021
219_0014 Command Response1 (uSDHC1_CMD_RSP1) 32 R 0000_0000h 58.8.6/4021
219_0018 Command Response2 (uSDHC1_CMD_RSP2) 32 R 0000_0000h 58.8.7/4022
219_001C Command Response3 (uSDHC1_CMD_RSP3) 32 R 0000_0000h 58.8.8/4022
219_0020 Data Buffer Access Port (uSDHC1_DATA_BUFF_ACC_PORT) 32 R/W 0000_0000h 58.8.9/4024
219_0024 Present State (uSDHC1_PRES_STATE) 32 R 0000_8080h 58.8.10/ 4024
219_0028 Protocol Control (uSDHC1_PROT_CTRL) 32 R/W 0880_0020h 58.8.11/ 4030
219_002C System Control (uSDHC1_SYS_CTRL) 32 R/W 8080_800Fh 58.8.12/ 4035
219_0030 Interrupt Status (uSDHC1_INT_STATUS) 32 w1c 0000_0000h 58.8.13/ 4038
219_0034 Interrupt Status Enable (uSDHC1_INT_STATUS_EN) 32 R/W 0000_0000h 58.8.14/ 4044
219_0038 Interrupt Signal Enable (uSDHC1_INT_SIGNAL_EN) 32 R/W 0000_0000h 58.8.15/ 4047
219_003C Auto CMD12 Error Status (uSDHC1_AUTOCMD12_ERR_STATUS) 32 R 0000_0000h 58.8.16/ 4050
219_0040 Host Controller Capabilities (uSDHC1_HOST_CTRL_CAP) 32 R 07F3_B407h 58.8.17/ 4053
219_0044 Watermark Level (uSDHC1_WTMK_LVL) 32 R/W 0810_0810h 58.8.18/ 4056
219_0048 Mixer Control (uSDHC1_MIX_CTRL) 32 R/W 8000_0000h 58.8.19/ 4057
219_0050 Force Event (uSDHC1_FORCE_EVENT) 32 W (always reads 0) 0000_0000h 58.8.20/ 4059
219_0054 ADMA Error Status Register (uSDHC1_ADMA_ERR_STATUS) 32 R 0000_0000h 58.8.21/ 4062
219_0058 ADMA System Address (uSDHC1_ADMA_SYS_ADDR) 32 R/W 0000_0000h 58.8.22/ 4064
219_0060 DLL (Delay Line) Control (uSDHC1_DLL_CTRL) 32 R/W 0000_0200h 58.8.23/ 4065
219_0064 DLL Status (uSDHC1_DLL_STATUS) 32 R 0000_0000h 58.8.24/ 4067
219_0068 CLK Tuning Control and Status (uSDHC1_CLK_TUNE_CTRL_STATUS) 32 R/W 0000_0000h 58.8.25/ 4068
219_00C0 Vendor Specific Register (uSDHC1_VEND_SPEC) 32 R/W 2000_7809h 58.8.26/ 4070
219_00C4 MMC Boot Register (uSDHC1_MMC_BOOT) 32 R/W 0000_0000h 58.8.27/ 4073
219_00C8 Vendor Specific 2 Register (uSDHC1_VEND_SPEC2) 32 R/W 0000_0006h 58.8.28/ 4074
219_00CC Tuning Control Register (uSDHC1_TUNING_CTRL) 32 R/W 0021_2800h 58.8.29/ 4076

将两个表格进行对比发现有很多寄存器是类似,甚至寄存器里的很多bit位都是类似的,但是uSDHC与标准SD主机控制器寄存器也有一些差异。

14.3.6.1 DMA System Address (uSDHCx_DS_ADDR)

SDMA传输时的物理内存的地址,用ADMA2时不是设置这个寄存器。

image-20220112142313267

14.3.6.2 Block Attributes (uSDHCx_BLK_ATT)

用来设置块的数目和大小。

image-20220112142340224

BLKCNT当前传输的块数目。

BLKSIZE传输的块的大小。

14.3.6.3 Command Argument (uSDHCx_CMD_ARG)

SD/MMC命令的参数。

image-20220112142404487

14.3.6.4 Command Transfer Type (uSDHCx_CMD_XFR_TYP)

用来控制数据传输的操作。

image-20220112142415095

Multi/Single Block Select Block Count Enable Block Count Function
0 Don't Care Don't Care Single Transfer
1 0 Don't Care Infinite Transfer
1 1 Positive Number Multiple Transfer
1 1 Zero No Data Transfer
Response Type Index Check Enable CRC Check Enable Name of Response Type
00 0 0 No Response
01 0 1 R2
10 0 0 R3,R4
10 1 1 R1,R5,R6
11 1 1 R1b,R5b

CMDINDEX command index命令的编号

CMDTYPE command type命令类型,大部分都是设置为0,normal command

DPSEL Data Present Select是否包含数据,为1表示会用到数据线,有两种情况,分别为

CICEN command index check enable为1表示检查response里的index

CCCEN command crc check enable 检查response里的CRC

RSPTYP response type response的类型00表示不需要响应,01表示响应长度是136,10表示长度是48,11表示长度是48并且需要检测忙信号

14.3.6.5 Command Response0 (uSDHCx_CMD_RSP0)

image-20220112142444186

14.3.6.6 Command Response1 (uSDHCx_CMD_RSP1)

image-20220112142451825

14.3.6.7 Command Response2 (uSDHCx_CMD_RSP2)

image-20220112142458607

14.3.6.8 Command Response3 (uSDHCx_CMD_RSP3)

image-20220112142504119

以上四个寄存器分别是从卡发送命令后得到的响应

Response Type Meaning of Response Response Field Response Register
R1,R1b (normal response) Card Status R[39:8] CMDRSP0
R1b (Auto CMD12 response) Card Status for Auto CMD12 R[39:8] CMDRSP3
R2 (CID, CSD register) CID/CSD register [127:8] R[127:8] {CMDRSP3[23:0], CMDRSP2, CMDRSP1, CMDRSP0}
R3 (OCR register) OCR register for memory R[39:8] CMDRSP0
R4 (OCR register) OCR register for I/O etc. R[39:8] CMDRSP0
R5, R5b SDIO response R[39:8] CMDRSP0
R6 (Publish RCA) New Published RCA[31:16] and card status[15:0] R[39:9] CMDRSP0

上图是响应类型与response寄存器的对应关系(来自reference manual P4022)

14.3.6.9 Data Buffer Access Port(uSDHCx_DATA_BUFF_ACC_PORT)

使用外部DMA时会用到,使用ADMA2时,该寄存器一直是0,不用管。

image-20220112142534276

14.3.6.10 Present State (uSDHCx_PRES_STATE)

只读寄存器,

image-20220112142541528

​ DLSL[7:0] data line signal level DATA引脚电平状态,可以用来调试以及检测DATA引脚从错误中恢复的情况。可以检测DATA0引脚电平来反映是否正忙。

​ CLSL CMD line Signal Level CMD引脚的电平状态,可以用来调试以及检测CMD引脚从错误中恢复的情况。

​ WPSPL Write Protect Swith Pin Level 写保护开关引脚电平。反映卡槽WP引脚的状态

​ CDPL Card Detect Pin Level 这个位反映卡槽CD_B引脚的状态,不过与CD_B脚的状态相反,uSDHC不对去抖。

​ CINST Card Inserted 卡插入状态。反映卡是否插入,uSDHC会对该信号去抖动。

​ TSCD Tape Select Change Done。反映延时设置,和tuning有关。

​ RTR Re-Tuning Request 采样时钟是否需要重新tuning,只有SD3.0 SDR104模式会用到。

​ BREN Buffer READ ENABLE 读缓冲区使能,非DMA传输时才会用到。

​ BWEN Buffer Write ENABLE 写缓冲区使能,非DMA传输时才会用到。

​ RTA READ Transfer active 正在进行读操作

​ WTA Write Transfer Active 正在进行写操作

​ SDOFF SDCLOCK Gated Off Internally SD时钟是否关闭

​ PEROFF IPG_PERCLK Gated Off Internally IPG_PERCLK时钟是否关闭

​ HCKOFF HCLK Gated Off Internally HCLK时钟是否关闭

​ IPGOFF IPG_CLK Gated Off Internally IPG_CLK时钟是否关闭

​ SDSTB SD时钟是否稳定,在修改时钟时,需要等待时钟稳定。

​ DLA Data Line Active Data线是否在使用。

​ CDIHB Command Inhabit(Data)当data线正在使用或者读传输正在进行,该位置位。

​ CIHB Command Inhabit 当为0时,表明CMD线不在使用,uSDHC可以发送SD/MMC命令。

14.3.6.11 Protocol Control (uSDHCx_PROT_CTRL)

image-20220112142627014

NON_EXACT_BK_READ 当前块是否是需要读的块,只要SDIO用到。

BURST_ELN_EN

WECRM Wakeup Event Enable On SD Card Removal 移除卡时知否产生唤醒事件

WECINS Wakeup Event Enable On SD Card Insert 插入卡时是否产生唤醒事件

WECINT Wakeup Event Enable On Card Interrupt 不使能时,时钟使能的情况下才能产生卡中断

RD_DONE_NO_8CLK 使用快间隙停止传输时才需要关心这个位

IABG interrupt At Block GAP 仅SDIO 4bit模式使用到,多块传输时是否需要检测中断。

RWCLT READ wait control 读等待控制,仅SDIO模式使用到。

CREQ continue request 继续传输,恢复使用块间隙停止传输的传输

DMASEL DMA select 选择DMA的模式00 不使用DMA或者使用SDMA,01 ADMA1, 10 ADMA2

CDSS Card Detect Signal Selection 选择卡检测信号的来源

CDTL Card Detect Test Level 卡检测测试电平

EMODE Endian Mode 大小端模式选择,一般选择小端

D3CD Data3 as Card Detection Pin data3引脚作为插拔卡检测

DTW Data Transfer Width 数据传输的宽度

LCTL LED控制

14.3.6.12 System Control (uSDHCx_SYS_CTRL)

image-20220112142639489

RSTT Reset Tuning 复位tuning 电路

INITA Initialization Active 置位时,发送80个SDCLK

RSTD Software Reset for DATA Line数据线软复位,数据模块和DMA模块复位。SW等待自清除。

RSTC Software Reset for CMD line 命令电路模块复位,软件等待自清除。

RSTA Software Reset for ALL 复位整个控制器模块,除了卡检测模块

IPP_RST_N 如果卡支持这个特性的话,硬件复位时直接发给卡的引脚

DTOCV Data Timeout Counter Value 数据超时计数器

SDCLKFS SDCLK Frequency Select SDCLK第二阶段分频选择

DVS divisorSDCLK第一阶段分频

14.3.6.13 Interrupt Status (uSDHCx_INT_STATUS)

​ 当Normal Interrupt Signal Enable使能相应的位才能产生相应的中断。所有的位都是写1清除,写0保持不变。一次可以清除多个位。

image-20220112142650887

image-20220112142709227

Command Complete Command Timeout Error Meaning of the Status
0 0 X
X 1 Response not received within 64 SDCLK cycles
1 0 Response received
Transfer Complete Data Timeout Error Meaning of the Status
0 0 X
0 1 Timeout occurred during transfer
1 X Data Transfer Complete
Command Complete Command Timeout Error Meaning of the Status
0 0 No error
0 1 Response Timeout Error
1 0 Response CRC Error
1 1 CMD line conflict

DMAE DMAError DMA 错误

TNE TuningERROR tuning错误

AC12E Auto CMD12 ERROR Auto CMD12错误

DEBE Data End Bit ERROR 数据结束位错误

DCE Data CRC ERROR 数据CRC错误

DTOE data timeout ERROR 数据超时错误

CIE Command Index Error 命令序号错误

CEBE Command End Bit ERROR 命令结束位错误

CCE Command CRC error 命令crc校验错误

CTOE Command timeout error 命令超时错误

TP tuning pass tuning通过(只有SD3.0 SDR104模式使用)

RTE Re-Tuning Event 重新Tuning事件(只有SD3.0 SDR104模式使用)

CINT card interrupt 卡中断

CRM card remove卡移除

CINS card insert 卡插入

BRR Buffer Read Ready 读缓冲准备好

BWR Buffer Read Ready 写缓冲准备好

DINT DMA interrupt DMA中断

BGE block gap event 块间隙事件

TC Transfer Complete 传输完成

CC Command Complete 命令完成

14.3.6.14 Interrupt Status Enable (uSDHCx_INT_STATUS_EN)

image-20220112142737457

image-20220112142740821

对应的位置位,Interrupt Status对应的位才会在相应的事件产生时置位。如果uSDHCx_INT_STATUS_EN对应的位清0,那么Interrupt Status对应的位也会清除

14.3.6.15 Interrupt Signal Enable (uSDHCx_INT_SIGNAL_EN)

对应的位设置的话,相应的事件发生时才会在中断线上产生中断信号

image-20220112142801615

14.3.6.16 Auto CMD12 Error Status (uSDHCx_AUTOCMD12_ERR_STATUS)

image-20220112142807960

Auto CMD12 CRC Error Auto CMD12 Timeout Error Type of Error
0 0 No Error
0 1 Response Timeout Error
1 0 Response CRC Error
1 1 CMD line conflict

SMP_CLK_SEL Sample Clock Select 采样时钟选择

EXECUTE_TUNING execute tuning 开始tuning

CNIBAC12E Command not Issued By Auto CMD12 Error Auto CMD12错误导致未发送命令

AC12IE Auto CMD12/23 Index Error Auto CMD12响应命令序号错误

AC12CE Auto CMD12/23 CRC Error Auto CMD12响应CRC错误

AC12EBE Auto CMD12/23 End Bit Error Auto CMD12结束位错误

AC12TOE Auto CMD12/23 Timeout Error Auto CMD12超时错误

AC12NE Auto CMD12 Not Executed Auto CMD12没有执行

14.3.6.17 Host Controller Capabilities (uSDHCx_HOST_CTRL_CAP)

主机控制器的功能指示寄存器,不会因为软件复位而变化

image-20220112142823623

image-20220112142827981

VS18 Voltage Support 1.8v 是否支持1.8v

VS30 Voltage Support 3.0v 是否支持3.0v

VS33 Voltage Support 3.3v 是否支持3.3v

SRS Suspend/Resume support 是否支持suspend/resume

DMAS DMA support是否支持DMA

HSS high speed support是否支持HS模式

ADMAS ADMA support是否支持ADMA

MBL Max Block Length 最大支持的块长度

RETURNIGN MODE retuning模式

USE_TUNING_SDR50 使用SDR50模式tuning

TIMER_COUNT_RETUNING

DDR50_SUPPORT 是否支持DDR50

SDR104_SUPPORT 是否支持SDR104

SDR50_SUPPORT 是否支持SDR50

14.3.6.18 Watermark Level (uSDHCx_WTMK_LVL)

image-20220112142845425

14.3.6.19 Mixer Control (uSDHCx_MIX_CTRL)

image-20220112142848599

FBCLK_SEL FeedBack clock Source Selection 只要SD3.0 SDR104模式才能使用到

AUTO_TUNE_EN使能自动tune

SMP_CLK_SEL Tuned clk或者fixed clk用来采样data/cmd

EXE_TUNE Execute Tuning (只有SD3.0 SDR104模式使用到)

AC23EN Auto CMD23 Enable Auto CMD23使能

NIBBLE_POS DDR 4bit模式会用到

MSBSEL 单块多个块选择

DTDSEL Data transfer Direction Select 数据传输方向选择

DDR_EN Dual Data rate Mode selection DDR模式选择

AC12EN Auto CMD12 Enable Auto CMD12使能

BCEN Block Count Enable 块数目使能

DMAEN DMA Enable DMA使能

14.3.6.20 Force Event (uSDHCx_FORCE_EVENT)

image-20220112142905813

14.3.6.21 ADMA Error Status Register(uSDHCx_ADMA_ERR_STATUS)

当ADMA错误中断产生时,ADMA Error Status Register保存ADMA状态,ADMA System Address register保存发生错误是描述符所在的地址

ST_STOP ADMA System Address register对应的前一个描述符传输发生错误

ST_FDS ADMA System Address register当前的描述符传输发生错误

ST_CADR change ADDRESS 递增描述符的指针,不会发生错误

ST_TRF ADMA System Address register对应的前一个描述符传输发生错误

ADMA Error State Coding

D01-D00 ADMA Error State (when error has occurred) Contents of ADMA System Address Register
00 ST_STOP (Stop DMA) Holds the address of the next executable Descriptor command
01 ST_FDS (Fetch Descriptor) Holds the valid Descriptor address
10 ST_CADR (Change Address) No ADMA Error is generated
11 ST_TFR (Transfer Data) Holds the address of the next executable Descriptor command

image-20220112142915422

ADMADCE ADMA Descriptor Error ADMA描述符错误,如果描述符有错误该位置位,在编写ADMA描述符生成的程序时很有用

ADMALME ADMA Length Mismatch Error ADMA长度不匹配错误,对应两种情况,如果BLOCK COUNT ENABLE设置了,但是描述符指示的总长度和BLOCK COUNT与BLOCK LENGTH的乘积不匹配;还有一种情况是总长度不能被BLOCK LENGTH整除

ADMAAES ADMA Error State ADMA出错状态。

14.3.6.22 ADMA System Address (uSDHCx_ADMA_SYS_ADDR)

进行ADMA传输的物理地址,其实是ADMA描述符对应的物理地址。

image-20220112142922547

14.3.6.23 DLL (Delay Line) Control (uSDHCx_DLL_CTRL)

14.3.6.24 DLL Status (uSDHCx_DLL_STATUS)

14.3.6.25 CLK Tuning Control and Status (uSDHCx_CLK_TUNE_CTRL_STATUS)

上面三个寄存器是用来tuning的,对于default speed模式和high speed模式使用不到。

14.3.6.26 Vendor Specific Register (uSDHCx_VEND_SPEC)

使用ADMA是,EXT_DMA_EN需要置0

14.3.6.27 MMC Boot Register (uSDHCx_MMC_BOOT)

image-20220112142955603

BOOT_CLK_CNT 块间隙停止传输自动模式的块数目

DISABLE_TIME_OUT 为0使能超时检测,为1时不检测

AUTO_SABG_EN boot期间,使能块间隙停止传输功能

BOOT_EN fastboot启动模式是否使能

BOOT_MODE 0表示normal boot,1表示Alternative boot

BOOT_ACK boot期间是否要ACK

DTOCVACK BOOT ack超时数器

14.3.6.28 Vendor Specific 2 Register (uSDHCx_VEND_SPEC2)

主要是SDR模式和tuning模式相关的寄存器。

image-20220112143020040

14.3.6.29 Tuning Control Register (uSDHCx_TUNING_CTRL)

对于default speed模式和high speed模式使用不到。

14.3.7 命令(CMD)发送和响应(Response)接收的基本操作

14.3.7.1 协议文档里发送命令的流程

注意:14.3.7.1-14.3.7.3来自SD Host_Controller_Simplified_Specification_Ver3.00,需要对照着协议文档里寄存器的介绍来看,因为imx6ull寄存器的位置和命名与协议文档有差异。

image-20220112143043372

(来自PartA2_SD Host_Controller_Simplified_Specification_Ver3.00的figure3-11)

(1)检查Present State寄存器的Command Inhabit(CMD)。重复检查直到Command Inhabit(CMD)变成0。也就是说Command Inhabit(CMD)为1的话,主机不会发送Command。

(2)如果主机发送的command需要用到busy信号的话,转到(3);如果不需要检查的话,转到(5);

(3)如果发送的是abort command,转到(5);如果不是abort command,转到(4)。

(4)检查Present State寄存器的Command Inhabit(DAT)。重复检查直到Command Inhabit(DAT)变成0。

(5)设置除command寄存器的其它寄存器。

(6)设置command寄存器。

注意:写command寄存器的高字节后,主机控制器就会发送command给卡。

(7)执行command complete sequence(后面有说明)。

14.3.7.2 协议文档里命令完成时的操作

(来自PartA2_SD Host_Controller_Simplified_Specification_Ver3.00的figure3-12)

image-20220112143222038

(1)如果在Transfer Mode寄存器的Response Interrupt Disable设置为1(response check使能),转到(4);否则等待Command Complete中断。如果发生了Command Complete中断,转到(2)。

(2)往Normal Interrupt Status寄存器的Command Complete写1,清除掉该bit。

(3)读Response寄存器,获得发送命令需要关注的信息。

(4)判断发送的命令是否使用到了Transfer Complete中断。如果使用的话,转到(5),否则转到(7)。

(5)等待Transfer Complete中断,如果该中断发生,转到(6)。

(6)往Normal Interrupt Status寄存器的Transfer Complete写1,清除掉该bit。

(7)检查Response data中是否有错误,如果没有转到(8);如果有错误转到(9)。

(8)返回状态No ERROR。

(9)返回状态Response Contents Error。

14.3.7.3 协议文档里使用DAT线通过ADMA传输数据

(来自PartA2_SD Host_Controller_Simplified_Specification_Ver3.00的figure3-15)

image-20220112143230507

(1)在系统主内存里创建ADMA描述符表。

(2)将ADMA描述符表的地址写入ADMA System Address寄存器。

(3)将要发送数据的一个块的长度写入Block Size寄存器。

(4)将要发送的数据的块的数目写入Block Count寄存器。

(5)将命令的参数写入到Argument寄存器。

(6)设置Transfer Mode寄存器,主机驱动需要设置Multi/Single Block Select,Block Count Enable,Data Transfer Direction,Auto CMD12 Enable和DMA enable。

(7)设置Command寄存器,设置后主机控制器会发送命令给卡。

(8)如果Response check使能,转到(11),否则等待Command Complete中断,如果Command Complete为1,转到(9)。

(9)往Normal Interrupt Status寄存器的Command Complete写1,清除掉该bit。

(10)读Response寄存器获得发送命令需要关注的信息。

(11)等待Transfer Complete中断和ADAM Error中断。

(12)如果Transfer Complete设置为1,转到(13);如果ADAM Error设置为1,转到(14)。

(13)往Normal Interrupt Status寄存器的Transfer Complete写1,清除掉该bit。

(14)往Error Interrupt Status寄存器的ADMA Error Interrupt Status写1,清除掉该bit。

(15)发送abort命令中止ADMA操作,

14.3.7.4 发送不用DATA线命令

注意:14.3.7.4-14.3.7.6根据SD Host_Controller_Simplified_Specification_Ver3.00协议文档对imx6ull的uSDHC控制器进行编程,需要对照着SD Host_Controller_Simplified_Specification_Ver3.00文档里的寄存器与imx6ull的寄存器一起看。

1等待CIHB清0

2根据命令设置CMDTYP, DPSEL, CICEN, CCCEN, RSTTYP, DTDSEL

3将命令参数写CMDARG寄存器

4写XFERTYP寄存器,开始发送命令

5等待INT_STATUS命令完成位CC设置,如果出错,报告错误

6写1清除CC和错误

14.3.7.5 发送使用到DATA线的命令

1等待CIHB清0

2等待CDIHB清0

3设置ADMA描述符

4设置ADMA_SYS_ADDR为ADMA描述符的地址

5设置BLK_ATT寄存器里块大小和块长度

6PROT_CTRL寄存器里DMA方式选择ADMA2

7MIX_CTRL寄存器里设置DTDSEL数据方向,使能DMAEN

8将命令参数写CMDARG寄存器

9写XFERTYP寄存器,开始发送命令

10等待INT_STATUS命令完成位CC设置,如果出错,报告错误

11写1清除CC和错误

12等待INT_STATUS传输完成位TC,如果出错,报告错误

13写1清除TC和错误

14.3.7.6 读写单个/多个块的流程

1等待CIHB清0

2等待CDIHB清0

3设置ADMA描述符

4设置ADMA_SYS_ADDR为ADMA描述符的地址

5设置BLK_ATT寄存器里块大小和块长度

6PROT_CTRL寄存器里DMA方式选择ADMA2

7MIX_CTRL寄存器里设置DTDSEL数据方向,使能DMAEN,读写多个块的话,还要设置块8计数使能BCEN,选择多个块MSBSEL,使能AUTO CMD12 AC12EN

9将读写块的首个块号写CMDARG寄存器

10写XFERTYP寄存器,开始发送命令

11等待INT_STATUS命令完成位CC设置,如果出错,报告错误

12写1清除CC和错误

13等待INT_STATUS传输完成位TC,如果出错,报告错误

14写1清除TC和错误

14.3.7.7 SD卡的初始化和识别过程 (只考虑Version2.0 以上的卡)

1 发送CMD0

2 对于SDHC卡,必须发送CMD8

3 只有Version2.0及以上的卡才对CMD8进行响应,legacy cards和非SD卡不会对CMD8响应

4 发送ACMD41获取OCR,如果参数为0的话,可以查询卡支持的电压范围

5 1S内循环发送ACMD41,如果主机支持HCS,参数里的HCS一定要设置为1,直到响应的big31位置1才能退出,响应里包含CCS,CCS为0的话表明是SDSC卡,为1的话表明是SDHC或者SDXC

6 如果需要切换到1.8v电压的话,发送ACMD41,参数里的S18R要设置,如果响应里的S18A为1,表示可以切换到1.8v,发送CMD11进行电压切换

7 发送CMD2获得CID

8 发送CMD3,参数为指定的RCA,不能为0

9 发送CMD9,参数为RCA,获得CSD

10 发送CMD7,参数为RCA,选中卡

11 发送ACMD51,参数为RCA,获得SCR

12 发送ACMD6,参数为指定的总线位宽,参数0表示1bit,参数2表示4bit

13 CMD6 function group 1设置卡的speed mode

14.3.7.8 eMMC卡的初始化和识别过程(翻译自JESD84-B51 的附录A.6)

a. 上电

1 上电,电压范围(2.7~3.6v)

2 设置时钟为400k或者更低

3 等待1ms,等待不少于74个时钟周期

4 发送CMD0复位总线

5 发送CMD1,参数为(0x00FF8000或者0x00000080)

6 收到R3响应

7 如果OCR 的busy位为0,重复5和6

8 从R3响应中可以知道设备是支持High Voltage还是Dual Voltage;如果响应是0x80FF8000说明只支持High Voltage,如果响应是0x80FF8080说明是Dual Voltage设备

9 如果R3响应是别的数的话,表明不兼容该设备(因为电压不兼容,设备会将自己置于inactive状态,不会响应),这种情况下,主机会关闭总线电源下电,开始进行自恢复

低电压上电

如果主机支持低电压的话,进入如下操作切换到低电压,否则直接跳到16

10 如果主机支持低电压,并且设备支持Dual Voltage,主机给总线下电

11 主机给总线上电,电压是低电压(1.70~1.95V)

12 等待1ms,等待不少于74个时钟周期

13 发送CMD1,参数为0x00000080

14 读取R3类型响应,值应该为0x80FF8080

15 如果OCR的busy位为0,重复13和14

b. CID读取和分配RCA

16 发送CMD2

17 收到R2类型的响应,获得设备的CID

18 发送CMD3,指定一个大于1的数作为卡的地址

读取CSD和主机适配

19 发送CMD9

20 收到R2类型的响应,获得设备的CSD

21 根据需要,按照CSD信息调整主机的一些参数。如果SPEC_VERS表明是V4.0及以上,表明是high speed设备并且支持SWITCH和SEND_EXT_CSD命令。否则设备是一个旧的MMC设备。无论设备的类型,最大支持的时钟频率可以设置到TRAN_SPEED域

切换到High Speed高速模式:

总线初始化完成后,如果卡支持high speed模式的话,可以切换到high speed模式

22 发送CMD7,参数为指定的RCA,设备进入到tran state

23 发送CMD8(SEND_EXT_CSD),从EXT_CSD,主机可以知道设备支持的power class,选择一个更宽的总线宽度(看步骤26-37)

24 发送CMD6,参数为0x03B9_0100,就是设置EXT_CSD的HS_TIMING为0x1

24.1 设备会进入到忙状态,响应为R1,知道busy状态清除

24.2 设备退出busy状态后,设备的high speed timing配置成功

25 调整时钟频率(0~26/52MHz)

改变数据总线宽度:

a. 总线测试流程

26 发送CMD19

27 发送数据,所有的数据线上都会传输特定的数据,具体数据参考协议文档

27.1 对于8条数据线,数据(MSB到LSB)是0x0000_0000_0000_AA55

27.2 对于4条数据线,数据(MSB到LSB)是0x0000_AA55

27.3 对于1条数据线,数据是0x80

28 发送之前等待NCR个时钟周期

29 发送CMD14,从所有有效的数据线收到数据

29.1 对于8条数据线,收到8字节数据

29.2 对于4条数据线,收到4字节数据

29.3 对于1条数据线,收到1字节数据

30 和步骤27中的数据XNOR(异或后取反)操作

31 根据如下对结果进行MASK操作

31.1 对于8条数据线,数据(MSB到LSB)是0x0000_0000_0000_FFFF

31.2 对于4条数据线,数据(MSB到LSB)是0x0000_FFFF

31.3 对于1条数据线,数据是0xC0

32 所有的结果需要是0,其它结果表明设备的连接有问题,这种情形下,主机需要下电并进行恢复操作

b 电源和数据宽度选择

33 选择需要工作的总线宽度

34 如果power class和选择宽度与默认的power class不匹配,发送CMD6,写入EXT_CSD的POWER_CLASS为响应的power class

35 发送CMD6后设备进入忙状态,等待设备退出忙状态

36 发送CMD6,写入EXT_CSD的BUS_WIDTH为响应的总线宽度,4bit对应的参数为0x03B7_0100,8bit对应的参数为0x03B7_0200

14.3.8 eMMC分区介绍

image-20220112143422049

​ 如上图所示,存储设备的默认区域包括一个用于存储数据的User Data Area,两个可能的用于引导的Boot Area Partitions和一个防止重放攻击的Replay Protected Memory Block Area(RPMB)。存储配置最初(在执行任何分区操作之前)由User Data AreaPartition和RPMB和Boot Area Partitions(其尺寸和技术参数由存储制造商定义)组成。

​ 因此,存储块区域可以分类如下:

  • 两个Boot Area Partitions,其大小为128 KB的倍数,可以让系统从eMMC的引导。
  • 一个RPMB分区,其大小定义为128 KB的倍数。
  • 四个General Purpose Area Partitions,用于存储敏感数据,其大小是Write Protect Group的整数倍。

​ 每个General Purpose Area Partition都可以通过enhanced or extended technological features来实现(例如更高的可靠性),以与默认存储介质区分。如果设备支持enhanced storage media features,那么boot和RPMB也默认支持。

​ boot和RPMB分区的大小和属性由存储制造商定义(只读),General Purpose Area Partitions的大小和属性只能由主机在设备生命周期中进行一次编程(只支持一次可编程)。一个可能的配置如下:

image-20220112143458999

​ 主机对General Purpose Area Partition和Enhanced User Data Area的配置可能会影响先前存储的数据(它们将被销毁)和设备初始化时间。特别是,由于内部控制器需要执行操作来设置主机指定的配置,因此配置之后的上电的初始化时间可能会超过规范定义的最大初始化时间。

​ 注意:这里的分区与磁盘里的分区有相同的地方,也有不同。比如两个boot分区和rpmb分区,user data area分区是默认就有的。这里是不需要什么分区表的,他们的大小和范围都在CSD或者EXT_CSD寄存器里定义。如果要使用General Purpose Area Partition就需要对eMMC卡进行配置(只能配置一次)。此外,我们还能像对硬盘进行分区操作那样对user data area进行类似的分区,这里需要使用到分区表,分区后可以安装文件系统。

14.4 代码详解及测试

​ 目的是将第五章编译好的led.imx烧录到TF卡和eMMC里,并且在烧录前后读出两个扇区的值并且再串口上打印出来。

​ 注意:不是直接可以烧录led.imx文件,而是将led.imx转化成一个头文件led.imx.h,头文件里是一个大的unsigned char数组led_imx_image,数组里的内容是由led.imx文件内容转化而来的。然后将led_imx_image这个数组里的内容分别写到TF卡的起始处和eMMC的boot分区起始处。

14.4.1 管脚设置

image-20220112143657344

​ 上图是100sdk_imx6ull开发板的SD卡原理图,使用了uSDHC1控制器。

image-20220112143701287

​ 上图是100sdk_imx6ull开发板eMMC的原理图,接在uSDHC2控制器上。

​ 根据电路设计,对于TF卡将管脚USDHC1_CMD,USDHC1_CLK,USDHC1_DATA0-3,USDHC1_CD_B设置成对应的功能,对于eMMC将管脚USDHC2_CMD,USDHC2_CLK,USDHC2_RESET_B,USDHC2_DATA0~3设置成对应的功能。直接调用IOMUXC_SetPinMux和IOMUXC_SetPinConfig进行设置。

14.4.2 时钟的设置

​ 通过前面的分析可知,USDHCx_CLK_ROOT为198M的话,就要由uSDHCx_SYS_CTRL[DVS]和uSDHCx_SYS_CTRL[SDCLKFS]分频产生400K,25M,26M,50M,52M等时钟。直接设置这两个寄存器对应的分频系数产生相应的时钟即可。

14.4.3 构造发送给CMD_XFR_TYP寄存器的高16bit

​ 根据命令的序号、响应的长度、是否需要命令的序号检测、是否需要CRC校验和是否用到DATA线进行数据传输来设置CMD_XFR_TYP寄存器的高16bit。

代码目录在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.h)

/*
 * CMDx:  normal command,
 * ACMDx: app command,
 * SCMDx: sd command,
 * MCMDx: mmc command
 * R1 R1b R3 R6 R7 48bit; 
 * R2 136bit
 * bit1-0(bit17-16) RSPTYP 00 no response; 01 lengeh 136; 02 length 48; 03 length 48 and check busy
 * bit3(bit19) CCCEN command crc check enable 
 * bit4(bit20) CICEN Command index check enable
 * bit5(bit21) DPSEL data present select
 */
#define CMD0	0x0000	/* GO_IDLE_STATE	no response */
#define CMD1	0x0102	/* SEND_OP_COND	R3 */
#define CMD2	0x0209	/* ALL_SEND_CID	R2 */
#define CMD3	0x031a	/* SET_RELATIVE_ADDR (R6/R1) */
#define ACMD6	0x061a	/* SET_BUS_WIDTH R1	*/
#define ACMD23	0x171a	/* SET_WR_BLK_ERASE_CNT	R1 */
#define SCMD6	0x063a	/* SWITCH_BUSWIDTH	R1 */
#define MCMD6	0x061b	/* SET_EXT_CSD R1B */
#define CMD7	0x071b	/* SELECT_CARD R1B */
#define CMD8	0x081a	/* SEND_IF_COND	R7 */
#define MCMD8	0x083a	/* GET_EXT_CSD R1 */
#define CMD9	0x0909	/* GET_THE_CSD R2 */
#define CMD11	0x0b1a	/* SWITCH VOLTAGE R1 */
#define CMD13	0x0d1a	/* SEND_STATUS R1 */
#define ACMD13	0x0d3a	/* SEND_STATUS R1 */
#define CMD16	0x101a	/* SET_BLOCKLEN	R1 */
#define CMD17	0x113a	/* READ_SINGLE_BLOCK R1 */
#define CMD18	0x123a	/* READ_MULTIPLE_BLOCK R1 for SD R7 */
#define SCMD19  0x133a  /* SEND_TUNING R1 */
#define MCMD21  0x153a  /* SEND_TUNING R1 */
#define CMD24	0x183a	/* WRITE_BLOCK R1 */
#define CMD25	0x193a	/* WRITE_MULTIPLE_BLOCK	R1 */
#define CMD32	0x201a	/* ERASE_WR_BLK_START R1 */
#define CMD33	0x211a	/* ERASE_WR_BLK_END	R1 */
#define CMD35	0x231a	/* ERASE_GROUP_START R1 */
#define CMD36	0x241a	/* ERASE_GROUP_END	R1 */
#define CMD38	0x261b	/* ERASE R1B */
#define ACMD41	0x2902	/* SD_SEND_OP_COND R3 */
#define ACMD42	0x2a1b	/* LOCK_UNLOCK R1B */
#define ACMD51	0x333a	/* SEND_SCR	R1 */
#define CMD55	0x371a	/* APP_CMD	R1 */

14.4.4 发送未使用到DATA线的命令的函数

​ 这类命令直接通过CMD线发送命令,响应也是通过CMD线由设备发送给主机。先判断PRES_STATE寄存器的CIHB置0,确保CMD线不在使用。直接将命令类型赋给CMD_XFR_TYP寄存器,命令的参数赋给CMD_ARG,赋值完后,需要等待命令执行完成。

​ 代码代码目录在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c)

static int USDHC_SendCommand(USDHC_Type *base, u32 command, u32 argument)
{
	/* Wait until command/data bus out of busy status. */
	while (base->PRES_STATE & USDHC_PRES_STATE_CIHB_MASK) {
	}
  
	/* config the command xfertype and argument */
	base->CMD_ARG = argument;
	base->CMD_XFR_TYP = command << 16;

	return USDHC_WaitCommandDone(base);
}

​ 等待命令执行完成函数主要功能是读取INT_STATUS寄存器,等待命令执行完成位(CC)设置,同时检查命令执行出错的位是否设置,出错的位包括命令超时错误、命令CRC错误、命令结束位错误和命令序号错误。如果出错的话调用USDHC_Dump_All函数dump出uSDHC所有的寄存器的值便于调试。最后向相应的位写1,清除掉中断标记位。

​ 代码目录在裸机Git仓库 NoosProgramProject/(14_TF卡编程/ 006_sd/sd.c):

static int USDHC_WaitCommandDone(USDHC_Type *base)
{
	int error = 0;
	uint32_t interruptStatus = 0U;

	/* Wait command complete or USDHC encounters error. */
	while (!(base->INT_STATUS & (USDHC_INT_STATUS_CC_MASK | kUSDHC_CommandErrorFlag))) {
	}

	interruptStatus = base->INT_STATUS;
	if ((interruptStatus & kUSDHC_CommandErrorFlag) != 0U)
	{
		printf("cmd errror, CMD is 0x%x, INT_STATUS is 0x%x\r\n", base->CMD_XFR_TYP, interruptStatus);
		USDHC_Dump_All(base);
		error = -1;
	}

	USDHC_ClearInterruptStatusFlags(
			base, (kUSDHC_CommandCompleteFlag | kUSDHC_CommandErrorFlag | kUSDHC_TuningErrorFlag));

	return error;
}

14.4.5 ADMA描述符的设置函数

​ 每个描述符行(也就是一个执行单元)包括三个域地址(address),长度(length)和属性(attribute)。属性指定了每个描述符行对应的操作。ADMA2不使用DMA System Address寄存器,而是使用ADMA System Address寄存器指向描述符表。

​ 函数有两个参数,分别为保存描述符的地址和地址的长度。首先计算需要多少行描述符,然后设置每个描述符行的地址、长度和属性。地址域按照每个描述符行的所能操作最大长度依次递增,长度域除了最后一个都是描述符行所能操作的最大长度,最后一个描述符行的长度域为最后剩下的长度,属性域每行都得设置tran和VALID,其中最后一个还要设置END,表示描述符行结束了。描述符存放在g_adma2_table[]数组里。

​ 代码目录在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c):

static int USDHC_CreateDescTable(u8 *data, u32 len)
{
	int i;
	u32 dma_len, entries;

	entries = len / USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY;
	if ((len % USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY) != 0U)
		entries++;

	for (i = 0; i < entries; i++)
	{
		if (len > USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY)
		{
			dma_len = USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY;
			len -= USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY;
		}
		else
		{
			dma_len = len;
		}

		/* Each descriptor for ADMA2 is 64-bit in length */
		g_adma2_tablbe[i].address = (uint32_t *)data;
		g_adma2_tablbe[i].attribute = (dma_len << USDHC_ADMA2_DESCRIPTOR_LENGTH_SHIFT);
		g_adma2_tablbe[i].attribute |= kUSDHC_Adma2DescriptorTypeTransfer;
		data += dma_len;
	}
	g_adma2_tablbe[entries - 1].attribute |= kUSDHC_Adma2DescriptorEndFlag;
	/*for (i = 0; i < entries; i++) {
	  printf("g_adma2_tablbe[i] address is 0x%x, attribute is 0x%x\r\n", 
	  g_adma2_tablbe[i].address, g_adma2_tablbe[i].attribute);
	  }
	  */

	return 0;
}

14.4.6 发送使用到DATA线的命令的函数

​ 对于这类命令涉及到操作data线。先判断PRES_STATE寄存器的CIHB和CDIHB置0,确保CMD线和DATA线不在使用。命令和响应还是通过CMD线传输。那么数据是如何在主机和设备之间传输呢?通过ADMA传输。写CMD_XFR_TYP寄存器启动发送命令之前,需要设置ADMA。数据传输有三要素,源、目的和长度。长度该如何确定呢?通过设置BLK_ATT寄存器的BLKCNT和BLKSIZE来决定。然后根据长度和传输的地址设置好ADMA描述符。将ADMA描述符的地址赋值给ADMA_SYS_ADDR寄存器。PROT_CTRL寄存器的ADMA传输方式选择ADMA2。这里假设数据的传输只用到从设备传输到主机。数据的传输方向由MIX_CTRL的DTDSEL来设置,为1表明由设备传输到主机。最后将命令的参数赋给CMD_ARG,将命令类型赋给CMD_XFR_TYP寄存器,然后先等待命令传输完成,再等待数据传输完成。等待命令传输完成还是使用USDHC_WaitCommandDone函数,等待数据传输完成使用USDHC_WaitDataDone函数。

​ 等待数据传输完成USDHC_WaitDataDone函数主要是读取INT_STATUS寄存器,等待传输完成位(TC)设置,同时检查传输出错的位是否设置,出错的位包括数据传输超时错误、数据CRC校验错误、数据结束位错误、Auto CMD12错误和DMA错误。如果出错的话调用USDHC_Dump_All函数dump出uSDHC所有的寄存器的值便于调试。主要观察INT_STATUS和ADMA_ERR_STATUS寄存器的值。最后向相应的位写1,清除掉中断标记位。代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c):

static int USDHC_WaitDataDone(USDHC_Type *base)
{
	int error = 0;
	uint32_t interruptStatus = 0U;

	/* Wait command complete or USDHC encounters error. */
	while (!(base->INT_STATUS & (kUSDHC_DataCompleteFlag | kUSDHC_DataErrorFlag | kUSDHC_DmaErrorFlag))) {
	}

	interruptStatus = base->INT_STATUS;

	if ((interruptStatus & (kUSDHC_DataErrorFlag | kUSDHC_DmaErrorFlag)) != 0U) {
		printf("data or ADMA errror, CMD is 0x%x, INT_STATUS is 0x%x\r\n", base->CMD_XFR_TYP, interruptStatus);
		USDHC_Dump_All(base);
		error = -1;
	}

	USDHC_ClearInterruptStatusFlags(
			base, (kUSDHC_DataCompleteFlag | kUSDHC_DataErrorFlag | kUSDHC_DmaErrorFlag));

	return error;
}

14.4.7 读扇区的函数

​ 先确保PRES_STATE寄存器的CIHB和CDIHB置0,保证CMD线和data线不在使用。根据要读缓冲区的地址和长度创建ADMA描述符,并将ADMA描述符的地址赋值给ADMA_SYS_ADDR寄存器。根据长度设置寄存器的BLKCNT和BLKSIZE。PROT_CTRL寄存器的DMA方式选择ADMA2。对于读一个扇区,设置MIX_CTRL的DTDSEL为1,表示是读,并且使能DMAEN。对于读多个扇区,还要使能MIX_CTRL的MSBSEL,表示是多个扇区,BCEN,表示启用块计数器,和AC12EN,这样数据传输结束后主机自动发送CMD12结束传输。最后将起始传输的块号设置到CMD_ARG,将CMD17(表示单个块读)或者CMD18(表示多个块读)设置到CMD_XFR_TYP启动传输。然后分别等待命令传输完成和数据传输完成。结束时发送cmd13查询状态,看读的过程是否有错误发生。代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c)

int sd_read_blocks(USDHC_Type *base, uint8_t *buffer, uint32_t startBlock, uint32_t blockCount)
{
	int err = 0;

	/* Wait until command/data bus out of busy status. */
	while (base->PRES_STATE & USDHC_PRES_STATE_CIHB_MASK) {
	}
	while (base->PRES_STATE & USDHC_PRES_STATE_CDIHB_MASK) {
	}
	/* set ADMA descriptor */
	USDHC_CreateDescTable(buffer, blockCount * 512);

	/* When use ADMA, disable simple DMA */
	base->DS_ADDR = 0U;
	base->ADMA_SYS_ADDR = (u32) g_adma2_tablbe;

	/* config data block size/block count */
	base->BLK_ATT = (USDHC_BLK_ATT_BLKSIZE(512) | USDHC_BLK_ATT_BLKCNT(blockCount));

	/* disable the external DMA if support */
	base->VEND_SPEC &= ~USDHC_VEND_SPEC_EXT_DMA_EN_MASK;
	/* select DMA mode and config the burst length */
	base->PROT_CTRL &= ~(USDHC_PROT_CTRL_DMASEL_MASK | USDHC_PROT_CTRL_BURST_LEN_EN_MASK);
	base->PROT_CTRL |=
		USDHC_PROT_CTRL_DMASEL(kUSDHC_DmaModeAdma2) | USDHC_PROT_CTRL_BURST_LEN_EN(kUSDHC_EnBurstLenForINCR);

	if (blockCount == 1) {
		/* single block read*/
		/* direction:read, enable DMA */
		base->MIX_CTRL &= ~(USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK | USDHC_MIX_CTRL_DTDSEL_MASK |
				USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_DMAEN_MASK);
		base->MIX_CTRL |= USDHC_MIX_CTRL_DTDSEL_MASK | USDHC_MIX_CTRL_DMAEN_MASK;

		/* config the command xfertype and argument */
		base->CMD_ARG = startBlock;
		base->CMD_XFR_TYP = CMD17 << 16;
	} else {
		/* multi block read */
		/* block count enable, Multiblcok, direction:read, enable DMA */
		base->MIX_CTRL &= ~(USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK | USDHC_MIX_CTRL_DTDSEL_MASK |
				USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_DMAEN_MASK);
		base->MIX_CTRL |= USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK | 
			USDHC_MIX_CTRL_DTDSEL_MASK | USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_DMAEN_MASK;

		/* config the command xfertype and argument */
		base->CMD_ARG = startBlock;
		base->CMD_XFR_TYP = CMD18 << 16;
	}

	err = USDHC_WaitCommandDone(base);
	if (err < 0)
		return err;

	err =  USDHC_WaitDataDone(base);
	if (err < 0)
		return err;

	err = SD_WaitReadWriteComplete(base);
	if (err & 0xc0200000) {
		err = -1;
		printf("%s read error\r\n", __func__);
	}

	return err;
}

14.4.8 写扇区的函数

​ 与读扇区函数类似,区别是MIX_CTRL的DTDSEL为0,表示是写。发送命令时,CMD24表示单个块的写,CMD25表示多个块的写。结束时发送cmd13查询状态,看写的过程是否有错误发生。

​ 代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c)

int sd_write_blocks(USDHC_Type *base, uint8_t *buffer, uint32_t startBlock, uint32_t blockCount)
{
	int err = 0;

	/* Wait until command/data bus out of busy status. */
	while (base->PRES_STATE & USDHC_PRES_STATE_CIHB_MASK) {
	}
	while (base->PRES_STATE & USDHC_PRES_STATE_CDIHB_MASK) {
	}
	/* set ADMA descriptor */
	USDHC_CreateDescTable(buffer, blockCount * 512);

	/* When use ADMA, disable simple DMA */
	base->DS_ADDR = 0U;
	base->ADMA_SYS_ADDR = (u32) g_adma2_tablbe;

	/* config data block size/block count */
	base->BLK_ATT = (USDHC_BLK_ATT_BLKSIZE(512) | USDHC_BLK_ATT_BLKCNT(blockCount));

	/* disable the external DMA if support */
	base->VEND_SPEC &= ~USDHC_VEND_SPEC_EXT_DMA_EN_MASK;
	/* select DMA mode and config the burst length */
	base->PROT_CTRL &= ~(USDHC_PROT_CTRL_DMASEL_MASK | USDHC_PROT_CTRL_BURST_LEN_EN_MASK);
	base->PROT_CTRL |=
		USDHC_PROT_CTRL_DMASEL(kUSDHC_DmaModeAdma2) | USDHC_PROT_CTRL_BURST_LEN_EN(kUSDHC_EnBurstLenForINCR);

	if (blockCount == 1) {
		/* single block wrtie*/
		/* direction:read, enable DMA */
		base->MIX_CTRL &= ~(USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK | USDHC_MIX_CTRL_DTDSEL_MASK |
				USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_DMAEN_MASK);
		base->MIX_CTRL |= USDHC_MIX_CTRL_DMAEN_MASK;

		/* config the command xfertype and argument */
		base->CMD_ARG = startBlock;
		base->CMD_XFR_TYP = CMD24 << 16;
	} else {
		/* multi block write */
		/* block count enable, Multiblcok, direction:read, enable DMA */
		base->MIX_CTRL &= ~(USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK | USDHC_MIX_CTRL_DTDSEL_MASK |
				USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_DMAEN_MASK);
		base->MIX_CTRL |= USDHC_MIX_CTRL_MSBSEL_MASK | USDHC_MIX_CTRL_BCEN_MASK |
			USDHC_MIX_CTRL_AC12EN_MASK | USDHC_MIX_CTRL_DMAEN_MASK;

		/* config the command xfertype and argument */
		base->CMD_ARG = startBlock;
		base->CMD_XFR_TYP = CMD25 << 16;
	}

	err = USDHC_WaitCommandDone(base);
	if (err < 0)
		return err;

	err =  USDHC_WaitDataDone(base);
	if (err < 0)
		return err;

	err = SD_WaitReadWriteComplete(base);
	if (err & 0xe4000000) {
		err = -1;
		printf("%s write error\r\n", __func__);
	}

	return err;
}

14.4.9 TF卡初始化函数

​ 发送命令的函数准备好后,直接根据协议发送命令,解析响应和数据得到卡的状态和信息。代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c):

int sd_init(USDHC_Type *base)
{
	int err;
	u32 retries, acmd41arg = 0, resp[4], raw_scr[2];

	USDHC_Init(base);
	CardInsertDetect(base);

	/* set DATA bus width */
	USDHC_SetDataBusWidth(base, kUSDHC_DataBusWidth1Bit);
	/*set card freq to 400KHZ*/
	g_sd_card.busClock_Hz = USDHC_SetSdClock(base, 198000000U, SDMMC_CLOCK_400KHZ);
	/* send card active */
	USDHC_SetCardActive(base, 100U);
	/* Get host capability. ignore,just decision form spec HOST_CTRL_CAP*/ 

	/* CMD0 - GO_IDLE_STATE software reset and set into idle */
	err = USDHC_SendCommand(base, CMD0, 0x0);
	if (err < 0)
		return -1;

	/* verify card interface operating condition. */
	for (retries = 0; retries < 10; retries++) {
		/* CMD8 (physical layer spec Ver2.0 is mandatory) */		
		err = USDHC_SendCommand(base, CMD8, 0x01aa);
		if (err == 0)
			break;
	}

	if (err == 0) {
		/* SDHC or SDXC card */
		acmd41arg |= kSD_OcrHostCapacitySupportFlag;
	} else {
		/* SDSC card */
		err = USDHC_SendCommand(base, CMD0, 0x0);
		if (err !=  0)
			return -1;
	}

	acmd41arg |= (kSD_OcrVdd32_33Flag | kSD_OcrVdd33_34Flag);
	for(retries = 0; retries < 5000; retries++) {
		/* rca = 0 since card is in IDLE state */
		err = USDHC_SendCommand(base, CMD55, 0x0);
		if (err < 0)
			return -1;

		/* ACMD41 to query OCR */
		err = USDHC_SendCommand(base, ACMD41, acmd41arg);
		if (err < 0)
			return -1;

		if (base->CMD_RSP0 & kSD_OcrPowerUpBusyFlag) {
			g_sd_card.ocr = base->CMD_RSP0;
			printf("ocr is 0x%x\r\n", g_sd_card.ocr);
			break;
		}
	}

	if (retries >= 1000 ) {
		printf("HandShakeOperationConditionFailed\r\n");
		return -1;
	}

	/* check 1.8V support */
	if (g_sd_card.ocr & kSD_OcrSwitch18AcceptFlag) {
		printf("support 1.8v\r\n");
	}
	// our board just support 3.3v, ignore it
	//SD_SwitchVoltage(card))

	/* get CID number */
	err = USDHC_SendCommand(base, CMD2, 0x0);
	if (err < 0)
		return -1;	
	USDHC_GetResponse(base, resp);
	memcpy(g_sd_card.rawCid, resp, sizeof(resp));
	SD_DecodeCid(&g_sd_card, g_sd_card.rawCid);

	/* publish a new relative card address(RCA) */
	err = USDHC_SendCommand(base, CMD3, 0x0);
	if (err < 0)
		return -1;
	g_sd_card.relativeAddress = base->CMD_RSP0 >> 16;
	printf("relative address is 0x%x\r\n", g_sd_card.relativeAddress);

	/* get CID number */
	err = USDHC_SendCommand(base, CMD9, g_sd_card.relativeAddress << 16);
	if (err < 0)
		return -1;	
	USDHC_GetResponse(base, resp);
	memcpy(g_sd_card.rawCsd, resp, sizeof(resp));
	SD_DecodeCsd(&g_sd_card, g_sd_card.rawCsd);
	printf("card is %s", g_sd_card.csd.csdStructure ? "SDHC/SDXC" : "SDSC");
	printf("card block count is %d\r\n", g_sd_card.blockCount);
	printf("card sector size is %d\r\n", g_sd_card.blockSize);
	printf("card command class is 0x%x\r\n", g_sd_card.csd.cardCommandClass);

	/* CMD7: SelectCard */
	err = USDHC_SendCommand(base, CMD7, g_sd_card.relativeAddress << 16);
	if (err < 0)
		return err;

	/* ACMD51: Read SCR */
	err = USDHC_SendCommand(base, CMD55, g_sd_card.relativeAddress << 16);
	if (err < 0)
		return err;
	err = USDHC_SendCommand_with_data(base, ACMD51, 0, raw_scr, 8);
	if (err < 0)
		return err;
	raw_scr[0] = SWAP_32(raw_scr[0]);
	raw_scr[1] = SWAP_32(raw_scr[1]);
	memcpy(g_sd_card.rawScr, raw_scr, sizeof(raw_scr));
	SD_DecodeScr(&g_sd_card, g_sd_card.rawScr);

	printf("scr[0] is 0x%x, scr[1] is 0x%x\r\n", raw_scr[0], raw_scr[1]);
	printf("sd specification is 0x%x\r\n", g_sd_card.scr.sdSpecification);
	if ((g_sd_card.scr.sdBusWidths & 0x4U) == 0) {
		printf("The card can not support 4bit width");
		return -1;
	}
	/* speed class control cmd */
	if ((g_sd_card.scr.commandSupport & 0x01U) == 0)
	{
		printf("The card can not support Speed Class Control (CMD20)\r\n");
	}
	/* set block count cmd */
	if ((g_sd_card.scr.commandSupport & 0x02U) == 0)
	{
		printf("The card can not support Support SetBlockCountCmd (CMD23)\r\n");
	}

	/* Set to max frequency in non-high speed mode. */
	g_sd_card.busClock_Hz = USDHC_SetSdClock(base, 198000000U, SD_CLOCK_25MHZ);

	/* Set to 4-bit data bus mode. */
	/* set card to 4 bit width*/
	err = USDHC_SendCommand(base, CMD55, g_sd_card.relativeAddress << 16);
	if (err < 0)
		return err;	
	err =  USDHC_SendCommand(base, ACMD6, 2);
	if (err < 0)
		return err;
	/* set host to 4 bit width*/
	base->PROT_CTRL = ((base->PROT_CTRL & ~USDHC_PROT_CTRL_DTW_MASK) | USDHC_PROT_CTRL_DTW(1));

	/* set block size to 512 : CMD16 SET_BLOCKLEN */
	err = USDHC_SendCommand(base, CMD16, FSL_SDMMC_DEFAULT_BLOCK_SIZE);
	if (err < 0)
		return err;	

	/* select high speed successful, switch clock to 50M */
	if (SD_SelectBusTiming(base) == 0)
		g_sd_card.busClock_Hz = USDHC_SetSdClock(base, 198000000U, SD_CLOCK_50MHZ);
		//g_sd_card.busClock_Hz = USDHC_SetSdClock(base, 396000000U, SD_CLOCK_50MHZ);
	//printf("clock is %d, base->SYS_CTRL is 0x%x\r\n", g_sd_card.busClock_Hz, base->SYS_CTRL);
	printf("sd init sccessful\r\n");
	
	return 0;
}

14.4.10 TF卡主函数

​ 将led.imx转换成十六进制的数据,并且保存在led.imx.h文件里。从第二个块读取两个块数据并且打印,再将led_imx_image的1024偏移位置开始的数据写入到TF卡,最后再将从第二个块读取两个块数据并且打印,查看读出的数据和写入的数据是否相同。

​ 代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c)

printf("start test sd\r\n");
			sd_init(USDHC1);

			memset(sd_read_buf, 0, sizeof(sd_read_buf));
			sd_read_blocks(USDHC1, sd_read_buf, 2, 2);

			printf("read 2 sectors from 2nd sector\r\n");
			for(i = 0; i < 1024; i++) {
				printf("%02x ", sd_read_buf[i]);
				if((i + 1)%16 == 0)
					printf("\r\n");
			}

			printf("burn led.imx to sd\r\n");
			sd_write_blocks(USDHC1, led_imx_image + 1024, 2, (sizeof(led_imx_image) - 1024) >> 9);
			printf("please set dip switch to SD boot, and push reset, then green led blink \r\n");

			memset(sd_read_buf, 0, sizeof(sd_read_buf));
			sd_read_blocks(USDHC1, sd_read_buf, 2, 2);

			printf("read 2 sectors from 2nd sector after burn led_imx_image:\r\n");
			for(i = 0; i < 1024; i++) {
				printf("%02x ", sd_read_buf[i]);
				if((i + 1)%16 == 0)
					printf("\r\n");
			}			
			printf("please set dip switch to SD boot, and push reset, then green led blink \r\n");		

对于eMMC,引脚的设置和时钟的设置与TF卡类似,发送命令和读写扇区的函数也类似,和TF卡共用。主要有以下几个不同:

14.4.11 eMMC切换boot partition函数

Extended CSD register

PARTITION_CONFIG[179]

image-20220112144324646

image-20220112144330003

当PARTITION_ACCESS为0时对应user partition,为1时对应boot1 partition,为2时对应boot2 partition。

image-20220112144334889

CMD6的格式

Bit31-26 设置为0

Bit25-24 为访问模式 access mode

Bit23-16 为寄存器序号

Bit15-8 为设置的值

Bit7-3 设置为0

Bit2-0 cmd set

image-20220112144339189

00时切换command set

01 根据value域中的1,置位指定字节中对应的位

10 根据value域中的1,清除指定字节中对应的位

11 将value写入到指定的字节

所以由user partition切换到boot1 partition时,设置的值为

(1<< 24) | (179 << 16) | (1 << 8) | 0

由boot1 partition切换回user partition时,设置的值为

(2<<24) | (179 << 16) | (1 << 8) | 0

代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c)

static status_t MMC_SetExtendedCsdConfig(USDHC_Type *base, mmc_extended_csd_access_mode_t access_mode, uint8_t index, uint8_t value)
{
	int err;

	err = USDHC_SendCommand(base, MCMD6, (access_mode << 24) | (index << 16) | (value << 8));
	if (err < 0)
		return -1;

	return 0;

}
static status_t MMC_SelectPartition(USDHC_Type *base, mmc_extended_csd_access_mode_t access_mode, mmc_access_partition_t part_number)
{
	int err;

	err = MMC_SetExtendedCsdConfig(base, access_mode, 179,  part_number);
	if (err < 0)
		return -1;

	err = SD_WaitReadWriteComplete(base);
	if (err & (1 << 7)) {
		printf("failed select partition\r\n");
		return -1;
	}

	return 0;
}
int MMC_SelectBoot1Partition(USDHC_Type *base)
{
	return	MMC_SelectPartition(base, kMMC_ExtendedCsdAccessModeSetBits, 1);
}

14.4.12 eMMC初始化函数

根据协议发送命令,解析响应和获得数据得到卡的状态和信息。

代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c)

int mmc_init(USDHC_Type *base)
{
	int err;
	u32 retries, resp[4], maxBusClock_Hz, bus_width = 4;

	USDHC_Init(base);

	/* set DATA bus width */
	USDHC_SetDataBusWidth(base, kUSDHC_DataBusWidth1Bit);
	/*set card freq to 400KHZ*/
	g_mmc_card.busClock_Hz = USDHC_SetSdClock(base, 198000000U, SDMMC_CLOCK_400KHZ);
	/* send card active */
	USDHC_SetCardActive(base, 100U);
	/* Get host capability. ignore,just decision form spec HOST_CTRL_CAP*/ 

	/* CMD0 - GO_IDLE_STATE software reset the bus and set into idle */
	err = USDHC_SendCommand(base, CMD0, 0x0);
	if (err < 0)
		return -1;

    /* Hand-shaking with card to validata the voltage range Host first sending its expected
       information.*/
    err = USDHC_SendCommand(base, CMD1, 0x0);
	if (err < 0)
		return -1;
	g_mmc_card.ocr = base->CMD_RSP0;
	g_mmc_card.ocr |= 2 << 29;/* set access mode to sector mode */
	for (retries = 0; retries < 1000; retries++) {		
		err = USDHC_SendCommand(base, CMD1, 0x0);
		if (err < 0)
			return -1;

		if ((base->CMD_RSP0 & MMC_OCR_BUSY_MASK))
			break;
	}

	if (retries >= 1000 ) {
		printf("HandShakeOperationConditionFailed\r\n");
		return -1;
	}
	g_mmc_card.ocr = base->CMD_RSP0;
	printf("ocr is 0x%x\r\n", g_mmc_card.ocr);

	/* switch the host voltage which the card can support */
	/* ignore switch voltage, our board just support 3.3v */


	/* CMD2 Get card CID */
	err = USDHC_SendCommand(base, CMD2, 0x0);
	if (err < 0)
		return -1;
	USDHC_GetResponse(base, resp);
	memcpy(g_mmc_card.rawCid, resp, sizeof(resp));
	MMC_DecodeCid(&g_mmc_card, g_mmc_card.rawCid);

	/* Send CMD3 with a chosen relative address, with value greater than 1 */
	g_mmc_card.relativeAddress = 2;
	err = USDHC_SendCommand(base, CMD3, g_mmc_card.relativeAddress << 16);
	if (err < 0)
		return -1;

	/* CMD9 Get the CSD register content */
	err = USDHC_SendCommand(base, CMD9, g_mmc_card.relativeAddress << 16);
	if (err < 0)
		return -1;
	USDHC_GetResponse(base, resp);
	memcpy(g_mmc_card.rawCsd, resp, sizeof(resp));
	MMC_DecodeCsd(&g_mmc_card, g_mmc_card.rawCsd);

	/* Set to maximum speed in normal mode. */
	/*used to calculate the max speed in normal
    mode not high speed mode.
    For cards supporting version 4.0, 4.1, and 4.2 of the specification, the value shall be 20MHz(0x2A).
    For cards supporting version 4.3, the value shall be 26 MHz (0x32H). In High speed mode, the max
    frequency is decided by CARD_TYPE in Extended CSD. */
	printf("csd tran speed is 0x%x\r\n", g_mmc_card.csd.transferSpeed);
	if (g_mmc_card.csd.transferSpeed == 0x32)
		maxBusClock_Hz = 26000000;
	else if ( g_mmc_card.csd.transferSpeed == 0x2A )
		maxBusClock_Hz = 20000000;

	g_mmc_card.busClock_Hz = USDHC_SetSdClock(base, 198000000U, maxBusClock_Hz);

    /* Send CMD7 with the card's relative address to place the card in transfer state. Puts current selected card in
    transfer state. */
	err = USDHC_SendCommand(base, CMD7, g_mmc_card.relativeAddress << 16);
	if (err < 0)
		return err;

	/* Get Extended CSD register content. */
	err = USDHC_SendCommand_with_data(base, MCMD8, 0, g_mmc_card.rawExtendedCsd, 512);
	if (err < 0)
		return err;
	MMC_DecodeExtendedCsd(&g_mmc_card, g_mmc_card.rawExtendedCsd);
	printf("g_mmc_card.extendedCsd.sectorCount is %d\r\n", g_mmc_card.extendedCsd.sectorCount);

	/* set block size to 512 : CMD16 SET_BLOCKLEN */
	err = USDHC_SendCommand(base, CMD16, FSL_SDMMC_DEFAULT_BLOCK_SIZE);
	if (err < 0)
		return err;	

    /* switch to host support speed mode, then switch MMC data bus width and select power class */
	/* select bus width */
	err = MMC_SetBusWidth(base, bus_width);
	if (err < 0)
		return -1;
	printf("bus width is %d\r\n", bus_width);

	/* switch to high speed mode */
	err = MMC_SwitchBusMode(base, kMMC_HighSpeedTiming);
	if (err < 0)
		return err;
	g_mmc_card.busClock_Hz = USDHC_SetSdClock(base, 198000000U, MMC_CLOCK_52MHZ);

	printf("init MMC sucessful\r\n");

	return 0;
}

14.4.13 eMMC主函数

​ 为了可以烧录到eMMC上执行led的测试程序,需要将partition切换到boot1 partition,烧写完后再切换回user partition。切到boot1 partition后,从第二个块读取两个块数据并且打印,再将led_imx_image的1024偏移位置开始的数据写入到eMMC的boot1 partition,再将从第二个块读取两个块数据并且打印,查看读出的数据和写入的数据是否相同,然后切换回user partition。

​ 代码在裸机Git仓库 NoosProgramProject/(14_TF卡编程/006_sd/sd.c)

   ```c

mmc_init(USDHC2); MMC_SelectBoot1Partition(USDHC2);

	memset(sd_read_buf, 0, sizeof(sd_read_buf));
	sd_read_blocks(USDHC2, sd_read_buf, 2, 2);

	printf("read 2 sectors from eMMC boot1 partition 2nd sector\r\n");
	for(i = 0; i < 1024; i++) {
		printf("%02x ", sd_read_buf[i]);
		if((i + 1)%16 == 0)
			printf("\r\n");
	}

	/* wirte led_imx_image to eMMC boot1 partition */
	sd_write_blocks(USDHC2, led_imx_image + 1024, 2, (sizeof(led_imx_image) - 1024) >> 9);
	printf("please set dip switch to eMMC boot, and push reset, then green led blink\r\n");

	printf("read 2 sectors from eMMC boot1 part 2nd sector after burn led_imx_image\r\n");
	memset(sd_read_buf, 0, sizeof(sd_read_buf));
	sd_read_blocks(USDHC2, sd_read_buf, 2, 2);
	MMC_ExitBoot1Partition(USDHC2);

	for(i = 0; i < 1024; i++) {
		printf("%02x ", sd_read_buf[i]);
		if((i + 1)%16 == 0)
			printf("\r\n");
	}	
   ```

14.4.14 参考章节《4-1.4编译程序》编译程序

进入 **裸机Git仓库 NoosProgramProject/(14_TF卡编程/**009_timer_epit_poll) 源码目录。

14.4.15 参考章节《3-1.4映像文件烧写、运行》烧写、运行程序

14.4.16 测试SD卡

1.需要先将编译好的 sd.imx裸机程序烧写至emmc存储器内。

2.插入TF卡,并设置启动方式为emmc启动。

3.打开开发板串口终端,并开启开发板电源。

4.在打印的串口终端下输入1,来选择测试SD卡,

image-20220112144530783

之后在弹出的对话框里输入字符 c 用以继续操作。

image-20220112144536176

程序读取烧录前从第2扇(从0计)区开始的两个扇区,烧录led.imx,读出烧录后的从第2扇(从0计)区开始的两个扇区。烧写完成后会提示 please set dip switch to SD boot, and push reset, then green led blink 此时设置开发板为SD卡启动方式,打开电源,即可看到绿色的LED灯在闪烁。

14.4.17 测试EMMC

1.需要先将编译好的 sd.imx裸机程序烧写至TF卡内。

2.插入TF卡,并设置启动方式为SD卡启动。

3.打开开发板串口终端,并开启开发板电源。

4.在打印的串口终端下输入2,来选择测试EMMC,

image-20220112144557698

程序读取boot1 partition烧录前从第2扇(从0计)区开始的两个扇区,烧录led.imx,读出烧录后的从第2扇(从0计)区开始的两个扇区。烧写完成后会有如下图所示的打印信息,此时移除TF并设置启动方式为EMMC启动,观察绿色LED灯是否在闪烁。

image-20220112144605048