SD卡初始化以及命令詳解


SD卡是嵌入式設備中很常用的一種存儲設備,體積小,容量大,通訊簡單,電路簡單所以受到很多設備廠商的歡迎,主要用來記錄設備運行過程中的各種信息,以及程序的各種配置信息,很是方便,有這樣幾點是需要知道的

SD 卡是基於 flash 的存儲卡。

SD 卡和 MMC 卡的區別在於初始化過程不同。SD卡並不是我們通常意義上的手機擴展卡,那種卡叫做TF,但是通訊以及驅動模式是類似的.

SD 卡的通信協議包括 SD SPI 兩類,SD卡上電之后默認處於SD狀態。

SD 卡使用卡內智能控制模塊進行 FLASH 操作控制,包括協議、安全算法、

數據存取、ECC 算法、缺陷處理和分析、電源管理、時鍾管理。這些都不需要用戶關系,這是SD卡廠商做的事情

驅動SD卡主要要實現讀扇區,寫扇區,初始化,獲取SD卡相關配置信息這幾個就可以了,

另外.SD卡本身只是一種數據介質,它不含有文件系統,文件系統是一種文件的組織格式,是獨立於存儲介質的一種規范


標准SD卡引腳序列


SD卡引腳功能表


TF卡引腳排序


TF卡引腳功能表

由此可見,TF卡比SD卡少了一個VSS引腳,也就是少了一個供電引腳

另外電路設計時若SD卡使用SPI模式,那么不用的幾根數據線應加上上拉電阻,否者會因為這幾根數據線的電流震盪引起電流損耗,造成電路上的不穩定

SD卡電路SPI驅動模式

 

SD卡內部有五個我們可以讀取的寄存器,分別如下

 

要讀取這些信息就需要與卡通訊,SD通訊是用命令+數據的形式進行的,命令格式如下


也就是說,一次SD卡命令發送一共要發送6個字節,對於SPI通訊而言,就是SPI總線上傳送六個字節

字節 1 的最高 2 位固定為 01,低 6 位為命令號(比如 CMD16

10000 16 進制的 0X10,完整的 CMD16,第一個字節為 01010000,即 0X10+0X40)。

字節 2~5 為命令參數,有些命令是沒有參數的。對於沒有參數的命令默認發送0即可

字節 6 的高七位為 CRC 值,最低位恆定為 1,crc計算遵循以下規律


GX為生成多項式,具體計算方法請查看CRC計算相關,不過有一點好處就是,SPI驅動模式下,不需要CRC校驗(默認SD卡在SPI模式下不開啟CRC校驗,SD模式下默認開始CRC校驗),所以我們只需要對CMD0進行CRC就可以了,后面的CRC都可以不管(因為在CMD0之前是SD模式,所以第一個命令需要,切換之后就不用了),CMD0CRC0x95(加上了之后的一位1)

:SPI模式下打開crc校驗需要用到CMD59的保留命令,請查閱相關資料

SD卡的命令表如下所示(以下僅寫出SPI模式的CMD)


CMD0 復位SD, 重置所有卡到 Idle狀態,參數為0

CMD1 設置SD卡到ACTIVATE模式,也就是推出IDLE模式


CMD8 發送接口狀態命令

CMD9 讀取CSD寄存器

CMD10 讀取CID寄存器


CMD12 在多塊讀取的時候請求停止讀取

CMD13讀取SD卡狀態寄存器


CMD16 設置單個扇區的大小一般都設置為512字節一個扇區

CMD17 讀取扇區命令

CMD18 讀取多個扇區知道發送停止命令


CMD24 寫扇區命令

CMD25 寫多個扇區命令


CMD27 編輯CSD

CMD28設置地址組保護位。寫保護由卡配置數據的WP_GRP_SIZE 指定

CMD29清除保護位


CMD30 要求卡發送寫保護狀態,參數中有要查詢的地址


CMD32 設置要擦除的第一個寫數據塊地址

CMD33 設置要擦除的最后一個寫數據塊地址


CMD38 擦除所有選中的塊


CMD42 設置SD卡的解鎖或者上鎖

CMD55 告訴SD卡下一個命令式卡應用命令,不是標准命令


CMD56 應用相關的數據塊讀寫命令


CMD58 讀取OCR信息

CMD59 設置crc校驗的使能與關閉(前面說到過)


ACMD13 發送SD卡狀態


ACMD18保留作為 SD 安全應用(也就是這命令沒用)


ACMD22發送寫數據塊的數目。響應為 32 +CRC

ACMD23設置寫前預擦除的數據塊數目(用來加速多數據塊寫操作)1=默認(一個塊)(1)

不管是否使用 ACMD23,在多數據塊寫操作中都需要 STOP_TRAN(CMD12)命令


ACMD25 26 38 保留作為安全應用


ACMD41要求訪問的卡發送它的操作條件寄存器(OCR)內容

ACMD42連接[1]/斷開[0]卡上CD/DAT3(pin 1] 50K 歐姆上拉電阻。上拉電阻可用來檢測卡

ACMD43-49保留作為安全應用

ACMD51讀取 SD 配置寄存器 SCR

 

ACMD命令,全稱應該是application CMD,所以使用ACMD都需要在發送CMD55之后

發出命令后會收到相應的響應, 所有響應通過 CMD 線傳輸,響應以 MSB 開始,不同類型的響應長度根據類型不同而不同。

響應以起始位開始(通常為“0),接着這是傳輸方向的位(卡為 0)。除了 R3 外其他

響應都有 CRC。每個響應都以結束位(通常為“1)結束。,SD卡響應格式有多種

1.       R1響應



2.       R1b響應


多了一個忙數據

3.       R2響應


4.       R3響應(針對於read ocr的響應 CMD58)


5.       響應R4R5都是正對於SD mode的響應

6.       針對CMD8命令的響應R7

 

 

SD卡的初始化以及識別過程(為了方便起見,我們只檢測響應的R1狀態)

1.  初始化與 SD 卡連接的硬件條件(MCU SPI 配置,IO 口配置);

2.  上電延時(>74 CLK(為了讓卡正常啟動)

3.  復位卡(CMD0),進入 IDLE 狀態,檢測R1的最低位,是否為閑置狀態

4.  發送 CMD8,檢查是否支持 2.0 協議,因為這個命令是在2.0的協議里面才添加的

5.  根據不同協議檢查 SD 卡(命令包括:CMD55CMD41CMD58 CMD1 等);

6.  取消片選,發多 8 CLK,結束初始化

具體請查看下圖


以下是網絡上找到的一份經我修改之后的SD卡驅動,不完全符合SD卡標准驅動,但是我用着一直還蠻正常,大家有興趣可以看看改改

 

Spisd.c

#include "spisd.h"

//預定義SD卡類型
u8  SD_Type=0;//SD卡的類型 

//這部分應根據具體的連線來修改!
#define	SD_CS  PAout(4) //SD卡片選引腳

//data:要寫入的數據
//返回值:讀到的數據
static u8 SdSpiReadWriteByte(u8 data)
{
    return Spi1ReadWriteByte(data);
}

//SD卡初始化的時候,需要低速
static void SdSpiSpeedLow(void)
{
    Spi1SetSpeed(SPI_SPEED_256);//設置到低速模式  用於初始化,最高spi速度為400k	
}

//SD卡正常工作的時候,可以高速了
static void SdSpiSpeedHigh(void)
{
    Spi1SetSpeed(SPI_SPEED_4);//設置到高速模式	初始化完成之后進行,最高可到25M,不過一般不用
}


static void SdIOInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA, ENABLE );	
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽輸出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化A4
	
	SD_CS = 1;
	Spi1Init();//初始化SPI接口
	SdSpiSpeedLow();//初始化設置為低速
}

//等待卡准備好
//返回值:0,准備好了;其他,錯誤代碼
static u8 SdWaitReady(void)
{
    u32 t=0;
    do
    {
        if(SdSpiReadWriteByte(0XFF)==0XFF)return 0;//OK
        t++;		  	
    }while(t<0XFFFFFF);//等待 
    return 1;
}

//取消選擇,釋放SPI總線
void SD_DisSelect(void)
{
    SD_CS=1;
    SdSpiReadWriteByte(0xff);//提供額外的8個時鍾
}

//選擇sd卡,並且等待卡准備OK
//返回值:0,成功;1,失敗;
u8 SdSelect(void)
{
    SD_CS=0;
    if(SdWaitReady()==0)return 0;//等待成功
    SD_DisSelect();
    return 1;//等待失敗
}

//等待SD卡回應
//Response:要得到的回應值
//返回值:0,成功得到了該回應值
//    其他,得到回應值失敗    期待得到的回應值
u8 SdGetResponse(u8 Response)
{
    u16 Count=0xFFF;//等待次數	   						  
    while ((SdSpiReadWriteByte(0XFF)!=Response)&&Count)Count--;//等待得到准確的回應  	  
    if (Count==0)return MSD_RESPONSE_FAILURE;//得到回應失敗   
    else return MSD_RESPONSE_NO_ERROR;//正確回應
}

//從sd卡讀取一個數據包的內容
//buf:數據緩存區
//len:要讀取的數據長度.
//返回值:0,成功;其他,失敗;
//0XFE數據起始令牌	
u8 SdRecvData(u8*buf,u16 len)
{			  	  
    if(SdGetResponse(0xFE))return 1;//等待SD卡發回數據起始令牌0xFE
    while(len--)//開始接收數據
    {
        *buf=SdSpiReadWriteByte(0xFF);
        buf++;
    }
    //下面是2個偽CRC(dummy CRC)
    SdSpiReadWriteByte(0xFF);
    SdSpiReadWriteByte(0xFF);									  					    
    return 0;//讀取成功
}

//向sd卡寫入一個數據包的內容 512字節
//buf:數據緩存區
//cmd:指令
//返回值:0,成功;其他,失敗;	
u8 SdSendBlock(u8*buf,u8 cmd)
{	
    u16 t;		  	  
    if(SdWaitReady())return 1;//等待准備失效
    SdSpiReadWriteByte(cmd);
    if(cmd!=0XFD)//不是結束指令
    {
        for(t=0;t<512;t++)SdSpiReadWriteByte(buf[t]);//提高速度,減少函數傳參時間
        SdSpiReadWriteByte(0xFF);//忽略crc
        SdSpiReadWriteByte(0xFF);
        t=SdSpiReadWriteByte(0xFF);//接收響應
        if((t&0x1F)!=0x05)return 2;//響應錯誤									  					    
    }						 									  					    
    return 0;//寫入成功
}


//向SD卡發送一個命令
//輸入: u8 cmd   命令 
//      u32 arg  命令參數
//      u8 crc   crc校驗值	   
//返回值:SD卡返回的響應															  
u8 SdSendCmd(u8 cmd, u32 arg, u8 crc)
{
    u8 r1;	
    u8 Retry=0; 
    SD_DisSelect();//取消上次片選
    if(SdSelect())return 0XFF;//片選失效 
    //發送
    SdSpiReadWriteByte(cmd | 0x40);//分別寫入命令
    SdSpiReadWriteByte(arg >> 24);
    SdSpiReadWriteByte(arg >> 16);
    SdSpiReadWriteByte(arg >> 8);
    SdSpiReadWriteByte(arg);	  
    SdSpiReadWriteByte(crc); 
    if(cmd==CMD12)SdSpiReadWriteByte(0xff);//Skip a stuff byte when stop reading
    //等待響應,或超時退出
    Retry=0X1F;
    do
    {
        r1=SdSpiReadWriteByte(0xFF);
    }while((r1&0X80) && Retry--);	 
    //返回狀態值
    return r1;
}	

//獲取SD卡的CID信息,包括制造商信息
//輸入: u8 *cid_data(存放CID的內存,至少16Byte)	  
//返回值:0:NO_ERR
//		 1:錯誤														   
u8 SdGetCID(u8 *cid_data)
{
    u8 r1;	   
    //發CMD10命令,讀CID
    r1=SdSendCmd(CMD10,0,0x01);
    if(r1==0x00)
    {
        r1=SdRecvData(cid_data,16);//接收16個字節的數據	 
    }
    SD_DisSelect();//取消片選
    if(r1)return 1;
    else return 0;
}	

//獲取SD卡的CSD信息,包括容量和速度信息
//輸入:u8 *cid_data(存放CID的內存,至少16Byte)	    
//返回值:0:NO_ERR
//		 1:錯誤														   
u8 SdGetCSD(u8 *csd_data)
{
    u8 r1;	 
    r1=SdSendCmd(CMD9,0,0x01);//發CMD9命令,讀CSD
    if(r1==0)
    {
    	r1=SdRecvData(csd_data, 16);//接收16個字節的數據 
    }
    SD_DisSelect();//取消片選
    if(r1)return 1;
    else return 0;
}

//獲取SD卡的總扇區數(扇區數)   
//返回值:0: 取容量出錯 
//其他:SD卡的容量(扇區數/512字節)
//每扇區的字節數必為512,因為如果不是512,則初始化不能通過.														  
u32 SdGetSectorCount(void)
{
    u8 csd[16];
    u32 Capacity;  
    u8 n;
    u16 csize;  					    
    //取CSD信息,如果期間出錯,返回0
    if(SdGetCSD(csd)!=0) return 0;	    
    //如果為SDHC卡,按照下面方式計算
    if((csd[0]&0xC0)==0x40)	 //V2.00的卡
    {	
        csize = csd[9] + ((u16)csd[8] << 8) + 1;
        Capacity = (u32)csize << 10;//得到扇區數	 		   
    }else//V1.XX的卡
    {	
        n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
        csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
        Capacity= (u32)csize << (n - 9);//得到扇區數   
    }
    return Capacity;
}


//初始化SD卡
//返回值:0,正常.
//其他,不正常.
u8 SdInitialize(void)
{
    u8 r1;      // 存放SD卡的返回值
    u16 retry;  // 用來進行超時計數
    u8 buf[4];  
    u16 i;
    
    SdIOInit();		//初始化IO 
    for(i=0;i<10;i++)SdSpiReadWriteByte(0XFF);//發送最少74個脈沖  ,這里發送了80個脈沖
    retry=20;
    do
    {
        r1=SdSendCmd(CMD0,0,0x95);//進入IDLE狀態
    }while((r1!=0X01) && retry--);
    SD_Type=0;//默認無卡
    if(r1==0X01)
    {
        if(SdSendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
        {
            for(i=0;i<4;i++)buf[i]=SdSpiReadWriteByte(0XFF);	//Get trailing return value of R7 resp
            if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
            {
                retry=0XFFFE;
                do
                {
                    SdSendCmd(CMD55,0,0X01);	//發送CMD55
                    r1=SdSendCmd(CMD41,0x40000000,0X01);//發送CMD41
                }while(r1&&retry--);
                if(retry&&SdSendCmd(CMD58,0,0X01)==0)//鑒別SD2.0卡版本開始
                {
                    for(i=0;i<4;i++)buf[i]=SdSpiReadWriteByte(0XFF);//得到OCR值
                    if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC;    //檢查CCS
                    else SD_Type=SD_TYPE_V2;   
                }
            }
        }else//SD V1.x/ MMC	V3
        {
            SdSendCmd(CMD55,0,0X01);		//發送CMD55
            r1=SdSendCmd(CMD41,0,0X01);	//發送CMD41
            if(r1<=1)
            {		
                SD_Type=SD_TYPE_V1;
                retry=0XFFFE;
                do //等待退出IDLE模式
                {
                    SdSendCmd(CMD55,0,0X01);	//發送CMD55
                    r1=SdSendCmd(CMD41,0,0X01);//發送CMD41
                }while(r1&&retry--);
            }else
            {
                SD_Type=SD_TYPE_MMC;//MMC V3
                retry=0XFFFE;
                do //等待退出IDLE模式
                {											    
                    r1=SdSendCmd(CMD1,0,0X01);//發送CMD1
                }while(r1&&retry--);  
            }
            if(retry==0||SdSendCmd(CMD16,512,0X01)!=0)SD_Type=SD_TYPE_ERR;//錯誤的卡
        }
    }
    SD_DisSelect();//取消片選
    SdSpiSpeedHigh();//高速
    if(SD_Type)return 0;
    else if(r1)return r1; 	   
    return 0xaa;//其他錯誤
}



//讀SD卡
//buf:數據緩存區
//sector:扇區
//cnt:扇區數
//返回值:0,ok;其他,失敗.
u8 SdReadDisk(u8*buf,u32 sector,u8 cnt)
{
    u8 r1;
    if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//轉換為字節地址
    if(cnt==1)
    {
        r1=SdSendCmd(CMD17,sector,0X01);//讀命令
        if(r1==0)//指令發送成功
        {
            r1=SdRecvData(buf,512);//接收512個字節	   
        }
    }else
    {
        r1=SdSendCmd(CMD18,sector,0X01);//連續讀命令
        do
        {
            r1=SdRecvData(buf,512);//接收512個字節	 
            buf+=512;  
        }while(--cnt && r1==0); 	
        SdSendCmd(CMD12,0,0X01);	//發送停止命令
    }   
    SD_DisSelect();//取消片選
    return r1;//
}

//寫SD卡
//buf:數據緩存區
//sector:起始扇區
//cnt:扇區數
//返回值:0,ok;其他,失敗.
u8 SdWriteDisk(u8*buf,u32 sector,u8 cnt)
{
    u8 r1;
    if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//轉換為字節地址
    if(cnt==1)
    {
        r1=SdSendCmd(CMD24,sector,0X01);//讀命令
        if(r1==0)//指令發送成功
        {
            r1=SdSendBlock(buf,0xFE);//寫512個字節	   
        }
    }else
    {
        if(SD_Type!=SD_TYPE_MMC)
        {
            SdSendCmd(CMD55,0,0X01);	
            SdSendCmd(CMD23,cnt,0X01);//發送指令	
        }
        r1=SdSendCmd(CMD25,sector,0X01);//連續讀命令
        if(r1==0)
        {
            do
            {
                r1=SdSendBlock(buf,0xFC);//接收512個字節	 
                buf+=512;  
            }while(--cnt && r1==0);
            r1=SdSendBlock(0,0xFD);//接收512個字節 
        }
    }   
    SD_DisSelect();//取消片選
    return r1;//
}	


Spisd.h

 

#ifndef __SPISD_H_
#define __SPISD_H_

#include "spi.h"
#include "delay.h"
#include "common.h"
#include "ioremap.h"

// SD卡類型定義  
#define SD_TYPE_ERR     0X00
#define SD_TYPE_MMC     0X01
#define SD_TYPE_V1      0X02
#define SD_TYPE_V2      0X04
#define SD_TYPE_V2HC    0X06	

// SD卡指令表  	   
#define CMD0    0       //卡復位
#define CMD1    1
#define CMD8    8       //命令8 ,SEND_IF_COND
#define CMD9    9       //命令9 ,讀CSD數據
#define CMD10   10      //命令10,讀CID數據
#define CMD12   12      //命令12,停止數據傳輸
#define CMD16   16      //命令16,設置SectorSize 應返回0x00
#define CMD17   17      //命令17,讀sector
#define CMD18   18      //命令18,讀Multi sector
#define CMD23   23      //命令23,設置多sector寫入前預先擦除N個block
#define CMD24   24      //命令24,寫sector
#define CMD25   25      //命令25,寫Multi sector
#define CMD41   41      //命令41,應返回0x00
#define CMD55   55      //命令55,應返回0x01
#define CMD58   58      //命令58,讀OCR信息
#define CMD59   59      //命令59,使能/禁止CRC,應返回0x00

// SD卡中的響應有許多種,R1為標准響應,最為常用。與R1響應相似的還有R1b、R2和R3。
// R1響應在除SEND_STATUS外其它命令后發送,也是最高位先發送,共1個字節。最高位為0。響應說明如下:
// 0x01:空閑狀態
// 0x02:擦除錯誤
// 0x04:命令錯誤
// 0x08:CRC通信錯誤
// 0x10:擦除次序錯誤
// 0x20:地址錯誤
// 0x40:參數錯誤

#define MSD_RESPONSE_NO_ERROR      0x00    //無錯誤
#define MSD_IN_IDLE_STATE          0x01    //空閑狀態
#define MSD_ERASE_RESET            0x02    //擦除錯誤
#define MSD_ILLEGAL_COMMAND        0x04    //命令錯誤
#define MSD_COM_CRC_ERROR          0x08    //CRC通信錯誤
#define MSD_ERASE_SEQUENCE_ERROR   0x10    //擦除次序錯誤
#define MSD_ADDRESS_ERROR          0x20    //地址錯誤
#define MSD_PARAMETER_ERROR        0x40    //參數錯誤
#define MSD_RESPONSE_FAILURE       0xFF    //這次命令根本是失敗的,沒有任何回應

u8 SdInitialize(void);

u8 SdGetCID(u8 *cid_data);

u8 SdGetCSD(u8 *csd_data);

u32 SdGetSectorCount(void);

u8 SdReadDisk(u8*buf,u32 sector,u8 cnt);

u8 SdWriteDisk(u8*buf,u32 sector,u8 cnt);


#endif







 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM