第36章 SDIO—SD卡讀寫測試
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx參考手冊》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》以及SD簡易規格文件《Physical Layer Simplified Specification V2.0》(版本號:2.00)。
特別說明,本書內容是以STM32F42x系列控制器資源講解。
閱讀本章內容之前,建議先閱讀SD簡易規格文件。
36.1 SDIO簡介
SD卡(Secure Digital Memory Card)在我們生活中已經非常普遍了,控制器對SD卡進行讀寫通信操作一般有兩種通信接口可選,一種是SPI接口,另外一種就是SDIO接口。SDIO全稱是安全數字輸入/輸出接口,多媒體卡(MMC)、SD卡、SD I/O卡都有SDIO接口。STM32F42x系列控制器有一個SDIO主機接口,它可以與MMC卡、SD卡、SD I/O卡以及CE-ATA設備進行數據傳輸。MMC卡可以說是SD卡的前身,現階段已經用得很少。SD I/O卡本身不是用於存儲的卡,它是指利用SDIO傳輸協議的一種外設。比如Wi-Fi Card,它主要是提供Wi-Fi功能,有些Wi-Fi模塊是使用串口或者SPI接口進行通信的,但Wi-Fi SDIO Card是使用SDIO接口進行通信的。並且一般設計SD I/O卡是可以插入到SD的插槽。CE-ATA是專為輕薄筆記本硬盤設計的硬盤高速通訊接口。
多媒體卡協會網站www.mmca.org中提供了有MMCA技術委員會發布的多媒體卡系統規范。
SD卡協會網站www.sdcard.org中提供了SD存儲卡和SDIO卡系統規范。
CE-ATA工作組網站www.ce-ata.org中提供了CE_ATA系統規范。
隨之科技發展,SD卡容量需求越來越大,SD卡發展到現在也是有幾個版本的,關於SDIO接口的設備整體概括見圖 361。
圖 361 SDIO接口的設備
關於SD卡和SD I/O部分內容可以在SD協會網站獲取到詳細的介紹,比如各種SD卡尺寸規則、讀寫速度標示方法、應用擴展等等信息。
本章內容針對SD卡使用講解,對於其他類型卡的應用可以參考相關系統規范實現,所以對於控制器中針對其他類型卡的內容可能在本章中簡單提及或者被忽略,本章內容不區分SDIO和SD卡這兩個概念。即使目前SD協議提供的SD卡規范版本最新是4.01版本,但STM32F42x系列控制器只支持SD卡規范版本2.0,即只支持標准容量SD和高容量SDHC標准卡,不支持超大容量SDXC標准卡,所以可以支持的最高卡容量是32GB。
36.2 SD卡物理結構
一張SD卡包括有存儲單元、存儲單元接口、電源檢測、卡及接口控制器和接口驅動器5個部分,見圖 362。存儲單元是存儲數據部件,存儲單元通過存儲單元接口與卡控制單元進行數據傳輸;電源檢測單元保證SD卡工作在合適的電壓下,如出現掉電或上狀態時,它會使控制單元和存儲單元接口復位;卡及接口控制單元控制SD卡的運行狀態,它包括有8個寄存器;接口驅動器控制SD卡引腳的輸入輸出。
圖 362 SD卡物理結構
SD卡總共有8個寄存器,用於設定或表示SD卡信息,參考表 361。這些寄存器只能通過對應的命令訪問,對SD卡進行控制操作並不是像操作控制器GPIO相關寄存器那樣一次讀寫一個寄存器的,它是通過命令來控制,SDIO定義了64個命令,每個命令都有特殊意義,可以實現某一特定功能,SD卡接收到命令后,根據命令要求對SD卡內部寄存器進行修改,程序控制中只需要發送組合命令就可以實現SD卡的控制以及讀寫操作。
表 361 SD卡寄存器
名稱 |
bit寬度 |
描述 |
CID |
128 |
卡識別號(Card identification number):用來識別的卡的個體號碼(唯一的) |
RCA |
16 |
相對地址(Relative card address):卡的本地系統地址,初始化時,動態地由卡建議,主機核准。 |
DSR |
16 |
驅動級寄存器(Driver Stage Register):配置卡的輸出驅動 |
CSD |
128 |
卡的特定數據(Card Specific Data):卡的操作條件信息 |
SCR |
64 |
SD配置寄存器(SD Configuration Register):SD 卡特殊特性信息 |
OCR |
32 |
操作條件寄存器(Operation conditions register) |
SSR |
512 |
SD狀態(SD Status):SD卡專有特征的信息 |
CSR |
32 |
卡狀態(Card Status):卡狀態信息 |
每個寄存器位的含義可以參考SD簡易規格文件《Physical Layer Simplified Specification V2.0》第5章內容。
36.3 SDIO總線
36.3.1 總線拓撲
SD卡一般都支持SDIO和SPI這兩種接口,本章內容只介紹SDIO接口操作方式,如果需要使用SPI操作方式可以參考SPI相關章節。另外,STM32F42x系列控制器的SDIO是不支持SPI通信模式的,如果需要用到SPI通信只能使用SPI外設。
SD卡總線拓撲參考圖 363。雖然可以共用總線,但不推薦多卡槽共用總線信號,要求一個單獨SD總線應該連接一個單獨的SD卡。
圖 363 SD卡總線拓撲
SD卡使用9-pin接口通信,其中3根電源線、1根時鍾線、1根命令線和4根數據線,具體說明如下:
CLK:時鍾線,由SDIO主機產生,即由STM32控制器輸出;
CMD:命令控制線,SDIO主機通過該線發送命令控制SD卡,如果命令要求SD卡提供應答(響應),SD卡也是通過該線傳輸應答信息;
D0-3:數據線,傳輸讀寫數據;SD卡可將D0拉低表示忙狀態;
VDD、VSS1、VSS2:電源和地信號。
在之前的I2C以及SPI章節都有詳細講解了對應的通信時序,實際上,SDIO的通信時序簡單許多,SDIO不管是從主機控制器向SD卡傳輸,還是SD卡向主機控制器傳輸都只以CLK時鍾線的上升沿為有效。SD卡操作過程會使用兩種不同頻率的時鍾同步數據,一個是識別卡階段時鍾頻率FOD,最高為400kHz,另外一個是數據傳輸模式下時鍾頻率FPP,默認最高為25MHz,如果通過相關寄存器配置使SDIO工作在高速模式,此時數據傳輸模式最高頻率為50MHz。
對於STM32控制器只有一個SDIO主機,所以只能連接一個SDIO設備,開發板上集成了一個Micro SD卡槽和SDIO接口的WiFi模塊,要求只能使用其中一個設備。SDIO接口的WiFi模塊一般集成有使能線,如果需要用到SD卡需要先控制該使能線禁用WiFi模塊。
36.3.2 總線協議
SD總線通信是基於命令和數據傳輸的。通訊由一個起始位("0"),由一個停止位("1")終止。SD通信一般是主機發送一個命令(Command),從設備在接收到命令后作出響應(Response),如有需要會有數據(Data)傳輸參與。
SD總線的基本交互是命令與響應交互,見圖 364。
圖 364 命令與響應交互
SD數據是以塊(Black)形式傳輸的,SDHC卡數據塊長度一般為512字節,數據可以從主機到卡,也可以是從卡到主機。數據塊需要CRC位來保證數據傳輸成功。CRC位由SD卡系統硬件生成。STM32控制器可以控制使用單線或4線傳輸,本開發板設計使用4線傳輸。圖 365為主機向SD卡寫入數據塊操作示意。
圖 365 多塊寫入操作
SD數據傳輸支持單塊和多塊讀寫,它們分別對應不同的操作命令,多塊寫入還需要使用命令來停止整個寫入操作。數據寫入前需要檢測SD卡忙狀態,因為SD卡在接收到數據后編程到存儲區過程需要一定操作時間。SD卡忙狀態通過把D0線拉低表示。
數據塊讀操作與之類似,只是無需忙狀態檢測。
使用4數據線傳輸時,每次傳輸4bit數據,每根數據線都必須有起始位、終止位以及CRC位,CRC位每根數據線都要分別檢查,並把檢查結果匯總然后在數據傳輸完后通過D0線反饋給主機。
SD卡數據包有兩種格式,一種是常規數據(8bit寬),它先發低字節再發高字節,而每個字節則是先發高位再發低位,4線傳輸示意如圖 366。
圖 366 8位寬數據包傳輸
4線同步發送,每根線發送一個字節的其中兩個位,數據位在四線順序排列發送,DAT3數據線發較高位,DAT0數據線發較低位。
另外一種數據包發送格式是寬位數據包格式,對SD卡而言寬位數據包發送方式是針對SD卡SSR(SD狀態)寄存器內容發送的,SSR寄存器總共有512bit,在主機發出ACMD13命令后SD卡將SSR寄存器內容通過DAT線發送給主機。寬位數據包格式示意見圖 367。
圖 367 寬位數據包傳輸
36.3.3 命令
SD命令由主機發出,以廣播命令和尋址命令為例,廣播命令是針對與SD主機總線連接的所有從設備發送的,尋址命令是指定某個地址設備進行命令傳輸。
1. 命令格式
SD命令格式固定為48bit,都是通過CMD線連續傳輸的(數據線不參與),見圖 368。
圖 368 SD命令格式
SD命令的組成如下:
起始位和終止位:命令的主體包含在起始位與終止位之間,它們都只包含一個數據位,起始位為0,終止位為1。
傳輸標志:用於區分傳輸方向,該位為1時表示命令,方向為主機傳輸到SD卡,該位為0時表示響應,方向為SD卡傳輸到主機。
命令主體內容包括命令、地址信息/參數和CRC校驗三個部分。
命令號:它固定占用6bit,所以總共有64個命令(代號:CMD0~CMD63),每個命令都有特定的用途,部分命令不適用於SD卡操作,只是專門用於MMC卡或者SD I/O卡。
地址/參數:每個命令有32bit地址信息/參數用於命令附加內容,例如,廣播命令沒有地址信息,這32bit用於指定參數,而尋址命令這32bit用於指定目標SD卡的地址。
CRC7校驗:長度為7bit的校驗位用於驗證命令傳輸內容正確性,如果發生外部干擾導致傳輸數據個別位狀態改變將導致校准失敗,也意味着命令傳輸失敗,SD卡不執行命令。
2. 命令類型
SD命令有4種類型:
無響應廣播命令(bc),發送到所有卡,不返回任務響應;
帶響應廣播命令(bcr),發送到所有卡,同時接收來自所有卡響應;
尋址命令(ac),發送到選定卡,DAT線無數據傳輸;
尋址數據傳輸命令(adtc),發送到選定卡,DAT線有數據傳輸。
另外,SD卡主機模塊系統旨在為各種應用程序類型提供一個標准接口。在此環境中,需要有特定的客戶/應用程序功能。為實現這些功能,在標准中定義了兩種類型的通用命令:特定應用命令(ACMD)和常規命令(GEN_CMD)。要使用SD卡制造商特定的ACMD命令如ACMD6,需要在發送該命令之前無發送CMD55命令,告知SD卡接下來的命令為特定應用命令。CMD55命令只對緊接的第一個命令有效,SD卡如果檢測到CMD55之后的第一條命令為ACMD則執行其特定應用功能,如果檢測發現不是ACMD命令,則執行標准命令。
3. 命令描述
SD卡系統的命令被分為多個類,每個類支持一種"卡的功能設置"。表 362列舉了SD卡部分命令信息,更多詳細信息可以參考SD簡易規格文件說明,表中填充位和保留位都必須被設置為0。
雖然沒有必須完全記住每個命令詳細信息,但越熟悉命令對后面編程理解非常有幫助。
表 362 SD部分命令描述
命令序號 |
類型 |
參數 |
響應 |
縮寫 |
描述 |
基本命令(Class 0) |
|||||
CMD0 |
bc |
[31:0]填充位 |
- |
GO_IDLE_STATE |
復位所有的卡到idle狀態。 |
CMD2 |
bcr |
[31:0]填充位 |
R2 |
ALL_SEND_CID |
通知所有卡通過CMD線返回CID值。 |
CMD3 |
bcr |
[31:0]填充位 |
R6 |
SEND_RELATIVE_ADDR |
通知所有卡發布新RCA。 |
CMD4 |
bc |
[31:16]DSR[15:0]填充位 |
- |
SET_DSR |
編程所有卡的DSR。 |
CMD7 |
ac |
[31:16]RCA[15:0]填充位 |
R1b |
SELECT/DESELECT_CARD |
選擇/取消選擇RCA地址卡。 |
CMD8 |
bcr |
[31:12]保留位[11:8]VHS[7:0]檢查模式 |
R7 |
SEND_IF_COND |
發送SD卡接口條件,包含主機支持的電壓信息,並詢問卡是否支持。 |
CMD9 |
ac |
[31:16]RCA[15:0]填充位 |
R2 |
SEND_CSD |
選定卡通過CMD線發送CSD內容 |
CMD10 |
ac |
[31:16]RCA[15:0]填充位 |
R2 |
SEND_CID |
選定卡通過CMD線發送CID內容 |
CMD12 |
ac |
[31:0]填充位 |
R1b |
STOP_TRANSMISSION |
強制卡停止傳輸 |
CMD13 |
ac |
[31:16]RCA[15:0]填充位 |
R1 |
SEND_STATUS |
選定卡通過CMD線發送它狀態寄存器 |
CMD15 |
ac |
[31:16]RCA[15:0]填充位 |
- |
GO_INACTIVE_STATE |
使選定卡進入"inactive"狀態 |
面向塊的讀操作(Class 2) |
|||||
CMD16 |
ac |
[31:0]塊長度 |
R1 |
SET_BLOCK_LEN |
對於標准SD卡,設置塊命令的長度,對於SDHC卡塊命令長度固定為512字節。 |
CMD17 |
adtc |
[31:0]數據地址 |
R1 |
READ_SINGLE_BLOCK |
對於標准卡,讀取SEL_BLOCK_LEN長度字節的塊;對於SDHC卡,讀取512字節的塊。 |
CMD18 |
adtc |
[31:0]數據地址 |
R1 |
READ_MULTIPLE_BLOCK |
連續從SD卡讀取數據塊,直到被CMD12中斷。塊長度同CMD17。 |
面向塊的寫操作(Class 4) |
|||||
CMD24 |
adtc |
[31:0]數據地址 |
R1 |
WRITE_BLOCK |
對於標准卡,寫入SEL_BLOCK_LEN長度字節的塊;對於SDHC卡,寫入512字節的塊。 |
CMD25 |
adtc |
[31:0]數據地址 |
R1 |
WRITE_MILTIPLE_BLOCK |
連續向SD卡寫入數據塊,直到被CMD12中斷。每塊長度同CMD17。 |
CMD27 |
adtc |
[31:0]填充位 |
R1 |
PROGRAM_CSD |
對CSD的可編程位進行編程 |
擦除命令(Class 5) |
|||||
CMD32 |
ac |
[31:0]數據地址 |
R1 |
ERASE_WR_BLK_START |
設置擦除的起始塊地址 |
CMD33 |
ac |
[31:0]數據地址 |
R1 |
ERASE_WR_BLK_END |
設置擦除的結束塊地址 |
CMD38 |
ac |
[31:0]填充位 |
R1b |
ERASE |
擦除預先選定的塊 |
加鎖命令(Class 7) |
|||||
CMD42 |
adtc |
[31:0]保留 |
R1 |
LOCK_UNLOCK |
加鎖/解鎖SD卡 |
特定應用命令(Class 8) |
|||||
CMD55 |
ac |
[31:16]RCA[15:0]填充位 |
R1 |
APP_CMD |
指定下個命令為特定應用命令,不是標准命令 |
CMD56 |
adtc |
[31:1]填充位[0]讀/寫 |
R1 |
GEN_CMD |
通用命令,或者特定應用命令中,用於傳輸一個數據塊,最低位為1表示讀數據,為0表示寫數據 |
SD卡特定應用命令 |
|||||
ACMD6 |
ac |
[31:2]填充位[1:0]總線寬度 |
R1 |
SET_BUS_WIDTH |
定義數據總線寬度('00'=1bit,'10'=4bit)。 |
ACMD13 |
adtc |
[31:0]填充位 |
R1 |
SD_STATUS |
發送SD狀態 |
ACMD41 |
Bcr |
[32]保留位[30]HCS(OCR[30]) [29:24]保留位[23:0]VDD電壓(OCR[23:0]) |
R3 |
SD_SEND_OP_COND |
主機要求卡發送它的支持信息(HCS)和OCR寄存器內容。 |
ACMD51 |
adtc |
[31:0]填充位 |
R1 |
SEND_SCR |
讀取配置寄存器SCR |
36.3.4 響應
響應由SD卡向主機發出,部分命令要求SD卡作出響應,這些響應多用於反饋SD卡的狀態。SDIO總共有7個響應類型(代號:R1~R7),其中SD卡沒有R4、R5類型響應。特定的命令對應有特定的響應類型,比如當主機發送CMD3命令時,可以得到響應R6。與命令一樣,SD卡的響應也是通過CMD線連續傳輸的。根據響應內容大小可以分為短響應和長響應。短響應是48bit長度,只有R2類型是長響應,其長度為136bit。各個類型響應具體情況如表 363。
除了R3類型之外,其他響應都使用CRC7校驗來校驗,對於R2類型是使用CID和CSD寄存器內部CRC7。
表 363 SD卡響應類型
R1(正常響應命令) |
||||||||
描述 |
起始位 |
傳輸位 |
命令號 |
卡狀態 |
CRC7 |
終止位 |
||
Bit |
47 |
46 |
[45:40] |
[39:8] |
[7:1] |
0 |
||
位寬 |
1 |
1 |
6 |
32 |
7 |
1 |
||
值 |
"0" |
"0" |
x |
x |
x |
"1" |
||
備注 |
如果有傳輸到卡的數據,那么在數據線可能有busy信號 |
|||||||
R2(CID,CSD寄存器) |
||||||||
描述 |
起始位 |
傳輸位 |
保留 |
[127:1] |
終止位 |
|||
Bit |
135 |
134 |
[133:128] |
127 |
0 |
|||
位寬 |
1 |
1 |
6 |
x |
1 |
|||
值 |
"0" |
"0" |
"111111" |
CID或者CSD寄存器[127:1]位的值 |
"1" |
|||
備注 |
CID寄存器內容作為CMD2和CMD10響應,CSD寄存器內容作為CMD9響應。 |
|||||||
R3(OCR寄存器) |
||||||||
描述 |
起始位 |
傳輸位 |
保留 |
OCR寄存器 |
保留 |
終止位 |
||
Bit |
47 |
46 |
[45:40] |
[39:8] |
[7:1] |
0 |
||
位寬 |
1 |
1 |
6 |
32 |
7 |
1 |
||
值 |
"0" |
"0" |
"111111" |
x |
"1111111" |
"1" |
||
備注 |
OCR寄存器的值作為ACMD41的響應 |
|||||||
R6(發布的RCA寄存器響應) |
||||||||
描述 |
起始位 |
傳輸位 |
CMD3 |
RCA寄存器 |
卡狀態位 |
CRC7 |
終止位 |
|
Bit |
47 |
46 |
[45:40] |
[39:8] |
[7:1] |
0 |
||
位寬 |
1 |
1 |
6 |
16 |
16 |
7 |
1 |
|
值 |
"0" |
"0" |
"000011" |
x |
x |
x |
"1" |
|
備注 |
專用於命令CMD3的響應 |
|||||||
R7(發布的RCA寄存器響應) |
||||||||
描述 |
起始位 |
傳輸位 |
CMD8 |
保留 |
接收電壓 |
檢測模式 |
CRC7 |
終止位 |
Bit |
47 |
46 |
[45:40] |
[39:20] |
[19:16] |
[15:8] |
[7:1] |
0 |
位寬 |
1 |
1 |
6 |
20 |
4 |
8 |
7 |
1 |
值 |
"0" |
"0" |
"001000" |
"00000h" |
x |
x |
x |
"1" |
備注 |
專用於命令CMD8的響應,返回卡支持電壓范圍和檢測模式 |
36.4 SD卡的操作模式及切換
36.4.1 SD卡的操作模式
SD卡有多個版本,STM32控制器目前最高支持《Physical Layer Simplified Specification V2.0》定義的SD卡,STM32控制器對SD卡進行數據讀寫之前需要識別卡的種類:V1.0標准卡、V2.0標准卡、V2.0高容量卡或者不被識別卡。
SD卡系統(包括主機和SD卡)定義了兩種操作模式:卡識別模式和數據傳輸模式。在系統復位后,主機處於卡識別模式,尋找總線上可用的SDIO設備;同時,SD卡也處於卡識別模式,直到被主機識別到,即當SD卡接收到SEND_RCA(CMD3)命令后,SD卡就會進入數據傳輸模式,而主機在總線上所有卡被識別后也進入數據傳輸模式。在每個操作模式下,SD卡都有幾種狀態,參考表 364,通過命令控制實現卡狀態的切換。
表 364 SD卡狀態與操作模式
操作模式 |
SD卡狀態 |
無效模式(Inactive) |
無效狀態(Inactive State) |
卡識別模式(Card identification mode) |
空閑狀態(Idle State) |
准備狀態(Ready State) |
|
識別狀態(Identification State) |
|
數據傳輸模式(Data transfer mode) |
待機狀態(Stand-by State) |
傳輸狀態(Transfer State) |
|
發送數據狀態(Sending-data State) |
|
接收數據狀態(Receive-data State) |
|
編程狀態(Programming State) |
|
斷開連接狀態(Disconnect State) |
36.4.2 卡識別模式
在卡識別模式下,主機會復位所有處於"卡識別模式"的SD卡,確認其工作電壓范圍,識別SD卡類型,並且獲取SD卡的相對地址(卡相對地址較短,便於尋址)。在卡識別過程中,要求SD卡工作在識別時鍾頻率FOD的狀態下。卡識別模式下SD卡狀態轉換如圖 369。
圖 369 卡識別模式狀態轉換圖
主機上電后,所有卡處於空閑狀態,包括當前處於無效狀態的卡。主機也可以發送GO_IDLE_STATE(CMD0)讓所有卡軟復位從而進入空閑狀態,但當前處於無效狀態的卡並不會復位。
主機在開始與卡通信前,需要先確定雙方在互相支持的電壓范圍內。SD卡有一個電壓支持范圍,主機當前電壓必須在該范圍可能才能與卡正常通信。SEND_IF_COND(CMD8)命令就是用於驗證卡接口操作條件的(主要是電壓支持)。卡會根據命令的參數來檢測操作條件匹配性,如果卡支持主機電壓就產生響應,否則不響應。而主機則根據響應內容確定卡的電壓匹配性。CMD8是SD卡標准V2.0版本才有的新命令,所以如果主機有接收到響應,可以判斷卡為V2.0或更高版本SD卡。
SD_SEND_OP_COND(ACMD41)命令可以識別或拒絕不匹配它的電壓范圍的卡。ACMD41命令的VDD電壓參數用於設置主機支持電壓范圍,卡響應會返回卡支持的電壓范圍。對於對CMD8有響應的卡,把ACMD41命令的HCS位設置為1,可以測試卡的容量類型,如果卡響應的CCS位為1說明為高容量SD卡,否則為標准卡。卡在響應ACMD41之后進入准備狀態,不響應ACMD41的卡為不可用卡,進入無效狀態。ACMD41是應用特定命令,發送該命令之前必須先發CMD55。
ALL_SEND_CID(CMD2)用來控制所有卡返回它們的卡識別號(CID),處於准備狀態的卡在發送CID之后就進入識別狀態。之后主機就發送SEND_RELATIVE_ADDR(CMD3)命令,讓卡自己推薦一個相對地址(RCA)並響應命令。這個RCA是16bit地址,而CID是128bit地址,使用RCA簡化通信。卡在接收到CMD3並發出響應后就進入數據傳輸模式,並處於待機狀態,主機在獲取所有卡RCA之后也進入數據傳輸模式。
36.4.3 數據傳輸模式
只有SD卡系統處於數據傳輸模式下才可以進行數據讀寫操作。數據傳輸模式下可以將主機SD時鍾頻率設置為FPP,默認最高為25MHz,頻率切換可以通過CMD4命令來實現。數據傳輸模式下,SD卡狀態轉換過程見圖 3610。
圖 3610 數據傳輸模式卡狀態轉換
CMD7用來選定和取消指定的卡,卡在待機狀態下還不能進行數據通信,因為總線上可能有多個卡都是出於待機狀態,必須選擇一個RCA地址目標卡使其進入傳輸狀態才可以進行數據通信。同時通過CMD7命令也可以讓已經被選擇的目標卡返回到待機狀態。
數據傳輸模式下的數據通信都是主機和目標卡之間通過尋址命令點對點進行的。卡處於傳輸狀態下可以使用表 362中面向塊的讀寫以及擦除命令對卡進行數據讀寫、擦除。CMD12可以中斷正在進行的數據通信,讓卡返回到傳輸狀態。CMD0和CMD15會中止任何數據編程操作,返回卡識別模式,這可能導致卡數據被損壞。
36.5 STM32的SDIO功能框圖
STM32控制器有一個SDIO,由兩部分組成:SDIO適配器和APB2接口,見圖 3611。SDIO適配器提供SDIO主機功能,可以提供SD時鍾、發送命令和進行數據傳輸。APB2接口用於控制器訪問SDIO適配器寄存器並且可以產生中斷和DMA請求信號。
圖 3611 SDIO功能框圖
SDIO使用兩個時鍾信號,一個是SDIO適配器時鍾(SDIOCLK=48MHz),另外一個是APB2總線時鍾(PCLK2,一般為90MHz)。
STM32控制器的SDIO是針對MMC卡和SD卡的主設備,所以預留有8根數據線,對於SD卡最多用四根數據線。
SDIO適配器是SD卡系統主機部分,是STM32控制器與SD卡數據通信中間設備。SDIO適配器由五個單元組成,分別是控制單元、命令路徑單元、數據路徑單元、寄存器單元以及FIFO,見圖 3612。
圖 3612 SDIO適配器框圖
1. 控制單元
控制單元包含電源管理和時鍾管理功能,結構如圖 3613。電源管理部件會在系統斷電和上電階段禁止SD卡總線輸出信號。時鍾管理部件控制CLK線時鍾信號生成。一般使用SDIOCLK分頻得到。
圖 3613 SDIO適配器控制單元
2. 命令路徑
命令路徑控制命令發送,並接收卡的響應,結構見圖 3614。
圖 3614 SDIO適配器命令路徑
關於SDIO適配器狀態轉換流程可以參考圖 369,當SD卡處於某一狀態時,SDIO適配器必然處於特定狀態與之對應。STM32控制器以命令路徑狀態機(CPSM)來描述SDIO適配器的狀態變化,並加入了等待超時檢測功能,以便退出永久等待的情況。CPSM的描述見圖 3615。
圖 3615 CPSM狀態機描述圖
3. 數據路徑
數據路徑部件負責與SD卡相互數據傳輸,內部結構見圖 3616。
圖 3616 SDIO適配器數據路徑
SD卡系統數據傳輸狀態轉換參考圖 3610,SDIO適配器以數據路徑狀態機(DPSM)來描述SDIO適配器狀態變化情況。並加入了等待超時檢測功能,以便退出永久等待情況。發送數據時,DPSM處於等待發送(Wait_S)狀態,如果數據FIFO不為空,DPSM變成發送狀態並且數據路徑部件啟動向卡發送數據。接收數據時,DPSM處於等待接收狀態,當DPSM收到起始位時變成接收狀態,並且數據路徑部件開始從卡接收數據。DPSM狀態機描述見圖 3617。
圖 3617 DPSM狀態機描述圖
4. 數據FIFO
數據FIFO(先進先出)部件是一個數據緩沖器,帶發送和接收單元。控制器的FIFO包含寬度為32bit、深度為32字的數據緩沖器和發送/接收邏輯。其中SDIO狀態寄存器(SDIO_STA)的TXACT位用於指示當前正在發送數據,RXACT位指示當前正在接收數據,這兩個位不可能同時為1。
當TXACT為1時,可以通過APB2接口將數據寫入到傳輸FIFO。
當RXACT為1時,接收FIFO存放從數據路徑部件接收到的數據。
根據FIFO空或滿狀態會把SDIO_STA寄存器位值1,並可以產生中斷和DMA請求。
5. 適配器寄存器
適配器寄存器包含了控制SDIO外設的各種控制寄存器及狀態寄存器,內容較多,可以通過SDIO提供的各種結構體來了解,這些寄存器的功能都被整合到了結構體或ST標准庫之中。
36.6 SDIO初始化結構體
標准庫函數對SDIO外設建立了三個初始化結構體,分別為SDIO初始化結構體SDIO_InitTypeDef、SDIO命令初始化結構體SDIO_CmdInitTypeDef和SDIO數據初始化結構體SDIO_DataInitTypeDef。這些結構體成員用於設置SDIO工作環境參數,並由SDIO相應初始化配置函數或功能函數調用,這些參數將會被寫入到SDIO相應的寄存器,達到配置SDIO工作環境的目的。
初始化結構體和初始化庫函數配合使用是標准庫精髓所在,理解了初始化結構體每個成員意義基本上就可以對該外設運用自如了。初始化結構體定義在stm32f4xx_sdio.h文件中,初始化庫函數定義在stm32f4xx_sdio.c文件中,編程時我們可以結合這兩個文件內注釋使用。
SDIO初始化結構體用於配置SDIO基本工作環境,比如時鍾分頻、時鍾沿、數據寬度等等。它被SDIO_Init函數使用。
代碼清單 361 SDIO初始化結構體
1 typedef struct {
2 uint32_t SDIO_ClockEdge; // 時鍾沿
3 uint32_t SDIO_ClockBypass; // 旁路時鍾
4 uint32_t SDIO_ClockPowerSave; // 節能模式
5 uint32_t SDIO_BusWide; // 數據寬度
6 uint32_t SDIO_HardwareFlowControl; // 硬件流控制
7 uint8_t SDIO_ClockDiv; // 時鍾分頻
8 } SDIO_InitTypeDef;
各結構體成員的作用介紹如下:
(1) SDIO_ClockEdge:主時鍾SDIOCLK產生CLK引腳時鍾有效沿選擇,可選上升沿或下降沿,它設定SDIO時鍾控制寄存器(SDIO_CLKCR)的NEGEDGE位的值,一般選擇設置為高電平。
(2) SDIO_ClockBypass:時鍾分頻旁路使用,可選使能或禁用,它設定SDIO_CLKCR寄存器的BYPASS位。如果使能旁路,SDIOCLK直接驅動CLK線輸出時鍾;如果禁用,使用SDIO_CLKCR寄存器的CLKDIV位值分頻SDIOCLK,然后輸出到CLK線。一般選擇禁用時鍾分頻旁路。
(3) SDIO_ClockPowerSave:節能模式選擇,可選使能或禁用,它設定SDIO_CLKCR寄存器的PWRSAV位的值。如果使能節能模式,CLK線只有在總線激活時才有時鍾輸出;如果禁用節能模式,始終使能CLK線輸出時鍾。
(4) SDIO_BusWide:數據線寬度選擇,可選1位數據總線、4位數據總線或8為數據總線,系統默認使用1位數據總線,操作SD卡時在數據傳輸模式下一般選擇4位數據總線。它設定SDIO_CLKCR寄存器的WIDBUS位的值。
(5) SDIO_HardwareFlowControl:硬件流控制選擇,可選使能或禁用,它設定SDIO_CLKCR寄存器的HWFC_EN位的值。硬件流控制功能可以避免FIFO發送上溢和下溢錯誤。
(6) SDIO_ClockDiv:時鍾分頻系數,它設定SDIO_CLKCR寄存器的CLKDIV位的值,設置SDIOCLK與CLK線輸出時鍾分頻系數:
CLK線時鍾頻率=SDIOCLK/([CLKDIV+2])。
36.7 SDIO命令初始化結構體
SDIO命令初始化結構體用於設置命令相關內容,比如命令號、命令參數、響應類型等等。它被SDIO_SendCommand函數使用。
代碼清單 362 SDIO命令初始化接口
1 typedef struct {
2 uint32_t SDIO_Argument; // 命令參數
3 uint32_t SDIO_CmdIndex; // 命令號
4 uint32_t SDIO_Response; // 響應類型
5 uint32_t SDIO_Wait; // 等待使能
6 uint32_t SDIO_CPSM; // 命令路徑狀態機
7 } SDIO_CmdInitTypeDef;
各個結構體成員介紹如下:
(1) SDIO_Argument:作為命令的一部分發送到卡的命令參數,它設定SDIO參數寄存器(SDIO_ARG)的值。
(2) SDIO_CmdIndex:命令號選擇,它設定SDIO命令寄存器(SDIO_CMD)的CMDINDEX位的值。
(3) SDIO_Response:響應類型,SDIO定義兩個響應類型:長響應和短響應。根據命令號選擇對應的響應類型。SDIO定義了四個32位的SDIO響應寄存器(SDIO_RESPx,x=1..4),短響應只用到SDIO_RESP1。
(4) SDIO_Wait:等待類型選擇,有三種狀態可選,一種是無等待狀態,超時檢測功能啟動;一種是等待中斷,另外一種是等待傳輸完成。它設定SDIO_CMD寄存器的WAITPEND位和WAITINT位的值。
(5) SDIO_CPSM:命令路徑狀態機控制,可選使能或禁用CPSM。它設定SDIO_CMD寄存器的CPSMEN位的值。
36.8 SDIO數據初始化結構體
SDIO數據初始化結構體用於配置數據發送和接收參數,比如傳輸超時、數據長度、傳輸模式等等。它被SDIO_DataConfig函數使用。
代碼清單 363 SDIO數據初始化結構體
1 typedef struct {
2 uint32_t SDIO_DataTimeOut; // 數據傳輸超時
3 uint32_t SDIO_DataLength; // 數據長度
4 uint32_t SDIO_DataBlockSize; // 數據塊大小
5 uint32_t SDIO_TransferDir; // 數據傳輸方向
6 uint32_t SDIO_TransferMode; // 數據傳輸模式
7 uint32_t SDIO_DPSM; // 數據路徑狀態機
8 } SDIO_DataInitTypeDef;
各結構體成員介紹如下:
(1) SDIO_DataTimeOut:設置數據傳輸以卡總線時鍾周期表示的超時周期,它設定SDIO數據定時器寄存器(SDIO_DTIMER)的值。在DPSM進入Wait_R或繁忙狀態后開始遞減,直到0還處於以上兩種狀態則將超時狀態標志置1.
(2) SDIO_DataLength:設置傳輸數據長度,它設定SDIO數據長度寄存器(SDIO_DLEN)的值。
(3) SDIO_DataBlockSize:設置數據塊大小,有多種尺寸可選,不同命令要求的數據塊可能不同。它設定SDIO數據控制寄存器(SDIO_DCTRL)寄存器的DBLOCKSIZE位的值。
(4) SDIO_TransferDir:數據傳輸方向,可選從主機到卡的寫操作,或從卡到主機的讀操作。它設定SDIO_DCTRL寄存器的DTDIR位的值。
(5) SDIO_TransferMode:數據傳輸模式,可選數據塊或數據流模式。對於SD卡操作使用數據塊類型。它設定SDIO_DCTRL寄存器的DTMODE位的值。
(6) SDIO_DPSM:數據路徑狀態機控制,可選使能或禁用DPSM。它設定SDIO_DCTRL寄存器的DTEN位的值。要實現數據傳輸都必須使能SDIO_DPSM。
36.9 SD卡讀寫測試實驗
SD卡廣泛用於便攜式設備上,比如數碼相機、手機、多媒體播放器等。對於嵌入式設備來說是一種重要的存儲數據部件。類似與SPI Flash芯片數據操作,可以直接進行讀寫,也可以寫入文件系統,然后使用文件系統讀寫函數,使用文件系統操作。本實驗是進行SD卡最底層的數據讀寫操作,直接使用SDIO對SD卡進行讀寫,會損壞SD卡原本內容,導致數據丟失,實驗前請注意備份SD卡的原內容。由於SD卡容量很大,我們平時使用的SD卡都是已經包含有文件系統的,一般不會使用本章的操作方式編寫SD卡的應用,但它是SD卡操作的基礎,對於原理學習是非常有必要的,在它的基礎上移植文件系統到SD卡的應用將在下一章講解。
36.9.1 硬件設計
STM32控制器的SDIO引腳是被設計固定不變的,開發板設計采用四根數據線模式。對於命令線和數據線須需要加一個上拉電阻。
圖 3618 SD卡硬件設計
36.9.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等沒有全部羅列出來,完整的代碼請參考本章配套的工程。有了之前相關SDIO知識基礎,我們就可以着手開始編寫SD卡驅動程序了,根據之前內容,可了解操作的大概流程:
初始化相關GPIO及SDIO外設;
配置SDIO基本通信環境進入卡識別模式,通過幾個命令處理后得到卡類型;
如果是可用卡就進入數據傳輸模式,接下來就可以進行讀、寫、擦除的操作。
雖然看起來只有三步,但它們有非常多的細節需要處理。實際上,SD卡是非常常用外設部件,ST公司在其測試板上也有板子SD卡卡槽,並提供了完整的驅動程序,我們直接參考移植使用即可。類似SDIO、USB這些復雜的外設,它們的通信協議相當龐大,要自行編寫完整、嚴謹的驅動不是一件輕松的事情,這時我們就可以利用ST官方例程的驅動文件,根據自己硬件移植到自己開發平台即可。
在"初始STM32標准庫"章節我們重點講解了標准庫的源代碼及啟動文件和庫使用幫助文檔這兩部分內容,實際上"Utilities"文件夾內容是非常有參考價值的,該文件夾包含了基於ST官方實驗板的驅動文件,比如LCD、SDRAM、SD卡、音頻解碼IC等等底層驅動程序,另外還有第三方軟件庫,如emWin圖像軟件庫和FatFs文件系統。雖然,我們的開發平台跟ST官方實驗平台硬件設計略有差別,但移植程序方法是完全可行的。學會移植程序可以減少很多工作量,加快項目進程,更何況ST官方的驅動代碼是經過嚴格驗證的。
在"STM32F4xx_DSP_StdPeriph_Lib_V1.5.1"文件下可以知道SD卡驅動文件,見圖 3619。我們需要stm324x9i_eval_sdio_sd.c和stm324x9i_eval_sdio_sd.h兩個文件的完整內容。另外還需要stm324x9i_eval.c和stm324x9i_eval.h兩個文件的部分代碼內容,為簡化工程,本章配置工程代碼是將這兩個文件需要用到的內容移植到stm324x9i_eval_sdio_sd.c文件中,具體可以參考工程文件。
圖 3619 ST官方實驗板SD卡驅動文件
我們把stm324x9i_eval_sdio_sd.c和stm324x9i_eval_sdio_sd.h兩個文件拷貝到我們的工程文件夾中,並將其對應改名為bsp_sdio_sd.c和bsp_sdio_sd.h,見圖 3620。另外,sdio_test.c和sdio_test.h文件包含了SD卡讀、寫、擦除測試代碼。
圖 3620 SD卡驅動文件
1. GPIO初始化和DMA配置
SDIO用到CLK線、CMD線和4根DAT線,使用之前必須傳輸相關GPIO,並設置為復用模式。SDIO可以生成DMA請求,使用DMA傳輸可以提高數據傳輸效率。SDIO可以設置為輪詢模式或DMA傳輸模式,SD卡驅動代碼針對這兩個模式做了區分處理,一般使用DMA傳輸模式,使用接下來代碼分析都以DMA傳輸模式介紹。
GPIO初始化和DMA配置這部分代碼從stm324x9i_eval.c和stm324x9i_eval.h兩個文件中移植而來。
DMA及相關配置宏定義
代碼清單 364 DMA及相關配置宏定義
1 #define SDIO_FIFO_ADDRESS ((uint32_t)0x40012C80)
2 /**
3 * @brief SDIO Intialization Frequency (400KHz max)
4 */
5 #define SDIO_INIT_CLK_DIV ((uint8_t)0x76)
6 /**
7 * @brief SDIO Data Transfer Frequency (25MHz max)
8 */
9 #define SDIO_TRANSFER_CLK_DIV ((uint8_t)0x0)
10
11 #define SD_SDIO_DMA DMA2
12 #define SD_SDIO_DMA_CLK RCC_AHB1Periph_DMA2
13
14 #define SD_SDIO_DMA_STREAM3 3
15
16 #ifdef SD_SDIO_DMA_STREAM3
17 #define SD_SDIO_DMA_STREAM DMA2_Stream3
18 #define SD_SDIO_DMA_CHANNEL DMA_Channel_4
19 #define SD_SDIO_DMA_FLAG_FEIF DMA_FLAG_FEIF3
20 #define SD_SDIO_DMA_FLAG_DMEIF DMA_FLAG_DMEIF3
21 #define SD_SDIO_DMA_FLAG_TEIF DMA_FLAG_TEIF3
22 #define SD_SDIO_DMA_FLAG_HTIF DMA_FLAG_HTIF3
23 #define SD_SDIO_DMA_FLAG_TCIF DMA_FLAG_TCIF3
24 #define SD_SDIO_DMA_IRQn DMA2_Stream3_IRQn
25 #define SD_SDIO_DMA_IRQHANDLER DMA2_Stream3_IRQHandler
26 #elif defined SD_SDIO_DMA_STREAM6
27 #define SD_SDIO_DMA_STREAM DMA2_Stream6
28 #define SD_SDIO_DMA_CHANNEL DMA_Channel_4
29 #define SD_SDIO_DMA_FLAG_FEIF DMA_FLAG_FEIF6
30 #define SD_SDIO_DMA_FLAG_DMEIF DMA_FLAG_DMEIF6
31 #define SD_SDIO_DMA_FLAG_TEIF DMA_FLAG_TEIF6
32 #define SD_SDIO_DMA_FLAG_HTIF DMA_FLAG_HTIF6
33 #define SD_SDIO_DMA_FLAG_TCIF DMA_FLAG_TCIF6
34 #define SD_SDIO_DMA_IRQn DMA2_Stream6_IRQn
35 #define SD_SDIO_DMA_IRQHANDLER DMA2_Stream6_IRQHandler
36 #endif /* SD_SDIO_DMA_STREAM3 */
使用宏定義編程對程序在同系列而不同型號主控芯片移植起到很好的幫助,同時簡化程序代碼。數據FIFO起始地址可用於DMA傳輸地址;SDIOCLK在卡識別模式和數據傳輸模式下一般是不同的,使用不同分頻系數控制。SDIO使用DMA2外設,可選擇stream3和stream6。
GPIO初始化
代碼清單 365 GPIO初始化
1 void SD_LowLevel_Init(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4
5 /* GPIOC and GPIOD Periph clock enable */
6 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD,
7 ENABLE);
8
9 GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_SDIO);
10 GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_SDIO);
11 GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SDIO);
12 GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SDIO);
13 GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SDIO);
14 GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_SDIO);
15
16 /* Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins */
17 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 |
18 GPIO_Pin_10 | GPIO_Pin_11;
19 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
20 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
21 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
22 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
23 GPIO_Init(GPIOC, &GPIO_InitStructure);
24
25 /* Configure PD.02 CMD line */
26 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
27 GPIO_Init(GPIOD, &GPIO_InitStructure);
28
29 /* Configure PC.12 pin: CLK pin */
30 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
31 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
32 GPIO_Init(GPIOC, &GPIO_InitStructure);
33
34 /* Enable the SDIO APB2 Clock */
35 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE);
36
37 /* Enable the DMA2 Clock */
38 RCC_AHB1PeriphClockCmd(SD_SDIO_DMA_CLK, ENABLE);
39 }
由於SDIO對應的IO引腳都是固定的,所以這里沒有使用宏定義方式給出,直接使用GPIO引腳,該函數初始化引腳之后還使能了SDIO和DMA2時鍾。
DMA傳輸配置
代碼清單 366 DMA傳輸配置
1 /* 配置使用DMA發送 */
2 void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
3 {
4 DMA_InitTypeDef SDDMA_InitStructure;
5
6 DMA_ClearFlag(SD_SDIO_DMA_STREAM,SD_SDIO_DMA_FLAG_FEIF |
7 SD_SDIO_DMA_FLAG_DMEIF|SD_SDIO_DMA_FLAG_TEIF|
8 SD_SDIO_DMA_FLAG_HTIF | SD_SDIO_DMA_FLAG_TCIF);
9
10 /* DMA2 Stream3 or Stream6 disable */
11 DMA_Cmd(SD_SDIO_DMA_STREAM, DISABLE);
12
13 /* DMA2 Stream3 or Stream6 Config */
14 DMA_DeInit(SD_SDIO_DMA_STREAM);
15
16 SDDMA_InitStructure.DMA_Channel = SD_SDIO_DMA_CHANNEL;
17 SDDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
19 SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferSRC;
20 SDDMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
21 SDDMA_InitStructure.DMA_BufferSize = 1;
22 SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
23 SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
24 SDDMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Word;
26 SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
27 SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
28 SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
29 SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
30 SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
31 SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
32 SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;
33 DMA_Init(SD_SDIO_DMA_STREAM, &SDDMA_InitStructure);
34 DMA_ITConfig(SD_SDIO_DMA_STREAM, DMA_IT_TC, ENABLE);
35 DMA_FlowControllerConfig(SD_SDIO_DMA_STREAM, DMA_FlowCtrl_Peripheral);
36
37 /* DMA2 Stream3 or Stream6 enable */
38 DMA_Cmd(SD_SDIO_DMA_STREAM, ENABLE);
39
40 }
41
42 /* 配置使用DMA接收 */
43 void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize)
44 {
45 DMA_InitTypeDef SDDMA_InitStructure;
46
47 DMA_ClearFlag(SD_SDIO_DMA_STREAM,SD_SDIO_DMA_FLAG_FEIF|
48 SD_SDIO_DMA_FLAG_DMEIF|SD_SDIO_DMA_FLAG_TEIF|
49 SD_SDIO_DMA_FLAG_HTIF|SD_SDIO_DMA_FLAG_TCIF);
50
51 /* DMA2 Stream3 or Stream6 disable */
52 DMA_Cmd(SD_SDIO_DMA_STREAM, DISABLE);
53
54 /* DMA2 Stream3 or Stream6 Config */
55 DMA_DeInit(SD_SDIO_DMA_STREAM);
56
57 SDDMA_InitStructure.DMA_Channel = SD_SDIO_DMA_CHANNEL;
58 SDDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
60 SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferDST;
61 SDDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
62 SDDMA_InitStructure.DMA_BufferSize = 1;
63 SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
64 SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
65 SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
67 SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
68 SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
69 SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
70 SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
71 SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
72 SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
73 SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;
74 DMA_Init(SD_SDIO_DMA_STREAM, &SDDMA_InitStructure);
75 DMA_ITConfig(SD_SDIO_DMA_STREAM, DMA_IT_TC, ENABLE);
76 DMA_FlowControllerConfig(SD_SDIO_DMA_STREAM, DMA_FlowCtrl_Peripheral);
77
78 /* DMA2 Stream3 or Stream6 enable */
79 DMA_Cmd(SD_SDIO_DMA_STREAM, ENABLE);
80 }
SD_LowLevel_DMA_TxConfig函數用於配置DMA的SDIO發送請求參數,並指定發送存儲器地址和大小。SD_LowLevel_DMA_RxConfig函數用於配置DMA的SDIO接收請求參數,並指定接收存儲器地址和大小。實際上,仔細分析代碼可以發現BufferSize參數在這里是沒有被用到,一般在使用DMA參數時都是指定傳輸的數量,DMA控制器在傳輸指定的數量后自動停止,但對於SD卡來說,可以生成硬件控制流,在傳輸完目標數量數據后即控制傳輸停止,所以這里調用DMA_FlowControllerConfig函數使能SD卡作為DMA傳輸停止的控制,這樣BufferSize參數無需用到也可以正確傳輸。對於DMA相關配置可以參考DMA章節內容。
2. 相關類型定義
打開bsp_sdio_sd.h文件可以發現有非常多的枚舉類型定義、結構體類型定義以及宏定義,把所有的定義在這里羅列出來肯定是不現實的,此處簡要介紹如下:
枚舉類型定義:有SD_Error、SDTransferState和SDCardState三個。SD_Error是列舉了控制器可能出現的錯誤、比如CRC校驗錯誤、CRC校驗錯誤、通信等待超時、FIFO上溢或下溢、擦除命令錯誤等等。這些錯誤類型部分是控制器系統寄存器的標志位,部分是通過命令的響應內容得到的。SDTransferState定義了SDIO傳輸狀態,有傳輸正常狀態、傳輸忙狀態和傳輸錯誤狀態。SDCardState定義卡的當前狀態,比如准備狀態、識別狀態、待機狀態、傳輸狀態等等,具體狀態轉換過程參考圖 369和圖 3610。
結構體類型定義:有SD_CSD、SD_CID、SD_CardStatus以及SD_CardInfo。SD_CSD定義了SD卡的特定數據(CSD)寄存器位,一般提供R2類型的響應可以獲取得到CSD寄存器內容。SD_CID結構體類似SD_CSD結構體,它定義SD卡CID寄存器內容,也是通過R2響應類型獲取得到。SD_CardStatus結構體定義了SD卡狀態,有數據寬度、卡類型、速度等級、擦除寬度、傳輸偏移地址等等SD卡狀態。SD_CardInfo結構體定義了SD卡信息,包括了SD_CSD類型和SD_CID類型成員,還有定義了卡容量、卡塊大小、卡相對地址RCA和卡類型成員。
宏定義內容:包含有命令號定義、SDIO傳輸方式、SD卡插入狀態以及SD卡類型定義。參考表 362列舉了描述了部分命令,文件中為每個命令號定義一個宏,比如將復位CMD0定義為SD_CMD_GO_IDLE_STATE,這與表 362中縮寫部分是類似的,所以熟悉命名用途可以更好理解SD卡操作過程。SDIO數據傳輸可以選擇是否使用DMA傳輸,SD_DMA_MODE宏定義選擇DMA傳輸,SD_POLLING_MODE使用普通掃描和傳輸,只能二選一使用。為提高系統性能,一般使用DMA傳輸模式,ST官方的SD卡驅動對這兩個方式做了區分出來,下面對SD卡操作都是以DMA傳輸模式為例講解。接下來還定義了檢測SD卡是否正確插入的宏,ST官方的SD卡驅動是以一個輸入引腳電平判斷SD卡是否正確插入,這里我們不使用,把引腳定義去掉(不然編譯出錯),保留SD_PRESENT和SD_NOT_PRESENT兩個宏定義。最后定義SD卡具體的類型,有V1.1版本標准卡、V2.0版本標准卡、高容量SD卡以及其他類型卡,前面三個是常用的類型。
在bsp_sdio_sd.c文件也有部分宏定義,這部分宏定義只能在該文件中使用。這部分宏定義包括命令超時時間定義、OCR寄存器位掩碼、R6響應位掩碼等等,這些定義更多是為提取特定響應位內容而設計的掩碼。
因為類型定義和宏定義內容沒有在本文中列舉出來,讀者有必要使用KEIL工具打開本章配套例程理解清楚。同時了解bsp_sdio_sd.c文件中定義的多個不同類型變量。
接下來我們就開始根據SD卡識別過程和數據傳輸過程理解SD卡驅動函數代碼。這部分代碼內容也是非常龐大,不可能全部在文檔中全部列出,對於部分函數只介紹其功能。
3. SD卡初始化
SD卡初始化過程主要是卡識別和相關SD卡狀態獲取。整個初始化函數可以實現圖 3621中的功能。
圖 3621 SD卡初始化和識別流程
SD卡初始化函數
代碼清單 367 SD_Init函數
1 SD_Error SD_Init(void)
2 {
3 __IO SD_Error errorstatus = SD_OK;
4
5 /**************配置SDIO中斷 DMA中斷**********************/
6 NVIC_InitTypeDef NVIC_InitStructure;
7
8 // Configure the NVIC Preemption Priority Bits
9 NVIC_PriorityGroupConfig (NVIC_PriorityGroup_1);
10 NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn;
11 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
12 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
13 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
14 NVIC_Init (&NVIC_InitStructure);
15 NVIC_InitStructure.NVIC_IRQChannel = SD_SDIO_DMA_IRQn;
16 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
17 NVIC_Init (&NVIC_InitStructure);
18 /**********************************************************/
19
20 /* SDIO Peripheral Low Level Init */
21 SD_LowLevel_Init();
22
23 SDIO_DeInit();
24
25 errorstatus = SD_PowerON();
26
27 if (errorstatus != SD_OK) {
28 /*!< CMD Response TimeOut (wait for CMDSENT flag) */
29 return (errorstatus);
30 }
31
32 errorstatus = SD_InitializeCards();
33
34 if (errorstatus != SD_OK) {
35 /*!< CMD Response TimeOut (wait for CMDSENT flag) */
36 return (errorstatus);
37 }
38
39 /*!< Configure the SDIO peripheral */
40 /*!< SDIO_CK = SDIOCLK / (SDIO_TRANSFER_CLK_DIV + 2) */
41 /*!< on STM32F4xx devices, SDIOCLK is fixed to 48MHz */
42 SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
43 SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
44 SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
45 SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
46 SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
47 SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
49 SDIO_Init(&SDIO_InitStructure);
50
51 /*----------------- Read CSD/CID MSD registers ------------------*/
52 errorstatus = SD_GetCardInfo(&SDCardInfo);
53
54 if (errorstatus == SD_OK) {
55 /*----------------- Select Card --------------------------------*/
56 errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
57 }
58
59 if (errorstatus == SD_OK) {
60 errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
61 }
62
63 return (errorstatus);
64 }
該函數的部分執行流程如下:
(1) 配置NVIC,SD卡通信用到SDIO中斷,如果用到DMA傳輸還需要配置DMA中斷。注意中斷服務函數不是定義在stm32f4xx_it.c文件的,是直接定義在bsp_sdio_sd.c文件中,中斷服務函數定義在個文件問題都不大,只要定義正確就可以的,編譯器會自動尋找。
(2) 執行SD_LowLevel_Init函數,其功能是對底層SDIO引腳進行初始化以及開啟相關時鍾,該函數在之前已經講解。
(3) SDIO_DeInit函數用於解除初始化SDIO接口,它只是簡單調用SD_LowLevel_DeInit函數。而SD_LowLevel_DeInit函數是與SD_LowLevel_Init函數相反功能,關閉相關時鍾,關閉SDIO電源,讓SDIO接近上電復位狀態。恢復復位狀態后再進行相關配置,可以防止部分沒有配置的參數采用非默認值而導致錯誤,這是ST官方驅動常用的一種初始化方式。
(4) 調用SD_PowerON函數,它用於查詢卡的工作電壓和時鍾控制配置,並返回SD_Error類型錯誤,該函數是整個SD識別精髓,有必要詳細分析。
SD_POWERON函數
代碼清單 368 SD_POWERON函數
1 SD_Error SD_PowerON(void)
2 {
3 __IO SD_Error errorstatus = SD_OK;
4 uint32_t response = 0, count = 0, validvoltage = 0;
5 uint32_t SDType = SD_STD_CAPACITY;
6 /*!< Power ON Sequence --------------------------------------------*/
7 SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
8 SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
9 SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
10 SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
11 SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
12 SDIO_InitStructure.SDIO_HardwareFlowControl =SDIO_HardwareFlowControl_Disable;
14 SDIO_Init(&SDIO_InitStructure);
15 /*!< Set Power State to ON */
16 SDIO_SetPowerState(SDIO_PowerState_ON);
17 /*!< Enable SDIO Clock */
18 SDIO_ClockCmd(ENABLE);
19 /*!< CMD0: GO_IDLE_STATE -----------------------------------------*/
20 SDIO_CmdInitStructure.SDIO_Argument = 0x0;
21 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE;
22 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No;
23 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
24 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
25 SDIO_SendCommand(&SDIO_CmdInitStructure);
26 errorstatus = CmdError();
27 if (errorstatus != SD_OK) {
28 return (errorstatus);
29 }
30 /*!< CMD8: SEND_IF_COND -------------------------------------------*/
31 SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN;
32 SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND;
33 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
34 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
35 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
36 SDIO_SendCommand(&SDIO_CmdInitStructure);
37 errorstatus = CmdResp7Error();
38 if (errorstatus == SD_OK) {
39 CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0; /*!< SD Card 2.0 */
40 SDType = SD_HIGH_CAPACITY;
41 } else {
42 /*!< CMD55 */
43 SDIO_CmdInitStructure.SDIO_Argument = 0x00;
44 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
45 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
46 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
47 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
48 SDIO_SendCommand(&SDIO_CmdInitStructure);
49 errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
50 }
51 /*!< CMD55 */
52 SDIO_CmdInitStructure.SDIO_Argument = 0x00;
53 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
54 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
55 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
56 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
57 SDIO_SendCommand(&SDIO_CmdInitStructure);
58 errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
59 /*!< If errorstatus is Command TimeOut, it is a MMC card */
60 /*!< If errorstatus is SD_OK it is a SD card: SD card 2.0
61 (voltage range mismatch)or SD card 1.x */
62 if (errorstatus == SD_OK) {
63 /*!< SD CARD */
64 /*!< Send ACMD41 SD_APP_OP_COND with Argument 0x80100000 */
65 while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL)) {
66 /*!< SEND CMD55 APP_CMD with RCA as 0 */
67 SDIO_CmdInitStructure.SDIO_Argument = 0x00;
68 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
69 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
70 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
71 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
72 SDIO_SendCommand(&SDIO_CmdInitStructure);
73 errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
74 if (errorstatus != SD_OK) {
75 return (errorstatus);
76 }
77 SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD|SDType;
78 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
79 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
80 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
81 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
82 SDIO_SendCommand(&SDIO_CmdInitStructure);
83 errorstatus = CmdResp3Error();
84 if (errorstatus != SD_OK) {
85 return (errorstatus);
86 }
87 response = SDIO_GetResponse(SDIO_RESP1);
88 validvoltage = (((response >> 31) == 1) ? 1 : 0);
89 count++;
90 }
91 if (count >= SD_MAX_VOLT_TRIAL) {
92 errorstatus = SD_INVALID_VOLTRANGE;
93 return (errorstatus);
94 }
95 if (response &= SD_HIGH_CAPACITY) {
96 CardType = SDIO_HIGH_CAPACITY_SD_CARD;
97 }
98 }/*!< else MMC Card */
99 return (errorstatus);
100 }
SD_POWERON函數執行流程如下:
(1) 配置SDIO_InitStructure結構體變量成員並調用SDIO_Init庫函數完成SDIO外設的基本配置,注意此處的SDIO時鍾分頻,由於處於卡識別階段,其時鍾不能超過400KHz。
(2) 調用SDIO_SetPowerState函數控制SDIO的電源狀態,給SDIO提供電源,並調用ClockCmd庫函數使能SDIO時鍾。
(3) 發送命令給SD卡,首先發送CMD0,復位所有SD卡, CMD0命令無需響應,所以調用CmdError函數檢測錯誤即可。CmdError函數用於無需響應的命令發送檢測,帶有等待超時檢測功能,它通過不斷檢測SDIO_STA寄存器的CMDSENT位即可知道命令發送成功與否。如果遇到超時錯誤則直接退出SD_PowerON函數。如果無錯誤則執行下面程序。
(4) 發送CMD8命令,檢測SD卡支持的操作條件,主要就是電壓匹配,CMD8的響應類型是R7,使用CmdResp7Error函數可獲取得到R7響應結果,它是通過檢測SDIO_STA寄存器相關位完成的,並具有等待超時檢測功能。如果CmdResp7Error函數返回值為SD_OK,即CMD8有響應,可以判定SD卡為V2.0及以上的高容量SD卡,如果沒有響應可能是V1.1版本卡或者是不可用卡。
(5) 使用ACMD41命令判斷卡的具體類型。在發送ACMD41之前必須先發送CMD55,CMD55命令的響應類型的R1。如果CMD55命令都沒有響應說明是MMC卡或不可用卡。在正確發送CMD55之后就可以發送ACMD41,並根據響應判斷卡類型,ACMD41的響應號為R3,CmdResp3Error函數用於檢測命令正確發送並帶有超時檢測功能,但並不具備響應內容接收功能,需要在判定命令正確發送之后調用SDIO_GetResponse函數才能獲取響應的內容。實際上,在有響應時,SDIO外設會自動把響應存放在SDIO_RESPx寄存器中,SDIO_GetResponse函數只是根據形參返回對應響應寄存器的值。通過判定響應內容值即可確定SD卡類型。
(6) 執行SD_PowerON函數無錯誤后就已經確定了SD卡類型,並說明卡和主機電壓是匹配的,SD卡處於卡識別模式下的准備狀態。退出SD_PowerON函數返回SD_Init函數,執行接下來代碼。判斷執行SD_PowerON函數無錯誤后,執行下面的SD_InitializeCards函數進行與SD卡相關的初始化,使得卡進入數據傳輸模式下的待機模式。
SD_InitializeCards函數
代碼清單 369 SD_InitializeCards函數
1 SD_Error SD_InitializeCards(void)
2 {
3 SD_Error errorstatus = SD_OK;
4 uint16_t rca = 0x01;
5 if (SDIO_GetPowerState() == SDIO_PowerState_OFF) {
6 errorstatus = SD_REQUEST_NOT_APPLICABLE;
7 return (errorstatus);
8 }
9 if (SDIO_SECURE_DIGITAL_IO_CARD != CardType) {
10 /*!< Send CMD2 ALL_SEND_CID */
11 SDIO_CmdInitStructure.SDIO_Argument = 0x0;
12 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;
13 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
14 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
15 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
16 SDIO_SendCommand(&SDIO_CmdInitStructure);
17 errorstatus = CmdResp2Error();
18 if (SD_OK != errorstatus) {
19 return (errorstatus);
20 }
21 CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
22 CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
23 CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
24 CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
25 }
26 if ( (SDIO_STD_CAPACITY_SD_CARD_V1_1==CardType) ||
27 (SDIO_STD_CAPACITY_SD_CARD_V2_0==CardType) ||
28 (SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)||
29 (SDIO_HIGH_CAPACITY_SD_CARD == CardType) ) {
30 /*!< Send CMD3 SET_REL_ADDR with argument 0 */
31 /*!< SD Card publishes its RCA. */
32 SDIO_CmdInitStructure.SDIO_Argument = 0x00;
33 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;
34 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
35 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
36 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
37 SDIO_SendCommand(&SDIO_CmdInitStructure);
38 errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca);
39 if (SD_OK != errorstatus) {
40 return (errorstatus);
41 }
42 }
43 if (SDIO_SECURE_DIGITAL_IO_CARD != CardType) {
44 RCA = rca;
45 /*!< Send CMD9 SEND_CSD with argument as card's RCA */
46 SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);
47 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
48 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
49 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
50 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
51 SDIO_SendCommand(&SDIO_CmdInitStructure);
52 errorstatus = CmdResp2Error();
53 if (SD_OK != errorstatus) {
54 return (errorstatus);
55 }
56 CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
57 CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
58 CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
59 CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
60 }
61 errorstatus = SD_OK; /*!< All cards get intialized */
62 return (errorstatus);
63 }
SD_InitializeCards函數執行流程如下:
(1) 判斷SDIO電源是否啟動,如果沒有啟動電源返回錯誤。
(2) SD卡不是SD I/O卡時會進入if判斷,執行發送CMD2,CMD2是用於通知所有卡通過CMD線返回CID值,執行CMD2發送之后就可以使用CmdResp2Error函數獲取CMD2命令發送情況,發送無錯誤后即可以使用SDIO_GetResponse函數獲取響應內容,它是個長響應,我們把CMD2響應內容存放在CID_Tab數組內。
(3) 發送CMD2之后緊接着就發送CMD3,用於指示SD卡自行推薦RCA地址,CMD3的響應為R6類型,CmdResp6Error函數用於檢查R6響應錯誤,它有兩個形參,一個是命令號,這里為CMD3,另外一個是RCA數據指針,這里使用rca變量的地址賦值給它,使得在CMD3正確響應之后rca變量即存放SD卡的RCA。R6響應還有一部分位用於指示卡的狀態,CmdResp6Error函數通用會對每個錯誤位進行必要的檢測,如果發現有錯誤存在則直接返回對應錯誤類型。執行完CmdResp6Error函數之后返回到SD_InitializeCards函數中,如果判斷無錯誤說明此刻SD卡已經處於數據傳輸模式。
(4) 發送CMD9給指定RCA的SD卡使其發送返回其CSD寄存器內容,這里的RCA就是在CmdResp6Error函數獲取得到的rca。最后把響應內容存放在CSD_Tab數組中。
執行SD_InitializeCards函數無錯誤后SD卡就已經處於數據傳輸模式下的待機狀態,退出SD_InitializeCards后會返回前面的SD_Init函數,執行接下來代碼,以下是SD_Init函數的后續執行過程:
(1) 重新配置SDIO外設,提高時鍾頻率,之前的卡識別模式都設定CMD線時鍾為小於400KHz,進入數據傳輸模式可以把時鍾設置為小於25MHz,以便提高數據傳輸速率。
(2) 調用SD_GetCardInfo函數獲取SD卡信息,它需要一個指向SD_CardInfo類型變量地址的指針形參,這里賦值為SDCardInfo變量的地址。SD卡信息主要是CID和CSD寄存器內容,這兩個寄存器內容在SD_InitializeCards函數中都完成讀取過程並將其分別存放在CID_Tab數組和CSD_Tab數組中,所以SD_GetCardInfo函數只是簡單的把這兩個數組內容整合復制到SDCardInfo變量對應成員內。正確執行SD_GetCardInfo函數后,SDCardInfo變量就存放了SD卡的很多狀態信息,這在之后應用中使用頻率是很高的。
(3) 調用SD_SelectDeselect函數用於選擇特定RCA的SD卡,它實際是向SD卡發送CMD7。執行之后,卡就從待機狀態轉變為傳輸模式,可以說數據傳輸已經是萬事俱備了。
(4) 擴展數據線寬度,之前的所有操作都是使用一根數據線傳輸完成的,使用4根數據線可以提高傳輸性能,調用可以設置數據線寬度,函數只有一個形參,用於指定數據線寬度。在SD_EnableWideBusOperation函數中,調用了SDEnWideBus函數使能使用寬數據線,然后傳輸SDIO_InitTypeDef類型變量並使用SDIO_Init函數完成使用4根數據線配置。
至此,SD_Init函數已經全部執行完成。如果程序可以正確執行,接下來就可以進行SD卡讀寫以及擦除等操作。雖然bsp_sdio_sd.c文件看起來非常長,但在SD_Init函數分析過程就已經涉及到它差不多一半內容了,另外一半內容主要就是讀、寫或擦除相關函數。
4. SD卡數據操作
SD卡數據操作一般包括數據讀取、數據寫入以及存儲區擦除。數據讀取和寫入都可以分為單塊操作和多塊操作。
擦除函數
代碼清單 3610 SD_Erase函數
1 SD_Error SD_Erase(uint64_t startaddr, uint64_t endaddr)
2 {
3 SD_Error errorstatus = SD_OK;
4 uint32_t delay = 0;
5 __IO uint32_t maxdelay = 0;
6 uint8_t cardstate = 0;
7 /*!< Check if the card coomnd class supports erase command */
8 if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0) {
9 errorstatus = SD_REQUEST_NOT_APPLICABLE;
10 return (errorstatus);
11 }
12 maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2);
13 if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) {
14 errorstatus = SD_LOCK_UNLOCK_FAILED;
15 return (errorstatus);
16 }
17 if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) {
18 startaddr /= 512;
19 endaddr /= 512;
20 }
21 /*!< ERASE_GROUP_START (CMD32) and erase_group_end(CMD33) */
22 if ( (SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) ||
23 (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) ||
24 (SDIO_HIGH_CAPACITY_SD_CARD == CardType) ) {
25 /*!< Send CMD32 SD_ERASE_GRP_START with argument as addr */
26 SDIO_CmdInitStructure.SDIO_Argument =(uint32_t)startaddr;
27 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
28 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
29 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
30 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
31 SDIO_SendCommand(&SDIO_CmdInitStructure);
32 errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START);
33 if (errorstatus != SD_OK) {
34 return (errorstatus);
35 }
36 /*!< Send CMD33 SD_ERASE_GRP_END with argument as addr */
37 SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)endaddr;
38 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;
39 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
40 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
41 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
42 SDIO_SendCommand(&SDIO_CmdInitStructure);
43 errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_END);
44 if (errorstatus != SD_OK) {
45 return (errorstatus);
46 }
47 }
48 /*!< Send CMD38 ERASE */
49 SDIO_CmdInitStructure.SDIO_Argument = 0;
50 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE;
51 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
52 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
53 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
54 SDIO_SendCommand(&SDIO_CmdInitStructure);
55 errorstatus = CmdResp1Error(SD_CMD_ERASE);
56 if (errorstatus != SD_OK) {
57 return (errorstatus);
58 }
59 for (delay = 0; delay < maxdelay; delay++) {
60 }
61 /*!< Wait till the card is in programming state */
62 errorstatus = IsCardProgramming(&cardstate);
63 delay = SD_DATATIMEOUT;
64 while ((delay > 0) && (errorstatus == SD_OK) &&
65 ((SD_CARD_PROGRAMMING == cardstate)||(SD_CARD_RECEIVING == cardstate))) {
66 errorstatus = IsCardProgramming(&cardstate);
67 delay--;
68 }
69 return (errorstatus);
70 }
SD_Erase函數用於擦除SD卡指定地址范圍內的數據。該函數接收兩個參數,一個是擦除的起始地址,另外一個是擦除的結束地址。對於高容量SD卡都是以塊大小為512字節進行擦除的,所以保證字節對齊是程序員的責任。SD_Erase函數的執行流程如下:
(1) 檢查SD卡是否支持擦除功能,如果不支持則直接返回錯誤。為保證擦除指令正常進行,要求主機一個遵循下面的命令序列發送指令:CMD32->CMD33->CMD38。如果發送順序不對,SD卡會設置ERASE_SEQ_ERROR位到狀態寄存器。
(2) SD_Erase函數發送CMD32指令用於設定擦除塊開始地址,在執行無錯誤后發送CMD33設置擦除塊的結束地址。
(3) 發送擦除命令CMD38,使得SD卡進行擦除操作。SD卡擦除操作由SD卡內部控制完成,不同卡擦除后是0xff還是0x00由廠家決定。擦除操作需要花費一定時間,這段時間不能對SD卡進行其他操作。
(4) 通過IsCardProgramming函數可以檢測SD卡是否處於編程狀態(即卡內部的擦寫狀態),需要確保SD卡擦除完成才退出SD_Erase函數。IsCardProgramming函數先通過發送CMD13命令SD卡發送它的狀態寄存器內容,並對響應內容進行分析得出當前SD卡的狀態以及可能發送的錯誤。
數據寫入操作
數據寫入可分為單塊數據寫入和多塊數據寫入,這里只分析單塊數據寫入,多塊的與之類似。SD卡數據寫入之前並沒有硬性要求擦除寫入塊,這與SPI Flash芯片寫入是不同的。ST官方的SD卡寫入函數包括掃描查詢方式和DMA傳輸方式,我們這里只介紹DMA傳輸模式。
代碼清單 3611 SD_WriteBlock函數
1 SD_Error SD_WriteBlock(uint8_t *writebuff, uint64_t WriteAddr,
2 uint16_t BlockSize)
3 {
4 SD_Error errorstatus = SD_OK;
5 TransferError = SD_OK;
6 TransferEnd = 0;
7 StopCondition = 0;
8 SDIO->DCTRL = 0x0;
9 #if defined (SD_DMA_MODE)
10 SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND |
11 SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE);
12 SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, BlockSize);
13 SDIO_DMACmd(ENABLE);
14 #endif
15 if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) {
16 BlockSize = 512;
17 WriteAddr /= 512;
18 }
19 /* Set Block Size for Card */
20 SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
21 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
22 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
23 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
24 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
25 SDIO_SendCommand(&SDIO_CmdInitStructure);
26 errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
27 if (SD_OK != errorstatus) {
28 return (errorstatus);
29 }
30 /*!< Send CMD24 WRITE_SINGLE_BLOCK */
31 SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr;
32 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK;
33 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
34 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
35 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
36 SDIO_SendCommand(&SDIO_CmdInitStructure);
37 errorstatus = CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK);
38
39 if (errorstatus != SD_OK) {
40 return (errorstatus);
41 }
42 SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
43 SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
44 SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
45 SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;
46 SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
47 SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
48 SDIO_DataConfig(&SDIO_DataInitStructure);
49 return (errorstatus);
50 }
SD_WriteBlock函數用於向指定的目標地址寫入一個塊的數據,它有三個形參,分別為指向待寫入數據的首地址的指針變量、目標寫入地址和塊大小。塊大小一般都設置為512字節。SD_WriteBlock寫入函數的執行流程如下:
(1) SD_WriteBlock函數開始將SDIO 數據控制寄存器 (SDIO_DCTRL)清理,復位之前的傳輸設置。
(2) 來調用SDIO_ITConfig函數使能相關中斷,包括數據CRC失敗中斷、數據超時中斷、數據結束中斷等等。
(3) 調用SD_LowLevel_DMA_TxConfig函數,配置使能SDIO數據向SD卡的數據傳輸的DMA請求,該函數可以參考代碼清單 366。為使SDIO發送DMA請求,需要調用SDIO_DMACmd函數使能。對於高容量的SD卡要求塊大小必須為512字節,程序員有責任保證數據寫入地址與塊大小的字節對齊問題。
(4) 對SD卡進行數據讀寫之前,都必須發送CMD16指定塊的大小,對於標准卡,要寫入BlockSize長度字節的塊;對於SDHC卡,寫入512字節的塊。接下來就可以發送塊寫入命令CMD24通知SD卡要進行數據寫入操作,並指定待寫入數據的目標地址。
(5) 利用SDIO_DataInitTypeDef結構體類型變量配置數據傳輸的超時、塊數量、數據塊大小、數據傳輸方向等參數並使用SDIO_DataConfig函數完成數據傳輸環境配置。
執行完以上代碼后,SDIO外設會自動生成DMA發送請求,將指定數據使用DMA傳輸寫入到SD卡內。
寫入操作等待函數
SD_WaitWriteOperation函數用於檢測和等待數據寫入完成,在調用數據寫入函數之后一般都需要調用,SD_WaitWriteOperation函數不僅使用於單塊寫入函數也適用於多塊寫入函數。
代碼清單 3612 SD_WaitWriteOperation函數
1 SD_Error SD_WaitWriteOperation(void)
2 {
3 SD_Error errorstatus = SD_OK;
4 uint32_t timeout;
5
6 timeout = SD_DATATIMEOUT;
7 while ( (DMAEndOfTransfer == 0x00) && (TransferEnd == 0) &&
8 (TransferError == SD_OK) && (timeout > 0) ) {
9 timeout--;
10 }
11 DMAEndOfTransfer = 0x00;
12 timeout = SD_DATATIMEOUT;
13 while (((SDIO->STA & SDIO_FLAG_TXACT)) && (timeout > 0)) {
14 timeout--;
15 }
16 if (StopCondition == 1) {
17 errorstatus = SD_StopTransfer();
18 StopCondition = 0;
19 }
20 if ((timeout == 0) && (errorstatus == SD_OK)) {
21 errorstatus = SD_DATA_TIMEOUT;
22 }
23 /*!< Clear all the static flags */
24 SDIO_ClearFlag(SDIO_STATIC_FLAGS);
25 if (TransferError != SD_OK) {
26 return (TransferError);
27 } else {
28 return (errorstatus);
29 }
30 }
該函數開始等待當前塊數據正確傳輸完成,並添加了超時檢測功能。然后不停監測SDIO_STA寄存器的TXACT位,以等待數據寫入完成。對於多塊數據寫入操作需要調用SD_StopTransfer函數停止數據傳輸,而單塊寫入則不需要。SD_StopTransfer函數實際是向SD卡發送CMD12,該命令專門用於停止數據傳輸,SD卡系統保證在主機發送CMD12之后整塊傳輸完后才停止數據傳輸。SD_WaitWriteOperation函數最后是清除相關標志位並返回錯誤。
數據讀取操作
同向SD卡寫入數據類似,從SD卡讀取數據可分為單塊讀取和多塊讀取。這里這介紹單塊讀操作函數,多塊讀操作類似理解即可。
代碼清單 3613 SD_ReadBlock函數
1 SD_Error SD_ReadBlock(uint8_t *readbuff, uint64_t ReadAddr,
2 uint16_t BlockSize)
3 {
4 SD_Error errorstatus = SD_OK;
5 TransferError = SD_OK;
6 TransferEnd = 0;
7 StopCondition = 0;
8
9 SDIO->DCTRL = 0x0;
10 #if defined (SD_DMA_MODE)
11 SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND |
12 SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE);
13 SDIO_DMACmd(ENABLE);
14 SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
15 #endif
16 if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) {
17 BlockSize = 512;
18 ReadAddr /= 512;
19 }
20 /* Set Block Size for Card */
21 SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
22 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
23 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
24 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
25 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
26 SDIO_SendCommand(&SDIO_CmdInitStructure);
27 errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
28 if (SD_OK != errorstatus) {
29 return (errorstatus);
30 }
31 SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
32 SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
33 SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
34 SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
35 SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
36 SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
37 SDIO_DataConfig(&SDIO_DataInitStructure);
38 /*!< Send CMD17 READ_SINGLE_BLOCK */
39 SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
40 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
41 SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
42 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
43 SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
44 SDIO_SendCommand(&SDIO_CmdInitStructure);
45 errorstatus = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);
46 if (errorstatus != SD_OK) {
47 return (errorstatus);
48 }
49 return (errorstatus);
50 }
數據讀取操作與數據寫入操作編程流程是類似,只是數據傳輸方向改變,使用到的SD命令號也有所不同而已。SD_ReadBlock函數有三個形參,分別為數據讀取存儲器的指針、數據讀取起始目標地址和單塊長度。SD_ReadBlock函數執行流程如下:
(1) 將SDIO 外設的數據控制寄存器 (SDIO_DCTRL)清理,復位之前的傳輸設置。
(2) 調用SDIO_ITConfig函數使能相關中斷,包括數據CRC失敗中斷、數據超時中斷、數據結束中斷等等。然后調用SD_LowLevel_DMA_RxConfig函數,配置使能SDIO從SD卡的讀取數據的DMA請求,該函數可以參考代碼清單 366。為使SDIO發送DMA請求,需要調用SDIO_DMACmd函數使能。對於高容量的SD卡要求塊大小必須為512字節,程序員有責任保證目標讀取地址與塊大小的字節對齊問題。
(3) 對SD卡進行數據讀寫之前,都必須發送CMD16指定塊的大小,對於標准卡,寫入BlockSize長度字節的塊;對於SDHC卡,寫入512字節的塊。
(4) 利用SDIO_DataInitTypeDef結構體類型變量配置數據傳輸的超時、塊數量、數據塊大小、數據傳輸方向等參數並使用SDIO_DataConfig函數完成數據傳輸環境配置。
(5) 最后控制器向SD卡發送單塊讀數據命令CMD17,SD卡在接收到命令后就會通過數據線把數據傳輸到控制器數據FIFO內,並自動生成DMA傳輸請求。
讀取操作等待函數
SD_WaitReadOperation函數用於等待數據讀取操作完成,只有在確保數據讀取完成了我們才可以放心使用數據。SD_WaitReadOperation函數也是適用於單塊讀取函數和多塊讀取函數的。
代碼清單 3614 SD_WaitReadOperation函數
1 SD_Error SD_WaitReadOperation(void)
2 {
3 SD_Error errorstatus = SD_OK;
4 uint32_t timeout;
5
6 timeout = SD_DATATIMEOUT;
7 while ((DMAEndOfTransfer == 0x00) && (TransferEnd == 0) &&
8 (TransferError == SD_OK) && (timeout > 0)) {
9 timeout--;
10 }
11
12 DMAEndOfTransfer = 0x00;
13 timeout = SD_DATATIMEOUT;
14 while (((SDIO->STA & SDIO_FLAG_RXACT)) && (timeout > 0)) {
15 timeout--;
16 }
17 if (StopCondition == 1) {
18 errorstatus = SD_StopTransfer();
19 StopCondition = 0;
20 }
21 if ((timeout == 0) && (errorstatus == SD_OK)) {
22 errorstatus = SD_DATA_TIMEOUT;
23 }
24 /*!< Clear all the static flags */
25 SDIO_ClearFlag(SDIO_STATIC_FLAGS);
26 if (TransferError != SD_OK) {
27 return (TransferError);
28 } else {
29 return (errorstatus);
30 }
31 }
該函數開始等待當前塊數據正確傳輸完成,並添加了超時檢測功能。然后不停監測SDIO_STA寄存器的RXACT位,以等待數據讀取完成。對於多塊數據讀取操作需要調用SD_StopTransfer函數停止數據傳輸,而單塊寫入則不需要。該函數最后是清除相關標志位並返回錯誤。
5. SDIO中斷服務函數
在進行數據傳輸操作時都會使能相關標志中斷,用於跟蹤傳輸進程和錯誤檢測。如果是使用DMA傳輸,也會使能DMA相關中斷。為簡化代碼,加之SDIO中斷服務函數內容一般不會修改,將中斷服務函數放在bsp_sdio_sd.c文件中,而不是放在常用於存放中斷服務函數的stm32f4xx_it.c文件。
代碼清單 3615 SDIO中斷服務函數
1 void SDIO_IRQHandler(void)
2 {
3 /* Process All SDIO Interrupt Sources */
4 SD_ProcessIRQSrc();
5 }
6
7 SD_Error SD_ProcessIRQSrc(void)
8 {
9 if (SDIO_GetITStatus(SDIO_IT_DATAEND) != RESET) {
10 TransferError = SD_OK;
11 SDIO_ClearITPendingBit(SDIO_IT_DATAEND);
12 TransferEnd = 1;
13 } else if (SDIO_GetITStatus(SDIO_IT_DCRCFAIL) != RESET) {
14 SDIO_ClearITPendingBit(SDIO_IT_DCRCFAIL);
15 TransferError = SD_DATA_CRC_FAIL;
16 } else if (SDIO_GetITStatus(SDIO_IT_DTIMEOUT) != RESET) {
17 SDIO_ClearITPendingBit(SDIO_IT_DTIMEOUT);
18 TransferError = SD_DATA_TIMEOUT;
19 } else if (SDIO_GetITStatus(SDIO_IT_RXOVERR) != RESET) {
20 SDIO_ClearITPendingBit(SDIO_IT_RXOVERR);
21 TransferError = SD_RX_OVERRUN;
22 } else if (SDIO_GetITStatus(SDIO_IT_TXUNDERR) != RESET) {
23 SDIO_ClearITPendingBit(SDIO_IT_TXUNDERR);
24 TransferError = SD_TX_UNDERRUN;
25 } else if (SDIO_GetITStatus(SDIO_IT_STBITERR) != RESET) {
26 SDIO_ClearITPendingBit(SDIO_IT_STBITERR);
27 TransferError = SD_START_BIT_ERR;
28 }
29
30 SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND |
31 SDIO_IT_TXFIFOHE | SDIO_IT_RXFIFOHF | SDIO_IT_TXUNDERR |
32 SDIO_IT_RXOVERR | SDIO_IT_STBITERR, DISABLE);
33 return (TransferError);
34 }
SDIO中斷服務函數SDIO_IRQHandler會直接調用SD_ProcessIRQSrc函數執行。SD_ProcessIRQSrc函數通過多個if判斷語句分辨中斷源,並對傳輸錯誤標志變量TransferError賦值以指示當前傳輸狀態。最后禁用SDIO中斷。
代碼清單 3616 DMA請求中斷
1 void SD_SDIO_DMA_IRQHANDLER(void)
2 {
3 /* Process DMA2 Stream3 or DMA2 Stream6 Interrupt Sources */
4 SD_ProcessDMAIRQ();
5 }
6
7 void SD_ProcessDMAIRQ(void)
8 {
9 if (DMA2->LISR & SD_SDIO_DMA_FLAG_TCIF) {
10 DMAEndOfTransfer = 0x01;
11 DMA_ClearFlag(SD_SDIO_DMA_STREAM,
12 SD_SDIO_DMA_FLAG_TCIF|SD_SDIO_DMA_FLAG_FEIF);
13 }
14 }
SD_SDIO_DMA_IRQHANDLER函數是DMA傳輸中斷服務函數,它直接調用SD_ProcessDMAIRQ函數執行。SD_ProcessDMAIRQ函數主要是判斷DMA的傳輸完成標志位。
至此,我們已經介紹了SD卡初始化、SD卡數據操作的基礎功能函數以及SDIO相關中斷服務函數內容,很多時候這些函數已經足夠我們使用了。接下來我們就編寫一些簡單的測試程序驗證移植的正確性。
6. 測試函數
測試SD卡部分的函數是我們自己編寫的,存放在sdio_test.c文件中。
SD卡測試函數
代碼清單 3617 SD_Test
1 void SD_Test(void)
2 {
3 LED_BLUE;
4 /*---------------------------- SD Init -------------------------- */
5 /* SD卡使用SDIO中斷及DMA中斷接收數據,中斷服務程序位於bsp_sdio_sd.c文件尾*/
6 if ((Status = SD_Init()) != SD_OK) {
7 LED_RED;
8 printf("SD卡初始化失敗,請確保SD卡已正確接入開發板,或換一張SD卡測試!\n");
9 } else {
10 printf("SD卡初始化成功!\n");
11 }
12 if (Status == SD_OK) {
13 LED_BLUE;
14 /*擦除測試*/
15 SD_EraseTest();
16
17 LED_BLUE;
18 /*single block 讀寫測試*/
19 SD_SingleBlockTest();
20
21 //暫不支持直接多塊讀寫,多塊讀寫可用多個單塊讀寫流程代替
22 LED_BLUE;
23 /*muti block 讀寫測試*/
24 SD_MultiBlockTest();
25 }
26 }
測試程序以開發板上LED燈指示測試結果,同時打印相關測試結果到串口調試助手。測試程序先調用SD_Init函數完成SD卡初始化,該函數具體代碼參考代碼清單 367,如果初始化成功就可以進行數據操作測試。
SD卡擦除測試
代碼清單 3618 SD_EraseTest
1 void SD_EraseTest(void)
2 {
3 /*------------------- Block Erase -------------------------------*/
4 if (Status == SD_OK) {
5 /* Erase NumberOfBlocks Blocks of WRITE_BL_LEN(512 Bytes) */
6 Status = SD_Erase(0x00, (BLOCK_SIZE * NUMBER_OF_BLOCKS));
7 }
8
9 if (Status == SD_OK) {
10 Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00,
11 BLOCK_SIZE, NUMBER_OF_BLOCKS);
12
13 /* Check if the Transfer is finished */
14 Status = SD_WaitReadOperation();
15
16 /* Wait until end of DMA transfer */
17 while (SD_GetStatus() != SD_TRANSFER_OK);
18 }
19
20 /* Check the correctness of erased blocks */
21 if (Status == SD_OK) {
22 EraseStatus = eBuffercmp(Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
23 }
24
25 if (EraseStatus == PASSED) {
26 LED_GREEN;
27 printf("SD卡擦除測試成功!\n");
28 } else {
29 LED_BLUE;
30 printf("SD卡擦除測試失敗!\n");
31 printf("溫馨提示:部分SD卡不支持擦除測試,若SD卡能通過下面的single \
32 讀寫測試,即表示SD卡能夠正常使用。\n");
33 }
34 }
SD_EraseTest函數主要編程思路是擦除一定數量的數據塊,接着讀取已擦除塊的數據,把讀取到的數據與0xff或者0x00比較,得出擦除結果。
SD_Erase函數用於擦除指定地址空間,源代碼參考代碼清單 3610,它接收兩個參數指定擦除空間的起始地址和終止地址。如果SD_Erase函數返回正確,表示擦除成功則執行數據塊讀取;如果SD_Erase函數返回錯誤,表示SD卡擦除失敗,並不是所有卡都能擦除成功的,部分卡雖然擦除失敗,但數據讀寫操作也是可以正常執行的。這里使用多塊讀取函數SD_ReadMultiBlocks,它有四個形參,分別為讀取數據存儲器、讀取數據目標地址、塊大小以及塊數量,函數后面都會跟隨等待數據傳輸完成相關處理代碼。接下來會調用eBuffercmp函數判斷擦除結果,它有兩個形參,分別為數據指針和數據字節長度,它實際上是把數據存儲器內所有數據都與0xff或0x00做比較,只有出現這兩個數之外就報錯退出。
單塊讀寫測試
代碼清單 3619 SD_SingleBlockTest函數
1 void SD_SingleBlockTest(void)
2 {
3 /*------------------- Block Read/Write --------------------------*/
4 /* Fill the buffer to send */
5 Fill_Buffer(Buffer_Block_Tx, BLOCK_SIZE, 0x320F);
6
7 if (Status == SD_OK) {
8 /* Write block of 512 bytes on address 0 */
9 Status = SD_WriteBlock(Buffer_Block_Tx, 0x00, BLOCK_SIZE);
10 /* Check if the Transfer is finished */
11 Status = SD_WaitWriteOperation();
12 while (SD_GetStatus() != SD_TRANSFER_OK);
13 }
14 if (Status == SD_OK) {
15 /* Read block of 512 bytes from address 0 */
16 Status = SD_ReadBlock(Buffer_Block_Rx, 0x00, BLOCK_SIZE);
17 /* Check if the Transfer is finished */
18 Status = SD_WaitReadOperation();
19 while (SD_GetStatus() != SD_TRANSFER_OK);
20 }
21 /* Check the correctness of written data */
22 if (Status == SD_OK) {
23 TransferStatus1 = Buffercmp(Buffer_Block_Tx,
24 Buffer_Block_Rx, BLOCK_SIZE);
25 }
26 if (TransferStatus1 == PASSED) {
27 LED_GREEN;
28 printf("Single block 測試成功!\n");
29 } else {
30 LED_RED;
31 printf("Single block 測試失敗,請確保SD卡正確接入開發板,或換一張SD卡測試!\n");
32 }
33 }
SD_SingleBlockTest函數主要編程思想是首先填充一個塊大小的存儲器,通過寫入操作把數據寫入到SD卡內,然后通過讀取操作讀取數據到另外的存儲器,然后在對比存儲器內容得出讀寫操作是否正確。
SD_SingleBlockTest函數一開始調用Fill_Buffer函數用於填充存儲器內容,它只是簡單實用for循環賦值方法給存儲區填充數據,它有三個形參,分別為存儲區指針、填充字節數和起始數選擇,這里的起始數選擇參數對本測試沒有實際意義。SD_WriteBlock函數和SD_ReadBlock函數分別執行數據寫入和讀取操作,具體可以參考代碼清單 3611和代碼清單 3613。Buffercmp函數用於比較兩個存儲區內容是否完全相等,它有三個形參,分別為第一個存儲區指針、第二個存儲區指針和存儲器長度,該函數只是循環比較兩個存儲區對應位置的兩個數據是否相等,只有發現存在不相等就報錯退出。
SD_MultiBlockTest函數與SD_SingleBlockTest函數執行過程類似,這里就不做詳細分析。
主函數
代碼清單 3620 main函數
1 int main(void)
2 {
3 /* 禁用WiFi模塊 */
4 BL8782_PDN_INIT();
5
6 /* 初始化LED燈 */
7 LED_GPIO_Config();
8 LED_BLUE;
9 /* 初始化獨立按鍵 */
10 Key_GPIO_Config();
11
12 /*初始化USART1*/
13 Debug_USART_Config();
14
15 printf("\r\n歡迎使用秉火 STM32 F429 開發板。\r\n");
16
17 printf("在開始進行SD卡基本測試前,請給開發板插入32G以內的SD卡\r\n");
18 printf("本程序會對SD卡進行非文件系統方式讀寫,會刪除SD卡的文件系統\r\n");
19 printf("實驗后可通過電腦格式化或使用SD卡文件系統的例程恢復SD卡文件系統\r\n");
20 printf("\r\n但sd卡內的原文件不可恢復,實驗前務必備份SD卡內的原文件!!!\r\n");
21
22 printf("\r\n若已確認,請按開發板的KEY1按鍵,開始SD卡測試實驗....\r\n");
23
24 /* Infinite loop */
25 while (1) {
26 /*按下按鍵開始進行SD卡讀寫實驗,會損壞SD卡原文件*/
27 if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON) {
28 printf("\r\n開始進行SD卡讀寫實驗\r\n");
29 SD_Test();
30 }
31 }
32 }
開發板板載了SDIO接口的WiFi模塊,可以認為是個SD I/O卡,因為STM32F42x系統控制器只有一個SDIO,為使用SD卡,需要把WiFi模塊的使能端拉低,禁用WiFi模塊,BL8782_PDN_INIT函數就是實現該功能。測試過程中有用到LED燈、獨立按鍵和調試串口,所以需要對這些模塊進行初始化配置。在無限循環中不斷檢測按鍵狀態,如果有被按下就執行SD卡測試函數。
36.9.3 下載驗證
把Micro SD卡插入到開發板右側的卡槽內,使用USB線連接開發板上的"USB TO UART"接口到電腦,電腦端配置好串口調試助手參數。編譯實驗程序並下載到開發板上,程序運行后在串口調試助手可接收到開發板發過來的提示信息,按下開發板左下邊沿的K1按鍵,開始執行SD卡測試,測試結果在串口調試助手可觀察到,板子上LED燈也可以指示測試結果。
36.10 每課一問
2. SD系統集成硬件CRC校驗,查閱相關資料了解CRC校驗原理。