打印
[其他ST产品]

【正点原子K210连载】第二十一章 SD卡实验《DNK210使用指南-SDK版》

[复制链接]
149|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
第二十一章 SD卡实验
本章将介绍如何使用Kendryte K210SPI功能,并实现对SD卡的驱动和读写功能。通过学习本章内容,读者将掌握运用SDK编程技术实现SD卡读写的方法。
本章分为如下几个小节:
21.1 SPI及SD卡简介
21.2 硬件设计
21.3 程序设计
21.4 运行验证
21.1 SPISD卡简介
21.1.1 SPI简介
有关Kendryte K210的SPI模块介绍,请见第16.1.1小节《SPI简介》。
21.1.2 SD卡简介21.1.2.1 SD物理结构
SD卡规范SD卡协会明确,可以访问https://www.sdcard.org查阅更多标准SD卡主有SDMini SD和microSD(原TF卡,2004年正式更名为Micro SD Card,为方便本文microSD表示)三种类型Mini SD已经被microSD取代,使用得不多,根据最新的SD规格列出的参数如表21.1.2.1.1所
形状
SD
microSD
尺寸
32 x 24 x 2.1 mm,
32 x 24 x 1.4 mm,
约重1.2g~2.5g
11 x 15 x 1.0 mm,约重0.5g
卡片种类(容量范围)
SD(2GB)SDHC(2GB~32GB)SDXC(32GB ~2TB)SDUC(2TB ~128TB)
硬件规格
脚位数
High Speed and UHS-I : 9 pins
High Speed and UHS-I : 8 pins
UHS-II and UHS-III: 17 pins
UHS-II and UHS-III: 16 pins
SD Express 1-lane: 17-19 pins
SD Express 1-lane: 17 pins
SD Express 2-lane: 25-27 pins
电压范围
3.3V版本: 2.7V - 3.6V
1.8V低电压版: 1.70V-1.95V
防写开关
表21.1.2.1.1 SD卡的主要规格参数
述表格的脚位,对应于实卡上的手指,不同类型的卡的触点数不同,访问的速度SD允许了同的接口来访问的内存储单元。常见的是SDIO模式和SPI模式,根据这两接口模式,们也列出SD引脚对应于这两种不同的电路模式的引脚功能定义,如表21.1.2.1.2 所
SD
SD Mode
SPI Mode
引脚编号
引脚名
引脚类型
功能描述
引脚名
引脚类型
功能描述
1
CD/DAT3
I/O/PP
识别/数据线位3
CS
I3
低电平有效
2
CMD
I/O/PP
命令/响应
DI
I
数据
3
VSS1
S
电源
VSS
S
电源地
4
VDD
S
DC电源
VDD
S
DC电源正极
5
CLK
I
Clock
SCLK
I
时钟
6
VSS2
S
电源地
VSS2
S
电源地
7
DAT0
I/O/PP
数据线位0
DO
O/PP
数据输出
8
DAT1
I/O/PP
数据线位1
RSV
9
DAT2
I/O/PP
数据线位2
RSV
表21.1.2.1.2 SD引脚编号(注:S:电源 I:输入 O挽输出 PP
对比的,我们来看一下microSD引脚见只比SD卡少了一个电源引脚VSS2,其它引脚功能类似
microSD
SD Mode
SPI Mode
引脚编号
引脚名
引脚类型
功能描述
引脚名
引脚类型
功能描述
1
DAT2
I/O/PP
数据线位2
RSV
2
CD/DAT3
I/O/PP
识别/数据线位3
CS
I
低电平有效
3
CMD
PP
命令/响应
DI
I
数据
4
VDD
S
DC电源正极
VDD
S
DC电源正极
5
CLK
I
时钟
SCLK
I
时钟
6
VSS
S
电源地
VSS
S
电源地
7
DAT0
I/O/PP
数据线位0
DO
O/PP
数据输出
8
DAT1
I/O/PP
数据线位1
RSV
表21.1.2.1.3 microSD引脚编号(注:S:电源 I:输入 O挽输出 PP
SD卡和micorSD只有引脚和形状大小不同,内部结构类似,操作时序完全相同,可以使用完全相同的代码驱动,下面以9Pin SD卡的内部结构为例,展示SD卡的存储结构
图21.1.2.1.1 SD卡内部物理结构RCA 寄存器在SPI模式下不访问
SD有自己的寄存器,但它不直接行读写操作,需要通过命令来控制,SDIO协议定义了些命令用于实现某一特定功能,SD卡根据收到的命令要求对内部寄存器进行修改。表21.1.2.1.4中描述的SD卡的寄存器我们和SD卡进行数据通讯的主要通道,如
名称
位宽
描述
CID
128
标识(Card identification):每个卡都是唯一的
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):卡状态信息
表21.1.2.1.4 SD信息
SD多信息和硬件设计规范可以参考SD协议Physical Layer Simplified Specification Version 2.00的相关章节。
21.1.2.2 命令和响应
一个整的SD操作过程是:主(单片机等)发起命令”,SD卡根据命令内容决定否发送响应信息及数据等,如果数据/操作,主还需要发送停止/数据的命令结束次操作,这意味着主机发起命令指令后,SD卡可以没响应、数据等过程,这取决于命令的含义这一过程21.1.2.2.1所示。
图 21.1.2.2.1 SD卡命令格式
SD卡有多种命令响应,它的格式义及含义在《SD协议V2.0第三和第四详细介绍,发送命令主机只能通过CMD引脚送给SD,串行逐位发送时发送最高(MSB),然后次高位样类推……接下来,我们看看SD卡的命令格式,如表 21.1.2.2.1所示:
字节1
字节2--5
字节6
47
46
45:40
39:8
7:1
0
0
1
command
命令参数
CRC7
1
21.1.2.2.1 SD控制命令格式
SD卡的命令固定为48,由6个字节组成,字节1的最高2位固定为01,低6位为命令号(比如CMD16,为10000B16进制的0X10,完整的CMD16,第一个字节为01010000,即0X10+0X40)。字节2~5为命令参数,有些命令是没有参数的。字节6的高七位为CRC值,最低位恒定为1
SD卡的命令总共有12类,分为Class0~Class11,本章,我们仅介绍几个比较重要的命令,如表21.1.2.2.2所示:
命令
参数
响应
描述
CMD0(0X00)
NONE
R1
复位SD
CMD8(0X08)
VHS+Checkpattern
R7
发送接口状态命令
CMD9(0X09)
NONE
R1
读取卡特定数据寄存器
CMD10(0X0A)
NONE
R1
读取卡标志数据寄存器
CMD16(0X10)
块大小
R1
设置块大小(字节数)
CMD17(0X11)
地址
R1
读取一个块的数据
CMD24(0X18)
地址
R1
写入一个块的数据
CMD41(0X29)
NONE
R3
发送给主机容量支持信息和激活
卡初始化过程
CMD55(0X37)
NONE
R1
告诉SD卡,下一个是特定应用命令
CMD58(0X3A)
NONE
R3
读取OCR寄存器
21.1.2.2.2 SD卡部分命令
上表中,大部分的命令是初始化的时候用的。表中的R1R3R7等是SD卡的应答个响应也有规定好的格式,如图21.1.2.2.2所示:
图21.1.2.2.2 SD卡命令传输过程
规定为有响应的命令下,每发送一个命令,SD卡都会给出一个应答,以告知主机该命令的执行情况,或者返回主机需要获取的数据,应答可以是R1~R7R1的应答,各位描述如表21.1.2.2.3所示:
R1
响应
描述
起始位
传输位
命令号
卡状态
CRC7
终止位
Bit
47
46
[45:40]
[39:8]
[7:1]
0
位宽
1
1
6
32
7
1
"0"
"0"
x
x
x
"1"
21.1.2.2.3 R1响应
R2~R7的响应,限于篇幅,我们就不介绍了但需要注意的是除了R2响应128外,其它的响应都是48,请大家参考SD2.0协议。
21.1.2.3 卡模式
SD卡系统(包括主机和SD)定义了SD卡的工作模式,在每个操作模式下,SD卡都有几种状态,参考表21.1.2.3.1状态通过命令控制实现卡状态的切换。
无效模式
(Inactive)
无效状态(Inactive State)
卡识别模
(Card identification mode)
空闲状态(Idle State)
准备状态(Ready State)
识别状态(Identification State)
数据传输模式
(Data transfer mode)
待机状态(Stand-by State)
传输状态(Transfer State)
发送数据状态(Sending-data State)
接收数据状态(Receive-data State)
编程状态(Programming State)
断开连接状态(Disconnect State)
21.1.2.3.1 SD卡状态与操作模式
对于我们来说两种效操作模式:卡识别模式和数据传输模式。在系统复位后,主机处于卡识别模式,寻找总线上可用的SDIO设备SD卡进行数据读写之前需要识别卡的种类:V1.0标准卡、V2.0标准卡、V2.0高容量卡或者不被识别卡;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡识别状态接收到CMD3 (SEND_RCA)命令后,SD卡就进入数据传输模式,而主机在总线上所有卡被识**也进入数据传输模式。
在卡识别模式下,主机会复位所有处于卡识别模式SD卡,确认其工作电压范围,识别SD卡类型,并且获取SD卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中,要求SD卡工作在识别时钟频率FOD的状态下。卡识别模式下SD卡状态转换图21.1.2.3.1
主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送GO_IDLE_STATE(CMD0)让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不会复位。
主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。SD卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。SEND_IF_COND(CMD8)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。CMD8SD卡标准V2.0版本才有的新命令,所以如果主机有接收到响应,可以判断卡为V2.0或更高版本SD卡。
SD_SEND_OP_COND(ACMD41)命令可以识别或拒绝不匹配它的电压范围的卡。ACMD41命令的VDD电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。对于对CMD8有响应的卡,把ACMD41命令的HCS位设置为1,可以测试卡的容量类型,如果卡响应的CCS位为1说明为高容量SD卡,否则为标准卡。卡在响应ACMD41之后进入准备状态,不响应ACMD41的卡为不可用卡,进入无效状态。ACMD41是应用特定命令,发送该命令之前必须先发CMD55
图21.1.2.3.2 卡识别模式状态转换图
ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。之后主机就发送SEND_RELATIVE_ADDR(CMD3)命令,让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA16bit地址,而CID128bit地址,使用RCA简化通信。卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。
21.1.2.4 数据模式
数据模式下们可以对SD卡的存储块进行读写访问操作SD电后默认一位数据总线访问,可以通过指令设置为宽总线模式,可以时使有4总线并行读数据,这样对于支持宽总线模式的接口(如:SDIOSPI等)都能数据操作速度。
图21.1.2.4.1 1数据线传输8bit的数据流格式
SD卡有两种数据模式,种是常规的8位宽一次按一字传输,另一种是一次按512节传输,我们只介绍前面一种。当按8-bit连续传输时,每次传输最低字节开始,每字节从最高位(MSB)开始送,当使一条数据线时,只能通过DAT0进行数据传输,它的数据传输结构如图21.1.2.4.1 1示。
使4线模式8-bit结构的数据时,数据仍按MSB送的原则,DAT[3:0]的发送高数据位,低位发送低数据位。硬件支持的情况下,使用4线传输可提升传输速率。
图21.1.2.4.2 4数据线传输8bit格式数据流格式
只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz,频率切换可以通过CMD4命令来实现。数据传输模式下,SD卡状态转换过程见图21.1.2.4.3
图21.1.2.4.3 数据传输模式卡状态转换
CMD7用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。同时通过CMD7命令也可以让已经被选择的目标卡返回到待机状态。
数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以通过命令对卡进行数据读写、擦除。CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0CMD15会中止任何数据编程操作,返回卡识别模式,注意谨慎使用,当操作可能导致卡数据被损坏。
        至我们已经介绍了SD操作的一些知识知道了SD卡操作的命令、响应和数据传输等状态,接下来我们来分析实际硬件接口如何SD卡发送我需要的数据
21.1.2.5 SPI模式下的SD卡初
SD2.0协议.pdf中提供了SD卡的SPI初始化时序,们可以按它建议的流程进行SD卡的初始化,如图21.1.2.5.1所示
21.1.2.5.1 SD的SPI初始化流程(SPI Mode Initialization Flow
要使SPI模式驱动SD卡,先得让SD卡进入SPI模式。方法如下:在SD卡收到复位命令(CMD0)时,CS为有效电平(低电平)则SPI模式被启用。不过在发送CMD0之前,要发送>74个时钟,这是因为SD卡内部有个供电电压上升时间,大概为64CLK,剩下的10CLK用于SD卡同步,之后才能开始CMD0的操作,在卡初始化的时候,CLK时钟最大不能超过400Khz
接着我们看看SD卡的初始化,前面SD卡基本介绍,我们知道SD卡是先发送数据高位的,SD卡的典型初始化过程如下:
1、初始化与SD卡连接的硬件条件(MCUSPI配置,IO口配置);
2低片选信号,上电延时(>74CLK);
3、复位卡(CMD0),进入IDLE状态;
4、发送CMD8,检查是否支持2.0协议;
5、根据不同协议检查SD卡(命令包括:CMD55ACMD41CMD58CMD1等);
6、取消片选,发多8CLK,结束初始化
这样我们就完成了对SD卡的初始化,注意末尾发送的8CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1V2V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。
SD扇区读取数据,这里通过CMD17来实现,具体过程如下:
1、发送CMD17
2、接收卡响应R1
3、接收数据起始令牌0XFE
4、接收数据;
5、接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8CLK
以上就是一个典型的读取SD卡数据过程,SD卡的写于读数据差不多,写数据通过CMD24来实现,具体过程如下:
1、发送CMD24;
2、接收卡响应R1
3、发送写数据起始令牌0XFE
4、发送数据;
5、发送2字节的伪CRC
6、禁止片选之后,发多8CLK
以上就是一个典型的写SD卡过程。关于SD卡的介绍,我们就介绍到这里,更详细的介绍请参考A盘资料硬件资料→SD卡资料→SDV2.0协议。
21.2 硬件设计
21.2.1 例程功能
1. 通过KEY1按键来控制SD卡的写入,通过按键KEY0来控制SD卡的读取。读取的SD卡数据将通过USB串口打印输出,LCD模块上显示SD卡容量信息和操作提醒。
21.2.2 硬件资源
1. 独立按键
        KEY0按键 - IO18
        KEY1按键 - IO19
        KEY2按键 - IO16
2. SD CARD
TF_MISO - IO26
TF_SCK - IO27
TF_MOSI - IO28
        TF_CS - IO29
3. LCD
        LCD_RD - IO34
        LCD_BL - IO35
        LCD_CS - IO36
        LCD_RST - IO37
        LCD_RS - IO38
        LCD_WR - IO39
        LCD_D0~LCD_D7 - SPI0_D0~SPI0_D7
21.2.3 原理图
本章实验内容,需要使用到板载的SD卡接口,正点原子DNK210开发板上的SD卡连接原理图,如下图所示:
21.2.3.1 SD CARD接口原理图
21.3 程序设计
21.3.1 SD卡驱动代码
SD卡驱动源码包括两个文件:sdcard.csdcard.h。这两个文件均来源于官方的SDK裸机编程DEMO,用于SD卡的底层驱动。这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
下面介绍sdcard.c文件几个重要的函数,首先是SD卡初始化函数,其定义如下:
uint8_t sd_init(void)
{
    uint8_t frame[10], index, result;
    /*!< Initialize SD_SPI */
    sd_lowlevel_init(0);
    /*!< SD chip select high */
    SD_CS_HIGH();
    /*!< Send dummy byte 0xFF, 10 times with CS high */
    /*!< Rise CS and MOSI for 80 clocks cycles */
    /*!< Send dummy byte 0xFF */
    for (index = 0; index < 10; index++)
        frame[index] = 0xFF;
    sd_write_data(frame, 10);
    /*------------Put SD in SPI mode--------------*/
    /*!< SD initialized and set to SPI mode properly */
    index = 0xFF;
    while (index--) {
        sd_send_cmd(SD_CMD0, 0, 0x95);
        result = sd_get_response();
        sd_end_cmd();
        if (result == 0x01)
            break;
    }
    if (index == 0)
    {
        printf("SD_CMD0 is %X\n", result);
        return 0xFF;
    }
    sd_send_cmd(SD_CMD8, 0x01AA, 0x87);
    /*!< 0x01 or 0x05 */
    result = sd_get_response();
    sd_read_data(frame, 4);
    sd_end_cmd();
    if (result != 0x01)
    {
        printf("SD_CMD8 is %X\n", result);
        return 0xFF;
    }
    index = 0xFF;
    while (index--) {
        sd_send_cmd(SD_CMD55, 0, 1);
        result = sd_get_response();
        sd_end_cmd();
        if (result != 0x01)
            return 0xFF;
        sd_send_cmd(SD_ACMD41, 0x40000000, 1);
        result = sd_get_response();
        sd_end_cmd();
        if (result == 0x00)
            break;
    }
    if (index == 0)
    {
        printf("SD_CMD55 is %X\n", result);
        return 0xFF;
    }
    index = 255;
    while(index--){
        sd_send_cmd(SD_CMD58, 0, 1);
        result = sd_get_response();
        sd_read_data(frame, 4);
        sd_end_cmd();
        if(result == 0){
            break;
        }
    }
    if(index == 0)
    {
        printf("SD_CMD58 is %X\n", result);
        return 0xFF;
    }
    if ((frame[0] & 0x40) == 0)
        return 0xFF;
    SD_HIGH_SPEED_ENABLE();
    return sd_get_cardinfo(&cardinfo);
}
该函数主要功能是初始化SD卡,首先是 配置SPI功能,然后将SD卡进入SPI模式,配置过程可参考21.1.2.5节《SPI模式下的SD卡初始化》进行理解,这里不再叙述。
下面介绍一下SD卡读取函数,SD卡读取数据的方式有两种,一种是普通方式读取,另外一种是通过DMA的方式读取,DMA的读取方式无需占用CPU资源,大家可以根据自己的需求选择,两种读取方式都有对应的函数,其定义如下:
uint8_t sd_read_sector(uint8_t *data_buff, uint32_t sector, uint32_t count)
{
    uint8_t frame[2], flag;
    /*!< Send CMD17 (SD_CMD17) to read one block */
    if (count == 1) {
        flag = 0;
        sd_send_cmd(SD_CMD17, sector, 1);
    } else {
        flag = 1;
        sd_send_cmd(SD_CMD18, sector, 1);
    }
    /*!< Check if the SD acknowledged the read block command: R1 response (0x00: no errors) */
    if (sd_get_response() != 0x00) {
        sd_end_cmd();
        return 0xFF;
    }
    while (count) {
        if (sd_get_response() != SD_START_DATA_SINGLE_BLOCK_READ)
            break;
        /*!< Read the SD block data : read NumByteToRead data */
        sd_read_data(data_buff, 512);
        /*!< Get CRC bytes (not really needed by us, but required by SD) */
        sd_read_data(frame, 2);
        data_buff += 512;
        count--;
    }
    sd_end_cmd();
    if (flag) {
        sd_send_cmd(SD_CMD12, 0, 1);
        sd_get_response();
        sd_end_cmd();
        sd_end_cmd();
    }
    /*!< Returns the reponse */
    return count > 0 ? 0xFF : 0;
}
uint8_t sd_read_sector_dma(uint8_t *data_buff, uint32_t sector, uint32_t count)
{
    uint8_t frame[2], flag;
    /*!< Send CMD17 (SD_CMD17) to read one block */
    if (count == 1) {
        flag = 0;
        sd_send_cmd(SD_CMD17, sector, 1);
    } else {
        flag = 1;
        sd_send_cmd(SD_CMD18, sector, 1);
    }
    /*!< Check if the SD acknowledged the read block command: R1 response (0x00: no errors) */
    if (sd_get_response() != 0x00) {
        sd_end_cmd();
        return 0xFF;
    }
    while (count) {
        if (sd_get_response() != SD_START_DATA_SINGLE_BLOCK_READ)
            break;
        /*!< Read the SD block data : read NumByteToRead data */
        sd_read_data_dma(data_buff);
        /*!< Get CRC bytes (not really needed by us, but required by SD) */
        sd_read_data(frame, 2);
        data_buff += 512;
        count--;
    }
    sd_end_cmd();
    if (flag) {
        sd_send_cmd(SD_CMD12, 0, 1);
        sd_get_response();
        sd_end_cmd();
        sd_end_cmd();
    }
    /*!< Returns the reponse */
    return count > 0 ? 0xFF : 0;
}
可以看到,这两个函数非常相似,区别就在于传输数据的时候采用不同的方式,读取成功函数返回0
接下来我们介绍一下SD卡写函数,写函数也有两种,其定义如下:
uint8_t sd_write_sector(uint8_t *data_buff, uint32_t sector, uint32_t count)
{
    uint8_t frame[2] = {0xFF};
    if (count == 1) {
        frame[1] = SD_START_DATA_SINGLE_BLOCK_WRITE;
        sd_send_cmd(SD_CMD24, sector, 1);
    } else {
        frame[1] = SD_START_DATA_MULTIPLE_BLOCK_WRITE;
        sd_send_cmd(SD_ACMD23, count, 1);
        sd_get_response();
        sd_end_cmd();
        sd_send_cmd(SD_CMD25, sector, 1);
    }
    /*!< Check if the SD acknowledged the write block command: R1 response (0x00: no errors) */
    if (sd_get_response() != 0x00) {
        sd_end_cmd();
        return 0xFF;
    }
    while (count--) {
        /*!< Send the data token to signify the start of the data */
        sd_write_data(frame, 2);
        /*!< Write the block data to SD : write count data by block */
        sd_write_data(data_buff, 512);
        /*!< Put CRC bytes (not really needed by us, but required by SD) */
        sd_write_data(frame, 2);
        data_buff += 512;
        /*!< Read data response */
        if (sd_get_dataresponse() != 0x00) {
            sd_end_cmd();
            return 0xFF;
        }
    }
    sd_end_cmd();
    sd_end_cmd();
    /*!< Returns the reponse */
    return 0;
}
uint8_t sd_write_sector_dma(uint8_t *data_buff, uint32_t sector, uint32_t count)
{
    uint8_t frame[2] = {0xFF};
    frame[1] = SD_START_DATA_SINGLE_BLOCK_WRITE;
    uint32_t i = 0;
    while (count--) {
        sd_send_cmd(SD_CMD24, sector + i, 1);
        /*!< Check if the SD acknowledged the write block command: R1 response (0x00: no errors) */
        if (sd_get_response() != 0x00) {
            sd_end_cmd();
            return 0xFF;
        }
        /*!< Send the data token to signify the start of the data */
        sd_write_data(frame, 2);
        /*!< Write the block data to SD : write count data by block */
        sd_write_data_dma(data_buff);
        /*!< Put CRC bytes (not really needed by us, but required by SD) */
        sd_write_data(frame, 2);
        data_buff += 512;
        /*!< Read data response */
        if (sd_get_dataresponse() != 0x00) {
            sd_end_cmd();
            return 0xFF;
        }
        i++;
    }
    sd_end_cmd();
    sd_end_cmd();
    /*!< Returns the reponse */
    return 0;
}
两个SD卡写数据的函数也非常相似,当只写一个扇区时,写的过程几乎一样,不同的是传输方式的区别。这里需要提下,DMA读写的方式不占用CPU资源,效率会更高,非常适合CPU资源有限的单片机数据传输。
20.3.2 main.c代码
main.c中的代码如下所示:
#define LCD_SPI_CLK_RATE 15000000
/**
* @brief       测试SD卡的读取
*   @NOTE      secaddr地址开始,读取seccnt个扇区的数据
* @param       secaddr : 扇区地址
* @param       seccnt  : 扇区数
* @retval      
*/
void sd_test_read(uint32_t secaddr, uint32_t seccnt)
{
    uint32_t i;
    uint8_t *rbuff = NULL;
    uint8_t sta = 0;
    rbuff = (uint8_t *)iomem_malloc(seccnt * cardinfo.CardBlockSize);
    sta = sd_read_sector_dma(rbuff, secaddr, seccnt); /* 读取secaddr扇区开始的内容 */
    if (sta == 0)
    {
        lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);         /* 清除显示 */
        lcd_draw_string(10, 50, "USB Sending Data...", BLUE);
        printf("SECTOR %d DATA:\r\n", secaddr);
        for (i = 0; i < seccnt * 512; i++)
        {
            printf("%x ", rbuff);              /* 打印secaddr开始的扇区数据 */
        }
        printf("\r\nDATA ENDED\r\n");
        lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);         /* 清除显示 */
        lcd_draw_string(10, 50, "USB Send Data Over!", BLUE);
    }
    else
    {
        printf("err:%d\r\n", sta);
        lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);         /* 清除显示 */
        lcd_draw_string(10, 50, "SD read Failure!      ", BLUE);
    }
    iomem_free(rbuff);; /* 释放内存 */
}
/**
* @brief       测试SD卡的写入
*   @note      secaddr地址开始,写入seccnt个扇区的数据
*              慎用!! 最好写全是0XFF的扇区,否则可能损坏SD.
*
* @param       secaddr : 扇区地址
* @param       seccnt  : 扇区数
* @retval      
*/
void sd_test_write(uint32_t secaddr, uint32_t seccnt)
{
    uint32_t i;
    uint8_t *wbuff = NULL;
    uint8_t sta = 0;
    wbuff = (uint8_t *)iomem_malloc(seccnt * cardinfo.CardBlockSize);
    for (i = 0; i < seccnt * 512; i++)          /* 初始化写入的数据,3的倍数. */
    {
        wbuff = i * 3;
    }
    sta = sd_write_sector_dma(wbuff, secaddr, seccnt); /* secaddr扇区开始写入seccnt个扇区内容 */
    if (sta == 0)
    {
        printf("Write over!\r\n");
    }
    else
    {
        printf("err:%d\r\n", sta);
    }
    iomem_free(wbuff); /* 释放内存 */
}
int main(void)
{
    uint8_t key;
    char data1show[35];
    char data2show[30];
    sysctl_pll_set_freq(SYSCTL_PLL0, 800000000);
    sysctl_pll_set_freq(SYSCTL_PLL1, 400000000);
    sysctl_pll_set_freq(SYSCTL_PLL2, 45158400);
    sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18);
    sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18);
    sysctl_set_spi0_dvp_data(1);
    key_init();                             /* 初始化按键 */
    lcd_init();                             /* 初始化LCD */
    lcd_set_direction(DIR_YX_LRUD);
    /* Initialize SD card */
    if (sd_init() != 0)
    {
        printf("SD card initialization failed!\n");
        while (1);
    }
    /* Show SD information */
    printf("SD card capacity: %ldMB\n", cardinfo.CardCapacity >> 20);
    printf("SD card sector size: %dB\n", cardinfo.CardBlockSize);
    sprintf((char *)data1show, "SD card capacity: %ldMB", cardinfo.CardCapacity >> 20);
    sprintf((char *)data2show, "SD card sector size: %dB", cardinfo.CardBlockSize);
    lcd_draw_string(10, 10, (char *)data1show, BLUE);         /* 显示SD卡容量 */
    lcd_draw_string(10, 30, (char *)data2show, BLUE);         /* 显示扇区大小 */
    while (1)
    {
        key = key_scan(0);
        if (key == KEY0_PRES)   /* KEY0按下了 */
        {
            sd_test_read(0,1);  /* 0扇区读取1*512字节的内容 */
        }
        else if (key == KEY1_PRES)   /* KEY1按下,写入 */
        {
            sd_test_write(0,1); /* 0扇区写入1*512字节的内容 */
            lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);        /* 清除显示 */
            lcd_draw_string(10, 50, "SD Write Finished!", BLUE); /* 提示传送完成 */
        }
        msleep(10);
    }
}
此部分代码除了main函数,还有两个函数,用于SD卡的读写操作,sd_test_write函数用于在指定的扇区写入数据,数据的内容为3的倍数,长度可以是一个扇区也可以是多个扇区。sd_read_sector_dma用于从指定扇区读取数据并通过串口打印,LCD显示器显示SD卡容量大小和操作状态。
main函数代码具体流程大致是:首先完成系统级和按键、LCDSD卡初始化工作,然后提醒交互信息,最后通过KEY0去读取扇区0的数据并通过USB串口打印出来;另外还可以通过KEY1在地址扇区0处重新写入倍数为3的数据,接着再次按KEY0打印输出。
21.4 运行验证
DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,可以看到LCD显示信息,LCD显示的内容如图20.4.1所示:
图20.4.1SD卡实验程序运行效果图
下面我们先打开串口调试助手,波特率选择115200,连接我们的开发板,然后通过先按下KEY1写入数据,然后再按KEY0读取数据,LCD显示得到如图20.4.2所示:
图20.4.2 操作后的显示效果图
串口助手输出扇区1的数据,如图20.4.3所示:

图20.4.3 USB串口输出数据

使用特权

评论回复
沙发
Stahan| | 2024-10-13 19:54 | 只看该作者
这两种类型的卡是一样的吗

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

87

主题

88

帖子

1

粉丝