S3C2416裸机开发系列十六_sd卡驱动实现

时间:2022-09-23 14:02:23

S3C2416裸机开发系列十六

sd卡驱动实现

象棋小子    1048272975

SD卡(Secure Digital Memory Card)具有体积小、容量大、传输数据快、可插拔、安全性好等长处。被广泛应用于便携式设备上。比如作为数码相机的存储卡,作为手机、平板多媒体扩展卡用的TF卡(micro sd)。笔者此处就s3c2416 sd卡驱动的实现作一个简单的介绍。

1. sd卡概述

sd卡技术是在MMC卡的基础上发展起来的,其尺寸与MMC卡一样,仅仅是比MMC卡厚了0.7mm,因此sd设备能够识别并存取MMC卡。sd卡接口除了保留MMC卡的7针外,还在两边加了2针,作为数据线,目的是通过把传输方式由串行变成并行。以提高传输速率。

此时的规范为sd1.0版本号。最高容量仅仅能到4GB。为了跟进产品的更新换代。sd联合协会在06年公布了容量更大、存储更快的下一代sd卡规范sd2.0。该规范又一次定义了sd卡的速度等级。分为三档:Class 2、4、6,分别相应写入速度2MB/s、4MB/s、6MB/s。

依据卡容量又分为标准卡(小于2GB)和高容量卡(2GB~32GB),眼下市面上应用的sd卡绝大部分都是sd2.0版本号的卡。为了让储存卡更加迷你。通过sd卡规范标准。又衍生了MiniSD卡和Micro
SD卡。这些卡均比标准sd卡尺寸小,通过sd转接卡能够当作一般的sd卡使用。尤其是Micro SD卡,能够算是最小的存储卡了,超小的体积能够极大的节省消费电子产品内部设计的空间。基本眼下的android手机均是选用Micro SD卡作为多媒体扩展储存卡。

随着科技的进步,sd2.0规范sd卡也渐渐无法满足应用的需求。在10年sd联合协会又公布了新的sd3.0规范。该规范定义了sdxc和uhs,并添加了Class10。容量范围为32GB~2TB。在sdxc卡仍需进一步坐等其价格下降的情况下。sd4.0规范已经開始在紧张的制订中,这已超出本文的讨论范围内了。

2. sd卡驱动编写

sd卡共支持三种传输模式:spi模式、1位sd模式、4位sd模式。

全部的sd卡都必须支持较老的spi/mmc模式。这个模式支持慢速的四线spi接口,使非常多微控制器都能够通过spi或模拟spi接口来读写sd卡。

因为s3c2416具有sd总线控制器。而且兼容sd2.0的sd卡,因此此处仅仅分析4位sd模式、sd2.0及sd1.0版本号的sd卡驱动实现。sd2.0以上版本号sd卡、MMC卡、spi方式读写sd卡在本文不适用。

sd卡驱动的编写必须參考sd2.0规范。此处仅仅依据sd2.0规范解说几个重要的过程或概念。这些过程详细的实现请參考sd驱动模块中对应的函数实现。

2.1. sd卡初始化及识别过程

sd卡上电后,将进入idle状态。此时的sd卡为1位sd模式。

通过拉低CS线将可使sd进入spi模式(不再讨论范围内)。在sd模式下卡的初始化及识别过程见图2.1.1。其过程例如以下:

1) 发送CMD0软件复位全部的卡到idle状态。

2) 发送CMD8来检查卡是否支持主机电压(2.7v~3.3v),这个命令在sd2.0以上才被定义,若没有收到回复信号。则可能为sd1.0或MMC卡,若接收到卡回复信号,说明为sd2.0版本号卡,跳转到步骤5

3) CMD8没有收到回复信号,可进一步发送ACMD41(CMD55+CMD41),參数HCS位为0(非高容量卡),假设没有回复信号,说明是MMC卡或其他不能识别的卡。可进一步发送CMD1确定是否MMC卡(此处不再分析)

4) ACMD41能收到回复。而且从回复中确定sd卡己准备好。就可以确定这是sd1.x版本号的卡,若回复中表明sd卡未准备好,则需反复发送ACMD41等待卡准备好,可通过超时(卡一直busy)推断卡不支持主机电压。此时表明卡不可用。推断出sd1.x的卡后,跳转到步骤9

5) CMD8有回复说明为sd2.0以上的卡。从回复中确定卡能否在该电压下工作,不能则觉得卡不可用。

6) 回复中确定卡能在2.7v~3.3v电压工作后,进一步发送ACMD41(CMD55+CMD41)。參数HCS位为1表明主机支持高容量的卡

7) 检查ACMD41卡回复中忙标志。若卡处于忙状态。则反复发送ACDM41,直到卡准备好,可通过超时(卡一直忙状态)可觉得该卡不可用。

8) ACMD41回复准备好后,再检查回复中的CCS位,该位为1说明是sd2.0高容量sdhc卡,若为0。则说明为sd2.0标准容量卡。

9) 在识别出sd1.x、sd2.0标准卡或sd2.0高容量卡后,此时卡进入ready态。

进一步通过CMD2请求卡发送其CID(Card Identification),此时卡进入Identification态。

10) 卡在Identification态后,发送CMD3请求卡公布一个16位新的相对地址(RCA),以后主机与卡之间的点对点通信均会以这个RCA地址来进行,此时卡进入Stand-by态。

11) 至此,卡的初始化及识别过程结束,此时卡进入传输数据模式(data transfer mode)

S3C2416裸机开发系列十六_sd卡驱动实现

图2.1.1. sd卡初始化及识别流程

2.2. 传输数据模式

sd卡主控制器是一个很典型的状态机。每一个状态仅仅会响应该个状态下的特定命令,不要尝试在某个状态下发送这个状态不支持的命令,sd卡不会对该命令进行响应。命令仅仅会超时。应该通过特定的触发条件转变状态或等待状态迁移完毕后。再发送相应状态的命令。如图2.2.1,要想写一个块的数据到sd卡,在stand-by态的情况下,必须通过CMD7选择卡,让卡进入transfer态,然后再发送CMD24单块写命令,再发送一块的数据,此时卡进入Programming态,这时假设又紧接发送CMD24进行单块写将不会成功,必须等待sd卡编程完,从Programming态返回到transfer态才干再次接收下一个块写命令。相同,在transfer态想通过CMD9来获得Card-Specific
Data(CSD)。必须通过CMD7取消选择卡。此时卡进入stand-by态后,就可以通过CMD9来获得卡信息。

S3C2416裸机开发系列十六_sd卡驱动实现

图2.2.1. sd卡传输数据模式

2.3. 主机控制器详细对卡的初始化

不论什么cpu的sd卡主机控制器都能够依据sd2.0规范给出的卡初始化及识别流程进行卡的初始化,对于详细的cpu,须要进行一些与控制器相关的设置,主要有下面几点,详细的实现可參考Hsmmc_Init()这个初始化函数。

1) 设置功能引脚。把对应引脚配置成sd接口用引脚

2) 设置sd卡时钟在100k~400k,sd卡在识别阶段必须用慢速时钟进行訪问

3) 依照规范给出的卡初始化流程对卡进行发送对应的命令并处理回复。成功后卡进入stand-by态

4) 通过发送CMD7选择卡,使卡进入transfer态,由于卡的大部分操作如读、写、擦除等均是在这个状态下来进行的。此时卡已全然准备好接收读写命令了。

5) 设置sd卡的时钟到一个较高值。sd卡默认支持最高25M时钟,能够设置成快速模式。最高支持50M,频率越高,传输数据速率越快

6) 通过ACMD6(CMD55+CMD6)来设置sd模式的位宽为4,sd卡初始化后默认是1线宽,很多其它的数据线将有更大的带宽。传输数据速率最高12.5MB/s(25M、4线)或25MB/s(50M、4线)。

7) 发送CMD16设置块长度。对于标准卡,可通过CMD16来设置块命令(如块读、块写)所操作块的长度(以字节数计),可实现字节的读写。但对于高容量卡这个命令将被忽略。高容量卡一个块的长度均是固定512字节的。

通常通过CMD16设置块长度为512字节。至此卡初始化完毕。

2.4. 主机命令的发送

sd规范对命令包格式、回复包、数据的传输方式等均作了详细的要求。尽管sd卡主机控制器能够帮我们对命令进行打包,对回复进行解包,产生CRC,并在sd总线上输出对应的时序。我们仍须要告诉sd卡主机控制器需发送的命令、这个命令的參数、这个命令发送后是否须要使用data线, sd卡的回复类型。详细到s3c2416的sd卡主机控制器。这些设置通过CMDREG寄存器来实现。

主要有下面几点。详细的实现可參考Hsmmc_IssueCommand()这个命令发送函数。

1) 命令发送时,需检查命令线是否已被使用,若是,则等待正在发送的命令发送完才干发送这个命令

2) 假设命令回复会带忙信号(如R1b回复),则需检查数据线是否已被使用,若是,则等待数据线空暇。带忙回复命令发送后。sd卡会拉低DAT[0]线表明sd卡正忙,数据线不可用。

3) 把命令參数写入ARGUMENT这个寄存器中

4) 在CMDREG中设置命令值[13:8]

5) 设置是否需使用data线,如块读、块写等命令发送后,会紧接着在data线上数据传输。其他不需数据传输的命令不要设置使用data线CMDREG[5]

6) 设置sd卡的回复类型,绝大部分命令在sd卡正确响应后,都会对主机进行回复(R1-R7。R1b)。每一个命令相应的回复类型请參考sd卡规范。

回复类型长度可能为136或48,回复中是否包括CRC或命令值的反馈,假设包括,则告诉主控制器检查回复中相应的CRC或命令值反馈是否正确,以确定传输正确。CMDREG设置好后。主控制器就会发送命令并接收设定长度的回复并依据设定检查CRC、命令值反馈是否正确(若回复中包括CRC或命令值反馈的话)

7) 等待命令完毕。检查中断状态位NORINTSTS[15]以确定命令是否有错误,若没有错误而且检測到NORINTSTS[0]命令完毕位为1,则说明命令发送成功。其他情况说明命令未能成功发送。

2.5. 主机对sd卡的读写

通常对于一个sd卡驱动模块,至少实现卡初始化、块读、块写这三个接口函数。

块读、块写必须在卡初始化完毕后,在transfer态下才有效。通常有下面几点须要注意。详细可參考Hsmmc_ReadBlock()和Hsmmc_WriteBlock()这两个函数的实现。

1) 通过发送CMD13获得眼下卡的状态,块读、块写时必须在transfer态,不然需等待状态转换的完毕

2) 设置特定主机控制器的传输方式。比如s3c2416可支持DMA数据传输,通过FIFO数据传输。笔者採用DMA数据传输,则需设置DMA传输内存的首地址,传输的块数等寄存器,在开了cache的情况下,DMA传输时必须注意cache与主存数据一致性的问题。

3) 注意块写的地址。对于标准卡(小于2GB),其块地址应为字节地址(最大寻址4GB)。但对于高容量SDHC卡,其块地址为512字节为单位。

4) 推断读写的块数,对于读写1块,则需发送命令CMD17(单块读)或CMD24(单块写),若多块读写则应发送命令CMD18(多块读)或CMD25(多块写)。通常上层的应用不应把数据块拆分成多个单块进行读写,这样总的读写性能会非常差,应该一次性进行多个数据块的读写。

5) 等待读写传输数据完毕,检查中断状态位NORINTSTS[15]以确定传输是否有错误。若没有错误而且检測到NORINTSTS[1]传输完毕位为1,则说明传输数据成功。

其他情况说明传输数据出错。

2.6. 驱动模块的其他接口

一个完好的sd卡驱动模块还应提供一些与文件系统相关的接口实现,如从卡CSD中获取容量等信息函数Hsmmc_Get_CSD()。块擦除函数Hsmmc_EraseBlock()等。

具体的实现能够參考以下完整的驱动源代码。为调试sd卡驱动。需用串口进行打印跟踪sd卡的状态及相关的寄存器值。串口打印调试的驱动在前面章节有具体的介绍。

Sd卡驱动模块实现Hsmmc.c例如以下:

#include "s3c2416.h"

#include "Hsmmc.h"

#include "UART0.h"

#define DEBUG_HSMMC

#ifdef  DEBUG_HSMMC

#define Debug(x...) Uart0_Printf(x)

#else

#define Debug(x...)

#endif

static unsigned char CardType; // 卡类型

static unsigned int RCA; // 卡相对地址

static unsigned char Hsmmc_Buffer[16*1024]

__attribute__((__aligned__(4),section(".no_cache")));

static void Delay_us(unsigned int nCount)

{

//延时1us,共延时nCount us

__asm__ __volatile__ (

"000:\n"

"ldr  r1, =100\n"  // Arm clock为400M

"111:\n"

"subs r1, r1,#1\n"  // 一个Arm clock

"bne  111b\n"      // 跳转会清流水线,3个Arm clock

"subs %0, %1,#1\n" // 调用者确保nCount不为0

"bne  000b\n"

:"=r"(nCount) // nCount寄存器的值会自减改变

:"0"(nCount) // 使用与输出对象同样的寄存器

: "r1"// 暂时使用了r1寄存器

);

}

static void Hsmmc_ClockOn(unsigned char On)

{

if (On) {

rHM1_CLKCON |=(1<<2); // sd时钟使能

while(!(rHM1_CLKCON & (1<<3))) {

// 等待SD输出时钟稳定

}

} else {

rHM1_CLKCON &=~(1<<2); // sd时钟禁止

}

}

static void Hsmmc_SetClock(unsigned int Div)

{

Hsmmc_ClockOn(0); // 关闭时钟

// 选择SCLK_HSMMC:EPLLout

rCLKSRC &=~(1<<17); // HSMMC1 EPLL(96M)

rHM1_CONTROL2 =0xc0000120; // SCLK_HSMMC

rHM1_CONTROL3 =(0<<31) | (0<<23) | (0<<15) | (0<<7);

// SDCLK频率值并使能内部时钟

rHM1_CLKCON &=~(0xff<<8);

rHM1_CLKCON |=(Div<<8) | (1<<0);

while (!(rHM1_CLKCON& (1<<1))) {

// 等待内部时钟振荡稳定

}

Hsmmc_ClockOn(1); // 全能时钟

}

static int Hsmmc_WaitForCommandDone()

{

unsigned int i;

int ErrorState;

// 等待命令发送完毕

for (i=0;i<20000000; i++) {

if (rHM1_NORINTSTS& (1<<15)) { // 出现错误

break;

}

if (rHM1_NORINTSTS& (1<<0)) {

do {

rHM1_NORINTSTS= (1<<0); // 清除命令完毕位

} while(rHM1_NORINTSTS & (1<<0));

return 0; // 命令发送成功

}

}

ErrorState =rHM1_ERRINTSTS & 0x1ff; // 可能通信错误,CRC检验错误,超时等

rHM1_NORINTSTS =rHM1_NORINTSTS; // 清除中断标志

rHM1_ERRINTSTS =rHM1_ERRINTSTS; // 清除错误中断标志

do {

rHM1_NORINTSTS =(1<<0); // 清除命令完毕位

} while(rHM1_NORINTSTS & (1<<0));

Debug("Commanderror, rHM1_ERRINTSTS = 0x%x ", ErrorState);

return ErrorState; // 命令发送出错

}

static int Hsmmc_WaitForTransferDone()

{

int ErrorState;

unsigned int i;

// 等待传输数据完毕

for (i=0;i<20000000; i++) {

if (rHM1_NORINTSTS& (1<<15)) { // 出现错误

break;

}

if (rHM1_NORINTSTS& (1<<1)) { // 传输数据完

do {

rHM1_NORINTSTS|= (1<<1); // 清除传输完毕位

} while(rHM1_NORINTSTS & (1<<1));

rHM1_NORINTSTS= (1<<3); // 清除DMA中断标志

return 0;

}

Delay_us(1);

}

ErrorState =rHM1_ERRINTSTS & 0x1ff; // 可能通信错误,CRC检验错误,超时等

rHM1_NORINTSTS =rHM1_NORINTSTS; // 清除中断标志

rHM1_ERRINTSTS =rHM1_ERRINTSTS; // 清除错误中断标志

Debug("Transfererror, rHM1_ERRINTSTS = 0x%04x\n\r", ErrorState);

do {

rHM1_NORINTSTS =(1<<1); // 出错后清除数据完毕位

} while(rHM1_NORINTSTS & (1<<1));

return ErrorState; // 传输数据出错

}

static int Hsmmc_IssueCommand(unsigned char Cmd, unsigned intArg, unsigned char Data, unsigned char Response)

{

unsigned int i;

unsigned int Value;

unsigned intErrorState;

// 检查CMD线是否准备好发送命令

for (i=0;i<1000000; i++) {

if (!(rHM1_PRNSTS& (1<<0))) {

break;

}

}

if (i == 1000000) {

Debug("CMDline time out, rHM1_PRNSTS: %04x\n\r", rHM1_PRNSTS);

return -1; // 命令超时

}

// 检查DAT线是否准备好

if (Response ==Response_R1b) { // R1b回复通过DAT0反馈忙信号

for (i=0;i<1000000; i++) {

if(!(rHM1_PRNSTS & (1<<1))) {

break;

}

}

if (i == 1000000){

Debug("Dataline time out, rHM1_PRNSTS: %04x\n\r", rHM1_PRNSTS);

return -2;

}

}

rHM1_ARGUMENT = Arg;// 写入命令參数

Value = (Cmd <<8); // command index

// CMD12可终止传输

if (Cmd == 0x12) {

Value |= (0x3<< 6); // command type

}

if (Data) {

Value |= (1<< 5); // 需使用DAT线作为传输等

}

switch (Response) {

case Response_NONE:

Value |=(0<<4) | (0<<3) | 0x0; // 没有回复,不检查命令及CRC

break;

case Response_R1:

case Response_R5:

case Response_R6:

case Response_R7:

Value |=(1<<4) | (1<<3) | 0x2; // 检查回复中的命令,CRC

break;

case Response_R2:

Value |=(0<<4) | (1<<3) | 0x1; // 回复长度为136位,包括CRC

break;

case Response_R3:

case Response_R4:

Value |=(0<<4) | (0<<3) | 0x2; // 回复长度48位,不包括命令及CRC

break;

case Response_R1b:

Value |=(1<<4) | (1<<3) | 0x3; // 回复带忙信号,会占用Data[0]线

break;

default:

break;

}

rHM1_CMDREG = Value;

ErrorState =Hsmmc_WaitForCommandDone();

if (ErrorState) {

Debug("Command= %d\r\n", Cmd);

}

return ErrorState; // 命令发送出错

}

// 512位的sd卡扩展状态位

int Hsmmc_GetSdState(unsigned char *pState)

{

int ErrorState;

unsigned int i;

if (CardType == SD_HC|| CardType == SD_V2 || CardType == SD_V1) {

if(Hsmmc_GetCardState() != 4) { // 必需在transfer status

return -1; // 卡状态错误

}

Hsmmc_IssueCommand(CMD55,RCA<<16, 0, Response_R1);

rHM1_SYSAD =(unsigned int)Hsmmc_Buffer; // 缓存地址

rHM1_BLKSIZE =(7<<12) | (64<<0); // 最大DMA缓存大小,block为512位64字节

rHM1_BLKCNT = 1;// 写入这次读1block的sd状态数据

rHM1_ARGUMENT = 0;// 写入命令參数

// DMA传输使能,读单块

rHM1_TRNMOD =(0<<5) | (1<<4) | (0<<2) | (1<<1) | (1<<0);

// 设置命令寄存器,读状态命令CMD13,R1回复

rHM1_CMDREG =(CMD13<<8)|(1<<5)|(1<<4)|(1<<3)|0x2;

ErrorState =Hsmmc_WaitForCommandDone();

if (ErrorState) {

Debug("CMD13error\r\n");

returnErrorState;

}

ErrorState =Hsmmc_WaitForTransferDone();

if (ErrorState) {

Debug("Getsd status error\r\n");

returnErrorState;

}

for (i=0; i<64;i++) {

*pState++ =Hsmmc_Buffer[i];

}

return 0;

}

return -1; // 非sd卡

}

int Hsmmc_Get_CSD(unsigned char *pCSD)

{

unsigned int i;

unsigned intResponse[4];

int State = 1;

if (CardType != SD_HC&& CardType != SD_V1 && CardType != SD_V2) {

return State; // 未识别的卡

}

// 取消卡选择,不论什么卡均不回复,已选择的卡通过RCA=0取消选择,

// 卡回到stand-by状态

Hsmmc_IssueCommand(CMD7,0, 0, Response_NONE);

for (i=0; i<1000;i++) {

if(Hsmmc_GetCardState() == 3) { // CMD9命令需在standy-by status

Debug("GetCSD: Enter to the Stand-by State\n\r");

break; // 状态正确

}

Delay_us(100);

}

if (i == 1000) {

return State; // 状态错误

}

// 请求已标记卡发送卡特定数据(CSD),获得卡信息

if(!Hsmmc_IssueCommand(CMD9, RCA<<16, 0, Response_R2)) {

pCSD++; // 路过第一字节,CSD中[127:8]位对位寄存器中的[119:0]

Response[0] =rHM1_RSPREG0;

Response[1] =rHM1_RSPREG1;

Response[2] =rHM1_RSPREG2;

Response[3] =rHM1_RSPREG3;

Debug("CSD:");

for (i=0; i<15;i++) { // 拷贝回复寄存器中的[119:0]到pCSD中

*pCSD++ =((unsigned char *)Response)[i];

Debug("%02x",*(pCSD-1));

}

State = 0; // CSD获取成功

}

Hsmmc_IssueCommand(CMD7,RCA<<16, 0, Response_R1); // 选择卡,卡回到transfer状态

return State;

}

// R1回复中包括了32位的card state,卡识别后,可在任一状态通过CMD13获得卡状态

int Hsmmc_GetCardState(void)

{

if(Hsmmc_IssueCommand(CMD13, RCA<<16, 0, Response_R1)) {

return -1; // 卡出错

} else {

return((rHM1_RSPREG0>>9) & 0xf); // 返回R1回复中的[12:9]卡状态

}

}

static int Hsmmc_SetBusWidth(unsigned char Width)

{

int State;

if ((Width != 1) ||(Width != 4)) {

return -1;

}

State = -1; // 设置初始为未成功

rHM1_NORINTSTSEN&= ~(1<<8); // 关闭卡中断

Hsmmc_IssueCommand(CMD55,RCA<<16, 0, Response_R1);

if (Width == 1) {

if(!Hsmmc_IssueCommand(CMD6, 0, 0, Response_R1)) { // 1位宽

rHM_HOSTCTL&= ~(1<<1);

State = 0; // 命令成功

}

} else {

if(!Hsmmc_IssueCommand(CMD6, 2, 0, Response_R1)) { // 4位宽

rHM_HOSTCTL |=(1<<1);

State = 0; // 命令成功

}

}

rHM1_NORINTSTSEN |=(1<<8); // 打开卡中断

return State; // 返回0为成功

}

int Hsmmc_EraseBlock(unsigned int StartBlock, unsigned intEndBlock)

{

unsigned int i;

if (CardType == SD_V1|| CardType == SD_V2) {

StartBlock<<= 9; // 标准卡为字节地址

EndBlock <<=9;

} else if (CardType !=SD_HC) {

return -1; // 未识别的卡

}

Hsmmc_IssueCommand(CMD32,StartBlock, 0, Response_R1);

Hsmmc_IssueCommand(CMD33,EndBlock, 0, Response_R1);

if(!Hsmmc_IssueCommand(CMD38, 0, 0, Response_R1b)) {

for (i=0;i<10000; i++) {

if (Hsmmc_GetCardState()== 4) { // 擦除完毕后返回到transfer状态

Debug("erasingcomplete!\n\r");

return 0;// 擦除成功

}

Delay_us(1000);

}

}

Debug("Eraseblock failed\n\r");

return 1; // 擦除失败

}

int Hsmmc_ReadBlock(unsigned char *pBuffer, unsigned intBlockAddr, unsigned int BlockNumber)

{

unsigned int Address =0;

unsigned intReadBlock;

unsigned int i;

int ErrorState;

if (pBuffer == 0 ||BlockNumber == 0) {

return -1;

}

// 均不中断使能,产生对应的中断信号

rHM1_NORINTSIGEN&= ~0xffff; // 清除全部中断使能

rHM1_NORINTSIGEN |=(1<<1); // 命令完毕中断使能

while (BlockNumber> 0) {

for (i=0;i<1000; i++) {

if(Hsmmc_GetCardState() == 4) { // 读写数据需在transfer status

break; // 状态正确

}

Delay_us(100);

}

if (i == 1000) {

return -2; // 状态错误

}

if (BlockNumber<= sizeof(Hsmmc_Buffer)/512) {

ReadBlock =BlockNumber; // 读取的块数小于缓存32 Block(16k)

BlockNumber =0; // 剩余读取块数为0

} else {

// 读取的块数大于32 Block,分多次读

ReadBlock =sizeof(Hsmmc_Buffer)/512;

BlockNumber -=ReadBlock;

}

// 依据sd主机控制器标准,按顺序写入主机控制器对应的寄存器

// 缓存地址,内存区域为关闭cache,作DMA传输

rHM1_SYSAD =(unsigned int)Hsmmc_Buffer;

rHM1_BLKSIZE =(7<<12) | (512<<0); // 最大DMA缓存大小,block为512字节

rHM1_BLKCNT =ReadBlock; // 写入这次读block数目

if (CardType ==SD_HC) {

Address =BlockAddr; // SDHC卡写入地址为block地址

} else if(CardType == SD_V1 || CardType == SD_V2) {

Address =BlockAddr << 9; // 标准卡写入地址为字节地址

}

BlockAddr +=ReadBlock; // 下一次读块的地址

rHM1_ARGUMENT =Address; // 写入命令參数

if (ReadBlock ==1) {

// 设置传输模式,DMA传输使能,读单块

rHM1_TRNMOD =(0<<5) | (1<<4) | (0<<2) | (1<<1) | (1<<0);

// 设置命令寄存器,单块读CMD17,R1回复

rHM1_CMDREG =(CMD17<<8)|(1<<5)|(1<<4)|(1<<3)|0x2;

} else {

// 设置传输模式,DMA传输使能,读多块

rHM1_TRNMOD =(1<<5) | (1<<4) | (1<<2) | (1<<1) | (1<<0);

// 设置命令寄存器,多块读CMD18,R1回复

rHM1_CMDREG =(CMD18<<8)|(1<<5)|(1<<4)|(1<<3)|0x2;

}

ErrorState =Hsmmc_WaitForCommandDone();

if (ErrorState) {

Debug("ReadCommand error\r\n");

returnErrorState;

}

ErrorState =Hsmmc_WaitForTransferDone();

if (ErrorState) {

Debug("Readblock error\r\n");

returnErrorState;

}

// 传输数据成功,拷贝DMA缓存的数据到指定内存

for (i=0;i<ReadBlock*512; i++) {

*pBuffer++ =Hsmmc_Buffer[i];

}

}

return 0; // 全部块读完

}

int Hsmmc_WriteBlock(unsigned char *pBuffer, unsigned intBlockAddr, unsigned int BlockNumber)

{

unsigned int Address =0;

unsigned intWriteBlock;

unsigned int i;

int ErrorState;

if (pBuffer == 0 ||BlockNumber == 0) {

return -1; // 參数错误

}

rHM1_NORINTSIGEN&= ~0xffff; // 清除全部中断使能

// 传输数据完毕中断使能

rHM1_NORINTSIGEN |=(1<<0);

while (BlockNumber> 0) {

for (i=0;i<1000; i++) {

if(Hsmmc_GetCardState() == 4) { // 读写数据需在transfer status

break; // 状态正确

}

Delay_us(100);

}

if (i == 1000) {

return -2; // 状态错误或Programming超时

}

if (BlockNumber<= sizeof(Hsmmc_Buffer)/512) {

WriteBlock =BlockNumber;// 写入的块数小于缓存32 Block(16k)

BlockNumber =0; // 剩余写入块数为0

} else {

// 写入的块数大于32 Block,分多次写

WriteBlock =sizeof(Hsmmc_Buffer)/512;

BlockNumber -=WriteBlock;

}

if (WriteBlock> 1) { // 多块写,发送ACMD23先设置预擦除块数

Hsmmc_IssueCommand(CMD55,RCA<<16, 0, Response_R1);

Hsmmc_IssueCommand(CMD23,WriteBlock, 0, Response_R1);

}

for (i=0;i<WriteBlock*512; i++) {

Hsmmc_Buffer[i]= *pBuffer++; // 待写数据从指定内存区复制到缓存区

}

// 依据sd主机控制器标准,按顺序写入主机控制器对应的寄存器

// 缓存地址,内存区域为关闭cache,作DMA传输

rHM1_SYSAD =(unsigned int)Hsmmc_Buffer;

rHM1_BLKSIZE =(7<<12) | (512<<0); // 最大DMA缓存大小,block为512字节

rHM1_BLKCNT =WriteBlock; // 写入block数目

if (CardType ==SD_HC) {

Address =BlockAddr; // SDHC卡写入地址为block地址

} else if(CardType == SD_V1 || CardType == SD_V2) {

Address =BlockAddr << 9; // 标准卡写入地址为字节地址

}

BlockAddr +=WriteBlock; // 下一次写地址

rHM1_ARGUMENT =Address; // 写入命令參数

if (WriteBlock ==1) {

// 设置传输模式,DMA传输写单块

rHM1_TRNMOD =(0<<5) | (0<<4) | (0<<2) | (1<<1) | (1<<0);

// 设置命令寄存器,单块写CMD24,R1回复

rHM1_CMDREG =(CMD24<<8)|(1<<5)|(1<<4)|(1<<3)|0x2;

} else {

// 设置传输模式,DMA传输写多块

rHM1_TRNMOD =(1<<5) | (0<<4) | (1<<2) | (1<<1) | (1<<0);

// 设置命令寄存器,多块写CMD25,R1回复

rHM1_CMDREG =(CMD25<<8)|(1<<5)|(1<<4)|(1<<3)|0x2;

}

ErrorState =Hsmmc_WaitForCommandDone();

if (ErrorState) {

Debug("WriteCommand error\r\n");

returnErrorState;

}

ErrorState =Hsmmc_WaitForTransferDone();

if (ErrorState) {

Debug("Writeblock error\r\n");

returnErrorState;

}

}

return 0; // 写全然部数据

}

int Hsmmc_Init()

{

unsigned int i;

unsigned int OCR;

// 设置HSMMC1的接口引脚配置

rGPLCON &=~((0xffff<<0) | (0xf<<16));

rGPLCON |=(0xaaaa<<0) | (0xa<<16);

rGPLUDP &=~((0xffff<<0) | (0xf<<16)); // 上下拉禁止

rHM1_SWRST = 0x7; // 复位HSMMC

Hsmmc_SetClock(0x80);// SDCLK=96M/256=375K

rHM1_TIMEOUTCON = (0xe<< 0); // 最大超时时间

rHM1_HOSTCTL &=~(1<<2); // 正常速度模式

rHM1_NORINTSTS =rHM1_NORINTSTS; // 清除中断状态标志

rHM1_ERRINTSTS =rHM1_ERRINTSTS; // 清除错误中断状态标志

rHM1_NORINTSTSEN =0x7fff; // [14:0]中断使能

rHM1_ERRINTSTSEN =0x3ff; // [9:0]错误中断使能

Hsmmc_IssueCommand(CMD0,0, 0, Response_NONE); // 复位全部卡到空暇状态

CardType =UnusableCard; // 卡类型初始化不可用

if (Hsmmc_IssueCommand(CMD8,0x1aa, 0, Response_R7)) { // 没回复,MMC/ v1.x/

for (i=0;i<1000; i++) {

Hsmmc_IssueCommand(CMD55,0, 0, Response_R1);

// CMD41有回复说明为sd卡

if(!Hsmmc_IssueCommand(CMD41, 0, 0, Response_R3)) {

OCR =rHM1_RSPREG0; // 获得回复的OCR(操作条件寄存器)值

if(OCR & 0x80000000) { // 卡上电是否完毕上电流程

CardType= SD_V1; // 正确识别出sd v1.x卡

Debug("SDcard version 1.x is detected\n\r");

break;

}

} else {

// MMC卡识别

}

Delay_us(100);

}

} else { // sd v2.0

// 推断卡是否支持2.7~3.3v电压

if(((rHM1_RSPREG0&0xff) == 0xaa) &&(((rHM1_RSPREG0>>8)&0xf) == 0x1)) {

OCR = 0;

for (i=0;i<1000; i++) {

Hsmmc_IssueCommand(CMD55,0, 0, Response_R1);

Hsmmc_IssueCommand(CMD41,OCR, 0, Response_R3); // reday态

OCR =rHM1_RSPREG0;

if (OCR& 0x80000000) { // 卡上电是否完毕上电流程,是否busy

if(OCR & (1<<30)) { // 推断卡为标准卡还是高容量卡

CardType= SD_HC; // 高容量卡

Debug("SDHCcard is detected\n\r");

} else{

CardType= SD_V2; // 标准卡

Debug("SDversion 2.0 standard card is detected\n\r");

}

break;

}

Delay_us(100);

}

}

}

if (CardType == SD_HC|| CardType == SD_V1 || CardType == SD_V2) {

// 请求卡发送CID(卡ID寄存器)号,进入ident

Hsmmc_IssueCommand(CMD2,0, 0, Response_R2);

// 请求卡公布新的RCA(卡相对地址),Stand-by状态

Hsmmc_IssueCommand(CMD3,0, 0, Response_R6);

RCA =(rHM1_RSPREG0 >> 16) & 0xffff; // 从卡回复中得到卡相对地址

// 选择已标记的卡,transfer状态

Hsmmc_IssueCommand(CMD7,RCA<<16, 0, Response_R1);

Debug("Enterto the transfer state\n\r");

Hsmmc_SetClock(0x2);// 设置SDCLK= 96M/4 = 24M

if(!Hsmmc_SetBusWidth(4)) {

Debug("Setbus width error\n\r");

return 1; // 位宽设置出错

}

if(Hsmmc_GetCardState() == 4) { // 此时卡应在transfer态

// 设置块长度为512字节

if(!Hsmmc_IssueCommand(CMD16, 512, 0, Response_R1)) {

rHM1_NORINTSTS= 0xffff; // 清除中断标志

Debug("CardInitialization succeed\n\r");

return 0;// 初始化成功

}

}

}

Debug("CardInitialization failed\n\r");

return 1; // 卡工作异常

}

Sd卡驱动模块头文件Hsmmc.h例如以下:

#ifndef __HSMMC_H__

#define __HSMMC_H__

#ifdef __cplusplus

extern "C" {

#endif

#define CMD0    0

#define CMD1    1

#define CMD2    2

#define CMD3    3

#define CMD6    6

#define CMD7    7

#define CMD8    8

#define CMD9    9

#define CMD13   13

#define CMD16   16

#define CMD17   17

#define CMD18   18

#define CMD23   23

#define CMD24   24

#define CMD25   25

#define CMD32   32

#define CMD33   33

#define CMD38   38

#define CMD41   41

#define CMD55   55

// 卡类型

#define     UnusableCard    0

#define     SD_V1       1

#define     SD_V2       2

#define     SD_HC       3

#define     MMC     4

#define Response_NONE       0

#define Response_R1     1

#define Response_R2     2

#define Response_R3     3

#define Response_R4     4

#define Response_R5     5

#define Response_R6     6

#define Response_R7     7

#define Response_R1b        8

int Hsmmc_Init(void);

int Hsmmc_GetCardState(void);

int Hsmmc_GetSdState(unsigned char *pState);

int Hsmmc_Get_CSD(unsigned char *pCSD);

int Hsmmc_EraseBlock(unsigned int StartBlock, unsigned int EndBlock);

int Hsmmc_WriteBlock(unsigned char *pBuffer,

unsigned int BlockAddr,unsigned int BlockNumber);

int Hsmmc_ReadBlock(unsigned char *pBuffer,

unsigned int BlockAddr, unsigned intBlockNumber);

#ifdef __cplusplus

}

#endif

#endif /*__HSMMC_H__*/

3. 附录

Hsmmc.rar,包括sd卡驱动模块实现Hsmmc.c/Hsmmc.h。

http://pan.baidu.com/s/1hqvaQgO

S3C2416裸机开发系列十六_sd卡驱动实现的更多相关文章

  1. S3C2416裸机开发系列19&lowbar;Fatfs播放录像wav音频文件

    S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩    1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...

  2. BizTalk开发系列&lpar;十六&rpar; XML命名空间

    BizTalk开发过程中如果有对XML进行开发操作,比如在自定义代码里操作XML消息或者在Mapping的时候使用Xpath对XML进行操 作.则有机会遇到XML命名空间的问题.常见的是使用Xpath ...

  3. arcgis api for js入门开发系列十六迁徙流动图

    最近公司有个arcgis api for js的项目,需要用到百度echarts迁徙图效果,而百度那个效果实现是结合百度地图的,怎么才能跟arcgis api结合呢,网上搜索,终于在github找到了 ...

  4. arcgis api 3&period;x for js 入门开发系列十六迁徙流动图

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  5. 【微信小程序开发•系列文章六】生命周期和路由

    这篇文章理论的知识比较多一些,都是个人观点,描述有失妥当的地方希望读者指出. [微信小程序开发•系列文章一]入门 [微信小程序开发•系列文章二]视图层 [微信小程序开发•系列文章三]数据层 [微信小程 ...

  6. C&num;微信公众号开发系列教程六(被动回复与上传下载多媒体文件)

    微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...

  7. 【Visual C&plus;&plus;】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/16384009 作者:毛星云 ...

  8. 【Qt编程】基于Qt的词典开发系列&lt&semi;十五&gt&semi;html特殊字符及正则表达式

    1.html特殊字符的显示 我们知道html语言和C语言一样也有一些特殊字符,它们是不能正常显示的,必须经过转义,在网上可以查到如何显示这些字符,如下图所示: 上图给了最常用的特殊字符的显示,下面我们 ...

  9. 学习ASP&period;NET Core Razor 编程系列十六——排序

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. Android线程处理

    对JAVA的线程相信大家都有一定的认识,本篇就让我们一起探讨一下Android中的线程问题,对于线程和进程的区别我就不再赘述,有兴趣的小童鞋可以百度一下,讲解的非常详细,相信大家经常可以听到关于线程的 ...

  2. js与php转换时间戳

    php时间:1368524732 js代码: function getLocalTime(nS) { return new Date(parseInt(nS) * 1000).toLocaleStri ...

  3. Greenplum:学习资料

    Greenplum技术浅析:http://www.cnblogs.com/end/archive/2012/08/17/2644290.html Greenplum 数据库架构分析:http://ww ...

  4. 使用codeblock实现JNI开发-2016&period;01&period;31

    使用交叉编译工具实现andorid平台下的jni开发,记录codeblock配置过程,方便后续参考. 1 工具版本信息 NDK r8b Code::Blocks 10.05 2 配置过程 使用code ...

  5. sql大小转换函数

    将字段值转换成大写 UPDATE t SET [name]=UPPER([name]) 将字段值转换成小写 UPDATE t SET [name]=LOWER([name])

  6. POJ 2240&Tab;Arbitrage Bellman&lowbar;ford 判读是否存在正环

    和POJ1860差不多,就是用bellmanford判读是否存在正环,注意的是同种货币之间也可以交换,就是说:A货币换A货币汇率是2的情况也是存在的. #include<stdio.h> ...

  7. 团队作业10——项目复审与事后分析(Beta阶段)

    一.Beta阶段项目复审 http://www.cnblogs.com/womenshuodedoudui/p/7001208.html 二.事后诸葛分析 http://www.cnblogs.com ...

  8. mysql常见的优化需要注意的点

    1.explain分析explian引用索引基数show indexes from table_name;主键索引具有最好的基数 测试时 不走缓存SELECT SQL_NO_CACHE id from ...

  9. 前端基础之初识HTML

    一.web服务的本质 import socket def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.b ...

  10. HTTP的缓存策略

    etag 与 if-match https://www.cnblogs.com/huangzhilong/p/4999207.html https://juejin.im/post/5c136bd16 ...