完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第四十三章 SD 卡实验 很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有 U 盘,FLASH 芯片, SD 卡等。他们各有优点,综合比较,最适合单片机系统的莫过于 SD 卡了,它不仅容量可以做 到很大(32GB 以上),支持 SPI/SDIO 驱动,而且有多种体积的尺寸可供选择(标准的 SD 卡尺 寸,以及 TF 卡尺寸等),能满足不同应用的要求。 只需要少数几个 IO 口即可外扩一个高达 32GB 以上的外部存储器,容量从几十 M 到几十 G 选择尺度很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。 ALIENTKE 探索者 STM32F4 开发板自带了标准的SD 卡接口,使用 STM32F4自带的 SDIO 接口驱动,4 位模式,最高通信速度可达 48Mhz(分频器旁路时),最高每秒可传输数据 24M 字节,对于一般应用足够了。在本章中,我们将向大家介绍,如何在 ALIENTEK 探索者 STM32F4 开发板上实现 SD 卡的读取。本章分为如下几个部分: 43.1 SDIO 接口简介 43.2 硬件设计 43.3 软件设计 43.4 下载验证 43.1 SDIO 简介 ALIENTEK 探索者 STM32F4 开发板自带 SDIO 接口,本节,我们将简单介绍 STM32F4 的 SDIO 接口,包括:主要功能及框图、时钟、命令与响应和相关寄存器简介等,最后,我们将 介绍 SD 卡的初始化流程。 43.1.1 SDIO 主要功能及框图 STM32F4 的 SDIO 控制器支持多媒体卡(MMC 卡)、SD 存储卡、SD I/O 卡和 CE-ATA 设 备等。SDIO 的主要功能如下: ➢ 与多媒体卡系统规格书版本 4.2 全兼容。支持三种不同的数据总线模式:1 位(默认)、 4 位和 8 位。 ➢ 与较早的多媒体卡系统规格版本全兼容(向前兼容)。 ➢ 与 SD 存储卡规格版本 2.0 全兼容。 ➢ 与 SD I/O 卡规格版本 2.0 全兼容:支持良种不同的数据总线模式:1 位(默认)和 4 位。 ➢ 完全支持 CE-ATA 功能(与 CE-ATA 数字协议版本 1.1 全兼容)。 8 位总线模式下数据 传输速率可达 48MHz(分频器旁路时)。 ➢ 数据和命令输出使能信号,用于控制外部双向驱动器。 STM32F4 的 SDIO 控制器包含 2 个部分:SDIO 适配器模块和 APB2 总线接口,其功能框 图如图 43.1.1.1 所示: 图 43.1.1.1 STM32F4 的 SDIO 控制器功能框图 复位后默认情况下 SDIO_D0 用于数据传输。初始化后主机可以改变数据总线的宽度(通 过 ACMD6 命令设置)。 如果一个多媒体卡接到了总线上,则 SDIO_D0、SDIO_D[3:0]或 SDIO_D[7:0]可以用于数 据传输。MMC 版本 V3.31 和之前版本的协议只支持 1 位数据线,所以只能用 SDIO_D0(为了 通用性考虑,在程序里面我们只要检测到是 MMC 卡就设置为 1 位总线数据)。 如果一个 SD 或 SD I/O 卡接到了总线上,可以通过主机配置数据传输使用 SDIO_D0 或 SDIO_D[3:0]。所有的数据线都工作在推挽模式。 SDIO_CMD 有两种操作模式: ① 用于初始化时的开路模式(仅用于 MMC 版本 V3.31 或之前版本) ② 用于命令传输的推挽模式(SD/SD I/O 卡和 MMC V4.2 在初始化时也使用推挽驱动) 43.1.2 SDIO 的时钟 从图 43.1.1.1 我们可以看到 SDIO 总共有 3 个时钟,分别是: 卡时钟(SDIO_CK):每个时钟周期在命令和数据线上传输 1 位命令或数据。对于多媒体 卡 V3.31 协议,时钟频率可以在 0MHz 至 20MHz 间变化;对于多媒体卡 V4.0/4.2 协议,时钟 频率可以在 0MHz 至 48MHz 间变化;对于 SD 或 SD I/O 卡,时钟频率可以在 0MHz 至 25MHz 间变化。 SDIO 适配器时钟(SDIOCLK):该时钟用于驱动 SDIO 适配器,来自 PLL48CK,一般为 48Mhz,并用于产生 SDIO_CK 时钟。 APB2总线接口时钟(PCLK2):该时钟用于驱动SDIO的APB2总线接口,其频率为HCLK/2, 一般为 84Mhz。 前面提到,我们的 SD 卡时钟(SDIO_CK),根据卡的不同,可能有好几个区间,这就涉及 到时钟频率的设置,SDIO_CK 与 SDIOCLK 的关系(时钟分频器不旁路时)为: SDIO_CK=SDIOCLK/(2+CLKDIV) 其中,SDIOCLK 为 PLL48CK,一般是 48Mhz,而 CLKDIV 则是分配系数,可以通过 SDIO 的 SDIO_CLKCR 寄存器进行设置(确保 SDIO_CK 不超过卡的最大操作频率)。注意,以上公 式,是时钟分频器不旁路时的计算公式,当时钟分频器旁路时,SDIO_CK 直接等于 SDIOCLK。 这里要提醒大家,在 SD 卡刚刚初始化的时候,其时钟频率(SDIO_CK)是不能超过 400Khz 的,否则可能无法完成初始化。在初始化以后,就可以设置时钟频率到最大了(但不可超过 SD 卡的最大操作时钟频率)。 43.1.3 SDIO 的命令与响应SDIO 的命令分为应用相关命令(ACMD)和通用命令(CMD)两部分,应用相关命令(ACMD) 的发送,必须先发送通用命令(CMD55),然后才能发送应用相关命令(ACMD)。 SDIO 的所有命令和响应都是通过 SDIO_CMD 引脚传输的,任何命令的长度都是固定为 48 位, SDIO 的命令格式如表 43.1.3.1 所示: 表 43.1.3.1 SDIO 命令格式 所有的命令都是由 STM32F4 发出,其中开始位、传输位、CRC7 和结束位由 SDIO 硬件控 制,我们需要设置的就只有命令索引和参数部分。其中命令索引(如 CMD0,CMD1 之类的) 在 SDIO_CMD 寄存器里面设置,命令参数则由寄存器 SDIO_ARG 设置。 一般情况下,选中的 SD 卡在接收到命令之后,都会回复一个应答(注意 CMD0 是无应答 的),这个应答我们称之为响应,响应也是在 CMD 线上串行传输的。STM32F4 的 SDIO 控制器 支持 2 种响应类型,即:短响应(48 位)和长响应(136 位),这两种响应类型都带 CRC 错误 检测(注意不带 CRC 的响应应该忽略 CRC 错误标志,如 CMD1 的响应)。 短响应的格式如表 43.1.3.2 所示: 表 43.1.3.2 SDIO 命令格式 长响应的格式如表 43.1.3.3 所示: 表 43.1.3.3 SDIO 命令格式 同样,硬件为我们滤除了开始位、传输位、CRC7 以及结束位等信息,对于短响应,命令 索引存放在 SDIO_RESPCMD 寄存器,参数则存放在 SDIO_RESP1 寄存器里面。对于长响应, 则仅留 CID/CSD 位域,存放在 SDIO_RESP1~SDIO_RESP4 等 4 个寄存器。 SD 存储卡总共有 5 类响应(R1、R2、R3、R6、R7),我们这里以 R1 为例简单介绍一下。 R1(普通响应命令)响应输入短响应,其长度为 48 位,R1 响应的格式如表 43.1.3.4 所示: 表 43.1.3.4 R1 响应格式 在收到 R1 响应后,我们可以从 SDIO_RESPCMD 寄存器和 SDIO_RESP1 寄存器分别读出 命令索引和卡状态信息。关于其他响应的介绍,请大家参考光盘:《SD 卡 2.0 协议.pdf》或 《STM32F4xx 中文参考手册》第 28 章。 最后,我们看看数据在 SDIO 控制器与 SD 卡之间的传输。对于 SDI/SDIO 存储器,数据是 以数据块的形式传输的,而对于 MMC 卡,数据是以数据块或者数据流的形式传输。本节我们 只考虑数据块形式的数据传输。 SDIO(多)数据块读操作,如图 43.1.3.1 所示: 图 43.1.3.1 SDIO(多)数据块读操作 从上图,我们可以看出,从机在收到主机相关命令后,开始发送数据块给主机,所有数据 块都带有 CRC 校验值(CRC 由 SDIO 硬件自动处理),单个数据块读的时候,在收到 1 个数据 块以后即可以停止了,不需要发送停止命令(CMD12)。但是多块数据读的时候,SD 卡将一直 发送数据给主机,直到接到主机发送的 STOP 命令(CMD12)。 SDIO(多)数据块写操作,如图 43.1.3.2 所示: 图 43.1.3.2 SDIO(多)数据块写操作 数据块写操作同数据块读操作基本类似,只是数据块写的时候,多了一个繁忙判断,新的 数据块必须在 SD 卡非繁忙的时候发送。这里的繁忙信号由 SD 卡拉低 SDIO_D0,以表示繁忙, SDIO 硬件自动控制,不需要我们软件处理。 SDIO 的命令与响应就为大家介绍到这里。 43.1.4 SDIO 相关寄存器介绍 第一个,我们来看 SDIO 电源控制寄存器(SDIO_POWER),该寄存器定义如图 43.1.4.1 所示: 图 43.1.4.1 SDIO_POWER 寄存器位定义 该寄存器复位值为 0,所以 SDIO 的电源是关闭的,我们要启用 SDIO,第一步就是要设置 该寄存器最低 2 个位均为 1,让 SDIO 上电,开启卡时钟。 第二个,我们看 SDIO 时钟控制寄存器(SDIO_CLKCR),该寄存器主要用于设置 SDIO_CK 的分配系数,开关等,并可以设置 SDIO 的数据位宽,该寄存器的定义如图 43.1.4.2 所示: 图 43.1.4.2 SDIO_CLKCR 寄存器位定义 上图仅列出了部分我们要用到的位设置,WIDBUS 用于设置 SDIO 总线位宽,正常使用的 时候,设置为 1,即 4 位宽度。BYPASS 用于设置分频器是否旁路,我们一般要使用分频器, 所以这里设置为 0,禁止旁路。CLKEN 则用于设置是否使能 SDIO_CK,我们设置为 1。最后, CLKDIV,则用于控制 SDIO_CK 的分频,一般设置为 0,即可得到 24Mhz 的 SDIO_CK 频率。 第三个,我们要介绍的是 SDIO 参数制寄存器(SDIO_ARG),该寄存器比较简单,就是一 个 32 位寄存器,用于存储命令参数,不过需要注意的是,必须在写命令之前先写这个参数寄存 器! 第四个,我们要介绍的是 SDIO 命令响应寄存器(SDIO_RESPCMD),该寄存器为 32 位, 但只有低 6 位有效,比较简单,用于存储最后收到的命令响应中的命令索引。如果传输的命令 响应不包含命令索引,则该寄存器的内容不可预知。 第五个,我们要介绍的是 SDIO 响应寄存器组(SDIO_RESP1~SDIO_RESP4),该寄存器组 总共由 4 个 32 位寄存器组成,用于存放接收到的卡响应部分信息。如果收到短响应,则数据存 放在 SDIO_RESP1 寄存器里面,其他三个寄存器没有用到。而如果收到长响应,则依次存放在 SDIO_RESP1~ SDIO_RESP4 里面,如表 43.1.4.1 所示: 表 43.1.4.1 响应类型和 SDIO_RESPx 寄存器 第七个,我们介绍 SDIO 命令寄存器(SDIO_CMD),该寄存器各位定义如图 43.1.4.3 所示: 图 43.1.4.3 SDIO_CMD 寄存器位定义 图中只列出了部分位的描述,其中低 6 位为命令索引,也就是我们要发送的命令索引号(比 如发送 CMD1,其值为 1,索引就设置为 1)。位[7:6],用于设置等待响应位,用于指示 CPSM 是否需要等待,以及等待类型等。这里的 CPSM,即命令通道状态机,我们就不详细介绍了, 请参阅《STM32F4xx 中文参考手册》第 776 页,有详细介绍。命令通道状态机我们一般都是开 启的,所以位 10 要设置为 1。 第八个,我们要介绍的是 SDIO 数据定时器寄存器(SDIO_DtiMER),该寄存器用于存储 以卡总线时钟(SDIO_CK)为周期的数据超时时间,一个计数器将从 SDIO_DTIMER 寄存器加 载数值,并在数据通道状态机(DPSM)进入 Wait_R 或繁忙状态时进行递减计数,当 DPSM 处在 这些状态时,如果计数器减为 0,则设置超时标志。这里的 DPSM,即数据通道状态机,类似 CPSM,详细请参考《STM32F4xx 中文参考手册》第 780 页。注意:在写入数据控制寄存器, 进行数据传输之前,必须先写入该寄存器(SDIO_DTIMER)和数据长度寄存器(SDIO_DLEN)! 第九个,我们要介绍的是 SDIO 数据长度寄存器(SDIO_DLEN),该寄存器低 25 位有效, 用于设置需要传输的数据字节长度。对于块数据传输,该寄存器的数值,必须是数据块长度(通 过 SDIO_DCTRL 设置)的倍数。 第十个,我们要介绍的是 SDIO 数据控制寄存器(SDIO_DCTRL),该寄存器各位定义如图 43.1.4.4 所示: 图 43.1.4.4 SDIO_DCTRL 寄存器位定义 该寄存器,用于控制数据通道状态机(DPSM),包括数据传输使能、传输方向、传输模式、 DMA 使能、数据块长度等信息,都是通过该寄存器设置。我们需要根据自己的实际情况,来 配置该寄存器,才可正常实现数据收发。 接下来,我们介绍几个位定义十分类似的寄存器,他们是:状态寄存器(SDIO_STA)、清 除中断寄存器(SDIO_ICR)和中断屏蔽寄存器(SDIO_MASK),这三个寄存器每个位的定义 都相同,只是功能各有不同。所以可以一起介绍,以状态寄存器(SDIO_STA)为例,该寄存 器各位定义如图 43.1.4.5 所示: 图 43.1.4.5 SDIO_STA 寄存器位定义 状态寄存器可以用来查询 SDIO 控制器的当前状态,以便处理各种事务。比如 SDIO_STA 的位 2 表示命令响应超时,说明 SDIO 的命令响应出了问题。我们通过设置 SDIO_ICR 的位 2 则可以清除这个超时标志,而设置 SDIO_MASK 的位 2,则可以开启命令响应超时中断,设置 为 0 关闭。其他位我们就不一一介绍了,请大家自行学习。 最后,我们向大家介绍 SDIO 的数据 FIFO 寄存器(SDIO_FIFO),数据 FIFO 寄存器包括 接收和发送 FIFO,他们由一组连续的 32 个地址上的 32 个寄存器组成,CPU 可以使用 FIFO 读 写多个操作数。例如我们要从 SD 卡读数据,就必须读 SDIO_FIFO 寄存器,要写数据到 SD 卡, 则要写 SDIO_FIFO 寄存器。SDIO 将这 32 个地址分为 16 个一组,发送接收各占一半。而我们 每次读写的时候,最多就是读取发送 FIFO 或写入接收 FIFO 的一半大小的数据,也就是 8 个字 (32 个字节),这里特别提醒,我们操作 SDIO_FIFO(不论读出还是写入)必须是以 4 字节对 齐的内存进行操作,否则将导致出错! 至此,SDIO 的相关寄存器介绍,我们就介绍完了。还有几个不常用的寄存器,我们没有介绍到,请大家参考《STM32F4xx 中文参考手册》第 28 章相关章节。 43.1.5 SD 卡初始化流程 最后,我们来看看 SD 卡的初始化流程,要实现 SDIO 驱动 SD 卡,最重要的步骤就是 SD 卡的初始化,只要 SD 卡初始化完成了,那么剩下的(读写操作)就简单了,所以我们这里重 点介绍 SD 卡的初始化。从 SD 卡 2.0 协议(见光盘资料)文档,我们得到 SD 卡初始化流程图 如图 43.1.5.1 所示: 图 43.1.5.1 SD 卡初始化流程 从图中,我们看到,不管什么卡(这里我们将卡分为 4 类:SD2.0 高容量卡(SDHC,最大 32G),SD2.0 标准容量卡(SDSC,最大 2G),SD1.x 卡和 MMC 卡),首先我们要执行的是卡 上电(需要设置 SDIO_POWER[1:0]=11),上电后发送 CMD0,对卡进行软复位,之后发送 CMD8 命令,用于区分 SD 卡 2.0,只有 2.0 及以后的卡才支持 CMD8 命令,MMC 卡和 V1.x 的卡,是 不支持该命令的。CMD8 的格式如表 43.1.5.1 所示: 表 43.1.5.1 CMD8 命令格式 这里,我们需要在发送 CMD8 的时候,通过其带的参数我们可以设置 VHS 位,以告诉 SD 卡,主机的供电情况,VHS 位定义如表 43.1.5.2 所示: 表 43.1.5.2 VHS 位定义 这里我们使用参数 0X1AA,即告诉 SD 卡,主机供电为 2.7~3.6V 之间,如果 SD 卡支持 CMD8,且支持该电压范围,则会通过 CMD8 的响应(R7)将参数部分原本返回给主机,如果 不支持 CMD8,或者不支持这个电压范围,则不响应。 在发送 CMD8 后,发送 ACMD41(注意发送 ACMD41 之前要先发送 CMD55),来进一步 确认卡的操作电压范围,并通过 HCS 位来告诉 SD 卡,主机是不是支持高容量卡(SDHC)。 ACMD41 的命令格式如表 43.1.5.3 所示: 表 43.1.5.3 ACMD41 命令格式 ACMD41 得到的响应(R3)包含 SD 卡 OCR 寄存器内容,OCR 寄存器内容定义如表 43.1.5.4 所示: 表 43.1.5.4 OCR 寄存器定义 对于支持 CMD8 指令的卡,主机通过 ACMD41 的参数设置 HCS 位为 1,来告诉 SD 卡主机支 SDHC 卡,如果设置为 0,则表示主机不支持 SDHC 卡,SDHC 卡如果接收到 HCS 为 0, 则永远不会反回卡就绪状态。对于不支持 CMD8 的卡,HCS 位设置为 0 即可。 SD 卡在接收到 ACMD41 后,返回 OCR 寄存器内容,如果是 2.0 的卡,主机可以通过判断 OCR 的 CCS 位来判断是 SDHC 还是 SDSC;如果是 1.x 的卡,则忽略该位。OCR 寄存器的最 后一个位用于告诉主机 SD 卡是否上电完成,如果上电完成,该位将会被置 1。 对于 MMC 卡,则不支持 ACMD41,不响应 CMD55,对 MMC 卡,我们只需要在发送 CMD0 后,在发送 CMD1(作用同 ACMD41),检查 MMC 卡的 OCR 寄存器,实现 MMC 卡的初始化。 至此,我们便实现了对 SD 卡的类型区分,图 43.1.5.1 中,最后发送了 CMD2 和 CMD3 命 令,用于获得卡 CID 寄存器数据和卡相对地址(RCA)。 CMD2,用于获得 CID 寄存器的数据,CID 寄存器数据各位定义如表 43.1.5.5 所示: 表 43.1.5.5 卡 CID 寄存器位定义 SD 卡在收到 CMD2 后,将返回 R2 长响应(136 位),其中包含 128 位有效数据(CID 寄 存器内容),存放在 SDIO_RESP1~4 等 4 个寄存器里面。通过读取这四个寄存器,就可以获得 SD 卡的 CID 信息。 CMD3,用于设置卡相对地址(RCA,必须为非 0),对于 SD 卡(非 MMC 卡),在收到 CMD3 后,将返回一个新的 RCA 给主机,方便主机寻址。RCA 的存在允许一个 SDIO 接口挂 多个 SD 卡,通过 RCA 来区分主机要操作的是哪个卡。而对于 MMC 卡,则不是由 SD 卡自动 返回 RCA,而是主机主动设置 MMC 卡的 RCA,即通过 CMD3 带参数(高 16 位用于 RCA 设 置),实现 RCA 设置。同样 MMC 卡也支持一个 SDIO 接口挂多个 MMC 卡,不同于 SD 卡的 是所有的 RCA 都是由主机主动设置的,而 SD 卡的 RCA 则是 SD 卡发给主机的。 在获得卡 RCA 之后,我们便可以发送 CMD9(带 RCA 参数),获得 SD 卡的 CSD 寄存器 内容,从 CSD 寄存器,我们可以得到 SD 卡的容量和扇区大小等十分重要的信息。CSD 寄存器 我们在这里就不详细介绍了,关于 CSD 寄存器的详细介绍,请大家参考《SD 卡 2.0 协议.pdf》。 至此,我们的 SD 卡初始化基本就结束了,最后通过 CMD7 命令,选中我们要操作的 SD 卡,即可开始对 SD 卡的读写操作了,SD 卡的其他命令和参数,我们这里就不再介绍了,请大 家参考《SD 卡 2.0 协议.pdf》,里面有非常详细的介绍。 43.2 硬件设计 本章实验功能简介:开机的时候先初始化 SD 卡,如果 SD 卡初始化完成,则提示 LCD 初 始化成功。按下 KEY0,读取 SD 卡扇区 0 的数据,然后通过串口发送到电脑。如果没初始化 通过,则在 LCD 上提示初始化失败。同样用 DS0 来指示程序正在运行。 本实验用到的硬件资源有: 1) 指示灯 DS0 2) KEY0 按键 3) 串口 4) TFTLCD 模块 5) SD 卡 前面四部分,在之前的实例已经介绍过了,这里我们介绍一下探索者 STM32F4 开发板板 载的 SD 卡接口和 STM32F4 的连接关系,如图 43.2.1 所示: 图43.2.1 SD卡接口与STM32F4连接原理图 探索者STM32F4开发板的SD卡座(SD_CARD),在PCB背面,SD卡座与STM32F4的连 接在开发板上是直接连接在一起的,硬件上不需要任何改动。 43.3 软件设计 打开本章实验工程可以看到,我们不但增加了 HAL 库 SDIO 支持文件 stm32f4xx_hal_sdio.c 以及头文件 stm32f4xx_hal_sdio.h,同时,我们还新增了 SD 卡的 SDIO 支持文件 sdio_sdcard.c 以及头文件 sdio_sdcard.h。由于 sdio_sdcard.c 里面代码比较多,由于篇幅限制,我们不贴出所 有代码,仅介绍几个重要的函数,第一个是 SD_Init 函数,该函数源码如下: //SD 卡初始化 //返回值:0 初始化正确;其他值,初始化错误 u8 SD_Init(void) { u8 SD_Error; //初始化时的时钟不能大于 400KHZ SDCARD_Handler.Instance=SDIO; SDCARD_Handler.Init.ClockEdge=SDIO_CLOCK_EDGE_RISING; //上升沿 SDCARD_Handler.Init.ClockBypass=SDIO_CLOCK_BYPASS_DISABLE; //不使用 bypass 模式,直接用 HCLK 进行分频得到 SDIO_CK SDCARD_Handler.Init.ClockPowerSave=SDIO_CLOCK_POWER_SAVE_DISABLE; //空闲时不关闭时钟电源 SDCARD_Handler.Init.BusWide=SDIO_BUS_WIDE_1B; //1 位数据线 SDCARD_Handler.Init.HardwareFlowControl= SDIO_HARDWARE_FLOW_CONTROL_DISABLE;//关闭硬件流控 SDCARD_Handler.Init.ClockDiv=SDIO_TRANSFER_CLK_DIV; //SD 传输时钟频率最大 25MHZ SD_Error=HAL_SD_Init(&SDCARD_Handler); if(SD_Error!=HAL_OK) return 1; //获取 SD 卡信息 HAL_SD_GetCardInfo(&SDCARD_Handler,&SDCardInfo); SD_Error=HAL_SD_ConfigWideBusOperation(&SDCARD_Handler, SDIO_BUS_WIDE_4B);//使能宽总线模式 if(SD_Error!=HAL_OK) return 2; return 0; }该函数先实现 SDIO 时钟及相关 IO 口的初始化,然后开始 SD 卡的初始化流程,这个过程在 43.1.5 节有详细介绍了。首先,通过 HAL_SD_Init 函数(该函数我们这里不做介绍,请参考本 例程源码),并获得 SD 卡的类型(SDHC/SDSC/SDV1.x/MMC),完成 SD 卡的初始化。 接下来,我们看看 SD 卡读块函数:SD_ReadBlock,该函数用于从 SD 卡指定地址读出一 个块(扇区)数据,该函数代码如下: //通过 DMA 读取 SD 卡一个扇区 //buf:读数据缓存区 //sector:扇区地址 //blocksize:扇区大小(一般都是 512 字节) //cnt:扇区个数 //返回值:错误状态;0,正常;其他,错误代码; u8 SD_ReadBlocks_DMA(uint32_t *buf,uint64_t sector,uint32_t blocksize,uint32_t cnt) { u8 err=0; u32 timeout=SD_TIMEOUT; err=HAL_SD_ReadBlocks_DMA(&SDCARD_Handler,(uint8_t*)buf,sector,cnt); //通过 DMA 读取 SD 卡 n 个扇区 if(err==0) { //等待 SD 卡读完 while(SD_GetCardState()!=SD_TRANSFER_OK) { if(timeout-- == 0) { err=SD_TRANSFER_BUSY; } } } return err; }限于篇幅,余下函数我们就不一一介绍了,大家可以参本实验考源代码。关于控制命令, 如果有不了解的,请参考《SD 卡 2.0 协议.pdf》里面都有非常详细的介绍。 最后,我们来看看 SDIO 与文件系统的两个接口函数:SD_ReadDisk 和 SD_WriteDisk,这 两个函数的代码如下: //读 SD 卡 //buf:读数据缓存区 //sector:扇区地址 //cnt:扇区个数 //返回值:错误状态;0,正常;其他,错误代码; u8 SD_ReadDisk(u8* buf,u32 sector,u32 cnt) { u8 sta=HAL_OK; long long lsector=sector; u32 timeout=SD_TIMEOUT; u8 n; sta=SD_ReadBlocks_DMA((uint32_t*)buf,lsector, 512,cnt); return sta; } //写 SD 卡 //buf:写数据缓存区 //sector:扇区地址 //cnt:扇区个数 //返回值:错误状态;0,正常;其他,错误代码; u8 SD_WriteDisk(u8 *buf,u32 sector,u32 cnt) { u8 sta=HAL_OK; long long lsector=sector; u32 timeout=SD_TIMEOUT; u8 n; sta=SD_WriteBlocks_DMA((uint32_t*)buf,lsector,512,cnt);//多个 sector 的写操作 return sta; }这两个函数在下一章(FATFS 实验)将会用到的,这里提前给大家介绍下,其中 SD_ReadDisk 用于读数据,通过调用 SD_ReadBlocks_DMA 实现。SD_WriteDisk 用于写数据,通过调用 SD_WriteBlocks_DMA 实现。 sdio_sdcard.c 的内容,我们就介绍到这里,sdio_sdcard.h 我们就不做介绍了,请大家参考本 例程源码。接下来,打开 main.c 文件,代码如下: //通过串口打印 SD 卡相关信息 void show_sdcard_info(void) { uint64_t CardCap; //SD 卡容量 HAL_SD_CardCIDTypeDef SDCard_CID; HAL_SD_GetCardCID(&SDCARD_Handler,&SDCard_CID); //获取 CID SD_GetCardInfo(&SDCardInfo); //获取 SD 卡信息 switch(SDCardInfo.CardType) { case CARD_SDSC: { if(SDCardInfo.CardVersion == CARD_V1_X) printf("Card Type:SDSC V1rn"); else if(SDCardInfo.CardVersion == CARD_V2_X) printf("Card Type:SDSC V2rn"); } break; case CARD_SDHC_SDXC:printf("Card Type:SDHCrn");break; } CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算 SD 卡容量 printf("Card ManufacturerID:%drn",SDCard_CID.ManufacturerID);//制造商 ID printf("Card RCA:%drn",SDCardInfo.RelCardAdd); //卡相对地址 printf("LogBlockNbr:%d rn",(u32)(SDCardInfo.LogBlockNbr)); //显示逻辑块数量 printf("LogBlockSize:%d rn",(u32)(SDCardInfo.LogBlockSize)); //显示逻辑块大小 printf("Card Capacity:%d MBrn",(u32)(CardCap>>20)); //显示容量 printf("Card BlockSize:%drnrn",SDCardInfo.BlockSize); //显示块大小 } //测试 SD 卡的读取 //从 secaddr 地址开始,读取 seccnt 个扇区的数据 //secaddr:扇区地址 //seccnt:扇区数 void sd_test_read(u32 secaddr,u32 seccnt) { u32 i; u8 *buf; u8 sta=0; buf=mymalloc(SRAMEX,seccnt*512); //申请内存 sta=SD_ReadDisk(buf,secaddr,seccnt);//读取 secaddr 扇区开始的内容 if(sta==0) { printf("SECTOR %d DATA:rn",secaddr); for(i=0;i printf("rnDATA ENDEDrn"); }else printf("err:%drn",sta); myfree(SRAMEX,buf); //释放内存 } //测试 SD 卡的写入(慎用,最好写全是 0XFF 的扇区,否则可能损坏 SD 卡.) //从 secaddr 地址开始,写入 seccnt 个扇区的数据 //secaddr:扇区地址 //seccnt:扇区数 void sd_test_write(u32 secaddr,u32 seccnt) { u32 i; u8 *buf; u8 sta=0; buf=mymalloc(SRAMEX,seccnt*512); //从 SDRAM 申请内存 for(i=0;i //初始化写入的数据,是 3 的倍数. sta=SD_WriteDisk(buf,secaddr,seccnt); //从 secaddr 扇区开始写入 seccnt 个扇区内容 if(sta==0) printf("Write over!rn"); else printf("err:%drn",sta); myfree(SRAMEX,buf); //释放内存 } int main(void) { u8 key;u32 sd_size;u8 t=0;u8 *buf; uint64_t CardCap; //SD 卡容量 HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz delay_init(168); //初始化延时函数 uart_init(115200); //初始化 USART usmart_dev.init(84); //初始化 USMART LED_Init(); //初始化 LED KEY_Init(); //初始化 KEY LCD_Init(); //初始化 LCD SRAM_Init(); //初始化外部 SRAM my_mem_init(SRAMIN); //初始化内部内存池 my_mem_init(SRAMEX); //初始化外部内存池 POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(30,50,200,16,16,"Explorer STM32F4"); LCD_ShowString(30,70,200,16,16,"SD CARD TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2017/4/18"); LCD_ShowString(30,130,200,16,16,"KEY0:Read Sector 0"); while(SD_Init())//检测不到 SD 卡 { LCD_ShowString(30,150,200,16,16,"SD Card Error!"); delay_ms(500); LCD_ShowString(30,150,200,16,16,"Please Check! "); delay_ms(500); LED0=!LED0;//DS0 闪烁 } show_sdcard_info(); //打印 SD 卡相关信息 POINT_COLOR=BLUE; //设置字体为蓝色 //检测 SD 卡成功 LCD_ShowString(30,150,200,16,16,"SD Card OK "); LCD_ShowString(30,170,200,16,16,"SD Card Size: MB"); CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算 SD 卡容量 LCD_ShowNum(30+13*8,170,CardCap>>20,5,16);//显示 SD 卡容量 while(1) { key=KEY_Scan(0); if(key==KEY0_PRES)//KEY0 按下了 { buf=mymalloc(0,512); //申请内存 if(SD_ReadDisk(buf,0,1)==0) //读取 0 扇区的内容 { LCD_ShowString(30,190,200,16,16,"USART1 Sending Data..."); printf("SECTOR 0 DATA:rn"); for(sd_size=0;sd_size<512;sd_size++)printf("%x ",buf[sd_size]);//扇区数据 printf("rnDATA ENDEDrn"); LCD_ShowString(30,190,200,16,16,"USART1 Send Data Over!"); } myfree(0,buf);//释放内存 } t++; delay_ms(10); if(t==20) { LED0=!LED0; t=0;} } }这里总共 2 个函数,show_sdcard_info 函数用于从串口输出 SD 卡相关信息。而 main 函数, 则先初化 SD 卡,初始化成功,则调用 show_sdcard_info 函数,输出 SD 卡相关信息,并在 LCD 上面显示 SD 卡容量。然后进入死循环,如果有按键 KEY0 按下,则通过 SD_ReadDisk 读取 SD 卡的扇区 0(物理磁盘,扇区 0),并将数据通过串口打印出来。这里,我们对上一章学过的内 存管理小试牛刀,稍微用了下,以后我们会尽量使用内存管理来设计。 43.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK 探索者 STM32F4 开发板上,可以 看到 LCD 显示如图 43.4.1 所示的内容(假设 SD 卡已经插上了): 图 43.4.1 程序运行效果图 打开串口调试助手,按下 KEY0 就可以看到从开发板发回来的数据了,如图 43.4.2 所示: 图 43.4.2 串口收到的 SD 卡扇区 0 内容 这里请大家注意,不同的 SD 卡,读出来的扇区 0 是不尽相同的,所以不要因为你读出来 的数据和图 43.4.2 不同而感到惊讶 |
|
相关推荐
|
|
991 浏览 0 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
970 浏览 2 评论
2080 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1177 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1599 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-23 03:37 , Processed in 0.562366 second(s), Total 65, Slave 48 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号