最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

Kinetis K60 SDHC模块对SD设备操作方法详解

IT圈 admin 26浏览 0评论

2024年9月26日发(作者:和夏璇)

Kinetis K60 SDHC模块对SD设备操作方法详解

中国科学技术大学电子科学与技术系2011级 游绩榕 2013年1月6日

【关键词】本文简单介绍了SD设备,并通过例程来详细解释如何使用K60的SDHC模块

对SD卡进行读写操作。

1 适用范围

本文所处的环境为:win7-32位系统,IAR System 6.4,P&E公司osjtag调试器,龙丘

MK60DN512ZVLL10P100。单片机主频200M,SD卡为金士顿512M卡1张,金士顿4G卡

(Class-4)1张。

本文所实现的功能为:使用SDHC模式,基于SD2.0协议对SD卡和SDHC卡进行单

扇区进行不连续的读操作和写操作,SD设备波特率为25M,扇区大小为512B,连续写操

作最小间隔为1.5ms(最短),2ms(稳定)。读写一次的时间约为50~100us。

SD家族介绍

SD设备分为许多类型。SD卡(Secure Digital Memory Card)是一种基于半导体闪存工

艺的存储卡, 1999年,由日本松下、东芝及美国SanDisk公司共同研制完成。2000年,这几

家公司发起成立了SD协会(Secure Digital Association简称SDA),阵容强大,吸引了大量

厂商参加。其中包括IBM,Microsoft,Motorola,NEC、Samsung等。在这些领导厂商的推

动下,SD卡已成为目前消费数码设备中应用最广泛的一种存储卡。

SD卡最初是从MMC(Multimedia Card)卡基础上发展起来的,可以与MMC卡实现兼

容;但SD卡数据传输速率更快,此外,还特别增加了CPRM(Content protection for Recordable

Media)功能,用于卡内数据的授权访问,实现内容保护。

SD卡尺寸为32mm x 24mm x 2.1mm,相当于邮票大小,这样尺寸的存储卡用在数码相

机、DV机中还算合适,但在记录不断被刷新的轻薄手机面前,SD卡显得过分“庞大”。为

了满足数码产品不断缩小存储卡体积的要求,SD卡逐渐演变出了Mini SD,Micro SD两种

规格。

SD卡背面共有9个引脚,包含4根数据线,支持1bit/4bit两种数据传输宽度,时钟最

高频率为25MHZ,故理论最高数据传输速率为12.5MB/S,工作电压为2.7~3.6V。

Mini SD

顾名思义,Mini SD卡相比标准SD卡,外形上更加小巧,仅有标准SD卡40%左右的

大小。尽管Mini SD卡的外形大小及接口形状与原来的SD卡有所不同,但接口规范保持不

变,确保了兼容性。若将Mini SD插入特定的转接卡中,可当作标准SD卡来使用。

TransFlash(Micro SD)

Transflash卡,也称T-Flash卡, TF或T卡,最早由SanDisk推出。T卡仅有11mm x 15mm

x 1mm 大小,仅相当于标准SD卡的1/4,比Mini SD卡还要小巧。

同样,T卡是与标准SD卡功能也是兼容的,将T卡插入特定的转接卡中,可以当作标

准SD卡或Mini SD卡来使用。

2005年7月,SDA协会正式发布了Micro SD标准,该标准与TransFlash卡完全兼容,

市场上的TransFlash卡和Micro SD卡可以不加区分的使用。这好比TransFlash是卡的小名,

等长大了就取了个Micro SD的学名,不过大家还是叫其TransFlash的多!

Mini SD、Micro SD这种小体积SD卡的出现,大大拓展了SD卡的应用范围,这使得

SD卡应用在一些小型手机中成为可能。另一方面,Mini SD、Micro SD功能与SD卡功能兼

容,只要将其插入特定转接卡中,即可当作标准SD卡来使用,这样,Mini SD、Micro SD

便可用于那些使用标准SD卡的数码设备,这加速了Mini SD、Micro SD的市场普及。

前几年,市场上的SD、Mini SD、Micro SD卡遵循的是SD Spec Ver1.0或1.1规范,最

大可能容量仅为2GB。2006年,SDA协会发布了SD Spec Ver2.0规范,符合此新规范的SD

卡容量可达4GB或更高。

符合2.0规范的SD卡,称为SDHC(SD high capacity)卡。SDHC卡外形维持与SD卡

一致,但是文件系统从FAT12、FAT16改为FAT32型;SDHC卡的最大容量可达32GB。除

了SDHC卡外, 还有Mini SDHC,Micro SDHC类型的卡。

SDHC卡与标准SD卡不再兼容,必须符合SD Spec Ver2.0的设备才能支持SDHC卡,

这样的设备都会带有SDHC logo。而支持SDHC卡的设备可以向下兼容标准SD卡。

为了充分发挥SDHC卡的性能,保证兼容性,SDA协会为SDHC卡定义了3个速度等

级:2,4,6;其含义是各等级分别可以忍受的写速率至少是2MB/S,4MB/S,6MB/S.速度

等级定义中使用的是数据写速率,数据读速率要比数据写速率快。现今绝大多数设备上使用

的都是SDHC卡和microSDHC卡。小容量的SD卡已经停产并逐渐淘汰(2012年)。

SDIO

SDIO (Input/Output)是一种IO接口规范。目前,其最主要用途是为带有SD卡槽的设备

进行外设功能扩展。SDIO卡是一种IO外设,而不是Memory。SDIO卡外形与SD卡一致,

可直接插入SD卡槽中。

目前市场上有多种SDIO接口的外设,比如SDIO蓝牙,SDIO GPS,SDIO无线网卡,

SDIO移动电视卡等。这些卡底部带有和SD卡外形一致的插头,可直接插入SDIO卡槽(即

为SD卡槽)的智能手机、PDA中,即可为这些手机、PDA带来丰富的扩展功能。用户可

根据实际需要,灵活选择外设扩展的种类、品牌和性能等级。SDIO已为成为数码产品外设

功能扩展的标准接口。

SDIO卡插入带有标准SD卡槽的设备后,如果该设备不支持SDIO,SDIO卡不会对SD

卡的命令作出响应,处于非激活状态,不影响设备的正常工作;如果该设备支持SDIO卡,

则按照规范的要求激活SDIO卡。

SDIO卡允许设备按IO的方式直接对寄存器进行访问,无须执行FAT文件结构或数据

sector等复杂操作。此外,SDIO卡还能向设备发出中断,这是与SD memory卡的本质区别。

本文针对的是符合SD2.0的microSD进行操作,但测试结果表明一部分SD1.1协议的

卡也可以使用【此结果与标准不相符】。

功能描述

K60内有一个SDHC模块,目标引脚为E0--E5。端口具体功能见端口复用表(K60

RM10.3.1)。

K60的SDHC模块设有二十多个寄存器,我们通过设置这些寄存器设置K60的SDHC

的主机,然后通过SDHC主机与SD卡的从机通信,再由SD从机向SD卡写数据。其中设

置SDHC模块主要参考K60RM,SDHC模块与SD卡通信过程主要参考SD2.0协议,并且

底层通信操作由SDHC模块自动完成,而SD从机向SD卡写数据无需我们关心。

我们通过操作SDHC相关寄存器,可以让SDHC向SD卡发送特定的命令。命令可以

带有参数。SD卡从机接收到命令后进行识别和动作,并可能反馈一些响应至SDHC模块寄

存器。

基本的操作时序如下:

设置SDHC主机属性

发送命令群以初始化SD卡从机并获取SD卡信息

发送读命令并接收SD卡反馈数据

发送写命令并等待SD卡读取数据

以下为本文所用程序的分析。本文所用程序参照苏州大学嵌入式中心给出的参考代码,

并对其做出修正和整理。读者可以同时参考苏大的代码。

本文给出的例程中提出几个接口函数:

int SDHC_On();

int SDHC_Init(uint32 Block_Size);

int SDHC_Inserted();

int SDHC_read(uint8 cache[],uint32 sector);

int SDHC_write(uint8 cache[],uint32 sector);

int SDHC_On(); 初始化SDHC主机,此阶段SDHC不与SD卡通信,不进行插卡检测。

成功返回0,错误返回1;

int SDHC_Init(uint32 Block_Size); 初始化SD卡。此阶段未进行任何插卡检测。参数

Block_Size表示每次读写扇区大小。但是只能设置为512。成功返回0,错误返回1;非可

支持卡返回-1;可支持的卡:SDHC卡,部分SD卡。

int SDHC_Inserted(); 插卡检测,检查是否有SD设备连在SDHC上。有卡返回1,错

误或无卡返回0;

int SDHC_read(uint8 cache[],uint32 sector); 从SD卡中读取一个扇区的数据,cache[]为

数据存放数组,sector为待读取的扇区号。成功返回0,错误返回1;

int SDHC_write(uint8 cache[],uint32 sector); 向SD卡中写入一个扇区的数据,cache[]为

数据存放数组,sector为待写入的扇区号。成功返回0,错误返回1;

调用方法:

1 调用SDHC_On();若初始化成功变量SDHC_Status变为SDHC_ON;否则为初始值

SDHC_OFF;

2 进行插卡检测SDHC_Inserted();

3 初始化SD卡SDHC_Init(512);若成功SDHC_Status变为SDHC_RW;

4 检测SDHC状态,若为SDHC_RW则可进行读写操作。

这是使用SDHC的一个例子。

......

SDHC_On();

while(!SDHC_Inserted());

SDHC_Init(512);

if(SDHC_Status != SDHC_RW){

......

}else{

SDHC_read(Cache,p);

SDHC_write(Cache,p);

}

......

注意:

程序中不可将PORTE0-E5用作其他用途;

SDHC_write操作不能连续调用。中间至少要有1.5ms(最短)/2ms(稳定)的延时。

SDHC_read操作无此限制。

SDHC_On、SDHC_Init、SDHC_read、int SDHC_write均不进行插卡检测,用户需

要自行调用SDHC_Inserted进行插卡检测。

插卡检测仅检测SD卡槽(E口)是否有设备连接,不检测设备是否可以识别。

代码分析

注意:本文将几个主要的函数列出如下,本例程只保留最基本的功能,其它功能关于中

断服务和DMA的部分全部注释掉了,其他类型卡检测的部分完全去除。附件中有一份精简

干净的和一份带注释的备用。下文引用的是例程中精简版的那一份。

int SDHC_On();

int SDHC_On(){//此函数的步骤参照苏大代码。

SIM_SCGC3 |= SIM_SCGC3_SDHC_MASK;

GPIO_On(PORTE); // Open PORTE

// Open Clock for SDHC module

uint32 pres, div; //SD波特率预分频、分频系数;

uint32 i;

uint32 error=0;

//-----Set Mutiplexing of E0-E5----//

for(i=0;i<=5;i++) PORT_PCR_REG(PORTE_BASE_PTR,i) =(PORT_PCR_MUX(4) | PORT_PCR_PE_MASK |

PORT_PCR_REG(PORTE_BASE_PTR,2) = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;

PORT_PCR_PS_MASK | PORT_PCR_DSE_MASK);

//--------------------------------//

// 注意:DSE、PE需要打开,PS选择PULL-UP,否则将出现命令发送后SDHC_PRSSTAT_CIHB位恒高的情

况。

// 注意:E2(SD_CLK)不能打开PE,否则SDHC无法向SD卡提供时钟。

SDHC_SYSCTL = SDHC_SYSCTL_RSTA_MASK | SDHC_SYSCTL_SDCLKFS(0x80); // reset all of sdhc

while(SDHC_SYSCTL & SDHC_SYSCTL_RSTA_MASK) if(error++>(tout)) return 1; // wait sdhc

to be reset

SDHC_IRQSTAT = ~0x0; // clean all int flag

SDHC_IRQSTATEN = 0x0; //Disable int

// 注意:本程序未使用所有SDHC模块的中断。经测试未发现因此带来的问题。

SDHC_VENDOR = 0; //disable DMA

SDHC_BLKATTR |= SDHC_BLKATTR_BLKSIZE(512); //set block size when sending command

// 注意:本程序未使用所有SDHC模块的DMA系统。可能会出现性能未完全发挥的问题。

// 注意:推荐512B。本程序未测试其它值的情况。

SDHC_PROCTL = SDHC_PROCTL_EMODE(2); // Little Endian Mode;

SDHC_PROCTL |= SDHC_PROCTL_D3CD_MASK;//set DAT3 as card detection pin;

SDHC_WML = SDHC_WML_RDWML(1) | SDHC_WML_WRWML(1); //Set Write/Read Watermark Level,

// 注意:Endian为小端模式,即低位优先。具体请参照相关计算机学科资料。

// 注意:DAT3可复用为SD detect功能。在SDHC模式下必须打开此功能,否则无法发现SD卡。

unknown their function.

//------------------------set SD baudrate--------------------------------//

while(SDHC_PRSSTAT & (SDHC_PRSSTAT_CIHB_MASK | SDHC_PRSSTAT_CDIHB_MASK)){

}

for(i = 0;i < 200000;i++);

if(error++>(tout)) return 1;

// wait CMD inhibit and DAT inhibit to be released

SDHC_SYSCTL |= SDHC_SYSCTL_SDCLKEN_MASK; // Open SD clock

//--------------------------------------------------------------------//

SDHC_SYSCTL = SDHC_SYSCTL & (~ (SDHC_SYSCTL_DTOCV_MASK | SDHC_SYSCTL_SDCLKFS_MASK |

SDHC_SYSCTL |= SDHC_SYSCTL_DTOCV(0x0E);

SDHC_SYSCTL |= SDHC_SYSCTL_SDCLKFS(pres >> 1);

SDHC_SYSCTL |= SDHC_SYSCTL_DVS(div - 1);

while(!(SDHC_PRSSTAT & SDHC_PRSSTAT_SDSTB_MASK)){

for(i = 0;i < 200000;i++);

if(error++>(tout)) return 1;

SDHC_SYSCTL_DVS_MASK));

SDHC_SYSCTL &= (~ SDHC_SYSCTL_SDCLKEN_MASK); // disable SD clock

pres = 2; div = 4; //baundrate = core_clk / pres / div , Ref RM52.4.12

// 注意:DTOCV,SDCLKFS,DVS的值均参考苏大代码,波特率为25M。未做更广泛的测试。

}// wait SDHC clock stable

}

SDHC_Status = SDHC_ON; //Open SDHC successfully

return 0;

int SDHC_Inserted();

int SDHC_Inserted(){

}

if(SDHC_PRSSTAT & SDHC_PRSSTAT_CINS_MASK) return 1; else return 0; // check

SDHC_PRSSTAT_CINS to find whether there is card.

for(i=0;i<=5;i++) PORT_PCR_REG(PORTE_BASE_PTR,i) =(PORT_PCR_MUX(4) | PORT_PCR_PE_MASK |

PORT_PCR_REG(PORTE_BASE_PTR,2) = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;

PORT_PCR_PS_MASK | PORT_PCR_DSE_MASK);

// 注意:需要重新设置E0-5口的拉选项。

SDHC_SYSCTL |= SDHC_SYSCTL_INITA_MASK; // start to send 80 SD-clocks to the card.

while(SDHC_SYSCTL & SDHC_SYSCTL_INITA_MASK) if(error++>(100)) return 0;//waiting.

for(i=0;i<=5;i++) PORT_PCR_REG(PORTE_BASE_PTR,i) =(PORT_PCR_MUX(4) | PORT_PCR_PE_MASK |

PORT_PCR_REG(PORTE_BASE_PTR,2) = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;

PORT_PCR_DSE_MASK);

// 注意:若E0-5口内部上拉则无法正确检测卡是否插入,故暂时使用内部下拉。原因未发现。

int error=0;

int i;

// 注意:需要重新发送初始化时钟刷新SD卡状态。

int SDHC_Init(uint32 Block_Size);

关于初始化的时序,参见SD2.0协议14页中的时序图:

我们操作的对象为SDHC卡,故采用的时序为:Send(CMD0) ---> idle ---> Send(CMD8)

---> compatitble ---> Send(ACMD41) ---> ready ---> Send(CMD2) ---> ident ---> Send(CMD3)

---> stby ---> Send(CMD7) ---> select card ---> Send(ACMD6) ---> set 1/4-bit mode --->

Send(CMD16) ---> set block size ---> SDHC_RW

注意:在本函数中将用到一些自定义的类型和变量如下(参见sdhc_CMD.h):

enum CMDn{......} 命令编号,如果是有效命令号值为编号值,否则为-1;所有ACMD

命令加了偏移0x40以作区别。而ACMD&0x3F操作则可以提取ACMD码编号。

COMMAND 命令结构体,包含有如下几个域:

uint8 CMDn; //命令码

uint8 TYPE; //命令类型

uint32 ARGUMENT; //命令参数

uint8 READ; //是否为读访问

uint32 BLOCKS; //操作块数

uint32 RESPONSE[4]; //应答

SDHC命令类型,有NORMAL、SUSPEND、RESUME、ABORT、SWITCH_BUSY几种,

参照苏大代码,意义未知,只在K60RM上有描述;本程序中一律使用NORMAL,经测试

未发现因此造成的问题。

const uint8 RSPTYP_cfg[]

const uint8 CICEN_cfg[]

const uint8 CCCEN_cfg[]

这三个数组表示了每一个命令的响应类型。回复类型共有R1~R7七种,根据K60RM的

Table52-8和SD2.0协议中7.3.1.3内容进行设置。

以下为代码:

int SDHC_Init(uint32 Block_Size){

//Ref SD-Page12

// Send(CMD0); // enter idle state

// Send(CMD8);

// test if SDHC card

= 0;

= CMD8; NT = 0x0100 | 0x00AA;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

if(SE[0]!=0x01AA) return -1;

= CMD0; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

SDHC_SYSCTL |= SDHC_SYSCTL_INITA_MASK;

// Ref K60RM

while(SDHC_SYSCTL & SDHC_SYSCTL_INITA_MASK) if(error++>(tout)) return 1;//waiting.

// start to send 80 SD-clocks to the card.

if(SDHC_Status != SDHC_ON) return 1;

int status;

COMMAND cmd;

uint32 error=0;

// 注意:CMD8用于检测是否为SDHC卡。但是部分SD卡也能通过。

// 注意:参数中0x0100为电压设置(3.3V),可以去掉;0x00AA是测试码。发送CMD8之后,若是可以

使用的卡将返回同样内容。

// 注意:本程序不继续检测其他类型的卡。其它类型卡的检测方法参见SD2.0协议及苏大代码。

= ACMD41; NT = 0x40300000;

= 0; = NORMAL; = 0;

status = Send_CMD(&cmd);

= CMD55; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

// Send(CMD55 + ACMD41); // enter ready state

if(status) return 1;

do{

= CMD55; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= ACMD41; NT = 0x40300000;

= 0;

= 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

if(error++>(tout)) return 1;

}while(!(SE[0] & 0x80000000));

// 注意:发送ACMD41用于切换至Ready状态。需要不断ACMD41命令,直到第31位为1才可停止。回

复中第31位为就绪标记,第30为高兼容性卡标记(HCS)(参见SD协议4.2.3)。测试中SD卡第30位为0,

SDHC卡第30位为1;是否有HCS记号不影响使用。

// 注意:发送ACMD类命令前必须先发送CMD55以表示下一条命令是ACMD类命令。发送CMD55命令成功

将回复0x120。

// Send(CMD2); // enter Identification State

= CMD2; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= CMD3; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

Card_address = ((SE[0]) & 0xFFFF0000);

= 0;

= 0;

// Send(CMD3); // enter standby state

// 注意:发送CMD3后将响应SD卡的地址号(RCA,Relative Card Address register)。CMD3的响应为

R6类型,所以高16位表示RCA。(参见SD协议4.9.5)

// 注意:之后发送的命令有许多的参数为卡的地址,故RCA是个重要参数。

// Send(ACMD6); // Set 1-bit mode;

= CMD7; NT = Card_address;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

// Send(CMD7); // Card_Select;

= CMD55; NT = Card_address;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= ACMD6; NT = 0x0; // cannot be set to 0x2..

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

= 0;

// 注意:SDHC可以进行1bit传输或4bit传输。ACMD6命令的参数若为0x2则是4-bit模式,0x0则是

1-bit模式。(参见Table4-26)

// 注意:本例程中无法使用4-bit传输模式,原因不明。

// Send(CMD16); // Set Block_Size;

= CMD16; NT = Block_Size;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

// 注意:本例程中Block_Size使用除512以外的值,会造成:1 读写扇区时多发放128个读写权限; 2

数据错误。 原因不明。

}

SDHC_BLKATTR |= SDHC_BLKATTR_BLKSIZE(Block_Size); //Set BlockSize of host

BlockSize = Block_Size;

SDHC_Status = SDHC_RW;

return 0;

int Send_CMD(COMMAND *cmd)

发送命令函数,参数为一个命令结构体地址,成功返回0,错误返回1,响应通过修改

cmd的RESPONSE域获取。

所谓发送命令,也是操作SDHC模块寄存器。将值写入SDHC_XFERTYP完成一次发

送操作。故我们需要先计算出XFERTYP应赋的值,并将其他需要写的寄存器写好,在对

SDHC_XFERTYP进行写入。写入后等待SDHC_PRSSTAT_CIHB位恢复为低点位即可在

SDHC_CMDRSP寄存器中读取响应。(参见K60RM52.6.1)

具体的代码如下:

int Send_CMD(COMMAND *cmd){

if(cmd->CMDn == (uint8)(-1)) return 1; else cmdnum = (cmd->CMDn & 0x3F); //get CMD

code

// 注意:CMD和ACMD在此处没有区别。SD卡识别ACMD命令是依赖之前发送的CMD55命令

uint32 error = 0;

uint32 xfertyp=0;

uint32 cmdnum;

while(SDHC_PRSSTAT & SDHC_PRSSTAT_CIHB_MASK) if(error++>(tout)) return 1; //waiting

CMDline idle

// set other register

SDHC_CMDARG = cmd->ARGUMENT; //write argument of command

// 注意:这里的参数设置寄存器不等于真正的参数。由于发给SD的参数有固定的格式要求,故写入寄

存器的只有参数的[39:8]段。

SDHC_BLKATTR &= (~ SDHC_BLKATTR_BLKCNT_MASK); //reset block counter

// 注意:这个寄存器代表着本次要发送的数据占用的块数。故每次要先清零。清零之后会自动变为1.

参见RM52.4.2

// SDHC_DSADDR = 0; //clean DMA cache;

//-----------------------------------计算xfertyp值。--------------------------------//

// CMDINX:

xfertyp |= SDHC_XFERTYP_CMDINX(cmdnum);

xfertyp |= SDHC_XFERTYP_CMDTYP(cmd->TYPE); // 读取命令类型

// CMDTYP:

// DPSEL:

if(cmd->BLOCKS!=0 || cmd->TYPE == RESUME) //块数不是0.那肯定是要写入的啦

xfertyp |= SDHC_XFERTYP_DPSEL_MASK;//如果命令类型为恢复CMD52写功能选择,则置数据传

送选择位

// CICEN:

xfertyp |= (CICEN_cfg[cmdnum]<< SDHC_XFERTYP_CICEN_SHIFT);

xfertyp |= (CCCEN_cfg[cmdnum]<< SDHC_XFERTYP_CICEN_SHIFT);

if(cmd->TYPE == SWITCH_BUSY) xfertyp |= SDHC_XFERTYP_RSPTYP(0x03);

else xfertyp |= SDHC_XFERTYP_RSPTYP(RSPTYP_cfg[cmdnum]);

if(cmd->BLOCKS > 1) xfertyp |= SDHC_XFERTYP_MSBSEL_MASK;

if(cmd->READ && (cmd->BLOCKS!=0)) xfertyp |= SDHC_XFERTYP_DTDSEL_MASK;

// CCCEN:

// RSPTYP:

// MSBSEL:

// DTDSEL:

// AC12EN:it seems that there is no need to send CMD12

// xfertyp |= SDHC_XFERTYP_AC12EN_MASK;

// BCEN:

if((cmd->BLOCKS!=(uint32)(-1))&&(cmd->BLOCKS!=0)){

}

SDHC_BLKATTR |= SDHC_BLKATTR_BLKCNT(cmd->BLOCKS);

xfertyp |= SDHC_XFERTYP_BCEN_MASK;

// DMAEN:DMA is not used

// xfertyp |= SDHC_XFERTYP_DMAEN_MASK;

//-------------------好吧既然它有这么多个寄存器位我们还是都设过去吧。----------------------//

// 注意:DMA不使能,自动发送CMD12不使能。未发现因此带来的问题。

// 注意:CICEN,CCCEN,RSPTYP要严格根据SD2.0协议上的响应类型来设置。详见头文件。

while(SDHC_PRSSTAT & SDHC_PRSSTAT_CIHB_MASK) if(error++>(tout)) return 1; // waiting

CIHB to be set to 0.

// SDHC_IRQSTAT |= SDHC_IRQSTAT_CC_MASK;

//------------------------------Get response------------------------------//

}

return 0;

cmd->RESPONSE[0] = 0;

cmd->RESPONSE[1] = 0;

cmd->RESPONSE[2] = 0;

cmd->RESPONSE[3] = 0;

uint8 type = (((xfertyp & SDHC_XFERTYP_RSPTYP_MASK))>>SDHC_XFERTYP_RSPTYP_SHIFT);

if(type != 0){

}

cmd->RESPONSE[0] = SDHC_CMDRSP(0);

cmd->RESPONSE[1] = SDHC_CMDRSP(1);

cmd->RESPONSE[2] = SDHC_CMDRSP(2);

cmd->RESPONSE[3] = SDHC_CMDRSP(3);

SDHC_XFERTYP = xfertyp; //Send command to SD

//------------------------------------------------------------------------//

读写数据实际上是在跟SD卡从机通信。基本过程是:

1 给SD卡发送读/写命令(CMD17、24)

2 SD卡响应成功,并发放读标记和写标记(SDHC_PRSSTAT的BREN、BWEN位)。

数量为BlockSize/4,但是如果BlockSize大于512,会多发放128个标记,原因未知。

3 向数据缓存寄存器SDHC_DATPORT写如或读出数据,一个标记只能读或写一次。

读出或写入之后读标记和写标记被自动清除。

4 等待下一个标记发放出来。

5 标记全部发放后,读操作结束。如果是写操作,则需要发送CMD13来完成写操作。

注意:每次操作必须用完所有发放的标记否则将导致DAT和CMD总线恒忙状态,无

法进行下一步操作。

代码如下:

int SDHC_read(uint8 cache[],uint32 sector){

if(SDHC_Status!=SDHC_RW) return 1;

uint32 error=0;

int status;

COMMAND cmd;

// Send(CMD24); // start to read 1 sector

uint32 i,temp;

for(i=0;i

}

while(!(SDHC_PRSSTAT & SDHC_PRSSTAT_BREN_MASK)) if(error++>(tout)) return 1 ; //

temp = SDHC_DATPORT;

cache[4*i] = (temp & 0xFF);

cache[4*i+1] = ((temp>>8) & 0xFF);

cache[4*i+2] = ((temp>>16) & 0xFF);

cache[4*i+3] = ((temp>>24) & 0xFF);

= CMD17; NT = sector * BlockSize;

= NORMAL; = 1;

status = Send_CMD(&cmd);

if(status) return 1;

= 1;

wait BREN set to 1

// 注意:尽管在初始化中没有设置成4-bit模式,但是读写SD卡仍然是以4个Byte为单位收发的。

// 注意:依照上文设置的小端模式,4个Byte的读写顺序是从低位到高位。

}

int SDHC_write(uint8 cache[],uint32 sector){

// Send(CMD24); // start to write 1 sector

// Send(CMD13); // wait the end of write

= CMD13; NT = Card_address;

uint32 i,temp;

}

for(i=0;i

while(!(SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN_MASK)) if(error++>(tout)) return 1;

temp = cache[4*i] + (cache[4*i+1]<<8) + (cache[4*i+2]<<16) + (cache[4*i+3]<<24);

SDHC_DATPORT = temp;

= CMD24; NT = sector * BlockSize;

= NORMAL; = 1;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

if(SDHC_Status!=SDHC_RW) return 1;

uint32 error=0;

int status;

COMMAND cmd;

return 0;

}

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

return 0;

= 0;

到这里为止,本例程就可以实现SDHC最基本的SD卡读写操作了。

待解决的问题

本例程测试较为稳定,也实现了基本功能,但也存在着问题。列出如下:

1 本例程有部分过程(如初始化SDHC模块)的顺序仅参照了苏州大学的代码,未能

在SD2.0协议和K60RM中找到对应资料。故无法判断是否存在未知的问题。

2 对某些参数未做更广泛的测试,如波特率、写数据的时间间隔等。对于某些寄存器的

作用也尚待研究。

3 扇区大小只能设置为512B,读写模式只能使用1-bit模式。

4 未实现多扇区读写功能和其他类型的卡的检测与读写功能。

5 其他细节和未发现的问题。

希望更有经验的读者能够做进一步的研究,并联系作者共同探讨。来信请寄:

yukyo@。

参考资料

《SD Specifications Physical Layer Simplified Specification Version 2.00》,SD Card

Association,September 25, 2006;

《K60P100M100SF2RM》,Freescale Semiconductor;

苏州大学嵌入式中心K60底层库。

2024年9月26日发(作者:和夏璇)

Kinetis K60 SDHC模块对SD设备操作方法详解

中国科学技术大学电子科学与技术系2011级 游绩榕 2013年1月6日

【关键词】本文简单介绍了SD设备,并通过例程来详细解释如何使用K60的SDHC模块

对SD卡进行读写操作。

1 适用范围

本文所处的环境为:win7-32位系统,IAR System 6.4,P&E公司osjtag调试器,龙丘

MK60DN512ZVLL10P100。单片机主频200M,SD卡为金士顿512M卡1张,金士顿4G卡

(Class-4)1张。

本文所实现的功能为:使用SDHC模式,基于SD2.0协议对SD卡和SDHC卡进行单

扇区进行不连续的读操作和写操作,SD设备波特率为25M,扇区大小为512B,连续写操

作最小间隔为1.5ms(最短),2ms(稳定)。读写一次的时间约为50~100us。

SD家族介绍

SD设备分为许多类型。SD卡(Secure Digital Memory Card)是一种基于半导体闪存工

艺的存储卡, 1999年,由日本松下、东芝及美国SanDisk公司共同研制完成。2000年,这几

家公司发起成立了SD协会(Secure Digital Association简称SDA),阵容强大,吸引了大量

厂商参加。其中包括IBM,Microsoft,Motorola,NEC、Samsung等。在这些领导厂商的推

动下,SD卡已成为目前消费数码设备中应用最广泛的一种存储卡。

SD卡最初是从MMC(Multimedia Card)卡基础上发展起来的,可以与MMC卡实现兼

容;但SD卡数据传输速率更快,此外,还特别增加了CPRM(Content protection for Recordable

Media)功能,用于卡内数据的授权访问,实现内容保护。

SD卡尺寸为32mm x 24mm x 2.1mm,相当于邮票大小,这样尺寸的存储卡用在数码相

机、DV机中还算合适,但在记录不断被刷新的轻薄手机面前,SD卡显得过分“庞大”。为

了满足数码产品不断缩小存储卡体积的要求,SD卡逐渐演变出了Mini SD,Micro SD两种

规格。

SD卡背面共有9个引脚,包含4根数据线,支持1bit/4bit两种数据传输宽度,时钟最

高频率为25MHZ,故理论最高数据传输速率为12.5MB/S,工作电压为2.7~3.6V。

Mini SD

顾名思义,Mini SD卡相比标准SD卡,外形上更加小巧,仅有标准SD卡40%左右的

大小。尽管Mini SD卡的外形大小及接口形状与原来的SD卡有所不同,但接口规范保持不

变,确保了兼容性。若将Mini SD插入特定的转接卡中,可当作标准SD卡来使用。

TransFlash(Micro SD)

Transflash卡,也称T-Flash卡, TF或T卡,最早由SanDisk推出。T卡仅有11mm x 15mm

x 1mm 大小,仅相当于标准SD卡的1/4,比Mini SD卡还要小巧。

同样,T卡是与标准SD卡功能也是兼容的,将T卡插入特定的转接卡中,可以当作标

准SD卡或Mini SD卡来使用。

2005年7月,SDA协会正式发布了Micro SD标准,该标准与TransFlash卡完全兼容,

市场上的TransFlash卡和Micro SD卡可以不加区分的使用。这好比TransFlash是卡的小名,

等长大了就取了个Micro SD的学名,不过大家还是叫其TransFlash的多!

Mini SD、Micro SD这种小体积SD卡的出现,大大拓展了SD卡的应用范围,这使得

SD卡应用在一些小型手机中成为可能。另一方面,Mini SD、Micro SD功能与SD卡功能兼

容,只要将其插入特定转接卡中,即可当作标准SD卡来使用,这样,Mini SD、Micro SD

便可用于那些使用标准SD卡的数码设备,这加速了Mini SD、Micro SD的市场普及。

前几年,市场上的SD、Mini SD、Micro SD卡遵循的是SD Spec Ver1.0或1.1规范,最

大可能容量仅为2GB。2006年,SDA协会发布了SD Spec Ver2.0规范,符合此新规范的SD

卡容量可达4GB或更高。

符合2.0规范的SD卡,称为SDHC(SD high capacity)卡。SDHC卡外形维持与SD卡

一致,但是文件系统从FAT12、FAT16改为FAT32型;SDHC卡的最大容量可达32GB。除

了SDHC卡外, 还有Mini SDHC,Micro SDHC类型的卡。

SDHC卡与标准SD卡不再兼容,必须符合SD Spec Ver2.0的设备才能支持SDHC卡,

这样的设备都会带有SDHC logo。而支持SDHC卡的设备可以向下兼容标准SD卡。

为了充分发挥SDHC卡的性能,保证兼容性,SDA协会为SDHC卡定义了3个速度等

级:2,4,6;其含义是各等级分别可以忍受的写速率至少是2MB/S,4MB/S,6MB/S.速度

等级定义中使用的是数据写速率,数据读速率要比数据写速率快。现今绝大多数设备上使用

的都是SDHC卡和microSDHC卡。小容量的SD卡已经停产并逐渐淘汰(2012年)。

SDIO

SDIO (Input/Output)是一种IO接口规范。目前,其最主要用途是为带有SD卡槽的设备

进行外设功能扩展。SDIO卡是一种IO外设,而不是Memory。SDIO卡外形与SD卡一致,

可直接插入SD卡槽中。

目前市场上有多种SDIO接口的外设,比如SDIO蓝牙,SDIO GPS,SDIO无线网卡,

SDIO移动电视卡等。这些卡底部带有和SD卡外形一致的插头,可直接插入SDIO卡槽(即

为SD卡槽)的智能手机、PDA中,即可为这些手机、PDA带来丰富的扩展功能。用户可

根据实际需要,灵活选择外设扩展的种类、品牌和性能等级。SDIO已为成为数码产品外设

功能扩展的标准接口。

SDIO卡插入带有标准SD卡槽的设备后,如果该设备不支持SDIO,SDIO卡不会对SD

卡的命令作出响应,处于非激活状态,不影响设备的正常工作;如果该设备支持SDIO卡,

则按照规范的要求激活SDIO卡。

SDIO卡允许设备按IO的方式直接对寄存器进行访问,无须执行FAT文件结构或数据

sector等复杂操作。此外,SDIO卡还能向设备发出中断,这是与SD memory卡的本质区别。

本文针对的是符合SD2.0的microSD进行操作,但测试结果表明一部分SD1.1协议的

卡也可以使用【此结果与标准不相符】。

功能描述

K60内有一个SDHC模块,目标引脚为E0--E5。端口具体功能见端口复用表(K60

RM10.3.1)。

K60的SDHC模块设有二十多个寄存器,我们通过设置这些寄存器设置K60的SDHC

的主机,然后通过SDHC主机与SD卡的从机通信,再由SD从机向SD卡写数据。其中设

置SDHC模块主要参考K60RM,SDHC模块与SD卡通信过程主要参考SD2.0协议,并且

底层通信操作由SDHC模块自动完成,而SD从机向SD卡写数据无需我们关心。

我们通过操作SDHC相关寄存器,可以让SDHC向SD卡发送特定的命令。命令可以

带有参数。SD卡从机接收到命令后进行识别和动作,并可能反馈一些响应至SDHC模块寄

存器。

基本的操作时序如下:

设置SDHC主机属性

发送命令群以初始化SD卡从机并获取SD卡信息

发送读命令并接收SD卡反馈数据

发送写命令并等待SD卡读取数据

以下为本文所用程序的分析。本文所用程序参照苏州大学嵌入式中心给出的参考代码,

并对其做出修正和整理。读者可以同时参考苏大的代码。

本文给出的例程中提出几个接口函数:

int SDHC_On();

int SDHC_Init(uint32 Block_Size);

int SDHC_Inserted();

int SDHC_read(uint8 cache[],uint32 sector);

int SDHC_write(uint8 cache[],uint32 sector);

int SDHC_On(); 初始化SDHC主机,此阶段SDHC不与SD卡通信,不进行插卡检测。

成功返回0,错误返回1;

int SDHC_Init(uint32 Block_Size); 初始化SD卡。此阶段未进行任何插卡检测。参数

Block_Size表示每次读写扇区大小。但是只能设置为512。成功返回0,错误返回1;非可

支持卡返回-1;可支持的卡:SDHC卡,部分SD卡。

int SDHC_Inserted(); 插卡检测,检查是否有SD设备连在SDHC上。有卡返回1,错

误或无卡返回0;

int SDHC_read(uint8 cache[],uint32 sector); 从SD卡中读取一个扇区的数据,cache[]为

数据存放数组,sector为待读取的扇区号。成功返回0,错误返回1;

int SDHC_write(uint8 cache[],uint32 sector); 向SD卡中写入一个扇区的数据,cache[]为

数据存放数组,sector为待写入的扇区号。成功返回0,错误返回1;

调用方法:

1 调用SDHC_On();若初始化成功变量SDHC_Status变为SDHC_ON;否则为初始值

SDHC_OFF;

2 进行插卡检测SDHC_Inserted();

3 初始化SD卡SDHC_Init(512);若成功SDHC_Status变为SDHC_RW;

4 检测SDHC状态,若为SDHC_RW则可进行读写操作。

这是使用SDHC的一个例子。

......

SDHC_On();

while(!SDHC_Inserted());

SDHC_Init(512);

if(SDHC_Status != SDHC_RW){

......

}else{

SDHC_read(Cache,p);

SDHC_write(Cache,p);

}

......

注意:

程序中不可将PORTE0-E5用作其他用途;

SDHC_write操作不能连续调用。中间至少要有1.5ms(最短)/2ms(稳定)的延时。

SDHC_read操作无此限制。

SDHC_On、SDHC_Init、SDHC_read、int SDHC_write均不进行插卡检测,用户需

要自行调用SDHC_Inserted进行插卡检测。

插卡检测仅检测SD卡槽(E口)是否有设备连接,不检测设备是否可以识别。

代码分析

注意:本文将几个主要的函数列出如下,本例程只保留最基本的功能,其它功能关于中

断服务和DMA的部分全部注释掉了,其他类型卡检测的部分完全去除。附件中有一份精简

干净的和一份带注释的备用。下文引用的是例程中精简版的那一份。

int SDHC_On();

int SDHC_On(){//此函数的步骤参照苏大代码。

SIM_SCGC3 |= SIM_SCGC3_SDHC_MASK;

GPIO_On(PORTE); // Open PORTE

// Open Clock for SDHC module

uint32 pres, div; //SD波特率预分频、分频系数;

uint32 i;

uint32 error=0;

//-----Set Mutiplexing of E0-E5----//

for(i=0;i<=5;i++) PORT_PCR_REG(PORTE_BASE_PTR,i) =(PORT_PCR_MUX(4) | PORT_PCR_PE_MASK |

PORT_PCR_REG(PORTE_BASE_PTR,2) = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;

PORT_PCR_PS_MASK | PORT_PCR_DSE_MASK);

//--------------------------------//

// 注意:DSE、PE需要打开,PS选择PULL-UP,否则将出现命令发送后SDHC_PRSSTAT_CIHB位恒高的情

况。

// 注意:E2(SD_CLK)不能打开PE,否则SDHC无法向SD卡提供时钟。

SDHC_SYSCTL = SDHC_SYSCTL_RSTA_MASK | SDHC_SYSCTL_SDCLKFS(0x80); // reset all of sdhc

while(SDHC_SYSCTL & SDHC_SYSCTL_RSTA_MASK) if(error++>(tout)) return 1; // wait sdhc

to be reset

SDHC_IRQSTAT = ~0x0; // clean all int flag

SDHC_IRQSTATEN = 0x0; //Disable int

// 注意:本程序未使用所有SDHC模块的中断。经测试未发现因此带来的问题。

SDHC_VENDOR = 0; //disable DMA

SDHC_BLKATTR |= SDHC_BLKATTR_BLKSIZE(512); //set block size when sending command

// 注意:本程序未使用所有SDHC模块的DMA系统。可能会出现性能未完全发挥的问题。

// 注意:推荐512B。本程序未测试其它值的情况。

SDHC_PROCTL = SDHC_PROCTL_EMODE(2); // Little Endian Mode;

SDHC_PROCTL |= SDHC_PROCTL_D3CD_MASK;//set DAT3 as card detection pin;

SDHC_WML = SDHC_WML_RDWML(1) | SDHC_WML_WRWML(1); //Set Write/Read Watermark Level,

// 注意:Endian为小端模式,即低位优先。具体请参照相关计算机学科资料。

// 注意:DAT3可复用为SD detect功能。在SDHC模式下必须打开此功能,否则无法发现SD卡。

unknown their function.

//------------------------set SD baudrate--------------------------------//

while(SDHC_PRSSTAT & (SDHC_PRSSTAT_CIHB_MASK | SDHC_PRSSTAT_CDIHB_MASK)){

}

for(i = 0;i < 200000;i++);

if(error++>(tout)) return 1;

// wait CMD inhibit and DAT inhibit to be released

SDHC_SYSCTL |= SDHC_SYSCTL_SDCLKEN_MASK; // Open SD clock

//--------------------------------------------------------------------//

SDHC_SYSCTL = SDHC_SYSCTL & (~ (SDHC_SYSCTL_DTOCV_MASK | SDHC_SYSCTL_SDCLKFS_MASK |

SDHC_SYSCTL |= SDHC_SYSCTL_DTOCV(0x0E);

SDHC_SYSCTL |= SDHC_SYSCTL_SDCLKFS(pres >> 1);

SDHC_SYSCTL |= SDHC_SYSCTL_DVS(div - 1);

while(!(SDHC_PRSSTAT & SDHC_PRSSTAT_SDSTB_MASK)){

for(i = 0;i < 200000;i++);

if(error++>(tout)) return 1;

SDHC_SYSCTL_DVS_MASK));

SDHC_SYSCTL &= (~ SDHC_SYSCTL_SDCLKEN_MASK); // disable SD clock

pres = 2; div = 4; //baundrate = core_clk / pres / div , Ref RM52.4.12

// 注意:DTOCV,SDCLKFS,DVS的值均参考苏大代码,波特率为25M。未做更广泛的测试。

}// wait SDHC clock stable

}

SDHC_Status = SDHC_ON; //Open SDHC successfully

return 0;

int SDHC_Inserted();

int SDHC_Inserted(){

}

if(SDHC_PRSSTAT & SDHC_PRSSTAT_CINS_MASK) return 1; else return 0; // check

SDHC_PRSSTAT_CINS to find whether there is card.

for(i=0;i<=5;i++) PORT_PCR_REG(PORTE_BASE_PTR,i) =(PORT_PCR_MUX(4) | PORT_PCR_PE_MASK |

PORT_PCR_REG(PORTE_BASE_PTR,2) = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;

PORT_PCR_PS_MASK | PORT_PCR_DSE_MASK);

// 注意:需要重新设置E0-5口的拉选项。

SDHC_SYSCTL |= SDHC_SYSCTL_INITA_MASK; // start to send 80 SD-clocks to the card.

while(SDHC_SYSCTL & SDHC_SYSCTL_INITA_MASK) if(error++>(100)) return 0;//waiting.

for(i=0;i<=5;i++) PORT_PCR_REG(PORTE_BASE_PTR,i) =(PORT_PCR_MUX(4) | PORT_PCR_PE_MASK |

PORT_PCR_REG(PORTE_BASE_PTR,2) = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;

PORT_PCR_DSE_MASK);

// 注意:若E0-5口内部上拉则无法正确检测卡是否插入,故暂时使用内部下拉。原因未发现。

int error=0;

int i;

// 注意:需要重新发送初始化时钟刷新SD卡状态。

int SDHC_Init(uint32 Block_Size);

关于初始化的时序,参见SD2.0协议14页中的时序图:

我们操作的对象为SDHC卡,故采用的时序为:Send(CMD0) ---> idle ---> Send(CMD8)

---> compatitble ---> Send(ACMD41) ---> ready ---> Send(CMD2) ---> ident ---> Send(CMD3)

---> stby ---> Send(CMD7) ---> select card ---> Send(ACMD6) ---> set 1/4-bit mode --->

Send(CMD16) ---> set block size ---> SDHC_RW

注意:在本函数中将用到一些自定义的类型和变量如下(参见sdhc_CMD.h):

enum CMDn{......} 命令编号,如果是有效命令号值为编号值,否则为-1;所有ACMD

命令加了偏移0x40以作区别。而ACMD&0x3F操作则可以提取ACMD码编号。

COMMAND 命令结构体,包含有如下几个域:

uint8 CMDn; //命令码

uint8 TYPE; //命令类型

uint32 ARGUMENT; //命令参数

uint8 READ; //是否为读访问

uint32 BLOCKS; //操作块数

uint32 RESPONSE[4]; //应答

SDHC命令类型,有NORMAL、SUSPEND、RESUME、ABORT、SWITCH_BUSY几种,

参照苏大代码,意义未知,只在K60RM上有描述;本程序中一律使用NORMAL,经测试

未发现因此造成的问题。

const uint8 RSPTYP_cfg[]

const uint8 CICEN_cfg[]

const uint8 CCCEN_cfg[]

这三个数组表示了每一个命令的响应类型。回复类型共有R1~R7七种,根据K60RM的

Table52-8和SD2.0协议中7.3.1.3内容进行设置。

以下为代码:

int SDHC_Init(uint32 Block_Size){

//Ref SD-Page12

// Send(CMD0); // enter idle state

// Send(CMD8);

// test if SDHC card

= 0;

= CMD8; NT = 0x0100 | 0x00AA;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

if(SE[0]!=0x01AA) return -1;

= CMD0; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

SDHC_SYSCTL |= SDHC_SYSCTL_INITA_MASK;

// Ref K60RM

while(SDHC_SYSCTL & SDHC_SYSCTL_INITA_MASK) if(error++>(tout)) return 1;//waiting.

// start to send 80 SD-clocks to the card.

if(SDHC_Status != SDHC_ON) return 1;

int status;

COMMAND cmd;

uint32 error=0;

// 注意:CMD8用于检测是否为SDHC卡。但是部分SD卡也能通过。

// 注意:参数中0x0100为电压设置(3.3V),可以去掉;0x00AA是测试码。发送CMD8之后,若是可以

使用的卡将返回同样内容。

// 注意:本程序不继续检测其他类型的卡。其它类型卡的检测方法参见SD2.0协议及苏大代码。

= ACMD41; NT = 0x40300000;

= 0; = NORMAL; = 0;

status = Send_CMD(&cmd);

= CMD55; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

// Send(CMD55 + ACMD41); // enter ready state

if(status) return 1;

do{

= CMD55; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= ACMD41; NT = 0x40300000;

= 0;

= 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

if(error++>(tout)) return 1;

}while(!(SE[0] & 0x80000000));

// 注意:发送ACMD41用于切换至Ready状态。需要不断ACMD41命令,直到第31位为1才可停止。回

复中第31位为就绪标记,第30为高兼容性卡标记(HCS)(参见SD协议4.2.3)。测试中SD卡第30位为0,

SDHC卡第30位为1;是否有HCS记号不影响使用。

// 注意:发送ACMD类命令前必须先发送CMD55以表示下一条命令是ACMD类命令。发送CMD55命令成功

将回复0x120。

// Send(CMD2); // enter Identification State

= CMD2; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= CMD3; NT = 0;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

Card_address = ((SE[0]) & 0xFFFF0000);

= 0;

= 0;

// Send(CMD3); // enter standby state

// 注意:发送CMD3后将响应SD卡的地址号(RCA,Relative Card Address register)。CMD3的响应为

R6类型,所以高16位表示RCA。(参见SD协议4.9.5)

// 注意:之后发送的命令有许多的参数为卡的地址,故RCA是个重要参数。

// Send(ACMD6); // Set 1-bit mode;

= CMD7; NT = Card_address;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

// Send(CMD7); // Card_Select;

= CMD55; NT = Card_address;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= ACMD6; NT = 0x0; // cannot be set to 0x2..

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

= 0;

// 注意:SDHC可以进行1bit传输或4bit传输。ACMD6命令的参数若为0x2则是4-bit模式,0x0则是

1-bit模式。(参见Table4-26)

// 注意:本例程中无法使用4-bit传输模式,原因不明。

// Send(CMD16); // Set Block_Size;

= CMD16; NT = Block_Size;

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

// 注意:本例程中Block_Size使用除512以外的值,会造成:1 读写扇区时多发放128个读写权限; 2

数据错误。 原因不明。

}

SDHC_BLKATTR |= SDHC_BLKATTR_BLKSIZE(Block_Size); //Set BlockSize of host

BlockSize = Block_Size;

SDHC_Status = SDHC_RW;

return 0;

int Send_CMD(COMMAND *cmd)

发送命令函数,参数为一个命令结构体地址,成功返回0,错误返回1,响应通过修改

cmd的RESPONSE域获取。

所谓发送命令,也是操作SDHC模块寄存器。将值写入SDHC_XFERTYP完成一次发

送操作。故我们需要先计算出XFERTYP应赋的值,并将其他需要写的寄存器写好,在对

SDHC_XFERTYP进行写入。写入后等待SDHC_PRSSTAT_CIHB位恢复为低点位即可在

SDHC_CMDRSP寄存器中读取响应。(参见K60RM52.6.1)

具体的代码如下:

int Send_CMD(COMMAND *cmd){

if(cmd->CMDn == (uint8)(-1)) return 1; else cmdnum = (cmd->CMDn & 0x3F); //get CMD

code

// 注意:CMD和ACMD在此处没有区别。SD卡识别ACMD命令是依赖之前发送的CMD55命令

uint32 error = 0;

uint32 xfertyp=0;

uint32 cmdnum;

while(SDHC_PRSSTAT & SDHC_PRSSTAT_CIHB_MASK) if(error++>(tout)) return 1; //waiting

CMDline idle

// set other register

SDHC_CMDARG = cmd->ARGUMENT; //write argument of command

// 注意:这里的参数设置寄存器不等于真正的参数。由于发给SD的参数有固定的格式要求,故写入寄

存器的只有参数的[39:8]段。

SDHC_BLKATTR &= (~ SDHC_BLKATTR_BLKCNT_MASK); //reset block counter

// 注意:这个寄存器代表着本次要发送的数据占用的块数。故每次要先清零。清零之后会自动变为1.

参见RM52.4.2

// SDHC_DSADDR = 0; //clean DMA cache;

//-----------------------------------计算xfertyp值。--------------------------------//

// CMDINX:

xfertyp |= SDHC_XFERTYP_CMDINX(cmdnum);

xfertyp |= SDHC_XFERTYP_CMDTYP(cmd->TYPE); // 读取命令类型

// CMDTYP:

// DPSEL:

if(cmd->BLOCKS!=0 || cmd->TYPE == RESUME) //块数不是0.那肯定是要写入的啦

xfertyp |= SDHC_XFERTYP_DPSEL_MASK;//如果命令类型为恢复CMD52写功能选择,则置数据传

送选择位

// CICEN:

xfertyp |= (CICEN_cfg[cmdnum]<< SDHC_XFERTYP_CICEN_SHIFT);

xfertyp |= (CCCEN_cfg[cmdnum]<< SDHC_XFERTYP_CICEN_SHIFT);

if(cmd->TYPE == SWITCH_BUSY) xfertyp |= SDHC_XFERTYP_RSPTYP(0x03);

else xfertyp |= SDHC_XFERTYP_RSPTYP(RSPTYP_cfg[cmdnum]);

if(cmd->BLOCKS > 1) xfertyp |= SDHC_XFERTYP_MSBSEL_MASK;

if(cmd->READ && (cmd->BLOCKS!=0)) xfertyp |= SDHC_XFERTYP_DTDSEL_MASK;

// CCCEN:

// RSPTYP:

// MSBSEL:

// DTDSEL:

// AC12EN:it seems that there is no need to send CMD12

// xfertyp |= SDHC_XFERTYP_AC12EN_MASK;

// BCEN:

if((cmd->BLOCKS!=(uint32)(-1))&&(cmd->BLOCKS!=0)){

}

SDHC_BLKATTR |= SDHC_BLKATTR_BLKCNT(cmd->BLOCKS);

xfertyp |= SDHC_XFERTYP_BCEN_MASK;

// DMAEN:DMA is not used

// xfertyp |= SDHC_XFERTYP_DMAEN_MASK;

//-------------------好吧既然它有这么多个寄存器位我们还是都设过去吧。----------------------//

// 注意:DMA不使能,自动发送CMD12不使能。未发现因此带来的问题。

// 注意:CICEN,CCCEN,RSPTYP要严格根据SD2.0协议上的响应类型来设置。详见头文件。

while(SDHC_PRSSTAT & SDHC_PRSSTAT_CIHB_MASK) if(error++>(tout)) return 1; // waiting

CIHB to be set to 0.

// SDHC_IRQSTAT |= SDHC_IRQSTAT_CC_MASK;

//------------------------------Get response------------------------------//

}

return 0;

cmd->RESPONSE[0] = 0;

cmd->RESPONSE[1] = 0;

cmd->RESPONSE[2] = 0;

cmd->RESPONSE[3] = 0;

uint8 type = (((xfertyp & SDHC_XFERTYP_RSPTYP_MASK))>>SDHC_XFERTYP_RSPTYP_SHIFT);

if(type != 0){

}

cmd->RESPONSE[0] = SDHC_CMDRSP(0);

cmd->RESPONSE[1] = SDHC_CMDRSP(1);

cmd->RESPONSE[2] = SDHC_CMDRSP(2);

cmd->RESPONSE[3] = SDHC_CMDRSP(3);

SDHC_XFERTYP = xfertyp; //Send command to SD

//------------------------------------------------------------------------//

读写数据实际上是在跟SD卡从机通信。基本过程是:

1 给SD卡发送读/写命令(CMD17、24)

2 SD卡响应成功,并发放读标记和写标记(SDHC_PRSSTAT的BREN、BWEN位)。

数量为BlockSize/4,但是如果BlockSize大于512,会多发放128个标记,原因未知。

3 向数据缓存寄存器SDHC_DATPORT写如或读出数据,一个标记只能读或写一次。

读出或写入之后读标记和写标记被自动清除。

4 等待下一个标记发放出来。

5 标记全部发放后,读操作结束。如果是写操作,则需要发送CMD13来完成写操作。

注意:每次操作必须用完所有发放的标记否则将导致DAT和CMD总线恒忙状态,无

法进行下一步操作。

代码如下:

int SDHC_read(uint8 cache[],uint32 sector){

if(SDHC_Status!=SDHC_RW) return 1;

uint32 error=0;

int status;

COMMAND cmd;

// Send(CMD24); // start to read 1 sector

uint32 i,temp;

for(i=0;i

}

while(!(SDHC_PRSSTAT & SDHC_PRSSTAT_BREN_MASK)) if(error++>(tout)) return 1 ; //

temp = SDHC_DATPORT;

cache[4*i] = (temp & 0xFF);

cache[4*i+1] = ((temp>>8) & 0xFF);

cache[4*i+2] = ((temp>>16) & 0xFF);

cache[4*i+3] = ((temp>>24) & 0xFF);

= CMD17; NT = sector * BlockSize;

= NORMAL; = 1;

status = Send_CMD(&cmd);

if(status) return 1;

= 1;

wait BREN set to 1

// 注意:尽管在初始化中没有设置成4-bit模式,但是读写SD卡仍然是以4个Byte为单位收发的。

// 注意:依照上文设置的小端模式,4个Byte的读写顺序是从低位到高位。

}

int SDHC_write(uint8 cache[],uint32 sector){

// Send(CMD24); // start to write 1 sector

// Send(CMD13); // wait the end of write

= CMD13; NT = Card_address;

uint32 i,temp;

}

for(i=0;i

while(!(SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN_MASK)) if(error++>(tout)) return 1;

temp = cache[4*i] + (cache[4*i+1]<<8) + (cache[4*i+2]<<16) + (cache[4*i+3]<<24);

SDHC_DATPORT = temp;

= CMD24; NT = sector * BlockSize;

= NORMAL; = 1;

status = Send_CMD(&cmd);

if(status) return 1;

= 0;

if(SDHC_Status!=SDHC_RW) return 1;

uint32 error=0;

int status;

COMMAND cmd;

return 0;

}

= NORMAL; = 0;

status = Send_CMD(&cmd);

if(status) return 1;

return 0;

= 0;

到这里为止,本例程就可以实现SDHC最基本的SD卡读写操作了。

待解决的问题

本例程测试较为稳定,也实现了基本功能,但也存在着问题。列出如下:

1 本例程有部分过程(如初始化SDHC模块)的顺序仅参照了苏州大学的代码,未能

在SD2.0协议和K60RM中找到对应资料。故无法判断是否存在未知的问题。

2 对某些参数未做更广泛的测试,如波特率、写数据的时间间隔等。对于某些寄存器的

作用也尚待研究。

3 扇区大小只能设置为512B,读写模式只能使用1-bit模式。

4 未实现多扇区读写功能和其他类型的卡的检测与读写功能。

5 其他细节和未发现的问题。

希望更有经验的读者能够做进一步的研究,并联系作者共同探讨。来信请寄:

yukyo@。

参考资料

《SD Specifications Physical Layer Simplified Specification Version 2.00》,SD Card

Association,September 25, 2006;

《K60P100M100SF2RM》,Freescale Semiconductor;

苏州大学嵌入式中心K60底层库。

发布评论

评论列表 (0)

  1. 暂无评论