09、安路SparkRoad国产FPGA测评【实战篇】按键控制VGA显示
本帖最后由 1nnocent 于 2022-7-31 10:39 编辑<p> 现在调整一下测评计划,增加一个按键控制VGA显示的实战篇。</p>
<p> 该实验实现按键控制VGA的显示,SW18控制输出模式(VGA例程的四种显示模式),SW20 控制显示器的分辨率(1280*1024和1024*768两种切换)。本来想实现四种输出分辨率的,但是PLL不是万能的,有些通道输出的频率不是那么准确,各种分辨率的像素时钟误差有一定的要求:频率偏差不超过±0.5%,这个可以在“<span style="color:#1abc9c;">VESA和计算机显示监视器计时(DMT)行业标准和指南</span>”中查看,文件在测评的第八篇。一共产生了五个像素时钟,最终只有两个符合要求,所以切换分辨率功能暂时只能实现两种分辨率的切换,后续有空再看看怎么多加点分辨率的切换种类(后续的工作也只是复制粘贴了,实现方式基本一致)。</p>
<p> 接下来讲解一下实现代码:</p>
<p> 首先是顶层模块:1、输入输出端口只需要在开发板原有VGA显示例程的基础上再加一个两位的按键输入接口,其他不变;2、当然还需要添加例程的按键消抖模块,后续需要根据按键的状态对显示模式和分辨率进行切换操作;3、PLL_OUT模块产生两个像素时钟,一个输出108MHz,一个输出65MHz(实际输出只有64.8MHz符合±0.5%的要求),两个像素时钟频率分别对应的分辨率为1280*1024、1024*768;4、最后是按照功能输出相应的显示状态。顶层模块代码(贴出来的代码tab键都没了,代码变成了左边对齐,看起来不是很美观是我设置的问题吗?插入代码的时候好像也没有设置的地方):</p>
<pre>
<code>`timescale 1ns/1ns
module VGA_Demo
(
inputwire clk_24m,
inputwire rst_n,
inputwire key, // 输入按键
//lcd interface
output wire vga_clk, //lcd pixel clock
output wire vga_hs, //lcd horizontal sync
output wire vga_vs, //lcd vertical sync
output wire vga_r, //lcd red data
output wire vga_g, //lcd green data
output wire vga_b //lcd blue data
);
wire clk_vga;
wire lcd_xpos; //lcd horizontal coordinate
wire lcd_ypos; //lcd vertical coordinate
wire lcd_data; //lcd data
wire Clk_Lock;
wire pll_clk1;
wire pll_clk2;
wire key_pulse;
wire vga_de; //lcd data enable
reg definiton; // 分辨率切换寄存器
// 按键消抖模块
debounce #(
.N (2 ),
.CNT_20MS (19'h75601 ) //系统时钟24MHz,要延时20ms左右时间
)u_debounce
(
.clk(clk_24m),
.rst_n(rst_n),
// key
.key(key),
.key_pulse(key_pulse)
);
//sync global clock and reset signal
PLL_OUT u_PLL_OUT(
.refclk(clk_24m),
.reset(~rst_n),
.extlock(Clk_Lock),
.clk0_out(pll_clk1), // 108
.clk1_out(pll_clk2) // 64.8
);
wire h_sync;
wire h_back;
wire h_disp;
wire h_front;
wire h_total;
wire v_sync;
wire v_back;
wire v_disp;
wire v_front;
wire v_total;
// 根据分辨率寄存器选择相应的场信息和像素时钟
assign clk_vga = definiton ? pll_clk2 : pll_clk1;
assign h_sync = definiton ? 136 : 112 ;
assign h_back = definiton ? 160 : 248 ;
assign h_disp = definiton ? 1024 : 1280 ;
assign h_front = definiton ? 24 : 48 ;
assign h_total = definiton ? 1344 : 1688 ;
assign v_sync = definiton ? 6 : 3 ;
assign v_back = definiton ? 29 : 38 ;
assign v_disp = definiton ? 768 : 1024 ;
assign v_front = definiton ? 3 : 1 ;
assign v_total = definiton ? 806 : 1066 ;
// 根据按键key的状态切换分辨率
always@(posedge clk_24m or negedge rst_n)begin
if(!rst_n)begin
definiton <= 1'b0;
end
else if(key_pulse)begin
definiton <= definiton + 1'b1;
end
else
definiton <= definiton;
end
//VGA driver timing
// 将选择好的行场信息传入模块
Driver u1_Driver(
// Input
.clk (clk_vga ),
.rst_n (rst_n ),
.lcd_data (lcd_data ),
// hs vs
.h_sync(h_sync) , // 行同步信号时间
.h_back(h_back) , // 行消隐后肩时间
.h_disp(h_disp) , // 行数据有效时间
.h_front(h_front) , // 行消隐前肩时间
.h_total(h_total) , // 行扫描总时间
.v_sync(v_sync) , // 列同步信号时间
.v_back(v_back) , // 列消隐后肩时间
.v_disp(v_disp) , // 列数据有效时间
.v_front(v_front) , // 列消隐前肩时间
.v_total(v_total) , // 列扫描总时间
// Output
.lcd_dclk (vga_clk ),
.lcd_hs (vga_hs ),
.lcd_vs (vga_vs ),
.lcd_en (vga_de ),
.lcd_rgb ({vga_r, vga_g ,vga_b} ),
.lcd_xpos (lcd_xpos ),
.lcd_ypos (lcd_ypos )
);
//lcd data simulation
reg display_cnt; // 显示模式寄存器
// 扫描按键key,按键每次按下都切换输出模式
always@(posedge clk_24m or negedge rst_n)begin
if(!rst_n)begin
display_cnt <= 2'd0;
end
else if(key_pulse)begin
display_cnt <= display_cnt + 1'b1;
end
else
display_cnt <= display_cnt;
end
// 将显示模式寄存器传入模块
Display u2_Display
(
// Input
.clk (clk_vga ),
.rst_n (rst_n ),
.lcd_xpos (lcd_xpos ),
.lcd_ypos (lcd_ypos ),
.display_cnt (display_cnt ), // 显示模式寄存器
.h_disp(h_disp),
.v_disp(v_disp),
// Output
.lcd_data (lcd_data )
);
endmodule
</code></pre>
<p> 其次是驱动模块(Driver.v):该模块与例程的差别是将原来的行场信息的常量参数改为了变量(因为此行场信息需要根据按键值进行切换),并由外部传入,模块内部再根据传入的行场信息产生相应的行场信号,用于驱动VGA显示。在改这个模块的时候编译器的替换功能省了不少功夫,代码如下:</p>
<pre>
<code>`timescale 1ns/1ns
/* VGA参数配置表
************ clk h_sync h_back h_disp h_front h_total v_sync v_back v_disp v_front v_total *
640x480@60Hz 25.2MHz 96 48 640 16 800 2 33 480 10 525 *
800x600@60Hz 40MHz 128 88 800 40 1056 4 23 600 1 628 *
1024x768@60Hz 65MHz 136 160 1024 24 1344 6 29 768 3 806 *
1280x720@60Hz 74.25MHz 40 220 1280 110 1650 5 20 720 5 750 *
1280x1024@60Hz 108MHz 112 248 1280 48 1688 3 38 1024 1 1066 *
1920x1080@60Hz 148.5MHz 44 148 1920 88 2200 5 36 1080 4 1125 *
*/
module Driver(
inputwire clk, //VGA clock
inputwire rst_n, //sync reset
inputwire lcd_data, //lcd data
//input vs hs
input h_sync , // 行同步信号时间
input h_back , // 行消隐后肩时间
input h_disp , // 行数据有效时间
input h_front , // 行消隐前肩时间
input h_total , // 行扫描总时间
input v_sync , // 列同步信号时间
input v_back , // 列消隐后肩时间
input v_disp , // 列数据有效时间
input v_front , // 列消隐前肩时间
input v_total , // 列扫描总时间
//lcd interface
output wire lcd_dclk, //lcd pixel clock
output wire lcd_hs, //lcd horizontal sync
output wire lcd_vs, //lcd vertical sync
output wire lcd_en, //lcd display enable
output wire lcd_rgb, //lcd display data
//user interface
output wire lcd_xpos, //lcd horizontal coordinate
output wire lcd_ypos //lcd vertical coordinate
);
localparam H_AHEAD = 12'd1;
reg hcnt;
reg vcnt;
wire lcd_request;
/*******************************************
SYNC--BACK--DISP--FRONT
*******************************************/
//h_sync counter & generator
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
hcnt <= 12'd0;
else
begin
if(hcnt < h_total - 1'b1) //line over
hcnt <= hcnt + 1'b1;
else
hcnt <= 12'd0;
end
end
assign lcd_hs = (hcnt <= h_sync - 1'b1) ? 1'b0 : 1'b1; // line over flag
//v_sync counter & generator
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
vcnt <= 12'b0;
else if(hcnt == h_total - 1'b1) //line over
begin
if(vcnt == v_total - 1'b1) //frame over
vcnt <= 12'd0;
else
vcnt <= vcnt + 1'b1;
end
end
assign lcd_vs = (vcnt <= v_sync - 1'b1) ? 1'b0 : 1'b1; // frame over flag
// LED clock
assign lcd_dclk = ~clk;
// Control Display
assign lcd_en = (hcnt >= h_sync + h_back&& hcnt < h_sync + h_back + h_disp) &&
(vcnt >= v_sync + v_back&& vcnt < v_sync + v_back + v_disp)
? 1'b1 : 1'b0; // Display Enable Signal
assign lcd_rgb = lcd_en ? lcd_data : 24'h000000;
//ahead x clock
assign lcd_request = (hcnt >= h_sync + h_back - H_AHEAD && hcnt < h_sync + h_back + h_disp - H_AHEAD) &&
(vcnt >= v_sync + v_back && vcnt < v_sync + v_back + v_disp)
? 1'b1 : 1'b0;
//lcd xpos & ypos
assign lcd_xpos = lcd_request ? (hcnt - (h_sync + h_back - H_AHEAD)) : 12'd0;
assign lcd_ypos = lcd_request ? (vcnt - (v_sync + v_back)) : 12'd0;
endmodule</code></pre>
<p>该模块在顶层模块的参数传递代码,根据definition选择行场信号,definition根据按键状态进行切换,最后将参数传入模块(wire型行场信息可以不用都设置为11位,以节省资源,这里直接同意设置成11位)。<span style="color:#c0392b;">另外在编写这个模块时碰到一个问题,就是在按键按下瞬间显示器闪屏一次,分辨率并没有改变。后来发现问题是在判断条件上出问题了之前是直接判断key_pulse状态为一时切换分辨率和时钟,key_pulse为一的状态其实只有按键按下的一瞬间,之后马上变为零,所以显示器仅仅闪屏一下,分辨率还是变为原来的了。后面是增加definition,key_pulse来一次,definition就自加一次,再通过definition的状态来切换分辨率和行场信息,最后完美解决问题</span>:</p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> 最后是显示模块(Display.v):该模块内部使用case语句对输出模式进行选择,与原来例程不同的地方是传入了输出模式控制信号display_cnt,并将四种模式组合到case语句中。</p>
<pre>
<code>`timescale 1ns/1ns
// Define colors RGB--8|8|8
`define RED 24'hFF0000
`define GREEN 24'h00FF00
`define BLUE 24'h0000FF
`define WHITE 24'hFFFFFF
`define BLACK 24'h000000
`define YELLOW 24'hFFFF00
`define CYAN 24'hFF00FF
`define ROYAL 24'h00FFFF
module Display(
inputwire clk,
inputwire rst_n,
inputwire lcd_xpos, //lcd horizontal coordinate
inputwire lcd_ypos, //lcd vertical coordinate
inputwire display_cnt,
input wire h_disp,
input wire v_disp,
output reg lcd_data //lcd data
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
lcd_data <= 24'h0;
end
else
case(display_cnt)
2'd0:begin
if (lcd_ypos >= 0 && lcd_ypos < (v_disp/8)*1)
lcd_data <= `RED;
else if(lcd_ypos >= (v_disp/8)*1 && lcd_ypos < (v_disp/8)*2)
lcd_data <= `GREEN;
else if(lcd_ypos >= (v_disp/8)*2 && lcd_ypos < (v_disp/8)*3)
lcd_data <= `BLUE;
else if(lcd_ypos >= (v_disp/8)*3 && lcd_ypos < (v_disp/8)*4)
lcd_data <= `WHITE;
else if(lcd_ypos >= (v_disp/8)*4 && lcd_ypos < (v_disp/8)*5)
lcd_data <= `BLACK;
else if(lcd_ypos >= (v_disp/8)*5 && lcd_ypos < (v_disp/8)*6)
lcd_data <= `YELLOW;
else if(lcd_ypos >= (v_disp/8)*6 && lcd_ypos < (v_disp/8)*7)
lcd_data <= `CYAN;
else
lcd_data <= `ROYAL;
end
2'd1:begin
if (lcd_xpos >= 0 && lcd_xpos < (h_disp/8)*1)
lcd_data <= `RED;
else if(lcd_xpos >= (h_disp/8)*1 && lcd_xpos < (h_disp/8)*2)
lcd_data <= `GREEN;
else if(lcd_xpos >= (h_disp/8)*2 && lcd_xpos < (h_disp/8)*3)
lcd_data <= `BLUE;
else if(lcd_xpos >= (h_disp/8)*3 && lcd_xpos < (h_disp/8)*4)
lcd_data <= `WHITE;
else if(lcd_xpos >= (h_disp/8)*4 && lcd_xpos < (h_disp/8)*5)
lcd_data <= `BLACK;
else if(lcd_xpos >= (h_disp/8)*5 && lcd_xpos < (h_disp/8)*6)
lcd_data <= `YELLOW;
else if(lcd_xpos >= (h_disp/8)*6 && lcd_xpos < (h_disp/8)*7)
lcd_data <= `CYAN;
else
lcd_data <= `ROYAL;
end
2'd2:begin
lcd_data <= lcd_xpos * lcd_ypos;
end
2'd3:begin
if(lcd_ypos < v_disp/2)
lcd_data <= {lcd_ypos, lcd_ypos, lcd_ypos};
else
lcd_data <= {lcd_xpos, lcd_xpos, lcd_xpos};
end
default:lcd_data <= lcd_data;
endcase
end
endmodule</code></pre>
<p>该模块在顶层文件中对display_cnt寄存器根据按键状态进行切换,并传入显示模块中。</p>
<p> </p>
<p> 接下来是实验效果(开始时让显示器显示了分辨率:1024*768,随后切换四种播放模式;第二种分辨率也进行了显示,分辨率为:1280*1024,随后也切换了四种播放模式;最后是按了复位键进行复位):</p>
<p>aa5e1aab9d5242265b305b292163a314<br />
源码:</p>
<p></p>
<p>最后的按键控制VGA显示效果还行</p>
Jacktang 发表于 2022-8-6 22:09
最后的按键控制VGA显示效果还行
<p>后面看看能不能多几个分辨率的控制,以及显示模式的切换,主要是想拿来当个信号源,以后当测试的信号源,这样测试就不用拔电脑主机的显示器来当输入源了</p>
页:
[1]