第十九章 ADC——电压采集
单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机 W55MH32是WIZnet重磅推出的高性能以...
单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机
W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。
在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。
W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入网站或者私信获取。
此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。
为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。
若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页,我们期待与您共同探索W55MH32的无限可能。
第二十五章 SDIO——SD卡读写测试
本章参考资料:《W55MH32参考手册》、SD简易规格文件 《Physical Layer Simplified SpecificationV2.0》(版本号:2.00)。
阅读本章内容之前,建议先阅读SD简易规格文件。
1 SDIO简介
SD卡(Secure Digital Memory Card)在我们生活中已经非常普遍了,控制器对SD卡进行读写通信操作一般有两种通信接口可选, 一种是SPI接口,另外一种就是SDIO接口。SDIO全称是安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O卡都有SDIO接口。 W55MH32系列控制器有一个SDIO主机接口,它可以与MMC卡、SD卡、SD I/O卡以及CE-ATA设备进行数据传输。MMC卡可以说是SD卡的前身, 现阶段已经用得很少。SD I/O卡本身不是用于存储的卡,它是指利用SDIO传输协议的一种外设。比如Wi-Fi Card, 它主要是提供Wi-Fi功能,有些Wi-Fi模块是使用串口或者SPI接口进行通信的,但Wi-Fi SDIO Card是使用SDIO接口进行通信的。 并且一般设计SD I/O卡是可以插入到SD的插槽。CE-ATA是专为轻薄笔记本硬盘设计的硬盘高速通讯接口。
多媒体卡协会网站www.mmca.org中提供了有MMCA技术委员会发布的多媒体卡系统规范。
SD卡协会网站www.sdcard.org中提供了SD存储卡和SDIO卡系统规范。
CE-ATA工作组网站www.ce-ata.org中提供了CE_ATA系统规范。
随之科技发展,SD卡容量需求越来越大,SD卡发展到现在也是有几个版本的, 关于SDIO接口的设备整体概括见下图,SDIO接口的设备 :
关于SD卡和SD I/O部分内容可以在SD协会网站获取到详细的介绍,比如各种SD卡尺寸规则、读写速度标示方法、应用扩展等等信息。
本章内容针对SD卡使用讲解,对于其他类型卡的应用可以参考相关系统规范实现,所以对于控制器中针对其他类型卡的内容可能在本章中简单提及或者被忽略, 本章内容不区分SDIO和SD卡这两个概念。即使目前SD协议提供的SD卡规范版本最新是4.01版本,但W55MH32系列控制器只支持SD卡规范版本2.0, 即只支持标准容量SD和高容量SDHC标准卡,不支持超大容量SDXC标准卡,所以可以支持的最高卡容量是32GB。
2 SD卡物理结构
一张SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器5个部分,见下图,SD卡物理结构。 存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;电源检测单元保证SD卡工作在合适的电压下, 如出现掉电或上状态时,它会使控制单元和存储单元接口复位;卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器;接口驱动器控制SD卡引脚的输入输出。
SD卡总共有8个寄存器,用于设定或表示SD卡信息,参考表 SD卡寄存器 。这些寄存器只能通过对应的命令访问, 对SD卡进行控制操作并不是像操作控制器GPIO相关寄存器那样一次读写一个寄存器的,它是通过命令来控制, SDIO定义了64个命令,每个命令都有特殊意义,可以实现某一特定功能,SD卡接收到命令后, 根据命令要求对SD卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。
名称 | bit 宽度 | 描述 |
CID | 128 | 卡识别号 (Card identification 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):卡状态信息 |
每个寄存器位的含义可以参考SD简易规格文件《Physical Layer Simplified Specification V2.0》第5章内容。
3 SDIO总线
3.1 总线拓扑
SD卡一般都支持SDIO和SPI这两种接口,本章内容只介绍SDIO接口操作方式,如果需要使用SPI操作方式可以参考SPI相关章节。 另外,W55MH32系列控制器的SDIO是不支持SPI通信模式的,如果需要用到SPI通信只能使用SPI外设。
SD卡总线拓扑参考图 SD卡总线拓扑 。虽然可以共用总线, 但不推荐多卡槽共用总线信号,要求一个单独SD总线应该连接一个单独的SD卡。
SD卡使用9-pin接口通信,其中3根电源线、1根时钟线、1根命令线和4根数据线,具体说明如下:
CLK:时钟线,由SDIO主机产生,即由W55MH32控制器输出;
CMD:命令控制线,SDIO主机通过该线发送命令控制SD卡,如果命令要求SD卡提供应答(响应),SD卡也是通过该线传输应答信息;
D0-3:数据线,传输读写数据;SD卡可将D0拉低表示忙状态;
VDD、VSS1、VSS2:电源和地信号。
在之前的I2C以及SPI章节都有详细讲解了对应的通信时序,实际上,SDIO的通信时序简单许多,SDIO不管是从主机控制器向SD卡传输, 还是SD卡向主机控制器传输都只以CLK时钟线的上升沿为有效。SD卡操作过程会使用两种不同频率的时钟同步数据,一个是识别卡阶段时钟频率FOD, 最高为400kHz,另外一个是数据传输模式下时钟频率FPP,默认最高为25MHz,如果通过相关寄存器配置使SDIO工作在高速模式,此时数据传输模式最高频率为50MHz。
对于W55MH32控制器只有一个SDIO主机,所以只能连接一个SDIO设备, 开发板上集成了一个Micro SD卡槽和SDIO接口的WiFi模块,要求只能使用其中一个设备。 SDIO接口的WiFi模块一般集成有使能线,如果需要用到SD卡需要先控制该使能线禁用WiFi模块。
3.2 总线协议
SD总线通信是基于命令和数据传输的。通讯由一个起始位(“0”),由一个停止位(“1”)终止。SD通信一般是主机发送一个命令(Command), 从设备在接收到命令后作出响应(Response),如有需要会有数据(Data)传输参与。
SD总线的基本交互是命令与响应交互,见下图,命令与响应交互:
SD数据是以块(Black)形式传输的,SDHC卡数据块长度一般为512字节,数据可以从主机到卡, 也可以是从卡到主机。数据块需要CRC位来保证数据传输成功。CRC位由SD卡系统硬件生成。 W55MH32控制器可以控制使用单线或4线传输,本开发板设计使用4线传输。图 多块写入操作 为主机向SD卡写入数据块操作示意。
SD数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。 数据写入前需要检测SD卡忙状态,因为SD卡在接收到数据后编程到存储区过程需要一定操作时间。SD卡忙状态通过把D0线拉低表示。
数据块读操作与之类似,只是无需忙状态检测。
使用4数据线传输时,每次传输4bit数据,每根数据线都必须有起始位、终止位以及CRC位,CRC位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过D0线反馈给主机。
SD卡数据包有两种格式,一种是常规数据(8bit宽),它先发低字节再发高字节,而每个字节则是先发高位再发低位,4线传输示意如图 8位宽数据包传输 。
4线同步发送,每根线发送一个字节的其中两个位,数据位在四线顺序排列发送,DAT3数据线发较高位,DAT0数据线发较低位。
另外一种数据包发送格式是宽位数据包格式,对SD卡而言宽位数据包发送方式是针对SD卡SSR(SD状态)寄存器内容发送的, SSR寄存器总共有512bit,在主机发出ACMD13命令后SD卡将SSR寄存器内容通过DAT线发送给主机。宽位数据包格式示意见图,宽位数据包传输:
3.3 命令
SD命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与SD主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。
3.3.1 命令格式
SD命令格式固定为48bit,都是通过CMD线连续传输的(数据线不参与),见下图,SD命令格式:
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.3.2 命令类型
SD命令有4种类型:
无响应广播命令(bc),发送到所有卡,不返回任务响应;
带响应广播命令(bcr),发送到所有卡,同时接收来自所有卡响应;
寻址命令(ac),发送到选定卡,DAT线无数据传输;
寻址数据传输命令(adtc),发送到选定卡,DAT线有数据传输。
另外,SD卡主机模块系统旨在为各种应用程序类型提供一个标准接口。在此环境中,需要有特定的客户/应用程序功能。为实现这些功能, 在标准中定义了两种类型的通用命令:特定应用命令(ACMD)和常规命令(GEN_CMD)。要使用SD卡制造商特定的ACMD命令如ACMD6, 需要在发送该命令之前发送CMD55命令,告知SD卡接下来的命令为特定应用命令。CMD55命令只对紧接的第一个命令有效, SD卡如果检测到CMD55之后的第一条命令为ACMD则执行其特定应用功能,如果检测发现不是ACMD命令,则执行标准命令。
3.3.3 命令描述
SD卡系统的命令被分为多个类,每个类支持一种“卡的功能设置”。表 SD部分命令描述 列举了SD卡部分命令信息, 更多详细信息可以参考SD简易规格文件说明,表中填充位和保留位都必须被设置为0。
虽然没有必须完全记住每个命令详细信息,但越熟悉命令对后面编程理解非常有帮助。
命令序号 | 类型 | 参数 | 响应 | 缩写 | 描述 |
CMD0 | bc | [31:0] 填充位 | -- | GO_IDLE_STATE | 复位所有的卡到 idle 状态。 |
CMD2 | bcr | [31:0] 填充位 | R2 | ALL_SEND_CID | 通知所有卡通过 CMD 线返回 CID 值。 |
CMD3 | bcr | [31:0] 填充位 | R6 | SEND_RELATIVE_ADDR | 通知所有卡发布新 RCA。 |
CMD4 | bc | [31:16]DSR[15:0] 填充位 |
-- | SET_DSR | 编程所有卡的 DSR。 |
CMD7 | ac | [31:16]RCA[15:0] 填充位 |
R1b | SELECT/DESELECT_CARD | 选择 / 取消选择 RCA 地址卡。 |
CMD8 | bcr | [31:12] 保留位 [11:8] VHS [7:0] 检查模式 |
R7 | SEND_IF_COND | 发送 SD 卡接口条件,包含主机支持的电压信息,并询问卡是否支持。 |
CMD9 | ac | [31:16]RCA[15:0] 填充位 |
R2 | SEND_CSD | 选定卡通过 CMD 线发送 CSD 内容 |
CMD10 | ac | [31:16]RCA[15:0] 填充位 |
R2 | SEND_CID | 选定卡通过 CMD 线发送 CID 内容 |
CMD12 | ac | [31:0] 填充位 | R1b | STOP_TRANSMISSION | 强制卡停止传输 |
CMD13 | ac | [31:16]RCA[15:0] 填充位 |
R1 | SEND_STATUS | 选定卡通过 CMD 线发送它状态寄存器 |
CMD15 | ac | [31:16]RCA[15:0] 填充位 |
-- | GO_INACTIVE_STATE | 使选定卡进入 “inactive” 状态 |
面向块的读操作 (Class 2) | |||||
CMD16 | ac | [31:0] 块长度填充位 | R1 | SET_BLOCK_LEN | 对于标准 SD 卡,设置块命令的长度,对于 SDHC 卡块命令长度固定为 512 字节。 |
CMD17 | adtc | [31:0] 数据地址 | R1 | READ_SINGLE_BLOCK | 对于标准卡,读取 SEL_BLOCK_LEN 长度字节的块;对于 SDHC 卡,读取 512 字节的块。 |
CMD18 | adtc | [31:0] 数据地址 | R1 | READ_MULTIPLE_BLOCK | 连续从 SD 卡读取数据块,直到被 CMD12 中断。块长度同 CMD17。 |
面向块的写操作 (Class 4) | |||||
CMD24 | adtc | [31:0] 数据地址 | R1 | WRITE_BLOCK | 对于标准卡,写入 SEL_BLOCK_LEN 长度字节的块;对于 SDHC 卡,写入 512 字节的块。 |
CMD25 | adtc | [31:0] 数据地址 | R1 | WRITE_MULTIPLE_BLOCK | 连续向 SD 卡写入数据块,直到被 CMD12 中断。每块长度同 CMD17。 |
CMD27 | adtc | [31:0] 填充位 | R1 | PROGRAM_CSD | 对 CSD 的可编程位进行编程 |
擦除命令 (Class 5) | |||||
CMD32 | ac | [31:0] 数据地址 | R1 | ERASE_WR_BLK_START | 设置擦除的起始块地址 |
CMD33 | ac | [31:0] 数据地址 | R1 | ERASE_WR_BLK_END | 设置擦除的结束块地址 |
CMD38 | ac | [31:0] 填充位 | R1b | ERASE | 擦除预先选定的块 |
加锁命令 (Class 7) | |||||
CMD42 | adtc | [31:0] 保留 | R1 | LOCK_UNLOCK | 加锁 / 解锁 SD 卡 |
特定应用命令 (Class 8) | |||||
CMD55 | ac | [31:16]RCA[15:0] 填充位 |
R1 | APP_CMD | 指定下个命令为特定应用命令,不是标准命令 |
CMD56 | adtc | [31:1] 填充位 [0] 读 / 写 | R1 | GEN_CMD | 通用命令,或特定应用命令中用于传输数据块,最低位为 1 表示读数据,0 表示写数据 |
SD 卡特定应用命令 | |||||
ACMD6 | ac | [31:2] 填充位 [1:0] 总线宽度 |
R1 | SET_BUS_WIDTH | 定义数据总线宽度 ('00'=1bit,'10'=4bit)。 |
ACMD13 | adtc | [31:0] 填充位 | R1 | SD_STATUS | 发送 SD 状态 |
ACMD41 | Bcr | [32] 保留位 [30]HCS(OCR[30]) [29:24] 保留位 [23:0] VDD 电压 (OCR[23:0]) |
R3 | SD_SEND_OP_COND | 主机要求卡发送支持信息 (HCS) 和 OCR 寄存器内容。 |
ACMD51 | adtc | [31:0] 填充位 | R1 | SEND_SCR | 读取配置寄存器 SCR |
3.4 响应
响应由SD卡向主机发出,部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。SDIO总共有7个响应类型(代号:R1~R7), 其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6。与命令一样, SD卡的响应也是通过CMD线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是48bit长度, 只有R2类型是长响应,其长度为136bit。各个类型响应具体情况如表 SD卡响应类型 。
除了R3类型之外,其他响应都使用CRC7校验来校验,对于R2类型是使用CID和CSD寄存器内部CRC7。
4 SD卡的操作模式及切换
4.1 SD卡的操作模式
SD卡有多个版本,W55MH32控制器目前最高支持《Physical Layer Simplified Specification V2.0》定义的SD卡,W55MH32控制器对SD卡进行数据读写之前需要识别卡的种类:V1.0标准卡、V2.0标准卡、V2.0高容量卡或者不被识别卡。
SD卡系统(包括主机和SD卡)定义了两种操作模式:卡识别模式和数据传输模式。在系统复位后,主机处于卡识别模式, 寻找总线上可用的SDIO设备;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡接收到SEND_RCA(CMD3)命令后, SD卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。在每个操作模式下, SD卡都有几种状态,参考表 SD卡状态与操作模式 ,通过命令控制实现卡状态的切换。
4.2 卡识别模式
在卡识别模式下,主机会复位所有处于“卡识别模式”的SD卡,确认其工作电压范围,识别SD卡类型, 并且获取SD卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中,要求SD卡工作在识别时钟频率FOD的状态下。 卡识别模式下SD卡状态转换如图 卡识别模式状态转换图 。
主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送GO_IDLE_STATE(CMD0)让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不会复位。
主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。SD卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。 SEND_IF_COND(CMD8)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应, 否则不响应。而主机则根据响应内容确定卡的电压匹配性。CMD8是SD卡标准V2.0版本才有的新命令,所以如果主机有接收到响应,可以判断卡为V2.0或更高版本SD卡。
SD_SEND_OP_COND(ACMD41)命令可以识别或拒绝不匹配它的电压范围的卡。ACMD41命令的VDD电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。 对于对CMD8有响应的卡,把ACMD41命令的HCS位设置为1,可以测试卡的容量类型,如果卡响应的CCS位为1说明为高容量SD卡,否则为标准卡。 卡在响应ACMD41之后进入准备状态,不响应ACMD41的卡为不可用卡,进入无效状态。ACMD41是应用特定命令,发送该命令之前必须先发CMD55。
ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。之后主机就发送
SEND_RELATIVE_ADDR(CMD3)命令, 让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA是16bit地址,而CID是128bit地址,使用RCA简化通信。卡在接收到CMD3并发出响应后就进入数据传输模式, 并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。
4.3 数据传输模式
只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz, 频率切换可以通过CMD4命令来实现。数据传输模式下,SD卡状态转换过程见图 数据传输模式卡状态转换 。
CMD7用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态, 必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。同时通过CMD7命令也可以让已经被选择的目标卡返回到待机状态。
数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。 卡处于传输状态下可以使用表 SD部分命令描述 中面向块的读写以及擦除命令对卡进行数据读写、擦除。 CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0和CMD15会中止任何数据编程操作, 返回卡识别模式,这可能导致卡数据被损坏。
5 W55MH32的SDIO功能框图
W55MH32控制器有一个SDIO,由两部分组成:SDIO适配器和AHB接口,见下图,SDIO功能框图。SDIO适配器提供SDIO主机功能, 可以提供SD时钟、发送命令和进行数据传输。AHB接口用于控制器访问SDIO适配器寄存器并且可以产生中断和DMA请求信号。
SDIO使用两个时钟信号,一个是SDIO适配器时钟(SDIOCLK=HCLK=72MHz),另外一个是AHB总线时钟的二分频(HCLK/2,一般为36MHz)。 适配器寄存器和FIFO使用AHB总线一侧的时钟(HCLK/2),控制单元、命令通道和数据通道使用SDIO适配器一侧的时钟(SDIOCLK)。
SDIO_CK是SDIO接口与SD卡用于同步的时钟信号。它使用SDIOCLK作为SDIO_CK的时钟来源, 可以通过设置BYPASS模式直接得到,这时SDIO_CK = SDIOCLK=HCLK。若禁止BYPASS模式, 可以通过配置时钟寄存器的CLKDIV位控制分频因子,即SDIO_CK=SDIOCLK/(2+CLKDIV)= HCLK/(2+CLKDIV)。 配置时钟时要注意,SD卡普遍要求SDIO_CK时钟频率不能超过25MHz。
W55MH32控制器的SDIO是针对MMC卡和SD卡的主设备,所以预留有8根数据线,对于SD卡最多用四根数据线。
SDIO适配器是SD卡系统主机部分,是W55MH32控制器与SD卡数据通信中间设备。SDIO适配器由五个单元组成, 分别是控制单元、命令路径单元、数据路径单元、寄存器单元以及FIFO,见下图,SDIO适配器框图:
5.1 控制单元
控制单元包含电源管理和时钟管理功能,结构如图 SDIO适配器控制单元 。 电源管理部件会在系统断电和上电阶段禁止SD卡总线输出信号。时钟管理部件控制CLK线时钟信号生成。一般使用SDIOCLK分频得到。
5.2 命令路径
命令路径控制命令发送,并接收卡的响应,结构见下图,SDIO适配器命令路径:
关于SDIO适配器状态转换流程可以参考图 卡识别模式状态转换图 , 当SD卡处于某一状态时,SDIO适配器必然处于特定状态与之对应。 W55MH32控制器以命令路径状态机(CPSM)来描述SDIO适配器的状态变化,并加入了等待超时检测功能, 以便退出永久等待的情况。CPSM的描述见下图,CPSM状态机描述图:
5.3 数据路径
数据路径部件负责与SD卡相互数据传输,内部结构见下图,SDIO适配器数据路径:
SD卡系统数据传输状态转换参考图 数据传输模式卡状态转换 , SDIO适配器以数据路径状态机(DPSM)来描述SDIO适配器状态变化情况。 并加入了等待超时检测功能,以便退出永久等待情况。发送数据时,DPSM处于等待发送(Wait_S)状态,如果数据FIFO不为空, DPSM变成发送状态并且数据路径部件启动向卡发送数据。接收数据时,DPSM处于等待接收状态,当DPSM收到起始位时变成接收状态, 并且数据路径部件开始从卡接收数据。DPSM状态机描述见下图,DPSM状态机描述图:
5.4 数据FIFO
数据FIFO(先进先出)部件是一个数据缓冲器,带发送和接收单元。控制器的FIFO包含宽度为32bit、 深度为32字的数据缓冲器和发送/接收逻辑。其中SDIO状态寄存器(SDIO_STA)的TXACT位用于指示当前正在发送数据, RXACT位指示当前正在接收数据,这两个位不可能同时为1。
当TXACT为1时,可以通过AHB接口将数据写入到传输FIFO。
当RXACT为1时,接收FIFO存放从数据路径部件接收到的数据。
根据FIFO空或满状态会把SDIO_STA寄存器位值1,并可以产生中断和DMA请求。
5.5 适配器寄存器
适配器寄存器包含了控制SDIO外设的各种控制寄存器及状态寄存器,内容较多, 可以通过SDIO提供的各种结构体来了解,这些寄存器的功能都被整合到了结构体或ST标准库之中。
6 SDIO初始化结构体
标准库函数对SDIO外设建立了三个初始化结构体,分别为SDIO初始化结构体SDIO_InitTypeDef、SDIO命令初始化结构体SDIO_CmdInitTypeDef和SDIO数据初始化结构体SDIO_DataInitTypeDef。 这些结构体成员用于设置SDIO工作环境参数,并由SDIO相应初始化配置函数或功能函数调用,这些参数将会被写入到SDIO相应的寄存器,达到配置SDIO工作环境的目的。
初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。 初始化结构体定义在w55mh32_sdio.h文件中,初始化库函数定义在w55mh32_sdio.c文件中,编程时我们可以结合这两个文件内注释使用。
SDIO初始化结构体用于配置SDIO基本工作环境,比如时钟分频、时钟沿、数据宽度等等。它被SDIO_Init()函数使用。
代码清单:SDIO-1 SDIO初始化结构体
typedef struct { uint32_t SDIO_ClockEdge; // 时钟沿 uint32_t SDIO_ClockBypass; // 旁路时钟 uint32_t SDIO_ClockPowerSave; // 节能模式 uint32_t SDIO_BusWide; // 数据宽度 uint32_t SDIO_HardwareFlowControl; // 硬件流控制 uint8_t SDIO_ClockDiv; // 时钟分频 } SDIO_InitTypeDef;
各结构体成员的作用介绍如下:
SDIO_ClockEdge:主时钟SDIOCLK产生CLK引脚时钟有效沿选择,可选上升沿或下降沿, 它设定SDIO时钟控制寄存器(SDIO_CLKCR)的NEGEDGE位的值,一般选择设置为高电平。
SDIO_ClockBypass:时钟分频旁路使用,可选使能或禁用,它设定SDIO_CLKCR寄存器的BYPASS位。如果使能旁路,SDIOCLK直接驱动CLK线输出时钟; 如果禁用,使用SDIO_CLKCR寄存器的CLKDIV位值分频SDIOCLK,然后输出到CLK线。一般选择禁用时钟分频旁路。
SDIO_ClockPowerSave:节能模式选择,可选使能或禁用,它设定SDIO_CLKCR寄存器的PWRSAV位的值。如果使能节能模式, CLK线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能CLK线输出时钟。
SDIO_BusWide:数据线宽度选择,可选1位数据总线、4位数据总线或8为数据总线,系统默认使用1位数据总线, 操作SD卡时在数据传输模式下一般选择4位数据总线。它设定SDIO_CLKCR寄存器的WIDBUS位的值。
SDIO_HardwareFlowControl:硬件流控制选择,可选使能或禁用,它设定SDIO_CLKCR寄存器的HWFC_EN位的值。 硬件流控制功能可以避免FIFO发送上溢和下溢错误。
SDIO_ClockDiv:时钟分频系数,它设定SDIO_CLKCR寄存器的CLKDIV位的值, 设置SDIOCLK与CLK线输出时钟分频系数:
CLK线时钟频率=SDIOCLK/([CLKDIV+2])。
7 SDIO命令初始化结构体
SDIO命令初始化结构体用于设置命令相关内容,比如命令号、命令参数、响应类型等等。它被SDIO_SendCommand()函数使用。
代码清单:SDIO-2 SDIO命令初始化接口
typedef struct { uint32_t SDIO_Argument; // 命令参数 uint32_t SDIO_CmdIndex; // 命令号 uint32_t SDIO_Response; // 响应类型 uint32_t SDIO_Wait; // 等待使能 uint32_t SDIO_CPSM; // 命令路径状态机 } SDIO_CmdInitTypeDef;
各个结构体成员介绍如下:
SDIO_Argument:作为命令的一部分发送到卡的命令参数, 它设定SDIO参数寄存器(SDIO_ARG)的值。
SDIO_CmdIndex:命令号选择, 它设定SDIO命令寄存器(SDIO_CMD)的CMDINDEX位的值。
SDIO_Response:响应类型,SDIO定义两个响应类型:长响应和短响应。根据命令号选择对应的响应类型。 SDIO定义了四个32位的SDIO响应寄存器(SDIO_RESPx,x=1..4),短响应只用到SDIO_RESP1。
SDIO_Wait:等待类型选择,有三种状态可选,一种是无等待状态,超时检测功能启动;一种是等待中断, 另外一种是等待传输完成。它设定SDIO_CMD寄存器的WAITPEND位和WAITINT位的值。
SDIO_CPSM:命令路径状态机控制,可选使能或禁用CPSM。 它设定SDIO_CMD寄存器的CPSMEN位的值。
8 SDIO数据初始化结构体
SDIO数据初始化结构体用于配置数据发送和接收参数,比如传输超时、数据长度、传输模式等等。它被SDIO_DataConfig()函数使用。
代码清单:SDIO-3 SDIO数据初始化结构体
typedef struct { uint32_t SDIO_DataTimeOut; // 数据传输超时 uint32_t SDIO_DataLength; // 数据长度 uint32_t SDIO_DataBlockSize; // 数据块大小 uint32_t SDIO_TransferDir; // 数据传输方向 uint32_t SDIO_TransferMode; // 数据传输模式 uint32_t SDIO_DPSM; // 数据路径状态机 } SDIO_DataInitTypeDef;
各结构体成员介绍如下:
SDIO_DataTimeOut:设置数据传输以卡总线时钟周期表示的超时周期,它设定SDIO数据定时器寄存器(SDIO_DTIMER)的值。 在DPSM进入Wait_R或繁忙状态后开始递减,直到0还处于以上两种状态则将超时状态标志置1.
SDIO_DataLength:设置传输数据长度, 它设定SDIO数据长度寄存器(SDIO_DLEN)的值。
SDIO_DataBlockSize:设置数据块大小,有多种尺寸可选, 不同命令要求的数据块可能不同。它设定SDIO数据控制寄存器(SDIO_DCTRL)寄存器的DBLOCKSIZE位的值。
SDIO_TransferDir:数据传输方向,可选从主机到卡的写操作, 或从卡到主机的读操作。它设定SDIO_DCTRL寄存器的DTDIR位的值。
SDIO_TransferMode:数据传输模式,可选数据块或数据流模式。 对于SD卡操作使用数据块类型。它设定SDIO_DCTRL寄存器的DTMODE位的值。
SDIO_DPSM:数据路径状态机控制,可选使能或禁用DPSM。 它设定SDIO_DCTRL寄存器的DTEN位的值。要实现数据传输都必须使能SDIO_DPSM。
9 SD卡读写测试
9.1 代码解析
基于 W55MH32 的 SD 卡文件系统测试程序,借助 FATFS 文件系统库对 SD 卡进行读写、目录创建与删除等操作。下面是对代码的详细解释:
1. 头文件包含
#include "w55mh32.h" #include < stdio.h > #include "bsp_sdio_sdcard.h" #include "sdio_test.h" #include "delay.h" #include "ff.h"
这些头文件涵盖了硬件相关的定义、标准输入输出库、SD 卡驱动、延时函数以及 FATFS 文件系统库。
2. 函数声明
void USART_Config(uint32_t bound); uint8_t GetCmd(void); void TestList(void); void ShowCardInfo(void); void SDInfoShow(void); void FatfsTest(void); void FatfsBigDataTest(void); void WriteFileTest(void); void CreateDir(void); void DeleteDirFile(void); void ViewRootDir(void);
这里声明了一系列函数,用于串口配置、命令获取、测试列表显示、SD 卡信息显示、文件系统测试等。
3. 全局变量定义
FATFS fs; FIL fnew; FRESULT res_sd; UINT fnum; BYTE ReadBuffer[1024] = {0}; BYTE WriteBuffer[] = "WIZnet, create a new file system test filen"; #define TEST_FILE_LEN (2 * 1024 * 1024) #define BUF_SIZE (4 * 1024) uint8_t TestBuf[BUF_SIZE];
这些变量用于存储 FATFS 文件系统对象、文件对象、文件操作结果、读写文件数量、读写缓冲区等。
4. 主函数 main()
int main(void) { uint8_t cmd = 0; RCC_ClocksTypeDef clocks; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE); delay_init(); UART_Configuration(115200); RCC_GetClocksFreq(&clocks); printf("n"); printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhzn", (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000, (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000); printf("SDIO SD Card Fatfs Test.n"); TestList(); while (1) { cmd = GetCmd(); switch (cmd) { case '1': { printf("1.--->>>FatfsTestrn"); FatfsTest(); TestList(); break; } case '2': { printf("1.--->>>FatfsBigDataTestrn"); FatfsBigDataTest(); TestList(); break; } case '3': { printf("2.--->>>ViewRootDirrn"); ViewRootDir(); TestList(); break; } case '4': { printf("3.--->>>CreateDirrn"); CreateDir(); TestList(); break; } case '5': { printf("4.--->>>DeleteDirFilern"); DeleteDirFile(); TestList(); break; } } } }
主函数的功能如下:
初始化 CRC 时钟、延时函数和串口。
打印系统时钟信息。
显示测试列表。
进入无限循环,不断获取用户输入的命令,并依据命令调用相应的测试函数。
5. 测试列表函数 TestList()
void TestList(void) { printf("/***************************SD Card Test*******************************/n"); printf("==========================List==========================n"); printf("1: Create a new file (FatFs read-write test file.txt) for read-write testingn"); printf("2: Read and write large amounts of data (FatFs read and write test file .txt), perform read and write testsn"); printf("3: Show the file test in the root directory of the SD Cardn"); printf("4: Create directory(/Dir1,/Dir1/Die1_1,/Dir2)n"); printf("5: Delete files and directories (/Dir1,/Dir1/Dir1_1,/Dir2, FatFs read and write test files.txt)n"); printf("****************************************************************************/n"); }
该函数用于显示 SD 卡测试的选项列表。
6. SD 卡信息显示函数 SDInfoShow()
void SDInfoShow(void) { printf("/***************************SD Info Show*******************************/n"); printf("SDCardInfo.CardType : %dn", SDCardInfo.CardType); printf("SDCardInfo.CardCapacity : %lld Byten", (SDCardInfo.CardCapacity)); printf("SDCardInfo.CardBlockSize : %d Byten", SDCardInfo.CardBlockSize); }
此函数用于显示 SD 卡的类型、容量和块大小等信息。
7. 获取命令函数 GetCmd()
uint8_t GetCmd(void) { uint8_t tmp = 0; if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE)) { tmp = USART_ReceiveData(USART1); } return tmp; }
该函数用于检查串口是否接收到数据,若接收到则返回该数据。
8. 串口配置函数 UART_Configuration()
void UART_Configuration(uint32_t bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART_TEST, &USART_InitStructure); USART_Cmd(USART_TEST, ENABLE); }
该函数用于配置串口的 GPIO 引脚和串口参数。
9. 大数据读写测试函数 FatfsBigDataTest()
void FatfsBigDataTest(void) { uint32_t i; res_sd = f_mount(&fs, "0:", 1); printf("nFile system test --->>> write testn"); res_sd = f_open(&fnew, "0:FatFs read and write test files.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ); if (res_sd == FR_OK) { printf("Open/create FatFs to read and write the test file.txt successfully, and write data to the filern"); for (i = 0; i < 0xFFFFF; i++) { res_sd = f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum); if ((i % 0x8FFF) == 0) { printf("......n"); } } if (res_sd == FR_OK) { printf("File written successfullyn"); } else { printf("File write failed (%d)n", res_sd); } f_close(&fnew); } else { printf("Failed to open/create, filern"); } }
该函数用于进行大数据量的文件写入测试,先挂载文件系统,然后打开文件,循环写入数据。
10. 文件系统测试函数 FatfsTest()
void FatfsTest(void) { res_sd = f_mount(&fs, "0:", 1); printf("n format testn"); if (res_sd == FR_NO_FILESYSTEM) { printf("The SD card has no file system and is about to be formattedrn"); res_sd = f_mkfs("0:", 0, 0); if (res_sd == FR_OK) { printf("The SD card successfully mounted the file systemrn"); res_sd = f_mount(NULL, "0:", 1); res_sd = f_mount(&fs, "0:", 1); } else { printf("SD card formatting failedrn"); while (1); } } else if (res_sd != FR_OK) { printf("SD card mount failed (%d), maybe SD card initialization failedrn", res_sd); while (1); } else { printf("The file system is mounted and can be read and written for testingrn"); } SDInfoShow(); printf("n file system test --->>> Write testn"); res_sd = f_open(&fnew, "0:FatFs read and write test files.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ); if (res_sd == FR_OK) { printf("Open/create FatFs to read and write the test file.txt successfully, and write data to the filern"); res_sd = f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum); if (res_sd == FR_OK) { printf("The file was written successfully, the number of bytes written:% d The data written is: n%srn", fnum, WriteBuffer); } else { printf("File write failed (%d)n", res_sd); } f_close(&fnew); } else { printf("Failed to open/create, filern"); } printf("n file system test --->>> read testn"); res_sd = f_open(&fnew, "0:FatFs read and write test files.txt", FA_OPEN_ALWAYS | FA_READ); if (res_sd == FR_OK) { printf("File successfully openedrn"); res_sd = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum); if (res_sd == FR_OK) { printf("File read successful. Bytes read:% d The data read was: n%srn", fnum, ReadBuffer); } else { printf("File read failed (%d)n", res_sd); } } else { printf("File opening failedn"); } f_close(&fnew); f_mount(NULL, "0:", 1); }
该函数用于进行文件系统的格式化、读写测试,若 SD 卡没有文件系统则进行格式化,然后进行文件的读写操作。
11. 创建目录函数 CreateDir()
void CreateDir(void) { res_sd = f_mount(&fs, "0:", 1); if (res_sd != FR_OK) { printf("Failed to mount file system (%d)rn", res_sd); } res_sd = f_mkdir("/Dir1"); if (res_sd == FR_OK) { printf("f_mkdir Dir1 OKrn"); } else if (res_sd == FR_EXIST) { printf("Dir1 Target already exists(%d)rn", res_sd); } else { printf("f_mkdir Dir1 fail(%d)rn", res_sd); return; } res_sd = f_mkdir("/Dir2"); if (res_sd == FR_OK) { printf("f_mkdir Dir2 OKrn"); } else if (res_sd == FR_EXIST) { printf("Dir2 Target already exists(%d)rn", res_sd); } else { printf("f_mkdir Dir2 fail (%d)rn", res_sd); return; } res_sd = f_mkdir("/Dir1/Dir1_1"); if (res_sd == FR_OK) { printf("f_mkdir Dir1_1 OKrn"); } else if (res_sd == FR_EXIST) { printf("Dir1_1 Target already exists(%d)rn", res_sd); } else { printf("f_mkdir Dir1_1 fail (%d)rn", res_sd); return; } f_mount(NULL, "0:", 1); }
该函数用于在 SD 卡上创建目录 /Dir1、/Dir2 和 /Dir1/Dir1_1。
12. 删除目录和文件函数 DeleteDirFile()
void DeleteDirFile(void) { res_sd = f_mount(&fs, "0:", 1); if (res_sd != FR_OK) { printf("Failed to mount file system (%d)rn", res_sd); } res_sd = f_unlink("/Dir1/Dir1_1"); if (res_sd == FR_OK) { printf("Delete subdirectory /Dir1/Dir1_1 successrn"); } else if ((res_sd == FR_NO_FILE) || (res_sd == FR_NO_PATH)) { printf("No file or directory found: %srn", "/Dir1/Dir1_1"); } else { printf("Deleting subdirectory/Dir1/Dir1_1 failed (errcode =% d) File read-only or directory not emptyrn", res_sd); } res_sd = f_unlink("/Dir1"); if (res_sd == FR_OK) { printf("Delete directory/Dir1 successfullyrn"); } else if ((res_sd == FR_NO_FILE) || (res_sd == FR_NO_PATH)) { printf("No file or directory found : %srn", "/Dir1"); } else { printf("Deleting subdirectory/Dir1/Dir1_1 failed (errcode =% d) File read-only or directory not emptyrn", res_sd); } res_sd = f_unlink("/Dir2"); if (res_sd == FR_OK) { printf("Delete/Dir2 successfullyrn"); } else if ((res_sd == FR_NO_FILE) || (res_sd == FR_NO_PATH)) { printf("No file or directory found : %srn", "/Dir2"); } else { printf("Deleting subdirectory/Dir1/Dir1_1 failed (errcode = %d) File read-only or directory is not emptyrn", res_sd); } res_sd = f_unlink("FatFs read and write test files.txt"); if (res_sd == FR_OK) { printf("Delete FatFs read and write test file.txt successfullyrn"); } else if ((res_sd == FR_NO_FILE) || (res_sd == FR_NO_PATH)) { printf("No file or directory found : %srn", "/FatFs read and write test files.txt"); } else { printf("Failed to delete FatFs read and write test file.txt(errcode = %d) File read-only or directory is not emptyrn", res_sd); } f_mount(NULL, "0:", 1); }
该函数用于删除之前创建的目录和文件。
13. 查看根目录函数 ViewRootDir()
void ViewRootDir(void) { DIR dirinf; FILINFO fileinf; uint32_t cnt = 0; char name[256]; res_sd = f_mount(&fs, "0:", 1); if (res_sd != FR_OK) { printf("Failed to mount file system (%d)rn", res_sd); } res_sd = f_opendir(&dirinf, "/"); if (res_sd != FR_OK) { printf("Failed to open root directory (%d)rn", res_sd); return; } fileinf.lfname = name; fileinf.lfsize = 256; printf("attribute | file size | short filename | long file namern"); for (cnt = 0;; cnt++) { res_sd = f_readdir(&dirinf, &fileinf); if (res_sd != FR_OK || fileinf.fname[0] == 0) { break; } if (fileinf.fname[0] == '.') { continue; } if (fileinf.fattrib & AM_DIR) { printf("(0x%02d)directory", fileinf.fattrib); } else { printf("(0x%02d)attribute", fileinf.fattrib); } printf("%10d ", fileinf.fsize); printf(" %s |", fileinf.fname); printf(" %srn", (char *)fileinf.lfname); } f_mount(NULL, "0:", 1); }
14. 串口发送字符函数 SER_PutChar() 和 fputc()
int SER_PutChar(int ch) { while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC)); USART_SendData(USART_TEST, (uint8_t)ch); return ch; } int fputc(int c, FILE *f) { if (c == 'n') { SER_PutChar('r'); } return (SER_PutChar(c)); }
这段代码借助 FATFS 文件系统库,实现了对 SD 卡的文件读写、目录创建与删除、查看根目录等操作,并且提供了一个简单的菜单界面,方便用户进行测试。
9.2 下载验证
1. 程序上电运行
2. 输入命令测试
通过键盘输入菜单中的数字键(1~5)执行对应功能:
(1) 测试 1: 基础文件读写 (输入 1)
功能:
创建文件 FatFs read and write test files.txt,写入文本并回读。
(2) 测试 2: 大数据量读写 (输入 2)
功能:
向文件中写入 2MB 数据(用于压力测试),每写入 36KB 输出一次进度。
(3) 测试 3: 查看根目录 (输入 3)
功能:
显示 SD 卡根目录下的文件和子目录。
(4) 测试 4: 创建目录 (输入 4)
功能:
创建 /Dir1, /Dir1/Dir1_1, /Dir2 目录。
(5)测试 5: 删除目录/文件 (输入 5)
功能:
删除测试文件和目录(需确保目录为空)。
WIZnet 是一家无晶圆厂半导体公司,成立于 1998 年。产品包括互联网处理器 iMCU™,它采用 TOE(TCP/IP 卸载引擎)技术,基于独特的专利全硬连线 TCP/IP。iMCU™ 面向各种应用中的嵌入式互联网设备。
WIZnet 在全球拥有 70 多家分销商,在香港、韩国、美国设有办事处,提供技术支持和产品营销。
香港办事处管理的区域包括:澳大利亚、印度、土耳其、亚洲(韩国和日本除外)。
当前非电脑浏览器正常宽度,请使用移动设备访问本站!