09. Anlu SparkRoad domestic FPGA evaluation [practical] key control VGA display
[Copy link]
This post was last edited by 1nnocent on 2022-7-31 10:39
Now let's adjust the evaluation plan and add a practical section on how to control the VGA display with one button.
This experiment realizes the key control of VGA display, SW18 controls the output mode (four display modes of VGA routine), and SW20 controls the resolution of the display (1280*1024 and 1024*768 are switched). I originally wanted to achieve four output resolutions, but PLL is not omnipotent. The output frequency of some channels is not so accurate. There are certain requirements for the pixel clock error of various resolutions: the frequency deviation does not exceed ±0.5%. This can be viewed in " VESA and Computer Display Monitor Timing (DMT) Industry Standards and Guidelines ", which is in the eighth article of the evaluation. A total of five pixel clocks were generated, and only two met the requirements in the end, so the resolution switching function can only achieve two resolutions for the time being. I will look at how to add more resolution switching types later (the subsequent work is just copy and paste, and the implementation method is basically the same).
Next, let's explain the implementation code:
First is the top-level module: 1. The input and output ports only need to add a two-bit key input interface based on the original VGA display routine of the development board, and the rest remain unchanged; 2. Of course, the key de-bouncing module of the routine needs to be added, and the display mode and resolution need to be switched according to the state of the key later; 3. The PLL_OUT module generates two pixel clocks, one outputs 108MHz and the other outputs 65MHz (the actual output is only 64.8MHz, which meets the requirement of ±0.5%). The two pixel clock frequencies correspond to resolutions of 1280*1024 and 1024*768 respectively; 4. Finally, the corresponding display status is output according to the function. Top-level module code (the tab key of the posted code is gone, and the code becomes left-aligned. It doesn’t look very beautiful. Is it a problem with my settings? There seems to be no setting when inserting the code):
`timescale 1ns/1ns
module VGA_Demo
(
input wire clk_24m,
input wire rst_n,
input wire [1:0] 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 [7:0] vga_r, //lcd red data
output wire [7:0] vga_g, //lcd green data
output wire [7:0] vga_b //lcd blue data
);
wire clk_vga;
wire [11:0] lcd_xpos; //lcd horizontal coordinate
wire [11:0] lcd_ypos; //lcd vertical coordinate
wire [23:0] lcd_data; //lcd data
wire Clk_Lock;
wire pll_clk1;
wire pll_clk2;
wire [1:0]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 [10:0]h_sync;
wire [10:0]h_back;
wire [10:0]h_disp;
wire [10:0]h_front;
wire [10:0]h_total;
wire [10:0]v_sync;
wire [10:0]v_back;
wire [10:0]v_disp;
wire [10:0]v_front;
wire [10:0]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[0]的状态切换分辨率
always@(posedge clk_24m or negedge rst_n)begin
if(!rst_n)begin
definiton <= 1'b0;
end
else if(key_pulse[0])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 [1:0] display_cnt; // 显示模式寄存器
// 扫描按键key[1],按键每次按下都切换输出模式
always@(posedge clk_24m or negedge rst_n)begin
if(!rst_n)begin
display_cnt <= 2'd0;
end
else if(key_pulse[1])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
The second is the driver module (Driver.v): The difference between this module and the routine is that the constant parameters of the original line field information are changed to variables (because this line field information needs to be switched according to the key value), and are passed in from the outside. The module generates the corresponding line field signal based on the passed in line field information to drive the VGA display. When modifying this module, the replacement function of the compiler saves a lot of effort. The code is as follows:
`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(
input wire clk, //VGA clock
input wire rst_n, //sync reset
input wire [23:0] lcd_data, //lcd data
//input vs hs
input [10:0]h_sync , // 行同步信号时间
input [10:0]h_back , // 行消隐后肩时间
input [10:0]h_disp , // 行数据有效时间
input [10:0]h_front , // 行消隐前肩时间
input [10:0]h_total , // 行扫描总时间
input [10:0]v_sync , // 列同步信号时间
input [10:0]v_back , // 列消隐后肩时间
input [10:0]v_disp , // 列数据有效时间
input [10:0]v_front , // 列消隐前肩时间
input [10:0]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 [23:0] lcd_rgb, //lcd display data
//user interface
output wire [11:0] lcd_xpos, //lcd horizontal coordinate
output wire [11:0] lcd_ypos //lcd vertical coordinate
);
localparam H_AHEAD = 12'd1;
reg [11:0] hcnt;
reg [11:0] 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
This module passes the parameter code of the top-level module, selects the row and field signal according to the definition, switches the definition according to the key status, and finally passes the parameters to the module (the wire type row and field information does not need to be set to 11 bits to save resources, and it is directly agreed to set it to 11 bits here). In addition, a problem was encountered when writing this module, that is, the display flashed once when the key was pressed, and the resolution did not change. Later, it was discovered that the problem was in the judgment condition. Before, the resolution and clock were switched when the key_pulse[0] state was directly judged to be one. The state of key_pulse[0] being one actually only lasted for the moment when the key was pressed, and then it immediately became zero, so the display only flashed, and the resolution remained the same. Later, the definition was added. When key_pulse[0] came once, the definition was added once, and then the resolution and row and field information were switched according to the definition status, and finally the problem was solved perfectly :
Finally, there is the display module (Display.v): This module uses the case statement to select the output mode. The difference from the original routine is that the output mode control signal display_cnt is passed in, and the four modes are combined into the case statement.
`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(
input wire clk,
input wire rst_n,
input wire [11:0] lcd_xpos, //lcd horizontal coordinate
input wire [11:0] lcd_ypos, //lcd vertical coordinate
input wire [1:0] display_cnt,
input wire [10:0] h_disp,
input wire [10:0] v_disp,
output reg [23:0] 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[7:0], lcd_ypos[7:0], lcd_ypos[7:0]};
else
lcd_data <= {lcd_xpos[7:0], lcd_xpos[7:0], lcd_xpos[7:0]};
end
default:lcd_data <= lcd_data;
endcase
end
endmodule
In the top-level file, this module switches the display_cnt register according to the key status and passes it to the display module.
The following is the experimental result (the monitor initially displayed a resolution of 1024*768, then switched to four playback modes; the second resolution was also displayed, with a resolution of 1280*1024, then switched to four playback modes; finally, the reset button was pressed to reset):
VGA_KEY_trl
Source code:
VGA_KEY_trl.rar
(2.12 MB, downloads: 20)
|