【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十四:SD卡模块

来源:互联网 时间:1970-01-01

 

驱动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.×开始动手。

图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的传输协议,即下降沿设置数据,上升沿锁存数据。

图24.2 写一个字节(主机视角)。

图24.2是主机视角写一个字节的理想时序。主机会利用时钟的下降沿,由高至低发送一个字节的数据。

图24.3 读一个字节(主机视角)。

图24.2是主机视角读一个字节的理想时序。从机会利用时钟的下降沿,由高至低发送一个字节的数据,主机则会利用时钟信号的上升沿,由高至低读取一个字节的数据。

图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行的锁存行为不是时钟的半周期(上升沿),而是四分之一呢?原因很单纯,因为数据在这个时候最为有效。

图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字节数据从指定的地址读出来。

图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并且产生完成信号。

图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)等两个常见状态。

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

如图24.8所示,那是版本V1.x的初始化流程图。主机先发送CMD0,SD卡接收以后如果反馈R1为8’h01便继续流程,否则重复发送CMD0。主机接着发送CMD1,如果SD卡接收并且反馈R1为8’h00,该结果表示SD卡以从待机状态进入传输状态,余下CMD24还有CMD17才有效。

图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 循环。

图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之余,也产生完成信号。

上述内容理解完毕以后,我们便可以开始建模了。

图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

图24.12 SD卡功能模块的建模图。

图24.12是SD卡功能模块的建模图,右边是驱动SD卡的顶层信号,左边则是沟通用,还有命令,iData与oData等数据信号。Call/Done位宽有两,其中[1]为写命令,[0]为读写数据。

图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

图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

图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. 

148. /********/

149. 

150. 7,8: // Read 1st and 2nd byte CRC

151. if( iDone ) begin D3 <= iData; isCall[0] <= 1'b0; i <= i + 1'b1; end 

152. else isCall[0] <= 1'b1;

153. 

154. 9: // Disable cs

155. begin rCS <= 1'b1; i <= i + 1'b1; end

156. 

157. 10: // Send free clock

158. if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end 

159. else begin isCall[0] <= 1'b1; D1 <= 8'hFF; end

160. 

161. 11: // Ready OK code

162. begin D2 <= CMD17OK; i <= i + 1'b1; end

163. 

164. 12: // Disable cs, generate done signal

165. begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end

166. 

167. 13:

168. begin isDone <= 1'b0; i <= 4'd0; end

169. 

170. endcase 


以上内容为命令CMD17。

171. else if( iCall[1] ) // cmd1

172. case( i )

173. 

174. 0: // Enable cs, prepare Cmd1

175. begin rCS <= 1'b0; D4 <= { 8'h41,32'd0,8'hff }; i <= i + 1'b1; end

176. 

177. 1: // Try 100 times, ready error code.

178. if( C1 == 10'd100 ) begin D2 <= CMD1ERR; C1 <= 16'd0; i <= 4'd5; end

179. else if( iDone && iData != 8'h00) begin isCall[1]<= 1'b0; C1 <= C1 + 1'b1; end

180. else if( iDone && iData == 8'h00 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end 

181. else isCall[1] <= 1'b1; 

182. 

183. 2: // Disable cs

184. begin rCS <= 1'b1; i <= i + 1'b1; end

185. 

186. 3: // Send free clock

187. if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end

188. else begin isCall[0] <= 1'b1; D1 <= 8'hff; end

189. 

190. /******************/

191. 

192. 4: // Disable cs, ready OK code.

193. begin D2 <= CMD1OK; i <= i + 1'b1; end

194. 

195. 5: // Disable cs, generate done signal

196. begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end

197. 

198. 6:

199. begin isDone <= 1'b0; i <= 4'd0; end

200. 

201. endcase


以上内容为命令CMD1。

202. else if( iCall[0] ) // cmd0

203. case( i )

204. 

205. 0: // Disable cs, prepare Cmd0

206. begin rCS <= 1'b1; D4 <= {8'h40, 32'd0, 8'h95}; i <= i + 1'b1; end

207. 

208. 1: // Wait 1MS for warm up;

209. if( C1 == T1MS -1) begin C1 <= 16'd0; i <= i + 1'b1; end

210. else begin C1 <= C1 + 1'b1; end

211. 

212. 2: // Send 80 free clock

213. if( C1 == 10'd10 ) begin C1 <= 16'd0; i <= i + 1'b1; end

214. else if( iDone ) begin isCall[0] <= 1'b0; C1 <= C1 + 1'b1; end

215. else begin isCall[0] <= 1'b1; D1 <= 8'hff; end

216. 

217. 3: // Enable cs

218. begin rCS <= 1'b0; i <= i + 1'b1; end

219. 

220. 4: // Try 200 time, ready error code.

221. if( C1 == 10'd200 ) begin D2 <= CMD0ERR; C1 <= 16'd0; i <= 4'd8; end

222. else if( iDone && iData != 8'h01) begin isCall[1] <= 1'b0; C1 <= C1 + 1'b1; end

223. else if( iDone && iData == 8'h01 ) begin isCall[1] <= 1'b0; C1 <= 16'd0; i <= i + 1'b1; end 

224. else isCall[1] <= 1'b1; 

225. 

226. 5: // Disable cs

227. begin rCS <= 1'b1 ; i <= i + 1'b1; end

228. 

229. 6: // Send free clock

230. if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end

231. else begin isCall[0] <= 1'b1; D1 <= 8'hff; end

232. 

233. 7: // Disable cs, ready OK code

234. begin D2 <= CMD0OK; i <= i + 1'b1; end 

235. 

236. 8: // Disbale cs, generate done signal

237. begin rCS <= 1'b1; isDone <= 1'b1; i <= i + 1'b1; end

238. 

239. 9:

240. begin isDone <= 1'b0; i <= 4'd0; end

241. 

242. endcase

243. 


以上内容为命令CMD0。

244. assign SD_NCS = rCS;

245. assign oDone = isDone;

246. assign oTag = D2;

247. assign oEn = isEn; 

248. assign oDataFF = D3;

249. assign oCall = isCall;

250. assign oAddr = D4;

251. assign oData = D1;

252. 

253. endmodule


以上内容为相关的输出驱动声明。

sdcard_basemod.v

该模块为SD卡基础模块,连线部署请参考图24.11。

1. module sdcard_basemod

2. (

3. input CLOCK, RESET,

4. input SD_DOUT,

5. output SD_CLK,

6. output SD_DI,

7. output SD_NCS, 

8. 

9. input [3:0]iCall,

10. output oDone,

11. input [22:0]iAddr,

12. output [7:0]oTag,

13. 

14. input [1:0]iEn,

15. input [7:0]iData,

16. output [7:0]oData

17. ); 

18. wire [1:0]EnU1;

19. wire [7:0]DataFFU1;

20. wire [1:0]CallU1;

21. wire [47:0]AddrU1;

22. wire [7:0]DataU1;

23. 

24. sdcard_ctrlmod U1

25. (

26. .CLOCK( CLOCK ),

27. .RESET( RESET ),

28. .SD_NCS( SD_NCS ), // > top

29. .iCall( iCall ), // < top

30. .oDone( oDone ), // < top

31. .iAddr( iAddr ), // < top

32. .oTag( oTag ), // > top

33. .oEn( EnU1 ), // > U2 & U3

34. .iDataFF( DataFFU2 ), // < U2 

35. .oDataFF( DataFFU1 ), // > U3

36. .oCall( CallU1 ), // > U4 

37. .iDone( DoneU4 ), // < U4

38. .oAddr( AddrU1 ), // > U4

39. .iData( DataU4 ), // < U4

40. .oData( DataU1 ) // > U4

41. );

42. 

43. wire [7:0]DataFFU2;

44. 

45. fifo_savemod U2

46. (

47. .CLOCK ( CLOCK ),

48. .RESET( RESET ),

49. .iEn ( {iEn[1],EnU1[0]} ), // < top & U1

50. .iData ( iData ), // < top

51. .oData ( DataFFU2 ), // > U1

52. .oTag ()

53. );

54. 

55. fifo_savemod U3

56. (

57. .CLOCK ( CLOCK ),

58. .RESET( RESET ),

59. .iEn ( {EnU1[1],iEn[0]} ), // < top & U1

60. .iData ( DataFFU1 ), // < U1

61. .oData ( oData ), // > top

62. .oTag ()

63. );

64. 

65. wire DoneU4;

66. wire [7:0]DataU4;

67. 

68. sdcard_funcmod U4

69. (

70. .CLOCK( CLOCK ), 

71. .RESET( RESET ),

72. .SD_CLK( SD_CLK ), // > top

73. .SD_DOUT( SD_DOUT ), // < top

74. .SD_DI( SD_DI ), // > top

75. .iCall( CallU1 ), // < U1

76. .oDone( DoneU4 ), // > U1

77. .iAddr( AddrU1 ), // < U1

78. .iData( DataU1 ), // < U1

79. .oData( DataU4 ) // > U1

80. );

81. 

82. endmodule


以上内容,读者自己看着办吧,笔者犯懒了。

sdcard_demo.v

图24.16 实验二十四的建模图。

图24.16是实验二十四的建模图,右边是SD卡基础模块,右边则是调用该模块的核心程序。核心程序先初始化SD卡,期间也将反馈信息经由TXD发送出去。再者,它将512个字节写入SD卡,又从中读出,然后经由TXD发送出去。具体内容让我们来看代码吧:

1. module sdcard_demo

2. (

3. input CLOCK,RESET,

4. output SD_NCS, 

5. output SD_CLK,

6. input SD_DOUT,

7. output SD_DI,

8. output TXD

9. );


以上内容为相关的出入端声明。

10. wire DoneU1;

11. wire [7:0]TagU1;

12. wire [7:0]DataU1;

13. 

14. sdcard_basemod U1

15. (

16. .CLOCK( CLOCK ), 

17. .RESET( RESET ),

18. .SD_DOUT( SD_DOUT ),

19. .SD_CLK( SD_CLK ),

20. .SD_DI( SD_DI ),

21. .SD_NCS( SD_NCS ), 

22. .iCall( isCall ),

23. .oDone( DoneU1 ),

24. .iAddr( D1 ),

25. .oTag( TagU1 ),

26. /**********/

27. .iEn( isEn ),

28. .iData( D2 ),

29. .oData( DataU1 )

30. );

31. 


以上内容为SD卡基础模块实例化,其中isCall驱动iCall,D1驱动iAddr,isEn驱动iEn,D2驱动iData。

32. parameter B115K2 = 11'd434, TXFUNC = 6'd16;

33. 

34. reg [5:0]i,Go;

35. reg [10:0]C1,C2;

36. reg [22:0]D1;

37. reg [7:0]D2;

38. reg [10:0]T;

39. reg [3:0]isCall;

40. reg [1:0]isEn;

41. reg rTXD;

42. 

43. always @ ( posedge CLOCK or negedge RESET )

44. if( !RESET )

45. begin

46. { i,Go } <= { 6'd0,6'd0 };

47. { C1,C2 } <= { 11'd0,11'd0 };

48. { D1,D2,T } <= { 23'd0,8'd0,11'd0 };

49. { isCall,isEn } <= { 4'd0,2'd0 };

50. rTXD <= 1'b1;

51. end

52. else


以上内容为相关的寄存器声明还有复位操作,当然也包括波特率的常量声明还有伪函数入口。

53. case( i )

54. 

55. 0: // cmd0

56. if( DoneU1 ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end

57. else begin isCall[0] <= 1'b1; end

58. 

59. 1:

60. begin T <= { 2'b11, TagU1, 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end

61. 

62. /********************/

63. 


步骤0执行CMD0,然后步骤1反馈执行结果。

64. 2: // cmd1

65. if( DoneU1 ) begin isCall[1] <= 1'b0; i <= i + 1'b1; end

66. else begin isCall[1] <= 1'b1; end

67. 

68. 3:

69. begin T <= { 2'b11, TagU1, 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end

70. 

71. /*********************/

72. 


步骤2执行CMD1,然后步骤3反馈执行结果。

73. 4: // write data to fifo

74. begin isEn[1] <= 1'b1; i <= i + 1'b1; end

75. 

76. 5:

77. begin isEn[1] <= 1'b0; i <= i + 1'b1; end

78. 

79. 6:

80. if( C2 == 511 ) begin C2 <= 11'd0; i <= i + 1'b1; end

81. else begin D2 <= D2 + 1'b1; C2 <= C2 + 1'b1; i <= 6'd4; end

82. 

83. /**************/ 

84. 


步骤4~6将数据00~FF写入FIFO两遍。

85. 7: // cmd24

86. if( DoneU1 ) begin isCall[3] <= 1'b0; i <= i + 1'b1; end

87. else begin isCall[3] <= 1'b1; D1 <= 23'd0; end

88. 

89. 8:

90. begin T <= { 2'b11, TagU1, 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end

91. 

92. /***************/

93. 


步骤7执行CMD24,写入地址为23’d0。步骤8反馈执行结果。

94. 9: // cmd17

95. if( DoneU1 ) begin isCall[2] <= 1'b0; i <= i + 1'b1; end

96. else begin isCall[2] <= 1'b1; D1 <= 23'd0; end

97. 

98. 10:

99. begin T <= { 2'b11, TagU1, 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end

100. 

101. /****************/

102. 


步骤9执行CMD17,步骤10则反馈执行结果。

103. 11: // Read data from fifo

104. begin isEn[0] <= 1'b1; i <= i + 1'b1; end

105. 

106. 12:

107. begin isEn[0] <= 1'b0; i <= i + 1'b1; end

108. 

109. 13:

110. begin T <= { 2'b11, DataU1, 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end

111. 

112. 14:

113. if( C2 == 511 ) begin C2 <= 11'd0; i <= i + 1'b1; end

114. else begin C2 <= C2 + 1'b1; i <= 6'd11; end

115. 

116. 15: 

117. i <= i;

118. 

119. /****************/

120. 


步骤11~14从FIFO哪里读出数据512次,然后再经由TXD发送出去。

121. 16,17,18,19,20,21,22,23,24,25,26:

122. if( C1 == B115K2 -1 ) begin C1 <= 11'd0; i <= i + 1'b1; end

123. else begin rTXD <= T[i - 16]; C1 <= C1 + 1'b1; end

124. 

125. 27:

126. i <= Go;

127. 

128. endcase

129. 

130. assign TXD = rTXD;

131. 

132. endmodule


步骤16~27是发送一帧数据的伪函数。综合完毕,插入版本V1.×的SD卡,例如笔者手上 IProc制,容量为256MB的SD卡,然后下载程序。演示过程如下:

A2 // CMD0 执行成功

A4 // CMD1 执行成功

A6 // CMD24 执行成功

A8 // CMD17 执行成功

00~FF // 读出数据 0~255

00~FF // 读出数据 256~511

图24.17 SD卡的内容。

为了验证SD卡是否成功写入 00~FF 两遍,笔者稍微瞧瞧 SD卡的内容 ... 如图24.17所示,地址0x00~0xF0(0~255)的内容是 00~FF,地址0x0100~0x01F0(256~511)的内容也是 00~FF。

细节一:完整的个体模块

虽然本实验的SD卡基础模块已经就绪,不过SD卡的前提条件必须是版本SDV1.×,还有健康的硬件。嘛,SD卡基础模块傻是傻了一点,不过它还可以继续扩展。


相关阅读:
Top