<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > 菜鳥用C8051F020 SPI讀寫SD卡FAT全攻略

          菜鳥用C8051F020 SPI讀寫SD卡FAT全攻略

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

          第一步:搭電路

          本文引用地址:http://www.ex-cimer.com/article/201611/317269.htm

          我買了一小塊蜂窩板和一個SD插槽,按照標準電路焊接上,由于用的是SPI模式,所以選電路圖的時候要看好,SD卡上的引腳順序不要看錯,912345678,最后兩根挨得很緊,焊接時不要連上了,相關引腳一定要按照要求接上47K的上拉電阻。電路雖然簡單,但一定要確保無誤。還有一點,SD卡座種類不一樣,有位學長買的是彈簧式的,焊完以后剛開始初始化都能成功,放了幾天突然不行了,檢查各引腳都沒問題,最后發(fā)現(xiàn)是卡座的問題,這樣的硬件問題很難發(fā)現(xiàn),還浪費時間,所以卡座還是直接買簡單的好。

          第二步:設置硬件SPI

          我用的是sililab的C8051F020,自帶硬件SPI,如果不帶可以用軟件模擬,關于軟件模擬SPI這塊網(wǎng)上有很多現(xiàn)成的程序。這一歩首先按照020手冊寫了一段程序,當然是將其設為主模式,這時候CONFIG(這個軟件可以視窗化操作C8051F的大多數(shù)寄存器并自動生成代碼)會分配4個引腳,CLK時鐘位,MISO和MOSI兩個數(shù)據(jù)傳輸位,還有一個NSS位,這個腳用不上,不要將其當作CS片選位,CS位選一個普通IO既可。所有從單片機上輸出的引腳都設為推挽輸出。設置完成后最好弄個示波器看一下輸出波形是否和你想象中的一樣,這樣就能確保你的SPI工作沒問題了,這一步也是關鍵,SPI是底層通信的基礎。

          第三步:SD卡初始化

          這一步正式進入單片機調(diào)試SD部分,了解SD卡的時序后(后面我會上傳這部分資料),網(wǎng)上眾說紛紜,還有說要看完178頁英文PDF,我都暈了,這個看完估計我都是專家了。關于初始化命令有很多說法,我發(fā)的是CMD0和CMD1就可以成功初始化。

          解釋一下這個命令格式的含義:這是個一個字節(jié)的命令格式為01xx xxxx 后六位是CMD后面的數(shù)字的二進制值,如CMD1=0100 0001=0x41 程序中寫為CMD | 0x40 CMD代表后面的數(shù)字。

          需要注意初始化時SPI速率不能超過400K,我設的是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ù)可以適當多一些
          } while(r1 != MMC_R1_IDLE_STATE); //正確應答為1

          嘗試發(fā)送的次數(shù)至少為200,有人建議2000的,隨便,如果不穩(wěn)定,比如有時候收的到有時候收不到,就可以適當增大發(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); //正確應答為0

          初始化有這兩個就可以完成了,有的程序中還會加上

          mmcSendCommand(MMC_CRC_ON_OFF, 0);//關CRC校驗

          mmcSendCommand(MMC_SET_BLOCKLEN, 512);//設置塊長度為512字節(jié)

          這個無關緊要,SPI模式下默認沒有CRC校驗的,而且每塊字節(jié)數(shù)就是512,這個塊大小就別改了,你要設置成別的大小,后面加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);// 講解標記(1)
          SPI_WriteByte(0xFF);// 講解標記(2)


          while((r1=SPI_WriteByte(0xFF))==0xFF)if(retry++>8)break;
          return r1;
          }

          arg這個參數(shù)看一下SD的SPI命令格式就知道這個字段是命令的屬性,一般為0

          講解標記(1)

          CRC位這個0x95只對CMD0有意義,發(fā)送其他命令時這個位可為任意值,所以不必修改

          講解標記(2)

          這個容易忽略,不忽略第一個字節(jié)你就可能收不到正確的響應,許多程序中這個叫做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 data start token

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

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

          r1 = SPI_WriteByte(0xFF);// read data response token
          if((r1&MMC_DR_MASK)!=MMC_DR_ACCEPT)return r1;//講解標記(1)
          while(!SPI_WriteByte(0xFF));// wait until card not busy
          CSH;// release chip select
          return 0;
          }

          講解標記(1)

          這個很重要?。?!我在這浪費了一個星期!?。?/p>

          許多程序包括網(wǎng)上的大多資料都說這個回應為0x05,可是我每次都收不到這個回應,收到的是0xE5,本來我以為是程序有問題,其實不然,我查了資料,找到了這個響應令牌的8位的含義,發(fā)現(xiàn)高三位是保留位,而0xE5和0x05低五位是一樣的說明響應是正確的,這個高三位可能由于廠家不同值不一樣。

          這個程序是比較完善的,響應r1與上個MMC_DR_MASK(宏定義值為0x0001 1111)就把高三位與成0了,網(wǎng)上有的程序是沒有這個過程的。

          如果你想驗證只能是否能正常讀寫,可以將值賦進數(shù)組寫入到SD卡的一個扇區(qū)里(這里的扇區(qū)是指物理扇區(qū),這個概念在FAT文件中再說)在用數(shù)組讀出來,在仿真器里看是否一樣,這個過程可能用不了winhex這款軟件,因為你寫入的那個扇區(qū)可能是引導區(qū),造成你將卡插到電腦中會提示你格式化。

          下面是CMD0的波形圖

          原來以為這個波形圖有問題,因為時序圖上片選線在數(shù)據(jù)傳送過程中是一直低的,還在網(wǎng)上問了一陣子,可惜沒人理我,其實是正確的,中間的電平跳變是由于SPI發(fā)送函數(shù)開頭和末位有把片選拉低和拉高,片選線一旦拉高,數(shù)據(jù)線就會跟著變高,所以出現(xiàn)了跳變,我嘗試著把SPI發(fā)送函數(shù)的開頭末位片選去掉,發(fā)現(xiàn)這樣也是可以的。

          第四步:加FAT

          如果上面測試都沒問題,那么底層通信就沒有問題了,到目前為止我們一直是把SD當成一個大的FLASH來操作的,但是要想在電腦上把用單片機寫的程序讀出來就要按照一定規(guī)則往里面寫,這個規(guī)則就是FAT。我用的是2G的金士頓SD卡,正好可以用FAT16,F(xiàn)AT16最大支持2G。

          這一塊的內(nèi)容可以參照http://www.sjhf.net/document/fat/#索引

          里面的講解很詳細,會幫助你理解文件系統(tǒng)

          需要把握的思路是:先用電腦把SD卡格式化成FAT16的(即FAT),然后讀寫的規(guī)則是:找到MBR(主引導區(qū))讀相關字節(jié)得到邏輯引導區(qū)的地址,在邏輯扇區(qū)里讀出BPB數(shù)據(jù),再對FAT表,根目錄和數(shù)據(jù)區(qū)進行對應操作

          這一塊可以用winhex看SD卡的物理扇區(qū)和邏輯扇區(qū),以便對照

          這一塊我講幾個我遇到的問題

          1>>如果是VISTA操作系統(tǒng),你要以管理員身份進入,不然無法看到物理扇區(qū),即鼠標停在winhex的圖標上點右鍵選取以管理身份運行即可(不要笑,我剛開始就不知道應該這樣操作,呵呵)

          2>>有些SD卡是沒有主引導區(qū)即MBR的,這樣更好,邏輯扇區(qū)就和物理扇區(qū)一樣了,那么怎樣判斷有沒有MBR呢?最簡單的你用winhex看一下物理和邏輯扇區(qū),如果數(shù)據(jù)一樣就是沒有MBR了。再有嚴謹一點的方法:注意看PDF中對邏輯引導區(qū)的解釋,邏輯引導區(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>>這里說一些關于編譯的問題,加入FAT部分的程序后,工程中程序文件會比較多,這里要注意重復包含的問題,這一塊網(wǎng)上很多,不再重復。有時候錯誤并不在指針提示的那一行

          比如有時候指的那一行只有int a;這樣的語句,這時候注意往上面看,是不是定義函數(shù)時漏了末尾的分號,造成編譯器將a也當作其形參了,這種錯誤有時候很隱蔽,比如int a;上面只有#include "b.h",這時候就要去b中看看,是不是文件末尾定義的那個函數(shù)后面忘了分號

          還有編譯器報出"segment too large"這時候須把編譯器選項中的Memory Model 中的Variable 設成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的相應路徑即可。

          還有就是有的程序是不支持文件夾嵌套的

          現(xiàn)在我說一個最最重要的問題,也是我遇到的最后一個問題,字節(jié)序問題,請先參考http://blog.csdn.net/sunshine1314/archive/2008/04/20/2309655.aspx

          了解了字節(jié)序,當然這里不用管比特序,如果你用的是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);


          }

          以上就是我遇到的大部分問題了,希望對大家有所幫助,由于水平有限,不足之處還請前輩們指教!



          關鍵詞: C8051F020SPI讀寫SD

          評論


          技術專區(qū)

          關閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();