1. 程式人生 > >【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十四:SD卡模組

【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十四:SD卡模組

驅動SD卡是件容易讓人抓狂的事情,驅動SD卡好比SDRAM執行頁讀寫,SD卡雖然不及SDRAM的麻煩要求(時序引數),但是驅動過程卻有猥瑣操作。除此此外,描述語言只要稍微比較一下C語言,描述語言一定會淚流滿面,因為巢狀迴圈,巢狀判斷,或者巢狀函式等都是它的痛。.

史萊姆模組是多模組建模的通病,意指結構能力非常脆弱的模組,暴力的巢狀行為往往會擊垮模組的美麗身軀,好讓脆弱結構更加脆弱還有慘不忍睹,最終搞垮模組的表達能力。描述語言預想駕馭SD卡,關鍵的地方就是如何提升模組的結構能力。簡單而言,描述語言如何不失自身的美麗,又用自身的方法,去實現巢狀迴圈或者巢狀函式等近似的內容呢?

低階建模I之際,論結構能力它確實有點勉強,所以SD卡的實驗才姍姍來遲。如今病貓已經進化為老虎,而且進化之初的新生兒都會肌餓如心焚,理智也不健全。因為如此,低階建模II才會不停舔著嘴脣,然後渴望新生的第一祭品。遇見SD卡,它彷彿遇見美味的獵物,口水都下流到一塌糊塗。

諸位少年少女們,讓我們一起歡呼活祭儀式的開始吧!

二十一世紀的今天,SD卡演化的速度簡直迅雷不及掩耳,如今SD卡已經逐漸突破64GB大關。對此,SD卡也存在N多版本,如版本SDV1.×,版本SDV2,或者SDHCV2等,當然未來還會繼續演化下去。所謂版本是指建造工藝還有協議,粗略而言,版本SDV1.×是指容量為2GB以下的SD卡,版本SDV2則指容量為2GB~4GB之間的SD卡,版本SDHCV2則是容量為4GB以上的SD卡。

話雖如此,不過實際情況還要根據各個廠商的心情而定,有些廠商的SD卡雖為4GB,但是版本卻是SDV1.×,還有廠商的SD卡的雖為 2GB,不過版本卻是SDV2,情況盡是讓人哭笑不得。此外,版本不會印刷在硬體的表面上,而且不同版本也有不同驅動方法。俗語有云,擒賊先擒卒——凡事從娃娃抓起,所以筆者遵循偉大的智慧,從版本SDV1.×開始動手。

clip_image002

圖24.1 SPI模式。

SD卡有SDIO還有SPI兩種模式,後者簡單又省事,所以SPI模式都是眾多懶惰鬼的喜愛。SPI模式一般只用4只引腳,而且主機(FPGA)與從機(SD卡)之間的連結如圖24.1所示,至於引腳的聶榮如表24.1所示:

表24.1 SD卡SPI模式的引腳說明。

引腳

說明

SD_CLK

序列時鐘,閒置為高

SD_NCS

片選,閒置為高,拉低有效

SD_DI

資料輸入,也是主機輸出

SD_DOUT

資料輸出,也是主機輸入

雖然DS1302也有SPI,但是資料線是雙向IO,反之SD卡則是一對出入的資料線。話雖如此,它們兩者都有乖乖遵守SPI的傳輸協議,即下降沿設定資料,上升沿鎖存資料。

clip_image004

圖24.2 寫一個位元組(主機視角)。

圖24.2是主機視角寫一個位元組的理想時序。主機會利用時鐘的下降沿,由高至低傳送一個位元組的資料。

clip_image006

圖24.3 讀一個位元組(主機視角)。

圖24.2是主機視角讀一個位元組的理想時序。從機會利用時鐘的下降沿,由高至低傳送一個位元組的資料,主機則會利用時鐘訊號的上升沿,由高至低讀取一個位元組的資料。

clip_image008

圖24.4 同時讀寫一個位元組(主機視角)。

我們知道SD卡有一對讀寫的資料線,為了節省時間,資料讀寫是同時發生的。如圖24.4所示,那是主機在同時讀寫的理想時序,讀者可以看成是圖24.2 還有圖24.3的結合體。

對此,Verilog可以這樣描述,結果如程式碼24.1所示:

1.    case(i)
2.        
3.        0,1,2,3,4,5,6,7:
4.        begin
5.             rDI <= iData[ 7-i ];                    
6.            
7.             if( C1 == 0 ) rSCLK <= 1'b0;
8.            else if( C1 == isHalf ) rSCLK <= 1'b1;
9.                                
10.            if( C1 == isQuarter ) D1[ 7-i ] <= SD_DOUT;
11.                                
12.            if( C1 == isFull -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end
13.            else begin C1 <= C1 + 1'b1; end
14.        end

程式碼24.1

如程式碼24.1所示,第12~13行表示步驟逗留的時間,其中isFull表示一個時鐘週期。第7~8行表示,C1為0拉低時鐘,C1為半個週期則拉高時鐘。第5行表示,任何時候都更新資料,也可以看成C1為0輸出資料。第10行表示,C1為四分之一週期鎖存資料。

第3行表示,步驟0~7造就一個位元組的讀寫。還有第5~10行的 D1[7-i] 表示,讀寫資料由高至低。

好奇的朋友一定會疑惑道,為何第10行的鎖存行為不是時鐘的半週期(上升沿),而是四分之一呢?原因很單純,因為資料在這個時候最為有效。

clip_image010

圖24.5 寫命令(主機視角)。

當然,SD卡不是給足兩隻骨頭就會滿足的哈士奇 ... 為此,除了單純的讀寫資料意外,SD卡還有所謂的寫命令,而寫命令則是讀寫位元組的複合體。如圖24.5所示,那是主機寫命令的理想時序,主機先由高至低傳送6個位元組的命令。SD卡接受完畢以後,便會反饋一個位元組的資料。期間,片選訊號必須處於拉低狀態。對此,Verilog可以這樣表示,結果如程式碼24.2所示:

1.     case( i )
2.            
3.         0:
4.         begin rCMD <= iAddr; i <= i + 1'b1; end                      
5.    
6.         1,2,3,4,5,6:
7.         begin T <= rCMD[47:40]; rCMD <= rCMD << 8; i <= FF_Write; Go <= i + 1'b1; end
8.                         
9.          7:
10.         begin i <= FF_Read; Go <= i + 1'b1; end
11.                         
12.         8:
13.         if( C2 == 100 ) begin C2 <= 10'd0; i <= i + 1'b1; end
14.         else if( D1 != 8'hff ) begin C2 <= 10'd0; i <= i + 1'b1; end
15.         else begin C2 <= C2 + 1'b1; i <= FF_Read; Go <= i; end
16.                         
17.         ...
18.                                              
19.         12,13,14,15,16,17,18,19: 
20.         begin
21.              rDI <= T[ 19-i ];        
22.              if( C1 == 0 ) rSCLK <= 1'b0;
23.             else if( C1 == isHalf ) rSCLK <= 1'b1;
24.                                
25.             if( C1 == isQuarter ) D1[ 19-i ] <= SD_DOUT;
26.                              
27.             if( C1 == isFull -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end
28.             else begin C1 <= C1 + 1'b1; end
29.         end
30.                         
31.        20: 
32.        begin i <= Go; end

程式碼24.2

步驟12~20是讀寫一個位元組的偽函式,步驟0準備6個位元組的命令,步驟1~6由高至低傳送命令,並且進入偽函式。步驟7進入偽函式,並且讀取一個位元組的反饋資料(注意FF_Write與FF_Read都指向步驟12)。反饋資料一般都是 8’hff 以外的結果,如果不是則重複讀取反饋資料100次,如果SD卡反應正常,都會在這100次以內反饋 8’hff以外的結果。

簡單而言,如何驅動SD卡就是如何使用相關的命令。版本SDV1.×的SD卡只需4個命令而已,亦即:

(一)CMD0,復位命令;

(二)CMD1,初始化命令;

(三)CMD24,寫命令;

(四)CMD17,讀命令。

CMD0用來複位SD卡,好讓SD卡處於(IDLE)待機狀態。CMD1用來初始化SD卡,好讓SD卡處於(Transfer)傳輸狀態。CMD24將512位元組資料寫入指定的地址,CMD17則將512位元組資料從指定的地址讀出來。

clip_image012

圖24.6 CMD0的理想時序圖。

圖24.6是CMD0的理想時序圖,首先在T1延遲1ms給予SD卡熱身時間,然後再在T2給予80個準備的時鐘。T3之際拉低CS,T4之際則傳送命令CMD0 { 8’h40, 32’d0, 8’h95},然後等待SD卡反饋資料R1。如果SD卡成功接收命令CMD0,內容則是8’h01。T5之際拉高CS,T6之際再8個結束時鐘。對此,Verilog可以這樣描述,結果如程式碼24.3所示:

1.     case( i )
2.                    
3.        0: // Disable cs, prepare Cmd0
4.        begin rCS <= 1'b1; D4 <=  {8'h40, 32'd0, 8'h95}; i <= i + 1'b1; end
5.                        
6.        1: // Wait 1MS for warm up;
7.        if( C1 == T1MS -1) begin C1 <= 16'd0; i <= i + 1'b1; end
8.        else begin C1 <= C1 + 1'b1; end
9.    
10.        2: // Send 80 free clock
11.        if( C1 == 10'd10 ) begin C1 <= 16'd0; i <= i + 1'b1; end
12.        else if( iDone ) begin isCall[0] <= 1'b0; C1 <= C1 + 1'b1; end
13.        else begin isCall[0] <= 1'b1; D1 <= 8'hff; end
14.                         
15.        3: // Enable cs
16.        begin rCS <= 1'b0; i <= i + 1'b1; end
17.                        
18.        4: // Try 200 time, ready error code.
19.        if( C1 == 10'd200 ) begin D2 <= CMD0ERR; C1 <= 16'd0; i <= 4'd8; end
20.        else if( iDone && iData != 8'h01) begin isCall[1] <= 1'b0; C1 <= C1 + 1'b1; end
21.        else if( iDone && iData == 8'h01 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end 
22.        else isCall[1] <= 1'b1;  
23.                         
24.        5: // Disable cs
25.        begin rCS <= 1'b1 ; i <= i + 1'b1; end
26.                         
27.        6: // Send free clock
28.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
29.        else begin isCall[0] <= 1'b1; D1 <= 8'hff; end
30.    
31.        7: // Disable cs, ready OK code
32.        begin D2 <= CMD0OK; i <= i + 1'b1; end //; 
33.    
34.         8: // Disbale cs, generate done signal
35.        begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end
36.                         
37.        9:
38.        begin isDone <= 1'b0; i <= 4'd0; end

程式碼24.3

我們先假設 isCall[1]執行寫命令,isCall[0]則是執行讀寫位元組。如程式碼24.3所示,步驟0用來準備CMD0命令。步驟1延遲1ms。步驟2執行10次無意義的讀寫,以示給予80個準備時鐘。在此讀者稍微注意一下第12行,每當完成一次讀寫C1便會遞增一下,C1遞增10次便表示讀寫執行10次。

步驟3拉低CS,並且步驟4傳送命令。步驟4可能會嚇壞一群小朋友,不過只要耐心解讀,其它它並不可怕。首先執行第22行的寫命令,如果反饋資料不為8’h01(第20行),消除isDo便遞增C1,然後再返回第22行。如果反饋資料為 8’h01(第21行)

,消除isDo與C1然後繼續步驟。如果重複執行100次都失敗,D2賦值CMD0的失敗資訊,消除C1並且i直接步驟8。

步驟5拉低CS,步驟6則給予8個結束時鐘。步驟7為D2賦值CMD0的成功資訊,步驟8~9拉高CS並且產生完成訊號。

clip_image014

圖24.7 CMD1的理想時序圖。

圖24.7是CMD1的理想時序圖,T0&T1之際拉低CS並且傳送六個位元組的命令CMD1 {8’h41,32’d0,8’hff}。SD卡接受命令以後便反饋資料R1——8’h00。T2&T3之際拉高CS並且給予8個結束時鐘。Verilog的描述結果如程式碼24.4所示:

1.     case( i )
2.                    
3.         0: // Enable cs, prepare Cmd1
4.         begin rCS <= 1'b0; D4 <= { 8'h41,32'd0,8'hff }; i <= i + 1'b1; end
5.                         
6.         1: // Try 100 times, ready error code.
7.         if( C1 == 10'd100 ) begin D2 <= CMD1ERR; C1 <= 16'd0; i <= 4'd5; end
8.         else if( iDone && iData != 8'h00) begin isCall[1]<= 1'b0; C1 <= C1 + 1'b1; end
9.         else if( iDone && iData == 8'h00 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end 
10.         else isCall[1] <= 1'b1;  
11.                         
12.         2: // Disable cs
13.         begin rCS <= 1'b1; i <= i + 1'b1; end
14.                         
15.        3: // Send free clock
16.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
17.        else begin isCall[0] <= 1'b1; D1 <= 8'hff; end
18.                         
19.        4: // Disable cs, ready OK code.
20.        begin D2 <= CMD1OK; i <= i + 1'b1; end
21.                         
22.        5: // Disable cs, generate done signal
23.        begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end
24.                         
25.        6:
26.        begin isDone <= 1'b0; i <= 4'd0; end

程式碼24.4

如程式碼24.4所示,步驟0準備命令CMD1。步驟1重複傳送CMD1命令100次,直至反饋資料R1為8’h00為止,否則反饋錯誤資訊。步驟2拉高CS,步驟3則給予結束時鐘。步驟4反饋成功資訊,步驟5~6拉高CS之餘也產生完成訊號。

好奇的同學一定會覺得疑惑,命令CMD0與命令CMD1同樣反饋資料R1,為何前者是8’h01,後者則是8’h00呢?事實上,R1的內容也反應SD卡的當前狀態,SD卡有待機狀態(IDLE)還有傳輸狀態(Transfer)等兩個常見狀態。

clip_image016

圖24.8 版本V1.×的初始化流程圖。

如圖24.8所示,那是版本V1.x的初始化流程圖。主機先發送CMD0,SD卡接收以後如果反饋R1為8’h01便繼續流程,否則重複傳送CMD0。主機接著傳送CMD1,如果SD卡接收並且反饋R1為8’h00,該結果表示SD卡以從待機狀態進入傳輸狀態,餘下CMD24還有CMD17才有效。

clip_image018

圖24.9 CMD24的理想時序圖。

圖24.9是CMD24的理想時序圖。T0~1之際,主機拉低CS之餘,主機也向SD卡傳送寫命令CMD24,其中Addr 3~Addr 0是寫入地址。SD卡接收以後便以反饋資料8’h00表示接收成功。保險起見,主機在T2給足800個準備時鐘,如果讀者嫌準備時鐘給太多,讀者可以自行縮小至80。T3之際,主機發送8’hfe以示寫512位元組開始,T4~T7則是寫512位元組的過程。T8~T9分別寫入兩個CRC位元組(CRC校驗)。

完後,SD卡便會反饋8’h05以示寫512位元組成功,此刻(T10~11)主機讀取並且檢測。事後直至SD卡傳送8’hff為止,SD卡都處於忙狀態。換言之,如果主機在T12成功讀取8’hff,結果表示SD卡已經忙完了。T13之際,主機再拉高CS。對此,Verilog可以這樣描述,結果如程式碼24.5所示:

1.    case(i)
2.    
3.         0: // Enable cs, prepare cmd24
4.        begin rCS <= 1'b0; D4 = { 8'h58, iAddr, 9'd0, 8'hFF }; i <= i + 1'b1; end
5.                         
6.        1: // Try 100 times, ready error code.
7.        if( C1 == 100 ) begin D2 <= CMD24ERR; C1 <= 16'd0; i <= 4'd14; end
8.        else if( iDone && iData != 8'h00) begin isCall[1] <= 1'b0; C1 <= C1 + 1'b1; end
9.        else if( iDone && iData == 8'h00 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end
10.        else isCall[1] <= 1'b1;
11.                         
12.        2: // Send 800 free clock 
13.        if( C1 == 100 ) begin C1 <= 16'd0; i <= i + 1'b1; end
14.        else if( iDone ) begin isCall[0] <= 1'b0; C1 <= C1 + 1'b1; end 
15.        else begin isCall[0] <= 1'b1; D1 <= 8'hFF; end
16.                         
17.        3: // Send Call byte 0xfe
18.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
19.        else begin isCall[0] <= 1'b1; D1 <= 8'hFE; end
20.                                              
21.        4: // Pull up read req.
22.        begin isEn[0] <= 1'b1; i <= i + 1'b1; end
23.                         
24.        5: // Pull down read req.
25.        begin isEn[0] <= 1'b0; i <= i + 1'b1; end
26.                         
27.        6: // Write byte from fifo
28.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
29.        else begin isCall[0] <= 1'b1; D1 <= iDataFF; end
30.                         
31.        7: // Repeat 512 times
32.        if( C1 == 10'd511 ) begin C1 <= 16'd0; i <= i + 1'b1; end
33.        else begin C1 <= C1 + 1'b1; i <= 4'd4; end
34.                         
35.        8: // Write 1st CRC
36.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
37.        else begin isCall[0] <= 1'b1; D1 <= 8'hff; end
38.                         
39.        9: // Write 2nd CRC
40.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
41.        else begin isCall[0] <= 1'b1; D1 <= 8'hff; end
42.                         
43.        10: // Read Respond
44.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
45.        else begin isCall[0] <= 1'b1; end
46.                         
47.        11: // if not 8'h05, faild and ready error code
48.        if( (iData & 8'h1F) != 8'h05 ) begin D2 <= CMD24ERR; i <= 4'd14; end
49.        else i <= i + 1'b1;
50.                         
51.        12: // Wait unitl sdcard free
52.        if( iDone && iData == 8'hff ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
53.        else if( iDone ) begin isCall[0] <= 1'b0; end
54.        else begin isCall[0] <= 1'b1; end
55.                         
56.         13: // Disable cs, ready OK code;
57.        begin D2 <= CMD24OK; i <= i + 1'b1; end
58.                         
59.        14: // Disable cs, generate done signal
60.        begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end
61.                         
62.        15:
63.        begin isDone <= 1'b0; i <= 4'd0; end

程式碼24.5

步驟0拉低CS之餘,它也準備寫命令CMD24,其中 { iAddr, 9’d0 } 表示地址有512偏移量,內容等價 iAddr << 9。步驟1嘗試寫命令100次,直至反饋內容為8’h00,否則便準備錯誤資訊。步驟2傳送800個準備時鐘,如果嫌多可以自行縮小。步驟3寫入開始位元組 8’hfe。步驟4~5主要是從FIFO取得資料,步驟6則是將資料寫入SD卡,步驟7用來控制迴圈的次數。步驟8~9分別寫入兩個CRC位元組,內容隨意。

步驟10讀取反饋資料,步驟11則檢測反饋資料是否為8’h05,是則繼續,不是則準備錯誤資訊,並且跳轉步驟14(結束操作)。步驟12不斷讀取資料,直至讀取內容為8’hff位置。步驟13準備成功資訊。步驟14~15拉高CS之餘也產生完成訊號。在此,讀者要稍微注意一下,步驟4~7組合起來,類似先執行後判斷的 do ... while 迴圈。

clip_image020

圖24.10 CMD17的理想時序圖。

圖24.10是CMD17的理想時序圖。T0~T1之際,主機先拉低CS再發送命令CMD17,其中Addr3~Addr0是指定的讀地址,事後SD卡便會反饋資料8’h00以示接收成功。T2之際,主機會不斷讀取資料,如果讀取內容是8’hfe,結果表示SD卡已經準備傳送512位元組資料。T3~T6之際,主機不斷從SD卡那裡讀取512個位元組的資料。T7~T8之際,主機讀取兩個位元組的CRC,然後在T9~10拉高CS之餘也給足8個結束時鐘。

換之,Verilog的描述結果如程式碼24.6所示:

1.     case( i )
2.                    
3.        0: // Enable cs, prepare cmd17
4.        begin rCS <= 1'b0; D4 <= { 8'h51, iAddr, 9'd0, 8'hFF }; i <= i + 1'b1; end
5.                         
6.        1: // Try 100 times, ready error code
7.        if( C1 == 100 ) begin D2 <= CMD17ERR; C1 <= 16'd0; i <= 4'd12; end
8.        else if( iDone && iData != 8'h00 ) begin isCall[1] <= 1'b0; C1 <= C1 + 1'b1; end
9.        else if( iDone && iData == 8'h00 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end
10.        else isCall[1] <= 1'b1;
11.                         
12.        2: // Wait read ready
13.        if( iDone && iData == 8'hfe ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
14.        else if( iDone && iData != 8'hfe ) begin isCall[0] <= 1'b0; end
15.        else isCall[0] <= 1'b1;
16.                         
17.        3:  // Read byte
18.        if( iDone ) begin D3 <= iData; isCall[0] <= 1'b0; i <= i + 1'b1;  end
19.        else begin isCall[0] <= 1'b1; end
20.                         
21.        4: // Pull up write req.
22.        begin isEn[1] <= 1'b1; i <= i + 1'b1; end
23.                                      
24.        5: // Pull down write req.
25.        begin isEn[1] <= 1'b0; i <= i + 1'b1; end 
26.                         
27.        6: // Repeat 512 times
28.        if( C1 == 10'd511 ) begin C1 <= 16'd0; i <= i + 1'b1; end
29.        else begin C1 <= C1 + 1'b1; i <= 4'd3; end
30.                         
31.        7,8: // Read 1st and 2nd byte CRC
32.        if( iDone ) begin D3 <= iData; isCall[0] <= 1'b0; i <= i + 1'b1; end 
33.        else isCall[0] <= 1'b1;
34.                         
35.        9: // Disable cs
36.        begin rCS <= 1'b1; i <= i + 1'b1; end
37.                         
38.        10: // Send free clock
39.        if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end 
40.        else begin isCall[0] <= 1'b1; D1 <= 8'hFF; end
41.                         
42.        11: // Ready OK code
43.        begin D2 <= CMD17OK; i <= i + 1'b1; end
44.                         
45.        12: // Disable cs, generate done signal
46.        begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end
47.                         
48.        13:
49.        begin isDone <= 1'b0; i <= 4'd0; end

程式碼24.6

步驟0拉低CS之餘也準備命令CMD17,其中 {iAddr,9’d0} 為 512的偏移量。步驟1重複100次寫命令,直至SD卡反饋8’h00,否則準備錯誤資訊,然後跳轉步驟12(結束操作)。步驟2不斷讀取資料,直至讀取內容為8’hfe為止。步驟3讀取資料,步驟4~5則將資料寫入FIFO,步驟6用來控制迴圈。步驟7~8讀取兩個位元組CRC。步驟9拉高CS,步驟10則給足8個結束時鐘。步驟11準備成功資訊。步驟12拉高CS之餘,也產生完成訊號。

上述內容理解完畢以後,我們便可以開始建模了。

clip_image022

圖24.11 SD卡基礎模組的建模圖。

圖24.11是SD卡基礎模組的建模圖,其中內容包括SD卡控制模組,SD卡功能模組,還有兩個fifo儲存模組。SD卡功能模組的溝通訊號Call/Done有兩位位寬,其中[1]為寫命令,[0]為位元組讀寫。SD卡控制模組的Call/Done位寬有四,表示它支援4個命令,其中[3]為CMD24,[2]為CMD17,[1]為CMD1,[0]為CMD0。兩隻FIFO儲存模組充當寫快取(上)還有讀快取(下),它們被外界呼叫以外,它們也被SD卡控制模組呼叫。

sdcard_funcmod.v

clip_image024

圖24.12 SD卡功能模組的建模圖。

圖24.12是SD卡功能模組的建模圖,右邊是驅動SD卡的頂層訊號,左邊則是溝通用,還有命令,iData與oData等資料訊號。Call/Done位寬有兩,其中[1]為寫命令,[0]為讀寫資料。

clip_image026

圖24.13 不同狀態之間的傳輸速率。

話題繼續之前,請允許筆者作足一些小補充。如圖24.13所示,待機狀態SD卡為低速狀態,速率推薦為100Khz~500Khz之間。保險起見,筆者取為100Khz。反之,傳輸狀態SD卡處於高速狀態,速率推薦為2Mhz或者以上。筆者衡量各種因數以後,筆者決定選擇10Mhz。喪心病狂的讀者當然可以選擇10Mhz以上的速率,如果硬體允許的話 ... 據說,100Mhz也沒有問題。

1.    module sdcard_funcmod
2.    (
3.         input CLOCK, RESET,
4.         input SD_DOUT,
5.         output SD_CLK,
6.         output SD_DI,
7.         
8.         input [1:0]iCall,
9.         output oDone,
10.         input [47:0]iAddr,
11.         input [7:0]iData,
12.         output [7:0]oData
13.    );

以上內容為出入端宣告。

14.         parameter FLCLK = 10'd500, FLHALF = 10'd250, FLQUARTER = 10'd125; //T20US = 100Khz
15.         parameter FHCLK = 10'd5, FHHALF = 10'd1, FHQUARTER = 10'd2; // T1us = 10Mhz
16.         
17.         reg [9:0]isFull,isHalf,isQuarter;
18.         
19.        always @ (  posedge CLOCK or negedge RESET )
20.            if( !RESET )
21.                begin 
22.                      isFull <= FLCLK;
23.                      isHalf <= FLHALF;
24.                      isQuarter <= FLQUARTER;
25.                end    
26.             else if( iAddr[47:40] == 8'h41 && isDone )
27.                begin
28.                      isFull <= FHCLK;
29.                      isHalf <= FHHALF;
30.                      isQuarter <= FHQUARTER;
31.                 end
32.                      

第14~15行是100Khz還有10Mhz的常量宣告,F×CLK為一個週期,F×HALF為半個週期,F×QUARTER為四分之一週期,其中×為L表示低速,×為H表示高速。第17~31行是設定速率的周邊操作,起始下為低速(第22~24)。不過,當命令CMD1執行成功以後,速率轉為為高速(第26~31行)。

33.         parameter FF_Write = 5'd12, FF_Read = 5'd12;
34.        
35.         reg [5:0]i,Go;
36.         reg [9:0]C1,C2;
37.         reg [7:0]T,D1;
38.         reg [47:0]rCMD;
39.         reg rSCLK,rDI;
40.         reg isDone;
41.         
42.        always @ ( posedge CLOCK or negedge RESET )
43.             if( !RESET )
44.                  begin
45.                         { i,Go } <= { 6'd0,6'd0};
46.                         { C1,C2 } <= { 10'd0, 10'd0 };
47.                         { T,D1 } <= { 8'd0,8'd0 };
48.                         rCMD <= 48'd0;
49.                         { rSCLK,rDI } <= 2'b11;
50.                         isDone <= 1'b0;
51.                    end

以上內容為相關的暫存器宣告還有復位操作。其中第33行是偽函式的入口地址。

52.              else if( iCall[1] )  
53.                  case( i )
54.            
55.                         0:
56.                         begin rCMD <= iAddr; i <= i + 1'b1; end  
57.                         
58.                         1,2,3,4,5,6:
59.                         begin T <= rCMD[47:40]; rCMD <= rCMD << 8; i <= FF_Write; Go <= i + 1'b1; end
60.                         
61.                         7:
62.                         begin i <= FF_Read; Go <= i + 1'b1; end
63.                         
64.                         8:
65.                         if( C2 == 100 ) begin C2 <= 10'd0; i <= i + 1'b1; end
66.                         else if( D1 != 8'hff ) begin C2 <= 10'd0; i <= i + 1'b1; end
67.                         else begin C2 <= C2 + 1'b1; i <= FF_Read; Go <= i; end
68.                         
69.                         9:
70.                         begin isDone <= 1'b1; i <= i + 1'b1; end
71.                         
72.                         10:
73.                         begin isDone <= 1'b0; i <= 6'd0; end
74.                         
75.                         /******************************/
76.                         
77.                         12,13,14,15,16,17,18,19: 
78.                         begin
79.                              rDI <= T[ 19-i ];
80.                                
81.                              if( C1 == 0 ) rSCLK <= 1'b0;
82.                              else if( C1 == isHalf ) rSCLK <= 1'b1;
83.                                
84.                              if( C1 == isQuarter ) D1[ 19-i ] <= SD_DOUT;
85.                              
86.                              if( C1 == isFull -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end
87.                              else begin C1 <= C1 + 1'b1; end
88.                         end
89.                         
90.                         20: 
91.                         begin i <= Go; end
92.            
93.                endcase    

以上內容為寫命令。

94.              else if( iCall[0] ) 
95.                  case( i )
96.                        
97.                         0,1,2,3,4,5,6,7:
98.                         begin
99.                              rDI <= iData[ 7-i ];
100.                                
101.                              if( C1 == 0 ) rSCLK <= 1'b0;
102.                              else if( C1 == isHalf ) rSCLK <= 1'b1;
103.                                
104.                              if( C1 == isQuarter ) D1[ 7-i ] <= SD_DOUT;
105.                                
106.                              if( C1 == isFull -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end
107.                              else begin C1 <= C1 + 1'b1; end
108.                         end
109.                         
110.                         8:
111.                         begin isDone <= 1'b1; i <= i + 1'b1; end
112.                         
113.                         9:
114.                         begin isDone <= 1'b0; i <= 6'd0; end
115.                    
116.                    endcase    
117.        

以上內容為讀寫一個位元組。

118.         assign SD_CLK = rSCLK;     
119.         assign SD_DI = rDI;     
120.         assign oDone = isDone;
121.         assign oData = D1;
122.    
123.    endmodule

以上內容為相關的輸出驅動。

fifo_savemod.v

clip_image028

圖24.14 FIFO儲存模組的建模圖。

圖24.14是大夥看爛的FIFO儲存模組,具體內容讓我們來看程式碼吧。

1.    module fifo_savemod
2.    (
3.         input CLOCK, RESET, 
4.         input [1:0]iEn,
5.         input [7:0]iData,
6.         output [7:0]oData,
7.         output [1:0]oTag
8.    );
9.        initial begin
10.             for( C1 = 0; C1 < 1024; C1 = C1 + 1'b1 )
11.                  begin  RAM[ C1 ] <= 8'd0; end    
12.         end
13.        
14.        reg [7:0] RAM [1023:0]; 
15.        reg [10:0] C1 = 11'd0,C2 = 11'd0; // N+1
16.         reg [7:0]D1;
17.                  
18.       always @ ( posedge CLOCK or negedge RESET )
19.            if( !RESET )
20.                 begin
21.                      C1 <= 11'd0;
22.                  end
23.           else if( iEn[1] ) 
24.                 begin   
25.                    RAM[ C1[9:0] ] <= iData; 
26.                     C1 <= C1 + 1'b1; 
27.                  end
28.                  
29.        always @ ( posedge CLOCK or negedge RESET )
30.            if( !RESET )
31.                 begin
32.                        C2 <= 11'd0;
33.                        D1 <= 8'd0;
34.                  end
35.             else if( iEn[0] )
36.                    begin 
37.                         D1 <= RAM[ C2[9:0] ]; 
38.                         C2 <= C2 + 1'b1; 
39.                    end
40.        
41.          assign oData = D1;                
42.          assign oTag[1] = ( C1[10]^C2[10] & C1[9:0] == C2[9:0] ); // Full Left
43.           assign oTag[0] = ( C1 == C2 ); // Empty Right
44.    
45.    endmodule

由於資料緩衝物件不是SDRAM,所以第41行的oData由D1驅動而不是RAM直接驅動。餘下內容,讀者自己看著辦吧。

sdcard_ctrlmod.v

clip_image030

圖24.15 SD卡控制模組的建模圖。

圖24.15是SD卡控制模組的建模圖,它好比一隻刺蝟,全身上下都長滿箭頭,讓人看見也怕怕。右邊是呼叫功能模組的訊號群,上下則是呼叫儲存模組的訊號群。左邊則是被外界呼叫的訊號群,其中頂層訊號SD_NCS是SD卡的片選訊號。此外,Call/Done位寬有4,表示該模組支援4個命令,[3]為CMD24, [2]為CMD17, [1]為CMD1,[0]為CMD0。至於oTag則是用來反饋命令的執行狀態。

1.    module sdcard_ctrlmod
2.    (
3.         input CLOCK, RESET,
4.         output SD_NCS,
5.         
6.         input [3:0]iCall,
7.         output oDone,
8.         input [22:0]iAddr,
9.         output [7:0]oTag,
10.         
11.         output [1:0]oEn, // [1] Write [0] Read
12.         input [7:0]iDataFF,
13.         output [7:0]oDataFF,
14.         
15.         output [1:0]oCall, 
16.         input iDone,
17.         output [47:0]oAddr,
18.         input [7:0]iData,
19.         output [7:0]oData
20.    );    

以上內容為相關的出入端宣告,第6~9行是外界呼叫的訊號,第11~13行是呼叫FIFO的訊號,第15~19則是呼叫功能模組的訊號。

21.         parameter CMD0ERR = 8'hA1, CMD0OK = 8'hA2, CMD1ERR = 8'hA3, CMD1OK = 8'hA4; 
22.         parameter CMD24ERR = 8'hA5, CMD24OK = 8'hA6, CMD17ERR = 8'hA7, CMD17OK = 8'hA8;
23.         parameter T1MS = 16'd10;
24.        

以上內容為各個命令的成功資訊還有失敗資訊之間的常量宣告。

25.         reg [3:0]i;
26.         reg [15:0]C1;
27.         reg [7:0]D1,D2,D3;  // D1 WrData, D2 FbData, D3 RdData
28.         reg [47:0]D4;       // D4 Cmd
29.         reg [1:0]isCall,isEn;
30.         reg rCS;
31.         reg isDone;
32.         
33.        always @ ( posedge CLOCK or negedge RESET )
34.             if( !RESET )
35.                  begin
36.                        i <= 4'd0;
37.                         C1 <= 16'd0;
38.                         { D1,D2,D3 } <= { 8'd0, 8'd0, 8'd0 };
39.                         D4 <= 48'd0;
40.                         { isCall, isEn } <= { 2'd0,2'd0 }; 
41.                         rCS <= 1'b1;
42.                    end

以上內容為相關的暫存器宣告還有復位操作,其中D1暫存寫資料,D2暫存反饋資訊,D3暫存讀資料,D4暫存命令,isDo控制寫命令還有位元組讀寫,isEn控制FIFO的讀寫。

所有暫存器的復位值為0,rCS除外。

43.            else if( iCall[3] ) // cmd24
44.                  case( i )
45.                    
46.                         0: // Enable cs, prepare cmd24
47.                         begin rCS <= 1'b0; D4 = { 8'h58, iAddr, 9'd0, 8'hFF }; i <= i + 1'b1; end
48.                         
49.                         1: // Try 100 times, ready error code.
50.                         if( C1 == 100 ) begin D2 <= CMD24ERR; C1 <= 16'd0; i <= 4'd14; end
51.                         else if( iDone && iData != 8'h00) begin isCall[1] <= 1'b0; C1 <= C1 + 1'b1; end
52.                         else if( iDone && iData == 8'h00 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end
53.                         else isCall[1] <= 1'b1;
54.                         
55.                         2: // Send 800 free clock 
56.                         if( C1 == 100 ) begin C1 <= 16'd0; i <= i + 1'b1; end
57.                         else if( iDone ) begin isCall[0] <= 1'b0; C1 <= C1 + 1'b1; end 
58.                         else begin isCall[0] <= 1'b1; D1 <= 8'hFF; end
59.                         
60.                         3: // Send Call byte 0xfe
61.                         if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
62.                         else begin isCall[0] <= 1'b1; D1 <= 8'hFE; end
63.                         
64.                         /*****************/
65.                         
66.                         4: // Pull up read req.
67.                         begin isEn[0] <= 1'b1; i <= i + 1'b1; end
68.                         
69.                         5: // Pull down read req.
70.                         begin isEn[0] <= 1'b0; i <= i + 1'b1; end
71.                         
72.                         6: // Write byte from fifo
73.                         if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
74.                         else begin isCall[0] <= 1'b1; D1 <= iDataFF; end
75.                         
76.                         7: // Repeat 512 times
77.                         if( C1 == 10'd511 ) begin C1 <= 16'd0; i <= i + 1'b1; end
78.                         else begin C1 <= C1 + 1'b1; i <= 4'd4; end
79.                         
80.                          /*****************/
81.                         
82.                         8: // Write 1st CRC
83.                         if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
84.                         else begin isCall[0] <= 1'b1; D1 <= 8'hff; end
85.                         
86.                         9: // Write 2nd CRC
87.                         if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
88.                         else begin isCall[0] <= 1'b1; D1 <= 8'hff; end
89.                         
90.                         10: // Read Respond
91.                         if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
92.                         else begin isCall[0] <= 1'b1; end
93.                         
94.                         11: // if not 8'h05, faild and ready error code
95.                         if( (iData & 8'h1F) != 8'h05 ) begin D2 <= CMD24ERR; i <= 4'd14; end
96.                         else i <= i + 1'b1;
97.                         
98.                         12: // Wait unitl sdcard free
99.                         if( iDone && iData == 8'hff ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
100.                         else if( iDone ) begin isCall[0] <= 1'b0; end
101.                         else begin isCall[0] <= 1'b1; end
102.                         
103.                         /*****************/
104.                         
105.                         13: // Disable cs, ready OK code;
106.                         begin D2 <= CMD24OK; i <= i + 1'b1; end
107.                         
108.                         14: // Disable cs, generate done signal
109.                         begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end
110.                         
111.                         15:
112.                         begin isDone <= 1'b0; i <= 4'd0; end
113.                    
114.                    endcase

以上內容為命令CMD24。

115.            else if( iCall[2] ) // cmd17
116.                  case( i )
117.                    
118.                        0: // Enable cs, prepare cmd17
119.                         begin rCS <= 1'b0; D4 <= { 8'h51, iAddr, 9'd0, 8'hFF }; i <= i + 1'b1; end
120.                         
121.                         1: // Try 100 times, ready error code
122.                         if( C1 == 100 ) begin D2 <= CMD17ERR; C1 <= 16'd0; i <= 4'd12; end
123.                         else if( iDone && iData != 8'h00 ) begin isCall[1] <= 1'b0; C1 <= C1 + 1'b1; end
124.                         else if( iDone && iData == 8'h00 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end
125.                         else isCall[1] <= 1'b1;
126.                         
127.                         2: // Wait read ready
128.                         if( iDone && iData == 8'hfe ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
129.                         else if( iDone && iData != 8'hfe ) begin isCall[0] <= 1'b0; end
130.                         else isCall[0] <= 1'b1;
131.                         
132.                         /********/
133.                         
134.                         3:  // Read byte
135.                         if( iDone ) begin D3 <= iData; isCall[0] <= 1'b0; i <= i + 1'b1;  end
136.                         else begin isCall[0] <= 1'b1; end
137.                         
138.                         4: // Pull up write req.
139.                         begin isEn[1] <= 1'b1; i <= i + 1'b1; end
140.                                              
141.                         5: // Pull down write req.
142.                         begin isEn[1] <= 1'b0; i <= i + 1'b1; end 
143.                         
144.                         6: // Repeat 512 times
145.                         if( C1 == 10'd511 ) begin C1 <= 16'd0; i <= i + 1'b1; end
146.                         else begin C1 <= C1 + 1'b1; i <= 4'd3; end
147.