1. 程式人生 > >SD卡在SPI模式下的初始化和詳細的代碼分析

SD卡在SPI模式下的初始化和詳細的代碼分析

是否 若有 細節 成功 oid 實驗 繼續 asi 初始化

SD卡在spi下的初始化:
1、初始化與SD卡鏈接的硬件條件(mcu的spi配置, IO口配置)
2、上電延時(>74個CLK)
3、復位卡(CMD0),進入idle狀態
4、發送CMD8,檢查是否支持2.0協議(CMD8就是判斷是否是支持2.0協議)
技術分享圖片
5、根據不同協議檢查sd卡(命令包括:cmd55、cmd41、cmd58、cmd1等)
6、取消片選,多發8個CLK,結束初始化
詳細描述:
上電後,包括熱插入,卡進入 idle 狀態。在該狀態 SD 卡忽略所有總線操作直到接收到 ACMD41 命令。ACMD41 命令是一個特殊的同步命令,用來協商操作電壓範圍,並輪詢所有的卡。除了操作電壓信息,ACMD41 的響應還包括一個忙標誌,表明卡還在 power-up 過程工作,還沒有準備好識別操作,即告訴主機卡還沒有就緒。主機等待(繼續輪詢)直到忙標誌清除。單個卡的最大上電時間不能操作 1 秒。
上電後,主機開始時鐘並在 CMD 線上發送初始化序列,初始化序列由連續的邏輯“1”組成。序列長度為最大 1 毫秒,74 個時鐘或 supply-ramp-up 時間。額外的 10 個時鐘(64 個時鐘後卡已準備就緒)用來實現同步。每個總線控制器必須能執行 ACMD41 和 CMD1。CMD1 要求 MMC 卡發送操作條件。在任何情況下,ACMD41 或 CMD1 必須通過各自的 CMD 線分別發送給每個卡。

讀操作步驟:
1、發送cmd17
技術分享圖片
2、接收sd卡發過來的響應r1
3、接收數據起始令牌0XFE
4、接收數據
5、接收2個字節的crc,如果不使用crc,這兩個字節在讀取後可以丟掉
6、進制片選之後,多發8個clk

寫操作步驟:
1、發送cmd24
2、接收卡響應R1
3、發送寫數據起始令牌0xFE
4、發送數據
5、發送2字節的偽crc;隨便發什麽,不起作用的crc,只是為了匹配數據格式
6、禁止片選之後,多發8個CLK

硬件連接:
SD_OUT-----SPI2_MISO PB14
SD_CLK------SPI2_CLK PB13
SD_DIN------SPI2_MOSI PB15
SD_CS--------隨便

代碼細節分析:
1、第一個就是spi選擇的模式在SD裏頭應該怎麽設置
首先我們來看下已經實現過的代碼:
void SPI_SetSpeed(u8 SpeedSet)
{
SPI_InitTypeDef SPI_InitStructure;

if(SpeedSet==SPI_SPEED_HIGH)//高速
{
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI2, &SPI_InitStructure);
/ SPI2 enable /
SPI_Cmd(SPI2, ENABLE);
}
else//低速
{
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI2, &SPI_InitStructure);
/* SPI2 enable */
SPI_Cmd(SPI2, ENABLE);  

}
}
接下來的問題是
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
為什麽是這個選項?
CPOL被置’1’,SCK引腳在空閑狀態保持高電平
CPHA(時鐘相位)位被置’1’,SCK時鐘的第二個邊沿,CPOL位為’1’時就是上升沿
技術分享圖片
SD 卡的Bus Timing如下:
技術分享圖片
看到時序圖就比較直觀的能看到CPOL和CPHA的取值。(具體怎麽看可查看“詳解spi中的極性CPOL和相位CPHA”)
2、SD卡發送命令的格式
/***

  • Function Name : SD_SendCommand
  • Description : 向SD卡發送一個命令
  • Input : u8 cmd 命令
  • u32 arg 命令參數
  • u8 crc crc校驗值
  • Output : None
  • Return : u8 r1 SD卡返回的響應
    **/
    u8 SD_SendCommand(u8 cmd, u32 arg,u8 crc)
    {
    unsigned char r1;
    unsigned int Retry = 0;

    SD_CS_DISABLE();
    SPI_ReadWriteByte(0xff);//提高兼容性,如果沒有這裏,有些SD卡可能不支持
    SD_CS_ENABLE();//片選端置低,選中SD卡

    / 發送命令序列 /
    SPI_ReadWriteByte(cmd | 0x40); //0100 0000
    SPI_ReadWriteByte((u8)(arg >> 24));//參數[31..24]
    SPI_ReadWriteByte((u8)(arg >> 16));//參數[23..16]
    SPI_ReadWriteByte((u8)(arg >> 8));//參數[15..8]
    SPI_ReadWriteByte((u8)arg); //參數[7..0]
    SPI_ReadWriteByte(crc);

    //等待響應,或超時退出
    while((r1 = SPI_ReadWriteByte(0xFF))==0xFF)
    {
    Retry++;
    if(Retry > 800)break; //根據實驗測得,最好重試次數多點
    }
    //關閉片選
    SD_CS_DISABLE();
    //在總線上額外增加8個時鐘,讓SD卡完成剩下的工作
    SPI_ReadWriteByte(0xFF);

    //返回狀態值
    return r1;
    }
    比如:SD_SendCommand(CMD0, 0,0x95); //發送CMD0,讓SD卡進入IDLE狀態技術分享圖片
    問一:那CMD0是什麽?
    答:頭文件裏有定義
    #define CMD0 0 //卡復位 (應答格式:R1)
    問二:為什麽(cmd | 0x40)?
    答:因為SD卡的指令由6個字節組成,字節1的最高2位固定為 01,低6位為命令號(比如cmd16,為二進制10000即0x10,完整的CMD16,第一個字節為01010000,即0x10+0x40)
    技術分享圖片
    問三:返回值為什麽是0xFF(while((r1==SPI_ReadWriteByte(0xFF))==0xFF))?
    首先我們得知道返回值得格式是什麽?
    技術分享圖片
    接下來就是為什麽要發送0xFF?
    答:這不是一個指令,而是用於維持MOSI的電平,SPI_ReadWriteByte(0xFF)啟動傳輸,這裏其實就是發送8個時鐘給從設備,而MOSI一直維持高電平,當然也可以維持低電平,寫0x00也可以,不過通常做法就是維持高電平。

這裏又出現新的問題,返回值和響應的區別是什麽?答案見下面
回到問題三,為什麽發送0xFF,同步收到的也是0xFF呢,這裏網上有人說是因為,沒有返回正確的值得時候,返回0xFF是報錯的一種返回。這裏不能準確判定,暫時這麽理解。

講完了怎麽發命令下面我們看下,怎麽去初始化sd卡
3、初始化SD卡
1)首先拉低cs pin
SD_CS_ENABLE();
2)
// 純延時,等待SD卡上電完成
for(i=0;i<0xf00;i++);

//先產生至少74個脈沖,讓SD卡自己初始化完成
for(i=0;i<10;i++)
{
SPI_ReadWriteByte(0xFF);//80clks
}
3)//-----------------SD卡復位到idle開始-----------------
//循環連續發送CMD0,直到SD卡返回0x01,進入IDLE狀態
//超時則直接退出
retry = 0;
do
{
r1 = SD_SendCommand(CMD0, 0,0x95);//發送CMD0,讓SD卡進入IDLE狀態
retry++;
}while((r1 != 0x01) && (retry<200));
//跳出循環後,檢查原因:初始化成功?or 重試超時?
if(retry==200) return 1; //超時返回1
註:這裏我們得知道一些命令和校驗碼
CMD0 : 0x40,0x00,0x00,0x00,0x00,0x95
CMD8 : 0x48,0x00,0x00,0x01,0xaa,0x87
CMD55:0x77,0x00,0x00,0x00,0x00,0xff 其中:0x77=0x40+0x37
ACMD41:0x69,0x40,0x00,0x00,0x00,0xff
CMD58: 0x7a,0x00,0x00,0x00,0x00,0xff

4)獲取卡片的SD版本信息
步驟1:如何獲取?
r1 = SD_SendCommand_NoDeassert(CMD8, 0x1aa,0x87);
然後判斷返回值
步驟2:如果卡片版本信息是v1.0版本的,即r1=0x05,則進行以下初始化
//設置卡類型為SDV1.0,如果後面檢測到為MMC卡,再修改為MMC
SD_Type = SD_TYPE_V1;
//如果是V1.0卡,CMD8指令後沒有後續數據
//片選置高,結束本次命令
SD_CS_DISABLE();
//多發8個CLK,讓SD結束後續操作
SPI_ReadWriteByte(0xFF);

步驟3://-----------------SD卡、MMC卡初始化開始-----------------
//發卡初始化指令CMD55+ACMD41
// 如果有應答,說明是SD卡,且初始化完成
// 沒有回應,說明是MMC卡,額外進行相應初始化
do
{
//先發CMD55,應返回0x01;否則出錯
r1 = SD_SendCommand(CMD55, 0, 0);
if(r1 != 0x01)
return r1;
//得到正確響應後,發ACMD41,應得到返回值0x00,否則重試400次
r1 = SD_SendCommand(ACMD41, 0, 0);
retry++;
}while((r1!=0x00) && (retry<400));
// 判斷是超時還是得到正確回應
// 若有回應:是SD卡;沒有回應:是MMC卡

CMD55指令是用來切換到試用ACMD指令的
技術分享圖片
步驟4:這裏是初始化mmc,不是mmc卡就不用管該步驟
//----------MMC卡額外初始化操作開始------------
if(retry==400)
{
retry = 0;
//發送MMC卡初始化命令(沒有測試)
do
{
r1 = SD_SendCommand(CMD1, 0, 0);
retry++;
}while((r1!=0x00)&& (retry<400));
if(retry==400)return 1; //MMC卡初始化超時
//寫入卡類型
SD_Type = SD_TYPE_MMC;
}
//----------MMC卡額外初始化操作結束------------
//設置SPI為高速模式
SPI_SetSpeed(SPI_SPEED_HIGH);
SPI_ReadWriteByte(0xFF);

 //禁止CRC校驗     
 r1 = SD_SendCommand(CMD59, 0, 0x95);
 if(r1 != 0x00)return r1;  //命令錯誤,返回r1         
 //設置Sector Size
 r1 = SD_SendCommand(CMD16, 512, 0x95);
 if(r1 != 0x00)return r1;//命令錯誤,返回r1         
 //-----------------SD卡、MMC卡初始化結束-----------------

步驟5: //下面是V2.0卡的初始化
//其中需要讀取OCR數據,判斷是SD2.0還是SD2.0HC卡
步驟1的 r1 = SD_SendCommand_NoDeassert(CMD8, 0x1aa,0x87);
如果得到的是0x01
else if(r1 == 0x01)
{
//V2.0的卡,CMD8命令後會傳回4字節的數據,要跳過再結束本命令
buff[0] = SPI_ReadWriteByte(0xFF); //should be 0x00
buff[1] = SPI_ReadWriteByte(0xFF); //should be 0x00
buff[2] = SPI_ReadWriteByte(0xFF); //should be 0x01
buff[3] = SPI_ReadWriteByte(0xFF); //should be 0xAA
SD_CS_DISABLE();
SPI_ReadWriteByte(0xFF);//the next 8 clocks
//判斷該卡是否支持2.7V-3.6V的電壓範圍
//if(buff[2]==0x01 && buff[3]==0xAA) //如不判斷,讓其支持的卡更多
// {
retry = 0;
//發卡初始化指令CMD55+ACMD41
do
{
r1 = SD_SendCommand(CMD55, 0, 0);
if(r1!=0x01)return r1;
r1 = SD_SendCommand(ACMD41, 0x40000000, 1);
if(retry>200)return r1; //超時則返回r1狀態
}while(r1!=0);
//初始化指令發送完成,接下來獲取OCR信息
//-----------鑒別SD2.0卡版本開始-----------
r1 = SD_SendCommand_NoDeassert(CMD58, 0, 0);
if(r1!=0x00)return r1; //如果命令沒有返回正確應答,直接退出,返回應答
//讀OCR指令發出後,緊接著是4字節的OCR信息
buff[0] = SPI_ReadWriteByte(0xFF);
buff[1] = SPI_ReadWriteByte(0xFF);
buff[2] = SPI_ReadWriteByte(0xFF);
buff[3] = SPI_ReadWriteByte(0xFF);

   //OCR接收完成,片選置高
   SD_CS_DISABLE();
   SPI_ReadWriteByte(0xFF);

   //檢查接收到的OCR中的bit30位(CCS),確定其為SD2.0還是SDHC
   //如果CCS=1:SDHC   CCS=0:SD2.0
   if(buff[0]&0x40)SD_Type = SD_TYPE_V2HC;    //檢查CCS    
   else SD_Type = SD_TYPE_V2;       

//-----------鑒別SD2.0卡版本結束-----------
//設置SPI為高速模式
SPI_SetSpeed(1);
// }
}
首先我們得知道CMD8返回的值是個什麽樣的值
技術分享圖片
問題一:為什麽發送CMD8,程序裏只接收8個字節呢?難道只返回了8個字節(32位麽)?
答:不是這樣的,這裏我們順便也可以回答,上面的一個問題,就是說返回和響應到底是什麽關系?其實返回就是響應中的一部分,因為返回就是響應的高8位的那幾位。
這裏我們看下R7
技術分享圖片
技術分享圖片
8+32+8=48
前面的8就是返回值,又是這張圖
技術分享圖片
後面+8的就是CRC和最後一個1的意思
分析32位: //should be 0x00 0000 0000
//should be 0x00 0000 0000
//should be 0x01 0000 0001
//should be 0xAA 1010 1010
高位開始排: 0000 0000 0000 0000 0000 0001 1010 1010這樣就對上了

問題2: r1 = SD_SendCommand_NoDeassert(CMD58, 0, 0);這條CMD58是保留命令,發這個58是什麽意思呢?
其實cmd58是讀取OCR的訊息 返回是R3格式
補充:cmd59 是設置crc校驗的使能與關閉 返回的是R1格式
OCR是寄存器,還有那些寄存器?
技術分享圖片

問題3: if(buff[0]&0x40)SD_Type = SD_TYPE_V2HC; //檢查CCS
else SD_Type = SD_TYPE_V2;
為什麽要與上0x40呢?
答: buff[0]是8位,0x40是0100 0000 指向的就是OCR的第30位,就是CCS
技術分享圖片
到此初始化完畢!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

SD卡在SPI模式下的初始化和詳細的代碼分析