1nnocent 发表于 2022-8-6 00:04

09、安路SparkRoad国产FPGA测评【实战篇】按键控制VGA显示

本帖最后由 1nnocent 于 2022-7-31 10:39 编辑

<p>&nbsp; &nbsp; 现在调整一下测评计划,增加一个按键控制VGA显示的实战篇。</p>

<p>&nbsp; &nbsp; 该实验实现按键控制VGA的显示,SW18控制输出模式(VGA例程的四种显示模式),SW20 控制显示器的分辨率(1280*1024和1024*768两种切换)。本来想实现四种输出分辨率的,但是PLL不是万能的,有些通道输出的频率不是那么准确,各种分辨率的像素时钟误差有一定的要求:频率偏差不超过&plusmn;0.5%,这个可以在&ldquo;<span style="color:#1abc9c;">VESA和计算机显示监视器计时(DMT)行业标准和指南</span>&rdquo;中查看,文件在测评的第八篇。一共产生了五个像素时钟,最终只有两个符合要求,所以切换分辨率功能暂时只能实现两种分辨率的切换,后续有空再看看怎么多加点分辨率的切换种类(后续的工作也只是复制粘贴了,实现方式基本一致)。</p>

<p>&nbsp; &nbsp; 接下来讲解一下实现代码:</p>

<p>&nbsp; &nbsp; 首先是顶层模块:1、输入输出端口只需要在开发板原有VGA显示例程的基础上再加一个两位的按键输入接口,其他不变;2、当然还需要添加例程的按键消抖模块,后续需要根据按键的状态对显示模式和分辨率进行切换操作;3、PLL_OUT模块产生两个像素时钟,一个输出108MHz,一个输出65MHz(实际输出只有64.8MHz符合&plusmn;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 &lt;= 1'b0;
        end
        else if(key_pulse)begin
                definiton &lt;= definiton + 1'b1;
        end
        else
                definiton &lt;= 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 &lt;= 2'd0;
        end
        else if(key_pulse)begin
                display_cnt &lt;= display_cnt + 1'b1;
        end
        else
                display_cnt &lt;= 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>&nbsp; &nbsp; 其次是驱动模块(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 &amp; generator
always @ (posedge clk or negedge rst_n)
begin
        if (!rst_n)
                hcnt &lt;= 12'd0;
        else
        begin
      if(hcnt &lt; h_total - 1'b1)                //line over                       
            hcnt &lt;= hcnt + 1'b1;
      else
            hcnt &lt;= 12'd0;
        end
end

assign        lcd_hs = (hcnt &lt;= h_sync - 1'b1) ? 1'b0 : 1'b1; // line over flag

//v_sync counter &amp; generator
always@(posedge clk or negedge rst_n)
begin
        if (!rst_n)
                vcnt &lt;= 12'b0;
        else if(hcnt == h_total - 1'b1)        //line over
                begin
                if(vcnt == v_total - 1'b1)                //frame over
                        vcnt &lt;= 12'd0;
                else
                        vcnt &lt;= vcnt + 1'b1;
                end
end

assign        lcd_vs = (vcnt &lt;= v_sync - 1'b1) ? 1'b0 : 1'b1; // frame over flag

// LED clock
assign        lcd_dclk = ~clk;

// Control Display
assign        lcd_en                =        (hcnt &gt;= h_sync + h_back&amp;&amp; hcnt &lt; h_sync + h_back + h_disp) &amp;&amp;
                                                (vcnt &gt;= v_sync + v_back&amp;&amp; vcnt &lt; 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 &gt;= h_sync + h_back - H_AHEAD &amp;&amp; hcnt &lt; h_sync + h_back + h_disp - H_AHEAD) &amp;&amp;
                                                (vcnt &gt;= v_sync + v_back &amp;&amp; vcnt &lt; v_sync + v_back + v_disp)
                                                ? 1'b1 : 1'b0;
//lcd xpos &amp; 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> &nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; 最后是显示模块(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 &lt;= 24'h0;
        end
        else
                case(display_cnt)
                        2'd0:begin
                                        if        (lcd_ypos &gt;= 0 &amp;&amp; lcd_ypos &lt; (v_disp/8)*1)
                                                lcd_data &lt;= `RED;
                                        else if(lcd_ypos &gt;= (v_disp/8)*1 &amp;&amp; lcd_ypos &lt; (v_disp/8)*2)
                                                lcd_data &lt;= `GREEN;
                                        else if(lcd_ypos &gt;= (v_disp/8)*2 &amp;&amp; lcd_ypos &lt; (v_disp/8)*3)
                                                lcd_data &lt;= `BLUE;
                                        else if(lcd_ypos &gt;= (v_disp/8)*3 &amp;&amp; lcd_ypos &lt; (v_disp/8)*4)
                                                lcd_data &lt;= `WHITE;
                                        else if(lcd_ypos &gt;= (v_disp/8)*4 &amp;&amp; lcd_ypos &lt; (v_disp/8)*5)
                                                lcd_data &lt;= `BLACK;
                                        else if(lcd_ypos &gt;= (v_disp/8)*5 &amp;&amp; lcd_ypos &lt; (v_disp/8)*6)
                                                lcd_data &lt;= `YELLOW;
                                        else if(lcd_ypos &gt;= (v_disp/8)*6 &amp;&amp; lcd_ypos &lt; (v_disp/8)*7)
                                                lcd_data &lt;= `CYAN;
                                        else
                                                lcd_data &lt;= `ROYAL;
                                end
                        2'd1:begin
                                        if        (lcd_xpos &gt;= 0 &amp;&amp; lcd_xpos &lt; (h_disp/8)*1)
                                                lcd_data &lt;= `RED;
                                        else if(lcd_xpos &gt;= (h_disp/8)*1 &amp;&amp; lcd_xpos &lt; (h_disp/8)*2)
                                                lcd_data &lt;= `GREEN;
                                        else if(lcd_xpos &gt;= (h_disp/8)*2 &amp;&amp; lcd_xpos &lt; (h_disp/8)*3)
                                                lcd_data &lt;= `BLUE;
                                        else if(lcd_xpos &gt;= (h_disp/8)*3 &amp;&amp; lcd_xpos &lt; (h_disp/8)*4)
                                                lcd_data &lt;= `WHITE;
                                        else if(lcd_xpos &gt;= (h_disp/8)*4 &amp;&amp; lcd_xpos &lt; (h_disp/8)*5)
                                                lcd_data &lt;= `BLACK;
                                        else if(lcd_xpos &gt;= (h_disp/8)*5 &amp;&amp; lcd_xpos &lt; (h_disp/8)*6)
                                                lcd_data &lt;= `YELLOW;
                                        else if(lcd_xpos &gt;= (h_disp/8)*6 &amp;&amp; lcd_xpos &lt; (h_disp/8)*7)
                                                lcd_data &lt;= `CYAN;
                                        else
                                                lcd_data &lt;= `ROYAL;
                                end
                        2'd2:begin
                                        lcd_data &lt;= lcd_xpos * lcd_ypos;
                                end
                        2'd3:begin
                                        if(lcd_ypos &lt; v_disp/2)
                                                lcd_data &lt;= {lcd_ypos, lcd_ypos, lcd_ypos};
                                        else
                                                lcd_data &lt;= {lcd_xpos, lcd_xpos, lcd_xpos};
                                end
                        default:lcd_data &lt;= lcd_data;
                endcase
end

endmodule</code></pre>

<p>该模块在顶层文件中对display_cnt寄存器根据按键状态进行切换,并传入显示模块中。</p>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; &nbsp;接下来是实验效果(开始时让显示器显示了分辨率:1024*768,随后切换四种播放模式;第二种分辨率也进行了显示,分辨率为:1280*1024,随后也切换了四种播放模式;最后是按了复位键进行复位):</p>

<p>aa5e1aab9d5242265b305b292163a314<br />
&nbsp; &nbsp; 源码:</p>

<p></p>

Jacktang 发表于 2022-8-6 22:09

<p>最后的按键控制VGA显示效果还行</p>

1nnocent 发表于 2022-8-7 16:12

Jacktang 发表于 2022-8-6 22:09
最后的按键控制VGA显示效果还行

<p>后面看看能不能多几个分辨率的控制,以及显示模式的切换,主要是想拿来当个信号源,以后当测试的信号源,这样测试就不用拔电脑主机的显示器来当输入源了</p>
页: [1]
查看完整版本: 09、安路SparkRoad国产FPGA测评【实战篇】按键控制VGA显示