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 秒。
讀操作步驟:
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;
{
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模式下的初始化和詳細的代碼分析