驱动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才有效。