第二十一章SD卡实验
本章将介绍如何使用Kendryte K210的SPI功能,并实现对SD卡的驱动和读写功能。通过学习本章内容,读者将掌握运用SDK编程技术实现SD卡读写的方法。
本章分为如下几个小节:
21.1 SPI及SD卡简介
21.2 硬件设计
21.3 程序设计
21.4 运行验证
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卡主要有SD、Mini SD和microSD(原名TF卡,2004年正式更名为Micro SD Card,为方便本文用microSD表示)三种类型,Mini SD已经被microSD取代,使用得不多,根据最新的SD卡规格列出的参数如表21.1.2.1.1所示:

表21.1.2.1.1 SD卡的主要规格参数
上述表格的“脚位数”,对应于实卡上的“金手指”数,不同类型的卡的触点数量不同,访问的速度也不相同。SD卡允许了不同的接口来访问它的内部存储单元。最常见的是SDIO模式和SPI模式,根据这两种接口模式,我们也列出SD卡引脚对应于这两种不同的电路模式的引脚功能定义,如表21.1.2.1.2 所示。

表21.1.2.1.2 SD卡引脚编号(注:S:电源 I:输入 O:推挽输出 PP:推挽)
对比的,我们来看一下microSD引脚,可见只比SD卡少了一个电源引脚VSS2,其它的引脚功能类似。

表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卡进行数据通讯的主要通道,如下:

表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所示:

表 21.1.2.2.1 SD卡控制命令格式
SD卡的命令固定为48位,由6个字节组成,字节1的最高2位固定为01,低6位为命令号(比如CMD16,为10000B即16进制的0X10,完整的CMD16,第一个字节为01010000,即0X10+0X40)。字节2~5为命令参数,有些命令是没有参数的。字节6的高七位为CRC值,最低位恒定为1。
SD卡的命令总共有12类,分为Class0~Class11,本章,我们仅介绍几个比较重要的命令,如表21.1.2.2.2所示:

表21.1.2.2.2 SD卡部分命令
上表中,大部分的命令是初始化的时候用的。表中的R1、R3和R7等是SD卡的应答信号,每个响应也有规定好的格式,如图21.1.2.2.2所示:

图21.1.2.2.2 SD卡命令传输过程
在规定为有响应的命令下,每发送一个命令,SD卡都会给出一个应答,以告知主机该命令的执行情况,或者返回主机需要获取的数据,应答可以是R1~R7,R1的应答,各位描述如表21.1.2.2.3所示:

表21.1.2.2.3 R1响应
R2~R7的响应,限于篇幅,我们就不介绍了,但需要注意的是除了R2响应是128位外,其它的响应都是48位,请大家参考SD卡2.0协议。
21.1.2.3 卡模式
SD卡系统(包括主机和SD卡)定义了SD卡的工作模式,在每个操作模式下,SD卡都有几种状态,参考表21.1.2.3.1,状态之间通过命令控制实现卡状态的切换。

表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)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。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。

图21.1.2.3.2 卡识别模式状态转换图
ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。之后主机就发送SEND_RELATIVE_ADDR(CMD3)命令,让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA是16bit地址,而CID是128bit地址,使用RCA简化通信。卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。
21.1.2.4 数据模式
在数据模式下我们可以对SD卡的存储块进行读写访问操作。SD卡上电后默认以一位数据总线访问,可以通过指令设置为宽总线模式,可以同时使有4位总线并行读写数据,这样对于支持宽总线模式的接口(如:SDIO和SPI等)都能加快数据操作速度。

图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可以中断正在进行的数据通信,让卡返回到传输状态。CMD0和CMD15会中止任何数据编程操作,返回卡识别模式,注意谨慎使用,不当操作可能导致卡数据被损坏。
至此,我们已经介绍了SD卡操作的一些知识,并知道了SD卡操作的命令、响应和数据传输等状态,接下来我们来分析实际的硬件接口如何向SD卡发送我们需要的数据。
21.1.2.5 SPI模式下的SD卡初始化
《SD卡2.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卡内部有个供电电压上升时间,大概为64个CLK,剩下的10个CLK用于SD卡同步,之后才能开始CMD0的操作,在卡初始化的时候,CLK时钟最大不能超过400Khz!
接着我们看看SD卡的初始化,由前面SD卡的基本介绍,我们知道SD卡是先发送数据高位的,SD卡的典型初始化过程如下:
1、初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置);
2、拉低片选信号,上电延时(>74个CLK);
3、复位卡(CMD0),进入IDLE状态;
4、发送CMD8,检查是否支持2.0协议;
5、根据不同协议检查SD卡(命令包括:CMD55、ACMD41、CMD58和CMD1等);
6、取消片选,发多8个CLK,结束初始化
这样我们就完成了对SD卡的初始化,注意末尾发送的8个CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1、V2、V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。
SD卡单扇区读取数据,这里通过CMD17来实现,具体过程如下:
1、发送CMD17;
2、接收卡响应R1;
3、接收数据起始令牌0XFE;
4、接收数据;
5、接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8个CLK;
以上就是一个典型的读取SD卡数据过程,SD卡的写于读数据差不多,写数据通过CMD24来实现,具体过程如下:
1、发送CMD24;
2、接收卡响应R1;
3、发送写数据起始令牌0XFE;
4、发送数据;
5、发送2字节的伪CRC;
6、禁止片选之后,发多8个CLK;
以上就是一个典型的写SD卡过程。关于SD卡的介绍,我们就介绍到这里,更详细的介绍请参考A盘资料→硬件资料→SD卡资料→SD卡V2.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.c和sdcard.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[i]); /* 打印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] = 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函数代码具体流程大致是:首先完成系统级和按键、LCD、SD卡初始化工作,然后提醒交互信息,最后通过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串口输出数据