关于DJYOS下SD卡驱动开发详解
王建忠 2011/6/21
硬件平台:tq2440(CPU: s3c2440)
操作系统:DJYOS1.0.0
Tq2440开发板用的cpu是S3C2440,由于s3c2440自带SD卡控制器,故我们选择操作SD卡的模式是SD卡模式。对SPI模式的SD卡驱动,这里我就不说明了。等以后在djyos下有移植spi模式的SD卡驱动,我就再写一篇技术文章。
网上关于SD卡得文章非常多,在这里关于SD卡协议等等细节的,我就不介绍了。大家去网上搜索几篇看看,在这里,我就简单介绍一点以及主要还是讲DJYOS下的SD卡驱动。
注意:这文档附带的sd卡驱动,不支持sdv2.0以上的sd卡(即2G以上sd卡不支持)
下面是tq2440开发板上面的SD卡模块电路图。
SD卡的指令由6字节(Byte)组成,如下:
Byte1:0 1 x x x x x x(命令号,由指令标志定义,如CMD39为100111即16进制0x27,那么完整的CMD39第一字节为01100111,即0x27+0x40)
Byte2-5:Command Arguments,命令参数,有些命令没有参数
Byte6:前7位为CRC(CyclicRedundacy Check,循环冗余校验)校验位,最后一位为停止位0
SD卡命令共分为12类,分别为class0到class11,
不同的SDd卡,主控根据其功能,支持不同的命令集 如下:
Class0 :(卡的识别、初始化等基本命令集)
CMD0:复位SD 卡.
CMD1:读OCR寄存器.
CMD9:读CSD寄存器.
CMD10:读CID寄存器.
CMD12:停止读多块时的数据传输
CMD13:读 Card_Status 寄存器
Class2 (读卡命令集):
CMD16:设置块的长度
CMD17:读单块.
CMD18:读多块,直至主机发送CMD12为止 .
Class4(写卡命令集) :
CMD24:写单块.
CMD25:写多块.
CMD27:写CSD寄存器 .
Class5 (擦除卡命令集):
CMD32:设置擦除块的起始地址.
CMD33:设置擦除块的终止地址.
CMD38: 擦除所选择的块.
Class6(写保护命令集):
CMD28:设置写保护块的地址.
CMD29:擦除写保护块的地址.
CMD30: Ask the card for the status of thewrite protection bits
class7:卡的锁定,解锁功能命令集
class8:申请特定命令集。
class10 -11 :保留
Tq2440,自带的sd卡程序。只是从0地址开始。简单的测试,可以读写就行。整个代码比较乱,sd卡驱动接口没有规范好。我在这里,根据DJYOS的需求,将tq2440自带的代码进行修改,变成只支持SD卡驱动,不支持MMC卡驱动。并且提供可以供上层使用的SD卡读写接口。
图3-1,是SD卡初始化的流程图。
图3-1 SD卡初始化流程图
module_init_SD(),是SD卡模块初始化函数。本函数,在djyos源码main.c文件djy_main()函数中调用。只要被调用了,这个时候上层就可以调用SD卡的读写函数了。
module_init_SD的源码,在3.1.1节的流程图介绍的已经比较清楚了。下面的代码里,也带有详细的注释。就不做很多解释了。下面代码中,有些带有MMC的代码,是因为MMC和SD卡类似,为了以后兼容MMC卡。
int module_init_SD(void) {
int i;
RCA=0; MMC=0; //GPIO初始化 rGPEUP = 0xf83f; // SDCMD, SDDAT[3:0] => PU En. rGPECON = 0xaaaaaaaa; //SDCMD, SDDAT[3:0] //设置频率为400KHz rSDIPRE=cn_pclk/(INICLK)-1; rSDICON=(1<<4)|1; // Type B, clk enable rSDIFSTA=rSDIFSTA|(1<<16); //YH 040223 FIFO reset rSDIBSIZE=0x200; // 512byte(128word) rSDIDTIMER=0x7fffff; // Set timeout count // Wait 74SDCLK for MMC card for(i=0;i<0x1000;i++); //发送命令0,使sd卡进入idle状态 cmd0();
//检查是否是MMC卡,并且设置目标工作电压 //设置MMC,为了以后兼容MMC if(chk_MMC_OCR()) { MMC=1; goto RECMD2; } //检查是否是SD卡,并且设置目标工作电压 if(!chk_SD_OCR()) return 0; RECMD2: rSDICARG=0x0; // CMD2(stuff bit) rSDICCON=(0x1<<10)|(0x1<<9)|(0x1<<8)|0x42; //lng_resp, wait_resp, start, CMD2 //发送CMD2,获取SD卡身份信息,并且使卡进入identification模式 if(!chk_cmd_end(2, 1)) goto RECMD2; rSDICSTA=0xa00; // Clear cmd_end(with rsp) RECMD3: //--Send RCA rSDICARG=MMC<<16; // CMD3(MMC:Set RCA, SD:Ask RCA-->SBZ) rSDICCON=(0x1<<9)|(0x1<<8)|0x43; // sht_resp, wait_resp, start, CMD3 //发送CMD3,SD卡获取RCA,进入stand_by状态 if(!chk_cmd_end(3, 1)) goto RECMD3;
rSDICSTA=0xa00; // Clear cmd_end(with rsp) RCA=( rSDIRSP0 & 0xffff0000 )>>16; rSDIPRE=cn_pclk/(SDCLK)-1; // Normal clock=25MHz //--State(stand-by) check if( rSDIRSP0 & 0x1e00!=0x600 ) // CURRENT_STATE check goto RECMD3; card_sel_desel(1); // Select set_4bit_bus(); return 1; } |
SD卡接口,就是指SD卡得读写函数。在这里,我们将使用读写一扇区,作为对应用层的接口,所以读写SD卡,都是一次性一个扇区的读写。没有精确到某个地址读写。
Tq2440下的SD卡,是直接由CPU控制的。CPU,拥有自己的SD卡控制器。对SD卡读写接口,在这里涉及到一个比较重要的寄存器,对其配置比较多。就是SDI数据控制寄存器(rSDIDCON)。
SD卡读写,基本上都是在该寄存器进行配置,所以配置这个寄存器的时候要特别注意。下面是该寄存器每比特位代表的含义。想看其他更多有关于SD卡控制器的寄存器,请看S3C2440A的芯片资料。
保留 [31:25] Burst4 enable [24] 在DMA模式下使能Burst4。仅当数据大小是字时该位被 (Burst4) 置位。0:无效 1:Burst4使能
Data Size [23:22] 指出用FIFO传输的大小,哪个类型,半字或字。00 = 字节 (DataSize) 传输,01 =半字传输 10 = 字传输, 11 = 保留
SDIO Interrupt [21] 决定SDIO的中断周期是 2个周期还是外部更多周期,当数据块 Period Type(PrdType) 最后被发送(对SDIO)。0=正好两个周期 1=更多周期(像单周期)
Transmit After [20] 决定数据传输在响应收到后开始或不开始。0= 在DatMode设置后直接 Response(TARSP) 1= 在响应收到后(假定设置DatMode设为 2b11)
Receive After [19] 决定数据传输在命令发出后开始或不开始 0= 在DatMode设置后直接 Command(RACMD) 1= 在命令发出后(假定设置DatMode设为2b10)
Busy After [18] 决定忙接收在命令发出后开始或不开始 0= 在DatMode设置后直接 Command(BACMD) 1= 在命令发出后(假定设置DatMode设为2b01)
Block mode [17] 数据传输模式 0=流数据传输 1=模块数据传输 (BlkMode)
Wide bus [16] 决定使能宽总线模式 0:标准总线模式(仅使用SDIDAT[0]) enable(WideBus) 1:宽总线模式(使用SDIDAT[3])
DMA Enable [15] 使能DMA(当DMA操作完成时,该位应该无效) (EnDMA) 0:无效(查询) 1:DMA使能
Data Transfer [14] 决定数据传输是否开始,该位自动清除。 Start(DTST) 0:数据准备好, 1:数据开始
Data Transfer [13:12] 决定数据传输的方向 00 =无操作, 01 = 仅忙检测模式 Mode (DatMode) 10 =数据接收模式,11 =数据发送模式
BlkNum [11:0] 模块数(0~4095),当流模式时不考虑 |
下面代码,是读一扇区的代码。其实整个代码的核心部分。是上面一开始就说的rSDIDCON寄存器的配置。现在我们就大概的讲一下读一个扇区的过程
1、 复位FIFO
2、 配置rSDIDCON寄存器
(2<<22)|(1<<19)|(1<<17)|(1<<16)|(1<<14)|(2<<12)|(1<<0)
分别代表:使用一字传输、数据传输在命令开始后、使用模块数据传输、使用宽总线模式(使用4个数据地址线引脚)、数据开始,该位自动清零、启动数据接收模式(用户读取SD卡的意思)、模块数为1。
3、 设置sector_addr,这里左移9位。代表的是扇区的意思,扇区是512字节大小(1左移9位,是512)。
4、 通过配置rSDICCON寄存器,开始读取数据。数据是一个字一个字的读。所以512字节,读取128次。
5、 通过chk_dat_end,检查数据是否结束
6、 一些清除操作,见代码
void read_one_sector(u32 sector_addr,unsigned int *buf) { unsigned int value; int status;
rd_cnt=0; rSDIFSTA=rSDIFSTA|(1<<16); // FIFO reset =(2<<22)|(1<<19)|(1<<17)|(1<<16)|(1<<14)|(2<<12)|(1<<0); //YH 040220 //rSDIDCON,[11:0] 我设置是1,所以这里block_addr,必须是地址,而不是扇区号 //移动9位,512字节。这样,block_addr就变成了"扇区号"了 rSDICARG= sector_addr <<9; // CMD17/18(addr)
//开始准备读取数据
rSDICCON=(0x1<<9)|(0x1<<8)|0x51; // sht_resp, wait_resp, dat, start, CMD17 if(!chk_cmd_end(17, 1)) //-- Check end of CMD17 return ; rSDICSTA=0xa00; // Clear cmd_end(with rsp) while(rd_cnt<128) // 128word=512byte { if((rSDIDSTA&0x20)==0x20) // Check timeout { rSDIDSTA=(0x1<<0x5); // Clear timeout flag break; } status=rSDIFSTA; if((status&0x1000)==0x1000) // Is Rx data? { value = rSDIDAT; byte_conversion(&value,buf); *buf++; rd_cnt++; } } //-- Check end of DATA if(!chk_dat_end()) return ;
rSDIDCON=rSDIDCON&~(7<<12); rSDIFSTA=rSDIFSTA&0x200; //Clear Rx FIFO Last data Ready rSDIDSTA=0x10; // Clear data Tx/Rx end detect
} |
下面代码,是写一扇区的代码。写一个扇区的大概过程如下:
1、 复位FIFO
2、 配置rSDIDCON寄存器
(2<<22)|(1<<20)|(1<<17)|(1<<16)|(1<<14)|(3<<12)|(1<<0)
分别代表:使用一字传输、数据传输在命令开始后、使用模块数据传输、使用宽总线模式(使用4个数据地址线引脚)、数据开始,该位自动清零、启动数据发送模式(用户写入SD卡的意思)、模块数为1。
3、 设置sector_addr,这里左移9位。代表的是扇区的意思,扇区是512字节大小(1左移9位,是512)。
4、 通过配置rSDICCON寄存器,开始写入数据。数据是一个字一个字的写入。所以512字节,写入128次。
5、 通过chk_dat_end,检查数据是否结束
6、 一些清除操作,见代码
void write_one_sector(u32 sector_addr,unsigned int *buf) { u32 value; int status;
buf=(u32*)buf;//由于FAT是是以u8传入。不知道上面是否强制转换了 wt_cnt=0; rSDIFSTA=rSDIFSTA|(1<<16); // FIFO reset
rSDIDCON=(2<<22)|(1<<20)|(1<<17)|(1<<16)|(1<<14)|(3<<12)|(1<<0); //YH 040220 //扇区号转为地址,移动9位,是512字节的意思 rSDICARG= sector_addr <<9; // CMD24/25(addr) rSDICCON=(0x1<<9)|(0x1<<8)|0x58; //sht_resp, wait_resp, dat, start, CMD24 if(!chk_cmd_end(24, 1)) //-- Check end of CMD24 return; rSDICSTA=0xa00; // Clear cmd_end(with rsp) while(wt_cnt<128) { status=rSDIFSTA; if((status&0x2000)==0x2000) { byte_conversion(buf,&value); rSDIDAT=value; *buf++; wt_cnt++; } } if(!chk_dat_end()) return; // Clear Data Transfer mode => no operation,Cleata //Data Transfer start rSDIDCON=rSDIDCON&~(7<<12); rSDIDSTA=0x10; // Clear data Tx/Rx end } |