天天看點

SD卡資料(轉)

   SD卡在現在的日常生活與工作中使用非常廣泛,時下已經成為最為通用的資料存儲卡。在諸如MP3、數位相機等裝置上也都采用SD卡作為其儲存設備。SD卡之是以得到如此廣泛的使用,是因為它價格低廉、存儲容量大、使用友善、通用性與安全性強等優點。既然它有着這麼多優點,那麼如果将它加入到單片機應用開發系統中來,将使系統變得更加出色。這就要求對SD卡的硬體與讀寫時序進行研究。對于SD卡的硬體結構,在官方的文檔上有很詳細的介紹,如SD卡内的存儲器結構、存儲單元組織方式等内容。要實作對它的讀寫,最核心的是它的時序,筆者在經過了實際的測試後,使用51單片機成功實作了對SD卡的扇區讀寫,并對其讀寫速度進行了評估。下面先來講解SD卡的讀寫時序。

(1)       SD卡的引腳定義:

          SD卡引腳功能詳述:

引腳

編号

SD模式         SPI模式
名稱 類型 描述 名稱 類型 描述
1 CD/DAT3 IO或PP

卡檢測/

資料線3

#CS I 片選
2 CMD PP

指令/

回應

DI I 資料輸入
3 VSS1 S 電源地 VSS S 電源地
4 VDD S 電源 VDD S 電源
5 CLK I 時鐘 SCLK I 時鐘
6 VSS2 S 電源地 VSS2 S 電源地
7 DAT0 IO或PP 資料線0 DO O或PP 資料輸出
8 DAT1 IO或PP 資料線1 RSV
9 DAT2 IO或PP 資料線2 RSV

       注:S:電源供給  I:輸入 O:采用推拉驅動的輸出

PP:采用推拉驅動的輸入輸出

          SD卡SPI模式下與單片機的連接配接圖:

    SD卡支援兩種總線方式:SD方式與SPI方式。其中SD方式采用6線制,使用CLK、CMD、DAT0~DAT3進行資料通信。而SPI方式采用4線制,使用CS、CLK、DataIn、DataOut進行資料通信。SD方式時的資料傳輸速度與SPI方式要快,采用單片機對SD卡進行讀寫時一般都采用SPI模式。采用不同的初始化方式可以使SD卡工作于SD方式或SPI方式。這裡隻對其SPI方式進行介紹。

(2)       SPI方式驅動SD卡的方法

     SD卡的SPI通信接口使其可以通過SPI通道進行資料讀寫。從應用的角度來看,采用SPI接口的好處在于,很多單片機内部自帶SPI控制器,不光給開發上帶來友善,同時也見降低了開發成本。然而,它也有不好的地方,如失去了SD卡的性能優勢,要解決這一問題,就要用SD方式,因為它提供更大的總線資料帶寬。SPI接口的選用是在上電初始時向其寫入第一個指令時進行的。以下介紹SD卡的驅動方法,隻實作簡單的扇區讀寫。

1)  指令與資料傳輸

1.       指令傳輸

SD卡自身有完備的指令系統,以實作各項操作。指令格式如下:

      指令的傳輸過程采用發送應答機制,過程如下:

          每一個指令都有自己指令應答格式。在SPI模式中定義了三種應答格式,如下表所示:

位元組 含義
1 7 開始位,始終為0
6 參數錯誤
5 位址錯誤
4 擦除序列錯誤
3 CRC錯誤
2 非法指令
1 擦除複位
閑置狀态
位元組 含義
1 7 開始位,始終為0
6 參數錯誤
5 位址錯誤
4 擦除序列錯誤
3 CRC錯誤
2 非法指令
1 擦除複位
閑置狀态
2 7 溢出,CSD覆寫
6 擦除參數
5 寫保護非法
4 卡ECC失敗
3 卡控制器錯誤
2 未知錯誤
1 寫保護擦除跳過,鎖/解鎖失敗
鎖卡
位元組 含義
1 7 開始位,始終為0
6 參數錯誤
5 位址錯誤
4 擦除序列錯誤
3 CRC錯誤
2 非法指令
1 擦除複位
閑置狀态
2~5 全部 操作條件寄存器,高位在前

          寫指令的例程:

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

  向SD卡中寫入指令,并傳回回應的第二個位元組

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

unsigned char Write_Command_SD(unsigned char *CMD)

{

   unsigned char tmp;

   unsigned char retry=0;

   unsigned char i;

   //禁止SD卡片選

   SPI_CS=1;

   //發送8個時鐘信号

   Write_Byte_SD(0xFF);

   //使能SD卡片選

   SPI_CS=0;

   //向SD卡發送6位元組指令

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

   {

      Write_Byte_SD(*CMD++);

   }

   //獲得16位的回應

   Read_Byte_SD(); //read the first byte,ignore it.

   do

   {  //讀取後8位

      tmp = Read_Byte_SD();

      retry++;

   }

   while((tmp==0xff)&&(retry<100));

   return(tmp);

}

2)  初始化

SD卡的初始化是非常重要的,隻有進行了正确的初始化,才能進行後面的各項操作。在初始化過程中,SPI的時鐘不能太快,否則會造初始化失敗。在初始化成功後,應盡量提高SPI的速率。在剛開始要先發送至少74個時鐘信号,這是必須的。在很多讀者的實驗中,很多是因為疏忽了這一點,而使初始化不成功。随後就是寫入兩個指令CMD0與CMD1,使SD卡進入SPI模式

           初始化時序圖:

           初始化例程:

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

    初始化SD卡到SPI模式

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

unsigned char SD_Init()

   unsigned char retry,temp;

   unsigned char i;

   unsigned char CMD[] = {0x40,0x00,0x00,0x00,0x00,0x95};

   SD_Port_Init(); //初始化驅動端口

   Init_Flag=1; //将初始化标志置1

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

   {

      Write_Byte_SD(0xff); //發送至少74個時鐘信号

   }

   //向SD卡發送CMD0

   retry=0;

   do

   { //為了能夠成功寫入CMD0,在這裡寫200次

     temp=Write_Command_SD(CMD);

     retry++;

     if(retry==200)

     { //超過200次

       return(INIT_CMD0_ERROR);//CMD0 Error!

     }

   }

   while(temp!=1);  //回應01h,停止寫入

   //發送CMD1到SD卡

   CMD[0] = 0x41; //CMD1

   CMD[5] = 0xFF;

   retry=0;

   do

   { //為了能成功寫入CMD1,寫100次

     temp=Write_Command_SD(CMD);

     retry++;

     if(retry==100)

     { //超過100次

       return(INIT_CMD1_ERROR);//CMD1 Error!

     }

   }

   while(temp!=0);//回應00h停止寫入

   Init_Flag=0; //初始化完畢,初始化标志清零

   SPI_CS=1;  //片選無效

   return(0); //初始化成功

}

3)  讀取CID

CID寄存器存儲了SD卡的辨別碼。每一個卡都有唯一的辨別碼。

CID寄存器長度為128位。它的寄存器結構如下:

名稱 資料寬度 CID劃分
生産辨別号 MID 8 [127:120]
OEM/應用辨別 OID 16 [119:104]
産品名稱 PNM 40 [103:64]
産品版本 PRV 8 [63:56]
産品序列号 PSN 32 [55:24]
保留 4 [23:20]
生産日期 MDT 12 [19:8]
CRC7校驗合 CRC 7 [7:1]
未使用,始終為1 1 [0:0]

它的讀取時序如下:

        與此時序相對應的程式如下:

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

    讀取SD卡的CID寄存器   16位元組   成功傳回0

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

unsigned char Read_CID_SD(unsigned char *Buffer)

{

   //讀取CID寄存器的指令

   unsigned char CMD[] = {0x4A,0x00,0x00,0x00,0x00,0xFF};

   unsigned char temp;

   temp=SD_Read_Block(CMD,Buffer,16); //read 16 bytes

   return(temp);

}

       4)讀取CSD

              CSD(Card-Specific Data)寄存器提供了讀寫SD卡的一些資訊。其中的一些單元可以由使用者重新程式設計。具體的CSD結構如下:

名稱 資料寬度 單元類型 CSD劃分
CSD結構 CSD_STRUCTURE 2 R [127:126]
保留 - 6 R [125:120]
資料讀取時間1 TAAC 8 R [119:112]
資料在CLK周期内讀取時間2(NSAC*100) NSAC 8 R [111:104]
最大資料傳輸率 TRAN_SPEED 8 R [103:96]
卡指令集合 CCC 12 R [95:84]
最大讀取資料塊長 READ_BL_LEN 4 R [83:80]
允許讀的部分塊 READ_BL_PARTIAL 1 R [79:79]
非線寫塊 WRITE_BLK_MISALIGN 1 R [78:78]
非線讀塊 READ_BLK_MISALIGN 1 R [77:77]
DSR條件 DSR_IMP 1 R [76:76]
保留 - 2 R [75:74]
裝置容量 C_SIZE 12 R [73:62]
最大讀取電流@VDD min VDD_R_CURR_MIN 3 R [61:59]
最大讀取電流@VDD max VDD_R_CURR_MAX 3 R [58:56]
最大寫電流@VDD min VDD_W_CURR_MIN 3 R [55:53]
最大寫電流@VDD max VDD_W_CURR_MAX 3 R [52:50]
裝置容量乘子 C_SIZE_MULT 3 R [49:47]
擦除單塊使能 ERASE_BLK_EN 1 R [46:46]
擦除扇區大小 SECTOR_SIZE 7 R [45:39]
寫保護群大小 WP_GRP_SIZE 7 R [38:32]
寫保護群使能 WP_GRP_ENABLE 1 R [31:31]
保留 - 2 R [30:29]
寫速度因子 R2W_FACTOR 3 R [28:26]
最大寫資料塊長度 WRITE_BL_LEN 4 R [25:22]
允許寫的部分部 WRITE_BL_PARTIAL 1 R [21:21]
保留 - 5 R [20:16]
檔案系統群 FILE_OFRMAT_GRP 1 R/W [15:15]
拷貝标志 COPY 1 R/W [14:14]
永久寫保護 PERM_WRITE_PROTECT 1 R/W [13:13]
暫時寫保護 TMP_WRITE_PROTECT 1 R/W [12:12]
檔案系統 FIL_FORMAT 2 R/W [11:10]
保留 - 2 R/W [9:8]
CRC CRC 7 R/W [7:1]
未用,始終為1 - 1 [0:0]

           讀取CSD 的時序:

           相應的程式例程如下:

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

    讀SD卡的CSD寄存器   共16位元組    傳回0說明讀取成功

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

unsigned char Read_CSD_SD(unsigned char *Buffer)

{  

   //讀取CSD寄存器的指令

   unsigned char CMD[] = {0x49,0x00,0x00,0x00,0x00,0xFF};

   unsigned char temp;

   temp=SD_Read_Block(CMD,Buffer,16); //read 16 bytes

   return(temp);

}

4)  讀取SD卡資訊

綜合上面對CID與CSD寄存器的讀取,可以知道很多關于SD卡的資訊,以下程式可以擷取這些資訊。如下:

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

//傳回

// SD卡的容量,機關為M

// sector count and multiplier MB are in

u08 == C_SIZE / (2^(9-C_SIZE_MULT))

// SD卡的名稱

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

void SD_get_volume_info()

{  

    unsigned char i;

    unsigned char c_temp[5];

    VOLUME_INFO_TYPE SD_volume_Info,*vinf;

    vinf=&SD_volume_Info; //Init the pointoer;

/讀取CSD寄存器

    Read_CSD_SD(sectorBuffer.dat);

//擷取總扇區數

vinf->sector_count = sectorBuffer.dat[6] & 0x03;

vinf->sector_count <<= 8;

vinf->sector_count += sectorBuffer.dat[7];

vinf->sector_count <<= 2;

vinf->sector_count += (sectorBuffer.dat[8] & 0xc0) >> 6;

// 擷取multiplier

vinf->sector_multiply = sectorBuffer.dat[9] & 0x03;

vinf->sector_multiply <<= 1;

vinf->sector_multiply += (sectorBuffer.dat[10] & 0x80) >> 7;

//擷取SD卡的容量

vinf->size_MB = vinf->sector_count >> (9-vinf->sector_multiply);

// get the name of the card

Read_CID_SD(sectorBuffer.dat);

vinf->name[0] = sectorBuffer.dat[3];

vinf->name[1] = sectorBuffer.dat[4];

vinf->name[2] = sectorBuffer.dat[5];

vinf->name[3] = sectorBuffer.dat[6];

vinf->name[4] = sectorBuffer.dat[7];

vinf->name[5] = 0x00; //end flag  

}

         以上程式将資訊裝載到一個結構體中,這個結構體的定義如下:

typedef struct SD_VOLUME_INFO

{ //SD/SD Card info

  unsigned int  size_MB;

  unsigned char sector_multiply;

  unsigned int  sector_count;

  unsigned char name[6];

} VOLUME_INFO_TYPE;

5)  扇區讀

扇區讀是對SD卡驅動的目的之一。SD卡的每一個扇區中有512個位元組,一次扇區讀操作将把某一個扇區内的512個位元組全部讀出。過程很簡單,先寫入指令,在得到相應的回應後,開始資料讀取。

扇區讀的時序:

             扇區讀的程式例程:

unsigned char SD_Read_Sector(unsigned long sector,unsigned char *buffer)

   unsigned char retry;

   //指令16

   unsigned char CMD[] = {0x51,0x00,0x00,0x00,0x00,0xFF};

   unsigned char temp;

   //位址變換   由邏輯塊位址轉為位元組位址

   sector = sector << 9; //sector = sector * 512

   CMD[1] = ((sector & 0xFF000000) >>24 );

   CMD[2] = ((sector & 0x00FF0000) >>16 );

   CMD[3] = ((sector & 0x0000FF00) >>8 );

   //将指令16寫入SD卡

   retry=0;

   do

   {  //為了保證寫入指令  一共寫100次

      temp=Write_Command_MMC(CMD);

      retry++;

      if(retry==100)

      {

        return(READ_BLOCK_ERROR); //block write Error!

      }

   }

   while(temp!=0);

   //Read Start Byte form MMC/SD-Card (FEh/Start Byte)

   //Now data is ready,you can read it out.

   while (Read_Byte_MMC() != 0xfe);

   readPos=0;

  SD_get_data(512,buffer) ;  //512位元組被讀出到buffer中

 return 0;

}

其中SD_get_data函數如下:

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

    擷取資料到buffer中

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

void SD_get_data(unsigned int Bytes,unsigned char *buffer)

{

   unsigned int j;

   for (j=0;j<Bytes;j++)

      *buffer++ = Read_Byte_SD();

}

6)  扇區寫

扇區寫是SD卡驅動的另一目的。每次扇區寫操作将向SD卡的某個扇區中寫入512個位元組。過程與扇區讀相似,隻是資料的方向相反與寫入指令不同而已。

    扇區寫的時序:

扇區寫的程式例程:

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

    寫512個位元組到SD卡的某一個扇區中去   傳回0說明寫入成功

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

unsigned char SD_write_sector(unsigned long addr,unsigned char *Buffer)

   unsigned char tmp,retry;

   unsigned int i;

   //, 指令24

   unsigned char CMD[] = {0x58,0x00,0x00,0x00,0x00,0xFF};

   addr = addr << 9; //addr = addr * 512

   CMD[1] = ((addr & 0xFF000000) >>24 );

   CMD[2] = ((addr & 0x00FF0000) >>16 );

   CMD[3] = ((addr & 0x0000FF00) >>8 );

   //寫指令24到SD卡中去

   retry=0;

   do

   {  //為了可靠寫入,寫100次

      tmp=Write_Command_SD(CMD);

      retry++;

      if(retry==100)

      {

        return(tmp); //send commamd Error!

      }

   }

   while(tmp!=0);

   //在寫之前先産生100個時鐘信号

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

   {

      Read_Byte_SD();

   }

   //寫入開始位元組

   Write_Byte_MMC(0xFE);  

   //現在可以寫入512個位元組

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

   {

      Write_Byte_MMC(*Buffer++);

   }

   //CRC-Byte

   Write_Byte_MMC(0xFF); //Dummy CRC

   Write_Byte_MMC(0xFF); //CRC Code

   tmp=Read_Byte_MMC();   // read response

   if((tmp & 0x1F)!=0x05) // 寫入的512個位元組是未被接受

   {

     SPI_CS=1;

     return(WRITE_BLOCK_ERROR); //Error!

   }

   //等到SD卡不忙為止

//因為資料被接受後,SD卡在向儲存陣列中程式設計資料

   while (Read_Byte_MMC()!=0xff){};

   //禁止SD卡

   SPI_CS=1;

   return(0);//寫入成功

}

    此上内容在筆者的實驗中都已調試通過。單片機采用STC89LE單片機(SD卡的初始化電壓為2.0V~3.6V,操作電壓為3.1V~3.5V,是以不能用5V單片機,或進行分壓處理),工作于22.1184M的時鐘下,由于所采用的單片機中沒硬體SPI,采用軟體模拟SPI,是以讀寫速率都較慢。如果要半SD卡應用于音頻、視訊等要求高速場合,則需要選用有硬體SPI的控制器,或使用SD模式,當然這就需要各位讀者對SD模式加以研究,有了SPI模式的基礎,SD模式應該不是什麼難事。