菜鳥用C8051F020 SPI讀寫SD卡FAT全攻略
第一步:搭電路
本文引用地址:http://butianyuan.cn/article/201611/317269.htm我買了一小塊蜂窩板和一個SD插槽,按照標(biāo)準(zhǔn)電路焊接上,由于用的是SPI模式,所以選電路圖的時候要看好,SD卡上的引腳順序不要看錯,912345678,最后兩根挨得很緊,焊接時不要連上了,相關(guān)引腳一定要按照要求接上47K的上拉電阻。電路雖然簡單,但一定要確保無誤。還有一點,SD卡座種類不一樣,有位學(xué)長買的是彈簧式的,焊完以后剛開始初始化都能成功,放了幾天突然不行了,檢查各引腳都沒問題,最后發(fā)現(xiàn)是卡座的問題,這樣的硬件問題很難發(fā)現(xiàn),還浪費時間,所以卡座還是直接買簡單的好。
第二步:設(shè)置硬件SPI
我用的是sililab的C8051F020,自帶硬件SPI,如果不帶可以用軟件模擬,關(guān)于軟件模擬SPI這塊網(wǎng)上有很多現(xiàn)成的程序。這一歩首先按照020手冊寫了一段程序,當(dāng)然是將其設(shè)為主模式,這時候CONFIG(這個軟件可以視窗化操作C8051F的大多數(shù)寄存器并自動生成代碼)會分配4個引腳,CLK時鐘位,MISO和MOSI兩個數(shù)據(jù)傳輸位,還有一個NSS位,這個腳用不上,不要將其當(dāng)作CS片選位,CS位選一個普通IO既可。所有從單片機上輸出的引腳都設(shè)為推挽輸出。設(shè)置完成后最好弄個示波器看一下輸出波形是否和你想象中的一樣,這樣就能確保你的SPI工作沒問題了,這一步也是關(guān)鍵,SPI是底層通信的基礎(chǔ)。
第三步:SD卡初始化
這一步正式進(jìn)入單片機調(diào)試SD部分,了解SD卡的時序后(后面我會上傳這部分資料),網(wǎng)上眾說紛紜,還有說要看完178頁英文PDF,我都暈了,這個看完估計我都是專家了。關(guān)于初始化命令有很多說法,我發(fā)的是CMD0和CMD1就可以成功初始化。
解釋一下這個命令格式的含義:這是個一個字節(jié)的命令格式為01xx xxxx 后六位是CMD后面的數(shù)字的二進(jìn)制值,如CMD1=0100 0001=0x41 程序中寫為CMD | 0x40 CMD代表后面的數(shù)字。
需要注意初始化時SPI速率不能超過400K,我設(shè)的是100K,初始化沒問題,還有發(fā)CMD0之前要向SD卡發(fā)送至少74個時鐘周期,只有CMD0需要這樣特殊。
下面是發(fā)送CMD0的程序段:
retry=0;
CSH;
do{
for(i=0;i<10;i++) SPI_WriteByte(0xFF);//發(fā)送 至少 74個時鐘周期注意片選線此時為高
r1=mmcSendCommand(MMC_GO_IDLE_STATE, 0);//發(fā)送CMD0,注意此時片選線才為低
retry++;
if(retry>0xfe) return -1;//嘗試的發(fā)送次數(shù)可以適當(dāng)多一些
} while(r1 != MMC_R1_IDLE_STATE); //正確應(yīng)答為1
嘗試發(fā)送的次數(shù)至少為200,有人建議2000的,隨便,如果不穩(wěn)定,比如有時候收的到有時候收不到,就可以適當(dāng)增大發(fā)送次數(shù)
這是緊隨其后發(fā)送的CMD1程序段:
retry=0;
do{
r1=mmcSendCommand(MMC_SEND_OP_COND, 0);//發(fā)送CMD1
retry++;
if(retry>100) return -1;
} while(r1!=0); //正確應(yīng)答為0
初始化有這兩個就可以完成了,有的程序中還會加上
mmcSendCommand(MMC_CRC_ON_OFF, 0);//關(guān)CRC校驗
mmcSendCommand(MMC_SET_BLOCKLEN, 512);//設(shè)置塊長度為512字節(jié)
這個無關(guān)緊要,SPI模式下默認(rèn)沒有CRC校驗的,而且每塊字節(jié)數(shù)就是512,這個塊大小就別改了,你要設(shè)置成別的大小,后面加FAT會出麻煩的。
下面解釋一下上面程序中的uint8_t mmcCommand(uint8_t cmd, uint32_t arg)函數(shù)
uint8_t mmcCommand(uint8_t cmd, uint32_t arg)
{
uint8_t r1,retry=0;
SPI_WriteByte(cmd|0x40);// send command
SPI_WriteByte(arg>>24);
SPI_WriteByte(arg>>16);
SPI_WriteByte(arg>>8);
SPI_WriteByte(arg);
SPI_WriteByte(0x95);// 講解標(biāo)記(1)
SPI_WriteByte(0xFF);// 講解標(biāo)記(2)
while((r1=SPI_WriteByte(0xFF))==0xFF)if(retry++>8)break;
return r1;
}
arg這個參數(shù)看一下SD的SPI命令格式就知道這個字段是命令的屬性,一般為0
講解標(biāo)記(1)
CRC位這個0x95只對CMD0有意義,發(fā)送其他命令時這個位可為任意值,所以不必修改
講解標(biāo)記(2)
這個容易忽略,不忽略第一個字節(jié)你就可能收不到正確的響應(yīng),許多程序中這個叫做dummy values。特別注意看時序圖,后面寫命令的程序中是要發(fā)送兩個字節(jié)的,不要和這個搞混了。
寫命令程序段:
uint8_t mmcWrite(uint32_t sector, uint8_t* buffer){
uint8_t r1;
uint16_t i;
CSL;// assert chip select
r1 = mmcCommand(MMC_WRITE_BLOCK,sector<<9);// issue command
if(r1 != 0)return r1;
SPI_WriteByte(0xFF);// send dummy
SPI_WriteByte(MMC_STARTBLOCK_WRITE);// send da
for(i=0; i<512; i++){
SPI_WriteByte(*buffer++);// write da
}
SPI_WriteByte(0xFF);// write 16-bit CRC (dummy values)看清楚!兩個字節(jié)哦!
SPI_WriteByte(0xFF);
r1 = SPI_WriteByte(0xFF);// read da
if((r1&MMC_DR_MASK)!=MMC_DR_ACCEPT)return r1;//講解標(biāo)記(1)
while(!SPI_WriteByte(0xFF));// wait until card not busy
CSH;// release chip select
return 0;
}
講解標(biāo)記(1)
這個很重要?。?!我在這浪費了一個星期?。?!
許多程序包括網(wǎng)上的大多資料都說這個回應(yīng)為0x05,可是我每次都收不到這個回應(yīng),收到的是0xE5,本來我以為是程序有問題,其實不然,我查了資料,找到了這個響應(yīng)令牌的8位的含義,發(fā)現(xiàn)高三位是保留位,而0xE5和0x05低五位是一樣的說明響應(yīng)是正確的,這個高三位可能由于廠家不同值不一樣。
這個程序是比較完善的,響應(yīng)r1與上個MMC_DR_MASK(宏定義值為0x0001 1111)就把高三位與成0了,網(wǎng)上有的程序是沒有這個過程的。
如果你想驗證只能是否能正常讀寫,可以將值賦進(jìn)數(shù)組寫入到SD卡的一個扇區(qū)里(這里的扇區(qū)是指物理扇區(qū),這個概念在FAT文件中再說)在用數(shù)組讀出來,在仿真器里看是否一樣,這個過程可能用不了winhex這款軟件,因為你寫入的那個扇區(qū)可能是引導(dǎo)區(qū),造成你將卡插到電腦中會提示你格式化。
下面是CMD0的波形圖
原來以為這個波形圖有問題,因為時序圖上片選線在數(shù)據(jù)傳送過程中是一直低的,還在網(wǎng)上問了一陣子,可惜沒人理我,其實是正確的,中間的電平跳變是由于SPI發(fā)送函數(shù)開頭和末位有把片選拉低和拉高,片選線一旦拉高,數(shù)據(jù)線就會跟著變高,所以出現(xiàn)了跳變,我嘗試著把SPI發(fā)送函數(shù)的開頭末位片選去掉,發(fā)現(xiàn)這樣也是可以的。
第四步:加FAT
如果上面測試都沒問題,那么底層通信就沒有問題了,到目前為止我們一直是把SD當(dāng)成一個大的FLASH來操作的,但是要想在電腦上把用單片機寫的程序讀出來就要按照一定規(guī)則往里面寫,這個規(guī)則就是FAT。我用的是2G的金士頓SD卡,正好可以用FAT16,F(xiàn)AT16最大支持2G。
這一塊的內(nèi)容可以參照http://www.sjhf.net/document/fat/#索引
里面的講解很詳細(xì),會幫助你理解文件系統(tǒng)
需要把握的思路是:先用電腦把SD卡格式化成FAT16的(即FAT),然后讀寫的規(guī)則是:找到MBR(主引導(dǎo)區(qū))讀相關(guān)字節(jié)得到邏輯引導(dǎo)區(qū)的地址,在邏輯扇區(qū)里讀出BPB數(shù)據(jù),再對FAT表,根目錄和數(shù)據(jù)區(qū)進(jìn)行對應(yīng)操作
這一塊可以用winhex看SD卡的物理扇區(qū)和邏輯扇區(qū),以便對照
這一塊我講幾個我遇到的問題
1>>如果是VISTA操作系統(tǒng),你要以管理員身份進(jìn)入,不然無法看到物理扇區(qū),即鼠標(biāo)停在winhex的圖標(biāo)上點右鍵選取以管理身份運行即可(不要笑,我剛開始就不知道應(yīng)該這樣操作,呵呵)
2>>有些SD卡是沒有主引導(dǎo)區(qū)即MBR的,這樣更好,邏輯扇區(qū)就和物理扇區(qū)一樣了,那么怎樣判斷有沒有MBR呢?最簡單的你用winhex看一下物理和邏輯扇區(qū),如果數(shù)據(jù)一樣就是沒有MBR了。再有嚴(yán)謹(jǐn)一點的方法:注意看PDF中對邏輯引導(dǎo)區(qū)的解釋,邏輯引導(dǎo)區(qū)基本是以E9和EB開頭的,單憑這一點就可以用函數(shù)輕松判斷了。所以寫文件之前先弄清你的SD卡有沒有MBR,想了解更多請參照http://hi.baidu.com/bg4uvr/blog/item/b59f2fde196efd5fcdbf1aee.html
有沒有MBR是可以轉(zhuǎn)換的,具體請看:http://hi.baidu.com/bg4uvr/blog/item/9489a6295f7bcff998250a48.html
3>>這里說一些關(guān)于編譯的問題,加入FAT部分的程序后,工程中程序文件會比較多,這里要注意重復(fù)包含的問題,這一塊網(wǎng)上很多,不再重復(fù)。有時候錯誤并不在指針提示的那一行
比如有時候指的那一行只有int a;這樣的語句,這時候注意往上面看,是不是定義函數(shù)時漏了末尾的分號,造成編譯器將a也當(dāng)作其形參了,這種錯誤有時候很隱蔽,比如int a;上面只有#include "b.h",這時候就要去b中看看,是不是文件末尾定義的那個函數(shù)后面忘了分號
還有編譯器報出"segment too large"這時候須把編譯器選項中的Memory Model 中的Variable 設(shè)成XDATA這是對sililab IDE開發(fā)環(huán)境而言的,或者放入xdata數(shù)組里也行Project——>Tool Chain Intergration——>Compiler——>Custmize——>Memory Model ——>Variable——>Large:XDATA
這個IDE官方下載的會限制代碼大小,因為里面用的編譯器是限制版的,這時候你如果裝了正版的KEIL就可以用KEIL的編譯器從而不受代碼限制。具體做法:Project——>Tool Chain Intergration將Compiler和Linker中的路徑修改到KEIL的相應(yīng)路徑即可。
還有就是有的程序是不支持文件夾嵌套的
現(xiàn)在我說一個最最重要的問題,也是我遇到的最后一個問題,字節(jié)序問題,請先參考http://blog.csdn.net/sunshine1314/archive/2008/04/20/2309655.aspx
了解了字節(jié)序,當(dāng)然這里不用管比特序,如果你用的是AVR單片機,恭喜你,和SD卡還有電腦的字序是一樣的,不用轉(zhuǎn)換字序的,網(wǎng)上大多數(shù)程序你都能用,我用的C8051F020則需要,每次和卡交換大于一個字節(jié)的數(shù)據(jù)時都需要做一次轉(zhuǎn)換,由于程序中只用到了8 16 32這類數(shù)據(jù),所以我只加入兩字節(jié)轉(zhuǎn)換函數(shù)和四字節(jié)轉(zhuǎn)換函數(shù),函數(shù)體如下:
uint16_t two_byte_exchange(uint16_t h)
{
if(!Big_Small_ending_Switch) //如果沒有使能字節(jié)轉(zhuǎn)換則返回原值
return h;
return (h >> 8) + (h << 8);
}
uint32_t four_byte_exchange(uint32_t h)
{
if(!Big_Small_ending_Switch) //如果沒有使能字節(jié)轉(zhuǎn)換則返回原值
return h;
return (h >> 24) + ((h >> 16) << 8)+ ((h >> 8) << 16)+ (h << 24);
}
以上就是我遇到的大部分問題了,希望對大家有所幫助,由于水平有限,不足之處還請前輩們指教!
評論