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