在FPGA上实现SD卡连续多块读的命令
CMD17命令一次只能读取1个块
CMD18命令一次可以连续读取多个块,直到停止命令CMD12
CMD18命令读的块数程序可任意设置
目录
前言
在用FPGA驱动LCD显示屏播放视频时,视频数据是存到SD卡里面的,播放视频对数据量的读需求非常大。如果用单块读CMD17命令显然无法满足我屏幕320*320的分辨率单像素16bit 24帧的需求,因为实测发现SD卡每次发单块读CMD17命令后,中间都要等很长一段时间,SD卡才会开始返回数据,这个等的时间比返回512个字节的时间还要多。所以得发送CMD18命令,改命令发一次可以连续读取多个块,直到发送停止命令CMD12,SD卡停止读,这样方可满足播放视频的需求。以下内容是对SD卡连续多块读操作的说明,同时也给出了相应的时序波形图,方便理解。
++提示:以下是本篇文章正文内容,均为作者本人原创,写文章实属不易,希望各位在引用时附上本文链接。++
一、SD卡多块读命令CMD18
关于SD的初始化,单块读写操作见文章,基于FPGA的SD卡音乐播放器之SD卡篇_sdio verilog-CSDN博客本文不再叙述。
CMD18命令芯片手册描述如下:


可见,发一次命令返回多个块,发送CMD18命令格式如下:
cmd_rd <= {8'h52,rd_sec_addr_buf,8'hff}; //写入连续读多个快CMD18
其中rd_sec_addr_buf是读的32位起始扇区地址,由于SD卡在SPI模式下默认不开启CRC校验,所以校验字节直接填的8'hff。发完命令后等8bit R1的响应,正常情况下响应的8bit为全0,然后准备开始解析SD卡返回的数据头0xfe, 解析到数据头0xfe后,接下来接收SD卡返回的512个字节的数据,紧接着又会有下一块,依然是先解析到数据头0xfe后,再接收SD卡返回的512个字节的数据,如此循环直到发送CMD12这个停止命令。由此可见一次读多少个块是由主机什么时候发停止命令决定的。为了方便,我在编程的时候定义了一个参数,即一次连续读多少个块,读够后即发送停止命令,该参数可随意设置,设置成1就和单块读没区别。
该命令的实测波形图如下,注意由于MISO下降沿稳定,所以ILA抓取时注意用clk的下降沿取抓数据。

观察这张波形图,就能明白为什么我开始说的发命令后等的时间比返回512个字节的时间还要多了。放大发命令的部分如下图所示,可见发完命令后,SD卡会返回R1数据8'h00。

返回数据部分放大如下:

下面是抓取的解析后的数据,解析时是每两个字节凑成16bit,和winhex中的数据完全对得上。

下图是两个块交界处的时序,可见第一个块数据返回完后,会接着有两字节的CRC,然后又去解析FE,之后便是下一个块的数据。


可见块连续读,一个块的数据返回完后,过24个CLK(图中9793-9769,2个FF+1个FE)就会接着返回下一个块的数据,比单块读效率高多了。
二、停止读命令CMD12
CMD12命令芯片手册描述如下:


R1b和R1类似,只是其带有忙信号。发送CMD18命令格式如下:
cmd_rd <= {8'h4C,32'b0,8'hff}; //停止读命令CMD12

parameter RD_BLOCKS = 20'd400; //SD卡连读读一次的块数
我在程序中定义了SD卡连读读一次的块数,支持修改为任意值,这里400是因为320*320的16bit像素一帧需要连续读400个块的数据。
三、SD卡初始化+SD卡连续块读操作的verilog代码
代码已经完成了SD卡的初始化,设置RD_BLOCKS后,连续读只需要将sd_rd_start_en拉高一个电平,在电平拉高的同时,给sd_rd_sec_addr复制块读开始读的扇区地址后,接下来sd_rd_val_en和sd_rd_val_data就会返回数据,其中sd_rd_val_en是是数据有效标志。
sd_ctrl_top
#(
.RD_BLOCKS(RD_BLOCKS)
)
u_sd_ctrl_top(
.clk_ref (clk),
.clk_ref_180deg (!clk),
.rst_n (rst_n),
//SD卡接口
.sd_miso (sd_miso),
.sd_clk (sd_clk),
.sd_cs (sd_cs),
.sd_mosi (sd_mosi),
//用户读SD卡接口
.rd_start_en (sd_rd_start_en),
.rd_sec_addr (sd_rd_sec_addr),
.rd_busy (sd_rd_busy),
.rd_val_en (sd_rd_val_en),
.rd_val_data (sd_rd_val_data),
.sd_init_done (sd_init_done)
);
总结
以上就是本文全部内容,给出了每个步骤的时序图,对理解块读操作很有帮助。写这篇文章的目的一是为了分享知识,二是为了记录实现方法,方便自己回顾。