SD卡已經看了兩天了,主要是因為測試出來的卡容量不對,所以一直找原因,最終還是發現了,總比不過是單位上面出現了問題,或許是之前沒有接觸到SD的緣故吧,所以對其中的一些寄存器很不瞭解,一切都是重新開始,對照這寄存器手冊,理解程式,修改程式。一步步還是總結一下!
首先關於SD卡的協議是有必要瞭解的,我今天花了一上午的課堂時間來理解這個SD卡的協議,就是基於這個文檔的,這個文檔很適合入門SD協議的(個人認為)。http://download.csdn.net/detail/king_bingge/5218183
初識SD之後,就可以開始正式學習SD卡了!
一、要使用SD卡,那麼首先肯定得對SD卡進行初始化,那麼如何進行初始化呢?(命令的參數暫且不提)
1、這裡涉及到很多指令了。協議規定了在給SD卡上電之後需要給出至少74個時鐘脈衝後,才能進行相關的SD初始化工作,雖然是這麼說,但是我不給74個時鐘,他照樣能初始化,看看。
for(i=0;i<10;i++)SD_SPI_ReadWriteByte(0XF);
但是,或許為了能夠更加成功的初始化吧,所以有這個規定所以,我們還是規規矩矩的好,給它74個時鐘,沒關係的嘛!
2、然後就是協議中說到當我們複位或者上電的時候,SD卡的SD控制寄存器處於卡識別模式中的空閑模式的,暫且這樣稱吧。本來我們是不需要發送複位命令了的,但是我們不知道我們的SD所支援的電壓範圍。所以,我們最好還是先給出一條複位指令,然後緊接著一條擷取工作電壓的指令,這樣也是比較保險,如果多SD卡工作電壓有疑問的,那麼就得去看晶片手冊了。有了這個知識,那下面的代碼就不成問題了
retry=20;do{r1=SD_SendCmd(CMD0,0,0x95);//進入IDLE狀態}while((r1!=0X01) && retry--); SD_Type=0;//預設無卡if(r1==0X01){if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0{for(i=0;i<4;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF);//Get trailing return value of R7 respif(buf[2]==0X01&&buf[3]==0XAA)//卡是否支援2.7~3.6V
3、協議上還提到ACMD41命令的目的是給予 SD卡控制器一個識別 SD卡是否可以在所給Vdd 範圍下工作的機制,如果 SD 記憶卡無法在指定 Vdd 範圍內工作,則它會進入非使用中(Inactive state ),所以我們接下來需要發送這個命令,但是在發送這個命令之前,要知道這是一個應用型的命令,所以要加上CMD55命令,所以有了下面的代碼。
if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支援2.7~3.6V{retry=0XFFFE;do{SD_SendCmd(CMD55,0,0X01);//發送CMD55r1=SD_SendCmd(CMD41,0x40000000,0X01);//發送CMD41}while(r1&&retry--);if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鑒別SD2.0卡版本開始//擷取供電狀態{for(i=0;i<4;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF);//得到OCR值if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC; //檢查CCSelse SD_Type=SD_TYPE_V2; }
這樣就擷取了卡的類型了,至此卡的初始化基本完成,當然根據協議上,我們還可以在這裡修改相對位址之類的。如果有必要的話,可以這樣做!
二、初始化完SD卡,接下來如果你想查看我們SD的容量,可以這樣做!
之前就是因為卡容量的問題,所以鬱悶了好久,理解了個大概!注意這裡函數名是讀取扇區數,實際上返回的值是我們卡的容量,這裡得注意了。
1、首先看代碼
u32 SD_GetSectorCount(void){ u8 csd[16]; u32 Capacity_KB,Capacity_MB ; u8 n;u16 csize; //取CSD資訊,如果期間出錯,返回0 if(SD_GetCSD(csd)!=0) return 0; 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_KB= (u32)csize << (n - 10);//得到扇區數 ,這裡的單位是KBCapacity_MB = Capacity_KB/1024; return Capacity_MB;}
這個計算的問題必須得看SD卡的手冊,也就是128位的CSD寄存器。這裡我把我分析的過程貼出來,我不得不說比較亂,或許只有我自己能看懂了,懶得整理了,僅供參考!
My SD_CardCSD寄存器中的值如下:00 7f ff 32 bit(127-96)csd0 - csd35f 59 83 cb bit(95--64)csd4 - csd7 0101 1111 0101 1001 1000 0011 1100 101176 db df ff bit(63--32)csd8 - csd11 0111 0110 1101 1011 1101 1111 1111 111196 40 00 97 bit(31---0)csd12 -csd15csize {62,73}csize_muti{47,49}read {80,83}csize = 1111 0010 1101 = 3885csize_muti = 111 = 7read = 1001 = 9 計算公式:blocknr = (csize+1)*mult= mult = (csize_muti < 8)*(2^(csize_muti + 2)) block_len = (read < 12)*(2^(read)) capacity = blocknr * block_len = 13*4*3516*98304依據下面代碼來計算我的容量: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_KB= (u32)csize << (n - 10);//得到扇區數 ,這裡的單位是KB // 00 7f ff 32 5f 59 83 cb 76 db df ff 96 40 00 97Capacity_MB = Capacity_KB/1024;1、(csd[8] >> 6) 得到的是 bit62和bit63的值去掉2位2、((u16)csd[7] << 2)得到的是bit64--bit69的值去掉6位3、((u16)(csd[6] & 3) << 10)得到的是bit70--bit73的值
其實我的問題還是出現在單位上面!
這樣我們就能看到顯示的容量值了,我的是1G的。列印出來是971M,和windows下面的是一致的。其實我們可以通過讀SD卡的開機磁區,從而把相關的資訊讀取出來,而不需要使用那些個寄存器。那麼現在我們的計算公式就是(這隻是我自己信手寫的,如果想要理解,你必須得看扇區的內容咯,我就是對照著那個MBR來寫的)
x=(((buf_read[34])*64*1024+(buf_read[33])*256+(buf_read[32])))*512/1024/1024;//列印大小printf("\n SD Sector Size:%d Mb\n",x);
雖然不怎麼雅觀,但是能用就是了。
2、接下來看如何用SPI讀一個扇區吧,先看代碼
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt){u8 r1;if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//轉換為位元組地址if(cnt==1){r1=SD_SendCmd(CMD17,sector,0X01);//讀命令if(r1==0)//指令發送成功{r1=SD_RecvData(buf,512);//接收512個位元組 }}else{r1=SD_SendCmd(CMD18,sector,0X01);//連續讀命令do{r1=SD_RecvData(buf,512);//接收512個位元組 buf+=512; }while(--cnt && r1==0); SD_SendCmd(CMD12,0,0X01);//發送停止命令} SD_DisSelect();//取消片選return r1;//}
這幾行代碼能實現單個和多個扇區的讀寫,跟蹤進去可以能夠看到這個函數
//SPIx 讀寫一個位元組//TxData:要寫入的位元組//傳回值:讀取到的位元組u8 SPIx_ReadWriteByte(u8 TxData){u8 retry=0; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //檢查指定的SPI標誌位設定與否:發送緩衝空標誌位{retry++;if(retry>200)return 0;} SPI_I2S_SendData(SPI1, TxData); //通過外設SPIx發送一個資料retry=0;while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); //檢查指定的SPI標誌位設定與否:接受緩衝非空標誌位{retry++;if(retry>200)return 0;} return SPI_I2S_ReceiveData(SPI1); //返回通過SPIx最近接收的資料 }
這個函數的功能實現了既可以進行發送又可以進行讀取資料,現在來總結一下!
讀一個扇區的過程
1、先發命令r1=SD_SendCmd(CMD17,sector,0X01);//讀單個扇區的命令
2、然後將接收到得資料存在臨時數組裡面
u8 SD_RecvData(u8*buf,u16 len){ if(SD_GetResponse(0xFE))return 1;//等待SD卡發回資料起始令牌0xFE while(len--)//開始接收資料 { *buf=SPIx_ReadWriteByte(0xFF); buf++; } //下面是2個偽CRC(dummy CRC) SD_SPI_ReadWriteByte(0xFF); SD_SPI_ReadWriteByte(0xFF); return 0;//讀取成功}
那麼對應的寫扇區也類似的
1、先發寫單個扇區的命令 r1=SD_SendCmd(CMD24,sector,0X01);//寫命令
2、將Buffer裡面的內容寫到對應的扇區裡面去
u8 SD_SendBlock(u8*buf,u8 cmd){u16 t; if(SD_WaitReady())return 1;//等待準備失效SD_SPI_ReadWriteByte(cmd);if(cmd!=0XFD)//不是結束指令{for(t=0;t<512;t++)SPIx_ReadWriteByte(buf[t]);//提高速度,減少函數傳參時間 SD_SPI_ReadWriteByte(0xFF);//忽略crc SD_SPI_ReadWriteByte(0xFF);t=SD_SPI_ReadWriteByte(0xFF);//接收響應if((t&0x1F)!=0x05)return 2;//響應錯誤 } return 0;//寫入成功}
到這裡,讀寫扇區就完成了,下一步就是,使用檔案系統來進行操作了。