16. 基于sd卡的hdmi图像显示¶
在上一章节,我们利用SDRAM做缓存,使用SD卡读写控制器读取SD卡中存储的图像数据,并将图像通过VGA显示器显示。本章节中,我们使用HDMI显示屏代替VGA显示器,实现基于SD卡的HDMI图像显示。
16.1. 理论学习¶
对于本章节涉及的SD卡相关理论知识,读者可查阅“基于SD卡的VGA图像显示”的理论学习小节;对于本章节涉及的HDMI显示的相关理论知识,读者可查阅“HDMI显示器驱动设计与验证”的理论学习小节,此处不再赘述。
16.2. 实战演练¶
16.2.1. 实验目标¶
使用SD卡数据读写控制器读取事先存储在SD卡的图片数据,将读取的图片数据通过SDRAM数据读写控制器暂存在SDRAM芯片中,通过HDMI显示屏将暂存在SDRAM 的图片显示出来。
SD卡内存储图片有两张,两张图片交替显示在HDMI显示屏上,分辨率为640*480。
16.2.2. 硬件资源¶
参见“SD卡数据读写控制”、“HDMI显示器驱动设计与验证”章节当然“硬件资源”小节。
16.3. 程序设计¶
16.3.1. 整体说明¶
在程序设计开始之前,我们先来对实验工程进行一个整体说明,让读者了解整个实验工程的框架结构。工程整体框图,具体见图 66‑1;各子功能模块简介,具体见表格 66‑1。
图 66‑1 工程整体框图
表格 66‑1 子功能模块功能描述
模块名称 |
功能描述 |
---|---|
clk_gen |
时钟生成模块 |
data_rd_ctrl |
图片数据读控制模块 |
sd_ctrl |
SD卡读写控制器 |
sdram_top |
SDRAM读写控制器 |
vga_ctrl |
VGA显示驱动模块 |
hdmi_ctrl |
HDMI显示驱动模块 |
sd_hdmi_pic |
顶层模块 |
本实验工程共调用7个模块(SD卡读写控制器和SDRAM读写控制器均视为单个模块),由图标可知,模块sd_hdmi_pic作为实验工程的顶层模块,内部实例化5个子功能模块。
时钟生成模块clk_gen,为各子功能模块、外部SD卡和SDRAM提供工作时钟;图片数据读控制模块data_rd_ctrl,控制SD卡读写控制器读取SD卡内待显示图片的数据读取;SD卡读写控制器sd_ctrl,读取SD卡内待显示图片数据;SDRAM读写控制器sdram_top,控制SDRAM芯片的数 据读写操作;VGA显示驱动模块vga_ctrl,读出SDRAM的图像数据按照VGA时序进行处理;hdmi_ctrl模块将VGA传入的图像数据进行HDMI时序处理,并显示再HDMI显示屏上。
16.3.1.1. 图片数据预处理¶
图片预处理,读者可参阅“基于SD卡的VGA图像显示”的“图片数据预处理”部分,此处不再赘述。
16.3.1.2. 时钟生成模块¶
时钟生成模块clk_gen,为各子功能模块、外部SD卡和SDRAM提供工作时钟。输入50MHz板卡晶振时钟信号,输出5路时钟信号c0-c4,时钟频率为125MHz、125MHz(相位偏移)、50MHz、50MHz(相位偏移)、25MHz,前两路时钟信号输入SDRAM读写控制器和SDRAM存储芯片;中 间两路输入SD卡读写控制器和SD存储卡;最后一路输入VGA显示驱动模块;25MHz、125MHz时钟作为HDMI显示驱动模块输入时钟。
时钟生成模块clk_gen,调动IP和生成,具体生成方法,读者可参阅“快速开发的法宝 — IP核”章节,此处不再赘述。
16.3.1.3. SD卡数据读写控制器¶
SD卡数据读写控制器的相关内容在“SD卡数据读写控制”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。
16.3.1.4. SDRAM数据读写控制器¶
SDRAM数据读写控制器的相关内容在“SDRAM读写控制器的设计与验证”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。
16.3.1.5. 图片数据读控制模块¶
图片数据读控制模块的相关内容在“基于SD卡的VGA图像显示”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。
16.3.1.6. VGA驱动控制模块¶
VGA驱动控制模块的相关内容在“VGA显示器驱动设计与验证”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。
16.3.1.7. HDMI驱动控制模块¶
HDMI驱动控制模块的相关内容在“HDMI显示器驱动设计与验证”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。
16.3.1.8. 顶层模块¶
讲到这里,实验工程涉及的子功能模块均已介绍完毕,接下来说明整个实验工程的顶层模块。
模块框图
顶层模块将各子功能模块实例化其中,连接各自对应信号,顶层模块模块框图如图 66‑2所示。
图 66‑2 顶层模块模块框图
由图可知,顶层模块有输入输出信号共22路,3路输入信号有时钟信号、复位信号、和SD卡传入的主输入从输出信号sd_miso;16路输出信号中有3路输出到SD卡,包括SD卡时钟信号sd_clk、片选信号sd_cs_n和主输出从输入信号sd_mosi; 10路输出到SDRAM,包括时钟信号、使能信号、地址信号和数据信号;4路输出到HDMI驱动控制模块,驱动控制图像显示。
代码编写
顶层模块的代码较为较为简单,只是对各模块进行实例化以及连接各自对应信号,无需波形图的绘制。编写顶层模块参考代码,具体见代码清单 66‑1。
代码清单 66‑1 顶层模块参考代码(sd_hdmi_pic.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | module sd_hdmi_pic
(
input wire sys_clk , //输入工作时钟,频率50MHz
input wire sys_rst_n , //输入复位信号,低电平有效
//SD卡
input wire sd_miso , //主输入从输出信号
output wire sd_clk , //SD卡时钟信号
output wire sd_cs_n , //片选信号
output wire sd_mosi , //主输出从输入信号
//SDRAM
output wire sdram_clk , //SDRAM 芯片时钟
output wire sdram_cke , //SDRAM 时钟有效
output wire sdram_cs_n , //SDRAM 片选
output wire sdram_ras_n , //SDRAM 行有效
output wire sdram_cas_n , //SDRAM 列有效
output wire sdram_we_n , //SDRAM 写有效
output wire [1:0] sdram_ba , //SDRAM Bank地址
output wire [1:0] sdram_dqm , //SDRAM 数据掩码
output wire [12:0] sdram_addr , //SDRAM 行/列地址
inout wire [15:0] sdram_dq , //SDRAM 数据
//HDMI
output wire ddc_scl ,
output wire ddc_sda ,
output wire tmds_clk_p ,
output wire tmds_clk_n , //HDMI时钟差分信号
output wire [2:0] tmds_data_p ,
output wire [2:0] tmds_data_n //HDMI图像差分信号
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter H_VALID = 24'd640 ; //行有效数据
parameter V_VALID = 24'd480 ; //列有效数据
//wire define
wire rst_n ; //复位信号
wire clk_125m ; //生成100MHz时钟
wire clk_125m_shift ; //生成100MHz时钟,相位偏移180度
wire clk_50m ; //生成50MHz时钟
wire clk_50m_shift ; //生成50MHz时钟,相位偏移180度
wire clk_25m ; //生成25MHz时钟
wire locked ; //时钟锁定信号
wire sys_init_end ; //系统初始化完成
wire vga_hs ; //输出行同步信号
wire vga_vs ; //输出场同步信号
wire [15:0] vga_rgb ; //输出像素信息
wire rgb_valid ; //VGA有效显示区域
wire sd_rd_en ; //开始写SD卡数据信号
wire [31:0] sd_rd_addr ; //读数据扇区地址
wire sd_rd_busy ; //读忙信号
wire sd_rd_data_en ; //数据读取有效使能信号
wire [15:0] sd_rd_data ; //读数据
wire sd_init_end ; //SD卡初始化完成信号
wire wr_en ; //sdram_ctrl模块写使能
wire [15:0] wr_data ; //sdram_ctrl模块写数据
wire rd_en ; //sdram_ctrl模块读使能
wire [15:0] rd_data ; //sdram_ctrl模块读数据
wire sdram_init_end ; //SDRAM初始化完成
////
//\* Main Code \//
////
//rdt_n:复位信号,系统复位与时钟锁定取与
assign rst_n = sys_rst_n && locked;
assign ddc_scl = 1'b1;
assign ddc_sda = 1'b1;
//sys_init_end:系统初始化完成,SD卡和SDRAM均完成初始化
assign sys_init_end = sd_init_end && sdram_init_end;
////
//\* Instantiation \//
////
//------------- clk_gen_inst -------------
clk_gen clk_gen_inst
(
.areset (~sys_rst_n ), //复位信号,高有效
.inclk0 (sys_clk ), //输入系统时钟,50MHz
.c0 (clk_125m ), //生成100MHz时钟
.c1 (clk_125m_shift ), //生成100MHz时钟,相位偏移180度
.c2 (clk_50m ), //生成50MHz时钟
.c3 (clk_50m_shift ), //生成50MHz时钟,相位偏移180度
.c4 (clk_25m ), //生成25MHz时钟
.locked (locked ) //时钟锁定信号
);
//------------- data_rd_ctrl_inst -------------
data_rd_ctrl data_rd_ctrl_inst
(
.sys_clk (clk_50m ), //输入工作时钟,频率50MHz
.sys_rst_n (rst_n & sys_init_end ), //输入复位信号,低电平有效
.rd_busy (sd_rd_busy ), //读操作忙信号
.rd_en (sd_rd_en ), //数据读使能信号
.rd_addr (sd_rd_addr ) //读数据扇区地址
);
//------------- sd_ctrl_inst -------------
sd_ctrl sd_ctrl_inst
(
.sys_clk (clk_50m ), //工作时钟,频率50MHz
.sys_clk_shift (clk_50m_shift ), //工作时钟,频率50MHz,相位偏移180度
.sys_rst_n (rst_n ), //复位信号,低电平有效
.sd_miso (sd_miso ), //主输入从输出信号
.sd_clk (sd_clk ), //SD卡时钟信号
.sd_cs_n (sd_cs_n ), //片选信号
.sd_mosi (sd_mosi ), //主输出从输入信号
.wr_en (1'b0 ), //数据写使能信号
.wr_addr (32'b0 ), //写数据扇区地址
.wr_data (16'b0 ), //写数据
.wr_busy ( ), //写操作忙信号
.wr_req ( ), //写数据请求信号
.rd_en (sd_rd_en ), //数据读使能信号
.rd_addr (sd_rd_addr ), //读数据扇区地址
.rd_busy (sd_rd_busy ), //读操作忙信号
.rd_data_en (sd_rd_data_en ), //读数据标志信号
.rd_data (sd_rd_data ), //读数据
.init_end (sd_init_end ) //SD卡初始化完成信号
);
//------------- sdram_top_inst -------------
sdram_top sdram_top_inst
(
.sys_clk (clk_125m ), //sdram 控制器参考时钟
.clk_out (clk_125m_shift ), //用于输出的相位偏移时钟
.sys_rst_n (rst_n ), //系统复位
//用户写端口
.wr_fifo_wr_clk (clk_50m ), //写端口FIFO: 写时钟
.wr_fifo_wr_req (sd_rd_data_en ), //写端口FIFO: 写使能
.wr_fifo_wr_data (sd_rd_data ), //写端口FIFO: 写数据
.sdram_wr_b_addr (24'd0 ), //写SDRAM的起始地址
.sdram_wr_e_addr (H_VALID*V_VALID), //写SDRAM的结束地址
.wr_burst_len (10'd512 ), //写SDRAM时的数据突发长度
.wr_rst (~rst_n ), //写端口复位
//用户读端口
.rd_fifo_rd_clk (clk_25m ), //读端口FIFO: 读时钟
.rd_fifo_rd_req (rd_en ), //读端口FIFO: 读使能
.rd_fifo_rd_data (rd_data ), //读端口FIFO: 读数据
.sdram_rd_b_addr (24'd0 ), //读SDRAM的起始地址
.sdram_rd_e_addr (H_VALID*V_VALID), //读SDRAM的结束地址
.rd_burst_len (10'd512 ), //从SDRAM中读数据时的突发长度
.rd_fifo_num ( ), //读fifo中的数据量
.rd_rst (~rst_n ), //读端口复位
//用户控制端口
.read_valid (1'b1 ), //SDRAM 读使能
.pingpang_en (1'b0 ), //SDRAM 乒乓操作使能
.init_end (sdram_init_end ), //SDRAM 初始化完成标志
//SDRAM 芯片接口
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码
);
//------------- vga_ctrl_inst -------------
vga_ctrl vga_ctrl_inst
(
.vga_clk (clk_25m ), //输入工作时钟,频率25MHz
.sys_rst_n (rst_n ), //输入复位信号,低电平有效
.data_in (rd_data ), //待显示数据输入
.rgb_valid (rgb_valid ), //VGA有效显示区域
.data_req (rd_en ), //数据请求信号
.hsync (vga_hs ), //输出行同步信号
.vsync (vga_vs ), //输出场同步信号
.rgb (vga_rgb ) //输出像素信息
);
//------------- hdmi_ctrl_inst -------------
hdmi_ctrl hdmi_ctrl_inst
(
.clk_1x (clk_25m ), //输入系统时钟
.clk_5x (clk_125m ), //输入5倍系统时钟
.sys_rst_n (rst_n ), //复位信号,低有效
.rgb_blue ({vga_rgb[4:0],3'b0} ), //蓝色分量
.rgb_green ({vga_rgb[10:5],2'b0} ), //绿色分量
.rgb_red ({vga_rgb[15:11],3'b0} ), //红色分量
.hsync (vga_hs ), //行同步信号
.vsync (vga_vs ), //场同步信号
.de (rgb_valid ), //使能信号
.hdmi_clk_p (tmds_clk_p ),
.hdmi_clk_n (tmds_clk_n ), //时钟差分信号
.hdmi_r_p (tmds_data_p[2] ),
.hdmi_r_n (tmds_data_n[2] ), //红色分量差分信号
.hdmi_g_p (tmds_data_p[1] ),
.hdmi_g_n (tmds_data_n[1] ), //绿色分量差分信号
.hdmi_b_p (tmds_data_p[0] ),
.hdmi_b_n (tmds_data_n[0] ) //蓝色分量差分信号
);
endmodule
|
RTL视图
顶层代码编写完成后,使用Quartus软件对实验工程进行编译,编译通过后查看RTL视图,如图 66‑3所示。RTL视图与实验工程框图一致,各信号连接正确。
图 66‑3 RTL视图
16.4. 上板调试¶
16.4.1. 引脚约束¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 66‑2所示。
表格 66‑2 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
Input |
E1 |
时钟 |
sys_rst_n |
Input |
M15 |
复位 |
sd_miso |
input |
J16 |
SD卡主输入从输出信号 |
sd_clk |
output |
J12 |
SD卡时钟信号 |
sd_cs_n |
output |
K12 |
SD卡片选信号 |
sd_mosi |
output |
J14 |
SD卡主输出从输入信号 |
sdram_clk |
Output |
R4 |
SDRAM芯片时钟 |
sdram_cke |
Output |
R9 |
SDRAM时钟有效信号 |
sdram_cs_n |
Output |
R12 |
SDRAM片选信号 |
sdram_cas_n |
Output |
R10 |
SDRAM列地址选通脉冲 |
sdram_ras_n |
Output |
R11 |
SDRAM行地址选通脉冲 |
sdram_we_n |
Output |
L9 |
SDRAM写允许位 |
sdram_ba[0] |
Output |
R13 |
SDRAM的L-Bank地址线 |
sdram_ba[1] |
Output |
R14 |
SDRAM的L-Bank地址线 |
sdram_addr[0] |
Output |
P11 |
SDRAM地址总线 |
sdram_addr[1] |
Output |
P14 |
SDRAM地址总线 |
sdram_addr[2] |
Output |
N9 |
SDRAM地址总线 |
sdram_addr[3] |
Output |
N11 |
SDRAM地址总线 |
sdram_addr[4] |
Output |
T14 |
SDRAM地址总线 |
sdram_addr[5] |
Output |
T13 |
SDRAM地址总线 |
sdram_addr[6] |
Output |
T12 |
SDRAM地址总线 |
sdram_addr[7] |
Output |
T11 |
SDRAM地址总线 |
sdram_addr[8] |
Output |
T10 |
SDRAM地址总线 |
sdram_addr[9] |
Output |
P9 |
SDRAM地址总线 |
sdram_addr[10] |
Output |
T15 |
SDRAM地址总线 |
sdram_addr[11] |
Output |
N12 |
SDRAM地址总线 |
sdram_addr[12] |
Output |
M11 |
SDRAM地址总线 |
sdram_dqm[0] |
Output |
M10 |
SDRAM数据掩码 |
sdram_dqm[1] |
Output |
M9 |
SDRAM数据掩码 |
sdram_dq[0] |
Output |
R3 |
SDRAM数据总线 |
sdram_dq[1] |
Output |
T9 |
SDRAM数据总线 |
sdram_dq[2] |
Output |
R5 |
SDRAM数据总线 |
sdram_dq[3] |
Output |
R6 |
SDRAM数据总线 |
sdram_dq[4] |
Output |
R7 |
SDRAM数据总线 |
sdram_dq[5] |
Output |
M8 |
SDRAM数据总线 |
sdram_dq[6] |
Output |
R8 |
SDRAM数据总线 |
sdram_dq[7] |
Output |
N8 |
SDRAM数据总线 |
sdram_dq[8] |
Output |
P8 |
SDRAM数据总线 |
sdram_dq[9] |
Output |
T8 |
SDRAM数据总线 |
sdram_dq[10] |
Output |
T7 |
SDRAM数据总线 |
sdram_dq[11] |
Output |
T6 |
SDRAM数据总线 |
sdram_dq[12] |
Output |
T5 |
SDRAM数据总线 |
sdram_dq[13] |
Output |
T4 |
SDRAM数据总线 |
sdram_dq[14] |
Output |
T3 |
SDRAM数据总线 |
sdram_dq[15] |
Output |
T2 |
SDRAM数据总线 |
tmds_clk_p |
Output |
R16 |
时钟差分信号 |
tmds_clk_n |
Output |
P16 |
时钟差分信号 |
tmds_data_p[2] |
Output |
K15 |
红色分量差分信号 |
tmds_data_n[2] |
Output |
K16 |
红色分量差分信号 |
tmds_data_p[1] |
Output |
L15 |
绿色分量差分信号 |
tmds_data_n[1] |
Output |
L15 |
绿色分量差分信号 |
tmds_data_p[0] |
Output |
N15 |
蓝色分量差分信号 |
tmds_data_n[0] |
Output |
N16 |
蓝色分量差分信号 |
下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 66‑4、图 66‑5所示。
图 66‑4 管脚分配
图 66‑5 管脚分配
16.4.1.1. 结果验证¶
如图 66‑6所示,开发板连接12V直流电源、USB-Blaster下载器JTAG端口、连接HDMI显示器、插入SD卡。线路正确连接后,打开开关为板卡上电。
图 66‑6 程序下载连线图
如图 66‑7所示,使用“Programmer”为开发板下载程序。
图 66‑7 程序下载图
程序下载完成后,两幅图片会交替显示再HDMI显示屏上,如图 66‑8、图 66‑9所示。
图 66‑8 SD卡存储图片显示
图 66‑9 SD卡存储图片显示
16.5. 章末总结¶
本章节我们使用前面章节设计的SD卡数据读写控制器,与HDMI显示相结合,并通过实验实现了SD卡存储图片的HDMI显示。读者要认真理解相关理论知识,切实掌握SD卡数据读写控制器的设计与实现。