SD 卡在正常读写操作之前,必须先对 SD 卡进行初始化, SD 卡的初始化过程就是向 SD 中写入命令,使其工作在预期的工作模式。在对 SD 卡进行读写操作时同样需要先发送写命令和读命令 。SD 卡的命令格式由 6 个字节组成, 发送数据时高位在前 。
顶层文件
module sd_bmp_hdmi( input sys_clk , //系统时钟 input sys_rst_n , //系统复位,低电平有效 //SD卡接口 input sd_miso , //SD卡SPI串行输入数据信号 output sd_clk , //SD卡SPI时钟信号 output sd_cs , //SD卡SPI片选信号 output sd_mosi , //SD卡SPI串行输出数据信号 // DDR3 inout [15:0] ddr3_dq , //DDR3 数据 inout [1:0] ddr3_dqs_n , //DDR3 dqs负 inout [1:0] ddr3_dqs_p , //DDR3 dqs正 output [13:0] ddr3_addr , //DDR3 地址 output [2:0] ddr3_ba , //DDR3 banck 选择 output ddr3_ras_n , //DDR3 行选择 output ddr3_cas_n , //DDR3 列选择 output ddr3_we_n , //DDR3 读写选择 output ddr3_reset_n , //DDR3 复位 output [0:0] ddr3_ck_p , //DDR3 时钟正 output [0:0] ddr3_ck_n , //DDR3 时钟负 output [0:0] ddr3_cke , //DDR3 时钟使能 output [0:0] ddr3_cs_n , //DDR3 片选 output [1:0] ddr3_dm , //DDR3_dm output [0:0] ddr3_odt , //DDR3_odt //hdmi接口 output tmds_clk_p , // TMDS 时钟通道 output tmds_clk_n , output [2:0] tmds_data_p , // TMDS 数据通道 output [2:0] tmds_data_n , output tmds_oen // TMDS 输出使能 );
时钟模块(clk_wiz_0)
clk_wiz_0 u_clk_wiz_0 ( // Clock out ports .clk_out1 (clk_200m), .clk_out2 (clk_50m), .clk_out3 (pixel_clk_5x), .clk_out4 (pixel_clk), .clk_out5 (clk_50m_180deg), // Status and control signals .reset (1'b0), .locked (locked), // Clock in ports .clk_in1 (sys_clk) );
200Mhz 时钟作为 DDR 控制模块的参考时钟,由 MIG IP 核产生的 ui_clk(100Mhz)作为 DDR 控制模块的驱动时钟, 50Mhz 时钟和 50Mhz(相位偏移 180 度)作为其余各模块的驱动时钟 .
SD 卡读取图片控制模块(sd_read_photo)
SD卡读取BMP图片
module sd_read_photo( input clk , //时钟信号 input rst_n , //复位信号,低电平有效 input [23:0] ddr_max_addr , //DDR读写最大地址 input [15:0] sd_sec_num , //SD卡读扇区个数 input rd_busy , //SD卡读忙信号 input sd_rd_val_en , //SD卡读数据有效信号 input [15:0] sd_rd_val_data, //SD卡读出的数据 output reg rd_start_en , //开始写SD卡数据信号 output reg [31:0] rd_sec_addr , //读数据扇区地址 output reg ddr_wr_en , //DDR写使能信号 output [15:0] ddr_wr_data //DDR写数据 );
SD 卡控制器模块(sd_ctrl_top)
sd_init ------> SD卡初始化模块
初始化代码完全一样,只需要移植.v文件即可。
module sd_init( input clk_ref , //时钟信号 input rst_n , //复位信号,低电平有效 input sd_miso , //SD卡SPI串行输入数据信号 output sd_clk , //SD卡SPI时钟信号 output reg sd_cs , //SD卡SPI片选信号 output reg sd_mosi , //SD卡SPI串行输出数据信号 output reg sd_init_done //SD卡初始化完成信号 );
sd_read -----> SD卡读模块
module sd_read( input clk_ref , //时钟信号 input clk_ref_180deg, //时钟信号,与clk_ref相位相差180度 input rst_n , //复位信号,低电平有效 //SD卡接口 input sd_miso , //SD卡SPI串行输入数据信号 output reg sd_cs , //SD卡SPI片选信号 output reg sd_mosi , //SD卡SPI串行输出数据信号 //用户读接口 input rd_start_en , //开始读SD卡数据信号 input [31:0] rd_sec_addr , //读数据扇区地址 output reg rd_busy , //读数据忙信号 output reg rd_val_en , //读数据有效信号 output reg [15:0] rd_val_data //读数据 );
读模块可以完全移植,sd卡读取的数据由sd_miso输入,rd_val_data信号是连续读的16个数据,这里是可以对连续的16个数据进行处理,如果想对其他位数进行处理,可以让rx_data_t变成相对应的位数,然后接受n*512/rx_data_t次。然后通过一个always语句输出相对应的数据,并且加一个.v文件来对数据进行处理。
n*512/rx_data_t次-->我们可以通过命令 CMD16 来配置单次读写操作的数据长度,以使每次读写的数据量为(n512) 个字节(n≥1),本次 SD 卡的读写操作使用 SD 卡默认配置,即单次读写操作的数据量为 512 个字节 。
SD 卡的读操作流程如下:
1、 拉低片选 CS 引脚, 发送命令 CMD17(0x51)读取单个数据块,命令发送完成后等待 SD 卡返回响应
数据;
2、 SD 卡返回正确响应数据 0x00 后, 准备开始解析 SD 卡返回的数据头 0xfe;
3、 解析到数据头 0xfe 后,接下来接收 SD 卡返回的 512 个字节的数据;
4、 数据解析完成后,接下来接收两个字节的 CRC 校验值。 由于 SPI 模式下不对数据进行 CRC 校验, 可
直接忽略这两个字节;
5、 校验数据接收完成后,等待 8 个时钟周期;
6、 拉高片选 CS 引脚,等待 8 个时钟周期后允许进行其它操作。
sd_write ----> SD卡写模块
module sd_write( input clk_ref , //时钟信号 input clk_ref_180deg, //时钟信号,与clk_ref相位相差180度 input rst_n , //复位信号,低电平有效 //SD卡接口 input sd_miso , //SD卡SPI串行输入数据信号 output reg sd_cs , //SD卡SPI片选信号 output reg sd_mosi , //SD卡SPI串行输出数据信号 //用户写接口 input wr_start_en , //开始写SD卡数据信号 input [31:0] wr_sec_addr , //写数据扇区地址 input [15:0] wr_data , //写数据 output reg wr_busy , //写数据忙信号 output reg wr_req //写数据请求信号 );
在移植sd卡写模块的时候,就无需改变啥代码了,可以直接移植,他是指把wr_data的数据写入到sd卡中,至于这个数据是怎么来的,是通过其他模块得到的。
SD 卡的写操作流程如下:
1、 拉低片选 CS 引脚, 发送命令 CMD24(0x58)读取单个数据块,命令发送完成后等待 SD 卡返回响应数据;
2、 SD 卡返回正确响应数据 0x00 后, 等待至少 8 个时钟周期, 开始发送数据头 0xfe;
3、 发送完数据头 0xfe 后,接下来开始发送 512 个字节的数据;
4、 数据发送完成后,发送 2 个字节的 CRC 校验数据。 由于 SPI 模式下不对数据进行 CRC 校验, 直接发送两个字节的 0xff 即可;
5、 校验数据发送完成后,等待 SD 卡响应;
6、 SD 卡返回响应数据后会进入写忙状态(MISO 引脚为低电平), 即此时不允许其它操作。当检测到MISO
引脚为高电平时, SD 卡此时退出写忙状态;
7、 拉高 CS 引脚,等待 8 个时钟周期后允许进行其它操作。
详细初始化步骤
1, SD 卡完成上电后, 主机 FPGA 先对从机 SD 卡发送至少 74 个以上的同步时钟, 在上电同步期间,片选 CS 引脚和 MOSI 引脚必须为高电平(MOSI 引脚除发送命令或数据外, 其余时刻都为高电平);
2,拉低片选 CS 引脚, 发送命令 CMD0(0x40) 复位 SD 卡,命令发送完成后等待SD 卡返回响应数据;
3, SD 卡返回响应数据后, 先等待 8个时钟周期再拉高片选 CS 信号, 此时判断返回的响应数据。如果返回的数据为复位完成信号 0x01,在接收返回信息期间片选 CS 为低电平,此时 SD 卡进入 SPI 模式,并开始进行下一步, 如果返回的值为其它值, 则重新执行第2步;
4,拉低片选 CS 引脚, 发送命令 CMD8(0x48)查询 SD 卡的版本号, 只有 SD2.0 版本的卡才支持此命令,命令发送完成后等待 SD 卡返回响应数据;
5, SD 卡返回响应数据后, 先等待 8 个时钟周期再拉高片选 CS 信号, 此时判断返回的响应数据。 如果返回的电压范围为 4’b0001 即 2.7V~3.6V, 说明此 SD 卡为 2.0 版本,进行下一步,否则重新执行第 4步;
6, 拉低片选 CS 引脚, 发送命令 CMD55(0x77)告诉 SD 卡下一次发送的命令是应用相关命令,命令发送完成后等待 SD 卡返回响应数据;
7, SD 卡返回响应数据后, 先等待 8 个时钟周期再拉高片选 CS 信号, 此时判断返回的响应数据。 如
果返回的数据为空闲信号 0x01,开始进行下一步,否则重新执行第 6 步。
8, 拉低片选 CS 引脚, 发送命令 ACMD41(0x69)查询 SD 卡是否初始化完成,命令发送完成后等待SD
卡返回响应数据;
9, SD 卡返回响应数据后, 先等待 8 个时钟周期再拉高片选 CS 信号, 此时判断返回的响应数据。 如果返回的数据为 0x00,此时初始化完成,否则重新执行第 6 步。
DDR3控制器模块(ddr3_top)
ddr3_top文件可以一般都是不变的,是用来把ddr3_rw,MIG ip核和fifo控制模块给串起来。总体来说这个DDR3控制模块对于图像显示基本是可以完全移植的,总体思路就是从什么地方得到数据,然后存到wr_fifo中然后存到fifo一次的写位数后哦写入到DDR3中,然后通过rd_fifo从DDR3中得到数据。
具体介绍详见:DDR3介绍-CSDN博客
module ddr3_top( input sys_clk_i , //MIG IP核输入时钟 input clk_ref_i , //ddr3参考时钟 input rst_n , //复位,低有效 input sys_init_done , //系统初始化完成 //DDR3接口信号 input [27:0] app_addr_rd_min , //读ddr3的起始地址 input [27:0] app_addr_rd_max , //读ddr3的结束地址 input [7:0] rd_bust_len , //从ddr3中读数据时的突发长度 input [27:0] app_addr_wr_min , //读ddr3的起始地址 input [27:0] app_addr_wr_max , //读ddr3的结束地址 input [7:0] wr_bust_len , //从ddr3中读数据时的突发长度 // DDR3 IO接口 inout [15:0] ddr3_dq , //ddr3 数据 inout [1:0] ddr3_dqs_n , //ddr3 dqs负 inout [1:0] ddr3_dqs_p , //ddr3 dqs正 output [13:0] ddr3_addr , //ddr3 地址 output [2:0] ddr3_ba , //ddr3 banck 选择 output ddr3_ras_n , //ddr3 行选择 output ddr3_cas_n , //ddr3 列选择 output ddr3_we_n , //ddr3 读写选择 output ddr3_reset_n , //ddr3 复位 output [0:0] ddr3_ck_p , //ddr3 时钟正 output [0:0] ddr3_ck_n , //ddr3 时钟负 output [0:0] ddr3_cke , //ddr3 时钟使能 output [0:0] ddr3_cs_n , //ddr3 片选 output [1:0] ddr3_dm , //ddr3_dm output [0:0] ddr3_odt , //ddr3_odt //用户 input ddr3_read_valid , //DDR3 读使能 input ddr3_pingpang_en , //DDR3 乒乓操作使能 input wr_clk , //wfifo时钟 input rd_clk , //rfifo的读时钟 input wr_en , //数据有效使能信号 input [15:0] wrdata , //有效数据 input rdata_req , //请求像素点颜色数据输入 input rd_load , //输出源更新信号 input wr_load , //输入源更新信号 output [15:0] rddata , //rfifo输出数据 output init_calib_complete //ddr3初始化完成信号 );
ddr3_fifo_ctrl
module ddr3_fifo_ctrl( input rst_n , //复位信号 input wr_clk , //wfifo时钟 input rd_clk , //rfifo时钟 input ui_clk , //用户时钟 input wr_en , //数据有效使能信号 input [15:0] wrdata , //有效数据 input [127:0] rfifo_din , //用户读数据 input rdata_req , //请求像素点颜色数据输入 input rfifo_wren , //从ddr3读出数据的有效使能 input wfifo_rden , //wfifo读使能 input rd_load , //输出源场信号 input wr_load , //输入源场信号 output [127:0] wfifo_dout , //用户写数据 output [9:0] rfifo_wcount , //rfifo剩余数据计数 output [9:0] wfifo_rcount , //wfifo写进数据计数 output reg [15:0] rddata //读有效数据 );
ddr3_rw
这个模块是用来控制读写的
module ddr3_rw( input ui_clk , //用户时钟 input ui_clk_sync_rst , //复位,高有效 input init_calib_complete , //DDR3初始化完成 input app_rdy , //MIG IP核空闲 input app_wdf_rdy , //MIG写FIFO空闲 input app_rd_data_valid , //读数据有效 input [9:0] wfifo_rcount , //写端口FIFO中的数据量 input [9:0] rfifo_wcount , //读端口FIFO中的数据量 input rd_load , //输出源更新信号 input wr_load , //输入源更新信号 input [27:0] app_addr_rd_min , //读DDR3的起始地址 input [27:0] app_addr_rd_max , //读DDR3的结束地址 input [7:0] rd_bust_len , //从DDR3中读数据时的突发长度 input [27:0] app_addr_wr_min , //写DDR3的起始地址 input [27:0] app_addr_wr_max , //写DDR3的结束地址 input [7:0] wr_bust_len , //从DDR3中写数据时的突发长度 input ddr3_read_valid , //DDR3 读使能 input ddr3_pingpang_en , //DDR3 乒乓操作使能 output rfifo_wren , //从ddr3读出数据的有效使能 output [27:0] app_addr , //DDR3地址 output app_en , //MIG IP核操作使能 output app_wdf_wren , //用户写使能 output app_wdf_end , //突发写当前时钟最后一个数据 output [2:0] app_cmd //MIG IP核操作命令,读或者写 );
HDMI 顶层模块(hdmi_top)
显示模块的代码可以移植hdmi的通用.v文件,RGB2DVI 模块 -----> DVI发送端顶层模块和video_driver------>视频显示驱动模块,在显示模块中,当像素坐标处于有有效区范围内时,video_driver会发送一个data_req(数据请求信号),rd_fifo就会从DDR3中一次读出128个数据,128/16=8,即8个像素点位置的数据,通过一个计数器,当这8个像素点的数据都显示出来后,再次让rd_fifo从DDR3中读取128个数据.这样,图像就能通过hdmi线顺利的显示在屏幕上了。
具体的hdmi模块如何移植详见:https://blog.csdn.net/m0_74221172/article/details/136285600