2010年11月18日 星期四

讀取 SD 記憶卡

本文在 2010/11/18 12:09 發表於 Yahoo!奇摩部落格
因Yahoo!奇摩部落格將於2013年12月26日終止服務故遷移至此


今回要玩的是這東西


這篇是庫存出清, 當兵的時候玩的
現在東西都已經收起來了
板子也確定掛了, 計畫暫停但沒終止
總有一天我會回來 resume 的XD



SD 卡有三種存取模式
SD 4-bit, SD 1-bit, SPI
SD 4-bit 可以有四條資料線和一條指令線
傳輸速度較快
SD 1-bit 只有一條資料線和一條指令線
傳輸速度較慢
不過其他資料線可用於其他用途
像是中斷控制
而 SPI 就只有兩條資料線
一條去, 一條回, 就像鐵路那樣
資料和指令夾雜在一起, 共用這兩條通道
速度又更慢了, 但是只要兩條線就可以傳輸
SD 有兩種外觀

一為標準 SD, 另一為 Micro SD
兩種傳輸方式相同
僅差在 Micro SD 比傳統的少一隻接地腳位
Micro SD 非常小
看起來好像是直接把晶片埋在電路板中
卡身上有電路接線
這種極端小的卡可以縮小產品體積

雖然 SD 模式有許多功能
但是擁有這項傳輸硬體的微控制器多很 "高檔"
那種 ARM9 + DMA 的很容易就有
而 SPI 這種傳輸硬體則是隨處可見
我採用的這顆控制器也有
所以我用這種模式傳輸

線路圖

接線時在資料輸出端一定要有顆 pull-up 電阻
若少了這顆電阻
當沒有 SD 卡接上時
讀出的資料會無法預期
會讓軟體誤以為卡已經初始化成功
可是讀寫卻一直失敗
容易產生大量的重試
浪費時間去判斷

用被我改得亂七八糟的板子實驗


近看


和電腦讀出的卡上資料比較


從第 245 個 sector 開始有資料
並不是從第一個 sector 開始寫
這花了我一些時間找這個起始點

這是 FAT 檔案系統的開機磁區
可以看到最後兩個 byte 有識別符號 55 AA
把這個 sector 的資料依照 FAT 規範的 sector 組成格式讀取
就可以得知這個槽裡面的資訊
這還需要一些時間研究

老樣子, 不提供完整程式, 僅留下重點
除非你有我這張亂改的版子才能用XD
若沒有, 給程式並無意義

首先, 初始化:
先初始化自己控制器上的 SPI 硬體
啟動它, 設定相關參數, 清空傳輸 FIFO
一開始先以較慢的速度傳輸
初始化 SD 卡時速度要放慢
開始傳資料時才可調快

SD 卡操作可參考 www.sdcard.org 發佈的 Physical Layer Simplified Specification Version 2.00
這裡記下重點

SD 卡指令由 6 個 byte 組成
第一個為指令代碼, 6-bit
和 0x40 做 OR 成 1 byte 後送出
所以 CMD8 代表第 8 號指令, 送出 0x48 作為第一個 byte
第 2-5 個 byte 構成一 32-bit 參數
最後一個 byte 為 CRC 值
將這整個指令做 CRC 後的數存在最後
用來檢查傳輸過程是否受干擾導致內容錯誤
以 SPI 傳輸模式時
只有 CMD0 和 CMD8 會檢查 CRC
應該是考量到 SPI 硬體沒有 CRC 功能
若用 CPU 去算會很耗時
只有在常重要的指令才檢查
所以我們程式可以偷懶
用個判斷式判斷指令

if(CMD8){
    crc = 0x87;
}else if(CMD0){
    crc = 0x95;
}else{
    crc = 0x0;
}

在發現 CMD8 時 CRC 就填 0x87, 發現 CMD0 時則填 0x95, 其他則隨便
或是乾脆只檢查 CMD8, 其他指令 CRC 全部填 0x95
送出指令後要等待回應
SPI 是資料進出時都靠 SCK 信號進行同步
SCK 由 master 發送
收發資料時都要 SCK 來觸發
因此接收時就是送沒有意義的資料 (dummy byte)
觸發 SPI 硬體去送 SCK
此時若 SD 卡有東西要回來
它會順便被這 SCK 給 "shift" 出來
所以我們就是丟垃圾出去
然後去接收緩衝那裡看看有沒有垃圾(?)進來XD
因此將 SD 卡的 DAT0 加裝 pull-up 電阻是很重要的
當 SD 卡沒資料, 或是正在忙碌無回應時
這 pull-up 可以讓傳回的資料永遠是 0xFF
讓我們知道這是垃圾, 不要理它
傳回來的東西若不是垃圾
有可能會是 card status, 識別用 token, 卡的規格...等
依送出的指令會有所不同
若是 card status 則為一 32-bit 整數
透過設定 bit 來標示錯誤或是狀態
而這狀態通常 bit 設定為 0 代表正常
因此我們不能用 pull-down 電阻接在 DAT0 上
那會變成指令永遠成功, 會更亂

接著回到初始化程序
當系統電源一啟動時
先將 CS 拉低電位, 送出 CMD0, 參數為 0, 將 SD 卡設定為 SPI 傳輸
接著丟垃圾等回應
由於每張卡的速度不會都一樣
比較早期的卡一定會跑得比較慢
不要只等一次回應發現是 0xFF 就認定失敗
多等幾次, 我這裡是設定 255 次
對岸還有人說有時連 CMD0 都會漏接
所以可以考慮發現 255 次以後都沒回應時再丟一次 CMD0
重複個 2-3 次後再放棄, 再判定沒有 SD 卡在插槽裡
由於這 SCK 震盪的很快
其實 255 次重試一閃就過去了
多重複個 2-3 次並不會太久
CMD0 的正常回覆是 0x01
收到 0x01 就表示已進入 SPI 模式
接著送 CMD8 詢問 SD 卡本地的電壓是否符合 SD 卡需求
以我的系統 3.3v 來說是送 0x000001AA 作為參數
1 代表 2.7-3.6v, AA 代表 check pattern, 用途目前我不清楚
傳回值若為 1 為正常, 且為版本 2 的卡
若沒回應有可能是電壓不正確, 或是版本 1 的卡

這裡若沒回應則送 ACMD41 看看有沒有回應
ACMD 和 CMD 送的方法相同
但是一定要先送 CMD55
ACMD 是 application specific command
會依廠商或產品有變動
玩法是這樣的:
假設要送 ACMD 41
就先送 CMD55, 參數 0, 回應 0
緊接著就送 CMD41
這個 CMD41 就會被識別為 ACMD41
參數和回應則依該 ACMD 為準
若 CMD8 無回應, 就送 CMD55 + ACMD41
ACMD41 參數 0, 傳回 1 為正常
參數 0 目的是將 HCS bit 設為 0
既然是版本 1 就不會有 High Capacity, 所以關閉 HCS bit
若到這裡 ACMD41 還是沒有回應則表示沒有 SD 卡

若是版本 2 的卡, 且 CMD8 有回應
還是要送 ACMD41
只是目的相反, 將 HCS bit 設為 1
接著送 CMD58 可以取得這張卡的規格
有需要就看一下, 沒需要不送這指令也行
這樣就完成了初始化的動作

目前我還沒有弄出檔案系統
胡亂寫會打亂裡面的檔案
會使得我的 SD 卡沒法用桌上型電腦放資料進去
所以我僅實驗讀取部分
讀取資料用 CMD17
參數填入 block 的位址
block 位址有人說要這樣填 :

ad_h = (addr & 0xFFC0) >> 7;
ad_l = (addr & 0x003F) << 9;
 
cmd_buf[1] = ad_h >> 0x08;
cmd_buf[2] = ad_h & 0xFF;
cmd_buf[3] = ad_l >> 0x08;
cmd_buf[4] = ad_l & 0xFF;

cmd_buf 為用來存指令的 6-byte 陣列
addr 為 block 位址
可是我直接依序放入
像這樣 :

cmd_buf[1] = addr >> 24;
cmd_buf[2] = (addr >> 16) & 0xFF;
cmd_buf[3] = (addr >> 8) & 0xFF;
cmd_buf[4] = addr & 0xFF;

它才會動
我的是版本 2 的卡
可能不同版本的卡玩法不一樣
這還需要多啃幾趟 spec 才會了解

版本 1 的卡好像可以只讀 block 中的一小塊資料
而版本 2 的卡則是讀資料每次至少讀一個 block, 通常為 512 bytes
所以如果要跑檔案系統
微控制器最少要有 512 bytes 以上的記憶體空間比較好處理資料
不過這不是問題
很多 8-bit 微控制器的記憶體都大於這數
16-bit 或 32-bit 就更不用說了
送出 CMD17 後等待資料開始的識別 token
是一個值為 0xFE 的 byte
收到這 byte 後之後就收 512 個 byte 的資料
最後再收 2 個 byte 的 CRC
這樣就是一個 block 的傳輸

參考資料 :
SD/SDHC Card Interfacing with ATmega8 /32 (FAT32 implementation)
有程式 + 線路圖
用麵包板插線, 非常熱血XD

SD 卡的使用算是有點複雜的
在沒有專用硬體下不太好搞
如果只是為特定一張卡設計可能還好
要相容所有卡就很有難度了
好像有廠商把這些程序包成函式庫來賣
做產品的時候可以買來節省開發時程
讀取檔案系統的函式庫也有人賣
不過對於我等熱血青年還是自己試試才是正道啊 ! <-自討苦吃XD

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。