色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          新聞中心

          EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 菜鳥(niǎo)用C8051F020 SPI讀寫(xiě)SD卡FAT全攻略

          菜鳥(niǎo)用C8051F020 SPI讀寫(xiě)SD卡FAT全攻略

          作者: 時(shí)間:2016-11-10 來(lái)源:網(wǎng)絡(luò) 收藏
          我從一個(gè)月前剛放暑假開(kāi)始弄單片機(jī)讀寫(xiě)SD卡,八月初完成FAT16,已經(jīng)可以寫(xiě)入TXT文件,并可在windows上讀出。由于網(wǎng)上資料比較散,所以一開(kāi)始走了不少?gòu)澛罚F(xiàn)在寫(xiě)一篇總結(jié),將我遇到的問(wèn)題詳細(xì)地列出來(lái),希望能幫助和我一樣的菜鳥(niǎo)們少走彎路。文中說(shuō)到的一些問(wèn)題對(duì)高手而言只是常識(shí)性的,還請(qǐng)包涵。

          第一步:搭電路

          本文引用地址: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 data start token

          for(i=0; i<512; i++){
          SPI_WriteByte(*buffer++);// write data
          }

          SPI_WriteByte(0xFF);// write 16-bit CRC (dummy values)看清楚!兩個(gè)字節(jié)哦!
          SPI_WriteByte(0xFF);

          r1 = SPI_WriteByte(0xFF);// read data response token
          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)前輩們指教!



          關(guān)鍵詞: C8051F020SPI讀寫(xiě)SD

          評(píng)論


          技術(shù)專區(qū)

          關(guān)閉