FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

FPGA实现LCD显示屏显示彩条

总体设计

系统总体分为五个模块,分别是:rd_id(读显示屏ID模块),clk_div(时钟分频模块),lcd_display(lcd屏显示模块),lcd_driver(lcd屏驱动模块),和顶层模块lcd_rgb。

总体框图如下:

图片[1]-FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

 读显示屏ID

因为不同的LCD屏有着不同的分辨率,并对应了不同的时序参数,我们需要读取LCD_B7/G7/R7这三个管脚的状态,来判断当前显示屏的型号。

查找IO分配表可知LCD_B7/G7/R7分别为lcd_rgb[4],lcd_rgb[10],lcd_rgb[15]。

图片[2]-FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug
LCD屏和ID对应关系

 读显示屏ID代码

module rd_id( input wire          sys_clk,  //系统时钟 input wire          sys_rst_n,  //系统复位 input wire [15:0]   lcd_rgb,  //初始像素数据,用于读取ID  output reg [15:0]   lcd_id  //读取到的ID );  reg rd_flag;  //读ID标志  always @(posedge sys_clk or negedge sys_rst_n) begin     if(!sys_rst_n) begin         rd_flag <= 1'b0;         lcd_id <= 16'b0;     end     else begin         if(rd_flag == 1'b0) begin //说明开始上电             rd_flag <= 1'b1;             case({lcd_rgb[4],lcd_rgb[10],lcd_rgb[15]})                 3'b000: lcd_id <= 16'h4342;  //4.3’  RES 480*272                 3'b001: lcd_id <= 16'h7084;  //7’    RES 800*480                 3'b010: lcd_id <= 16'h7016;  //7’    RES 1024*600                 3'b100: lcd_id <= 16'h4384;  //4.3’  RES 800*480                 3'b101: lcd_id <= 16'h1018;  //10’   RES 1280*800                 default : lcd_id <= 16'b0;             endcase         end     end end  endmodule

时钟分频

不同分辨率有着不同的时序参数,为了提高兼容性,我增加了时钟分频模块,能根据传入的ID号来产生对应所需的时钟。

其中时钟分频模块采用了quartus的IP核。

图片[3]-FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug图片[4]-FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

 时钟分频代码

module clk_div(
input wire          sys_clk,  //系统时钟50MHz
input wire          sys_rst_n,  //系统复位
input wire [15:0]   lcd_id,  //ID号
output reg          lcd_pclk  //lcd驱动时钟
);
 
wire                clk_10;
wire                clk_34;
wire                clk_50;
wire                clk_70;
 
wire                locked;
 
pll_clk pll_clk_inst (
    .areset ( ~sys_rst_n ),  //锁相环高电平复位
    .inclk0 ( sys_clk ),
    .c0 ( clk_10 ),
    .c1 ( clk_34 ),
    .c2 ( clk_50 ),
    .c3 ( clk_70 ),
    .locked ( locked )
    );
 
//---------根据分辨率选择对应的时钟-----------//
always @(*) begin
    if(locked) begin
        case(lcd_id)
            16'h4342 : lcd_pclk = clk_10;  //9M
            16'h7084 : lcd_pclk = clk_34;  //33.3M
            16'h7016 : lcd_pclk = clk_50;  //50M
            16'h4384 : lcd_pclk = clk_34;  //33.3M
            16'h1018 : lcd_pclk = clk_70;  //70M
            default : lcd_pclk = 1'b0;
        endcase
    end
    else begin
        lcd_pclk = 1'b0;
    end
end
 
endmodule

 

 LCD显示

显示的图案可以根据自己喜好设置,需要注意的就是h_disp和v_disp这两个信号,可以理解成LCD屏的有效显示边框/范围。原来是根据例程写了五等分的彩条(注释部分),后来图好玩改成了法国国旗,参数中的蓝色和红色我也改过了。

因为电脑自带画板显示的一般都是RGB888,但是LCD显示屏用的是RGB565,两者的位宽不一样。但RGB888转RGB565的方法也很简单,就是分别取8位红绿蓝的高位。

lcd显示模块代码

module lcd_display(
input wire          lcd_pclk,       //不同分辨率对应的时钟
input wire          sys_rst_n,      //系统复位信号
input wire [10:0]   pixel_xpos,     //当前像素点横坐标  (像素点数最大为1440,11位)
input wire [10:0]   pixel_ypos,     //当前像素点纵坐标
input wire [10:0]   h_disp,         //LCD屏水平分辨率
input wire [10:0]   v_disp,         //LCD屏垂直分辨率
output reg [15:0]   pixel_data  //像素数据
);
 
//parameter define  
parameter WHITE     = 16'b11111_111111_11111;  //白色
parameter BLACK     = 16'b00000_000000_00000;  //黑色
parameter RED       = 16'b11111_001111_00110;  //红色
parameter GREEN     = 16'b00000_111111_00000;  //绿色
parameter BLUE      = 16'b00000_010101_10100;  //蓝色
parameter YELLOW    = 16'b11111_111111_00000;  //黄色
 
 
always @(posedge lcd_pclk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        pixel_data <= BLACK;
    end
    else begin
//        if(pixel_xpos > 11'b0 && pixel_xpos < h_disp/5*1)
//            pixel_data <= RED;
//        else if(pixel_xpos > h_disp/5*1 && pixel_xpos < h_disp/5*2)
//            pixel_data <= YELLOW;
//        else if(pixel_xpos > h_disp/5*2 && pixel_xpos < h_disp/5*3)
//            pixel_data <= BLUE;
//        else if(pixel_xpos > h_disp/5*3 && pixel_xpos < h_disp/5*4)
//            pixel_data <= GREEN;
//        else
//            pixel_data <= WHITE;
 
        if(pixel_xpos > 11'b0 && pixel_xpos < h_disp/3*1)
            pixel_data <= BLUE;
        else if(pixel_xpos > h_disp/3*1 && pixel_xpos < h_disp/3*2)
            pixel_data <= WHITE;
        else
            pixel_data <= RED;
    end
end
 
endmodule

 

LCD驱动模块

每个时钟内会扫描一个像素点,所以需要设置计数器对扫到的像素点进行计数。根据计数值可以圈定LCD屏的有效区域,当处在这个范围之内时,lcd_de拉高,将传入的像素数据pixel_data通过数据端口lcd_rgb传出至屏幕。

图片[5]-FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug图片[6]-FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

在这里加一段别的博主对于lcd_req设置的理解,里面提到的子母计数器是h_cnt和v_cnt。

    //…此处做一个简单的分析,lcd_de拉高,lcd_rgb[15:0]的值瞬间到达屏幕的对应像素点上。
//   我们最开始的分析是:cnt值->产生坐标->根据坐标确认有效范围->再往范围里放颜色值
//   所以说我们在上面写的那个就是有效范围,由于触发器的特性,所以要提前一个时钟周期将坐标值送出,才来的及在lcd_en为1时候准确的送出颜色值
//   而一场是由很多行组成的,母计数器,要比子计数器慢。子计数器变很多次,母计数器才变一次。所以对子计数器需要提前一个时钟周期发送出坐标。
//   PS:如果没懂的话也没关系,因为我也比较晕。但只要时序对,屏幕就能出现颜色然后进行simulation或者实物调试
————————————————
版权声明:本文为CSDN博主「搞IC的那些年」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/helloworld573/article/details/113773634

lcd驱动代码

module lcd_driver(
input wire          lcd_pclk,       //不同分辨率对应的时钟
input wire          sys_rst_n,      //系统复位信号
input wire [15:0]     lcd_id,         //不同显示屏对应的ID
input wire [15:0]   pixel_data,      //像素数据
output wire [10:0]   pixel_xpos,     //当前像素点横坐标  (像素点数最大为1440,11位)
output wire [10:0]   pixel_ypos,     //当前像素点纵坐标
output reg  [10:0]   h_disp,         //LCD屏水平分辨率
output reg  [10:0]   v_disp,         //LCD屏垂直分辨率
output wire         lcd_de,         //RGB LCD数据使能
output wire         lcd_hs,         //RGB LCD行同步
output wire         lcd_vs,         //RGB LCD场同步
output wire         lcd_clk,        //RGB LCD像素时钟
output wire         lcd_bl,         //RGB LCD背光控制
output wire         lcd_rst,        //RGB LCD系统复位,低有效
output wire [15:0]  lcd_rgb         //RGB565颜色数据
);
 
wire                lcd_en;
wire                data_req;
 
//reg define
reg  [10:0]         h_sync ;
reg  [10:0]         h_back ;
reg  [10:0]         h_total;
reg  [10:0]         v_sync ;
reg  [10:0]         v_back ;
reg  [10:0]         v_total;
reg  [10:0]         h_cnt  ;
reg  [10:0]         v_cnt  ;
 
//数据使能
assign lcd_de = lcd_en;
 
//行场同步
assign lcd_hs = 1'b1;
assign lcd_vs = 1'b1;
 
//像素时钟
assign lcd_clk = lcd_pclk;
 
//复位和背光
assign lcd_rst = (sys_rst_n == 1'b0) ? 1'b0 : 1'b1;
assign lcd_bl = (sys_rst_n == 1'b0) ? 1'b0 : 1'b1;
 
//parameter define  
// 4.3' 480*272
parameter  H_SYNC_4342   =  11'd41;     //行同步
parameter  H_BACK_4342   =  11'd2;      //行显示后沿
parameter  H_DISP_4342   =  11'd480;    //行有效数据
parameter  H_FRONT_4342  =  11'd2;      //行显示前沿
parameter  H_TOTAL_4342  =  11'd525;    //行扫描周期
   
parameter  V_SYNC_4342   =  11'd10;     //场同步
parameter  V_BACK_4342   =  11'd2;      //场显示后沿
parameter  V_DISP_4342   =  11'd272;    //场有效数据
parameter  V_FRONT_4342  =  11'd2;      //场显示前沿
parameter  V_TOTAL_4342  =  11'd286;    //场扫描周期
   
// 7' 800*480   
parameter  H_SYNC_7084   =  11'd128;    //行同步
parameter  H_BACK_7084   =  11'd88;     //行显示后沿
parameter  H_DISP_7084   =  11'd800;    //行有效数据
parameter  H_FRONT_7084  =  11'd40;     //行显示前沿
parameter  H_TOTAL_7084  =  11'd1056;   //行扫描周期
   
parameter  V_SYNC_7084   =  11'd2;      //场同步
parameter  V_BACK_7084   =  11'd33;     //场显示后沿
parameter  V_DISP_7084   =  11'd480;    //场有效数据
parameter  V_FRONT_7084  =  11'd10;     //场显示前沿
parameter  V_TOTAL_7084  =  11'd525;    //场扫描周期       
   
// 7' 1024*600   
parameter  H_SYNC_7016   =  11'd20;     //行同步
parameter  H_BACK_7016   =  11'd140;    //行显示后沿
parameter  H_DISP_7016   =  11'd1024;   //行有效数据
parameter  H_FRONT_7016  =  11'd160;    //行显示前沿
parameter  H_TOTAL_7016  =  11'd1344;   //行扫描周期
   
parameter  V_SYNC_7016   =  11'd3;      //场同步
parameter  V_BACK_7016   =  11'd20;     //场显示后沿
parameter  V_DISP_7016   =  11'd600;    //场有效数据
parameter  V_FRONT_7016  =  11'd12;     //场显示前沿
parameter  V_TOTAL_7016  =  11'd635;    //场扫描周期
   
// 10.1' 1280*800   
parameter  H_SYNC_1018   =  11'd10;     //行同步
parameter  H_BACK_1018   =  11'd80;     //行显示后沿
parameter  H_DISP_1018   =  11'd1280;   //行有效数据
parameter  H_FRONT_1018  =  11'd70;     //行显示前沿
parameter  H_TOTAL_1018  =  11'd1440;   //行扫描周期
   
parameter  V_SYNC_1018   =  11'd3;      //场同步
parameter  V_BACK_1018   =  11'd10;     //场显示后沿
parameter  V_DISP_1018   =  11'd800;    //场有效数据
parameter  V_FRONT_1018  =  11'd10;     //场显示前沿
parameter  V_TOTAL_1018  =  11'd823;    //场扫描周期
 
// 4.3' 800*480   
parameter  H_SYNC_4384   =  11'd128;    //行同步
parameter  H_BACK_4384   =  11'd88;     //行显示后沿
parameter  H_DISP_4384   =  11'd800;    //行有效数据
parameter  H_FRONT_4384  =  11'd40;     //行显示前沿
parameter  H_TOTAL_4384  =  11'd1056;   //行扫描周期
   
parameter  V_SYNC_4384   =  11'd2;      //场同步
parameter  V_BACK_4384   =  11'd33;     //场显示后沿
parameter  V_DISP_4384   =  11'd480;    //场有效数据
parameter  V_FRONT_4384  =  11'd10;     //场显示前沿
parameter  V_TOTAL_4384  =  11'd525;    //场扫描周期    
 
//行场时序参数
always @(posedge lcd_pclk) begin
    case(lcd_id)
        16'h4342 : begin
            h_sync  <= H_SYNC_4342; 
            h_back  <= H_BACK_4342; 
            h_disp  <= H_DISP_4342; 
            h_total <= H_TOTAL_4342;
            v_sync  <= V_SYNC_4342; 
            v_back  <= V_BACK_4342; 
            v_disp  <= V_DISP_4342; 
            v_total <= V_TOTAL_4342;            
        end
        16'h7084 : begin
            h_sync  <= H_SYNC_7084; 
            h_back  <= H_BACK_7084; 
            h_disp  <= H_DISP_7084; 
            h_total <= H_TOTAL_7084;
            v_sync  <= V_SYNC_7084; 
            v_back  <= V_BACK_7084; 
            v_disp  <= V_DISP_7084; 
            v_total <= V_TOTAL_7084;        
        end
        16'h7016 : begin
            h_sync  <= H_SYNC_7016; 
            h_back  <= H_BACK_7016; 
            h_disp  <= H_DISP_7016; 
            h_total <= H_TOTAL_7016;
            v_sync  <= V_SYNC_7016; 
            v_back  <= V_BACK_7016; 
            v_disp  <= V_DISP_7016; 
            v_total <= V_TOTAL_7016;            
        end
        16'h4384 : begin
            h_sync  <= H_SYNC_4384; 
            h_back  <= H_BACK_4384; 
            h_disp  <= H_DISP_4384; 
            h_total <= H_TOTAL_4384;
            v_sync  <= V_SYNC_4384; 
            v_back  <= V_BACK_4384; 
            v_disp  <= V_DISP_4384; 
            v_total <= V_TOTAL_4384;             
        end        
        16'h1018 : begin
            h_sync  <= H_SYNC_1018; 
            h_back  <= H_BACK_1018; 
            h_disp  <= H_DISP_1018; 
            h_total <= H_TOTAL_1018;
            v_sync  <= V_SYNC_1018; 
            v_back  <= V_BACK_1018; 
            v_disp  <= V_DISP_1018; 
            v_total <= V_TOTAL_1018;        
        end
        default : begin
            h_sync  <= H_SYNC_4342; 
            h_back  <= H_BACK_4342; 
            h_disp  <= H_DISP_4342; 
            h_total <= H_TOTAL_4342;
            v_sync  <= V_SYNC_4342; 
            v_back  <= V_BACK_4342; 
            v_disp  <= V_DISP_4342; 
            v_total <= V_TOTAL_4342;          
        end
    endcase
end
 
//行计数器对行像素点计数
always @(posedge lcd_pclk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        h_cnt <= 11'b0;
    end
    else begin
        h_cnt <= (h_cnt == h_total - 1'b1) ? 11'b0 : h_cnt + 1'b1;
    end
end
 
//场计数器对场像素点进行计数
always @(posedge lcd_pclk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        v_cnt <= 11'b0;
    end
    else if(h_cnt == h_total - 1'b1)begin
        v_cnt <= (v_cnt == v_total - 1'b1) ? 11'b0 : v_cnt + 1'b1;
    end
end
 
//使能RGB565数据输出,lcd_en表示区域内像素点有效
assign lcd_en = (h_cnt >= h_sync + h_back) && (h_cnt < h_sync + h_back + h_disp) 
                && (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp) ? 1'b1 : 1'b0;
 
//请求像素点颜色数据输入,提前一时钟将数据载入到lcd驱动模块
assign data_req = (h_cnt >= h_sync + h_back - 1'b1) && (h_cnt < h_sync + h_back + h_disp - 1'b1) 
                && (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp) ? 1'b1 : 1'b0;
                
//RGB565数据输出
assign lcd_rgb = lcd_en ? pixel_data : 16'b0;
 
//像素点坐标
assign pixel_xpos = data_req ? (h_cnt - (h_sync + h_back - 1'b1)) : 11'b0;
assign pixel_ypos = data_req ? (v_cnt - (v_sync + v_back - 1'b1)) : 11'b0;
 
endmodule

 

顶层模块

比较需要注意的就是这里要把lcd_rgb定义为inout类型。当lcd_de信号为高电平时,此时输出的像素数据有效,将lcd_rgb的引脚方向切换成输出,并将LCD驱动模块输出的lcd_rgb_o(像素数据)连接至lcd_rgb引脚;当lcd_de信号为低电平时,此时输出的像素数据无效,将lcd_rgb 的引脚方向切换成输入。代码中将高阻状态“Z”赋值给lcd_rgb的引脚,表示此时lcd_rgb的引脚电平由外围电路决定,此时可以读取lcd_rgb的引脚电平,从而获取到LCD屏的ID。

顶层模块代码

module lcd_rgb(
input wire          sys_clk,       //系统时钟
input wire          sys_rst_n,      //系统复位信号
output wire         lcd_de,         //RGB LCD数据使能
output wire         lcd_hs,         //RGB LCD行同步
output wire         lcd_vs,         //RGB LCD场同步
output wire         lcd_clk,       //RGB LCD像素时钟
output wire         lcd_bl,         //RGB LCD背光控制
output wire         lcd_rst,        //RGB LCD系统复位,低有效
inout  wire [15:0]  lcd_rgb         //RGB565颜色数据
);
 
wire [15:0]         lcd_rgb_i;
wire [15:0]         lcd_rgb_o;
wire [15:0]         lcd_id;
wire                lcd_pclk;  //分频时钟
wire [10:0]         pixel_xpos;
wire [10:0]         pixel_ypos;
wire [10:0]         h_disp;  
wire [10:0]         v_disp;  
wire [15:0]         pixel_data;
 
//数据双向端口
assign lcd_rgb = lcd_de ? lcd_rgb_o : 16'bz;
assign lcd_rgb_i = lcd_rgb;
 
rd_id u_rd_id(
        .sys_clk(sys_clk),
        .sys_rst_n(sys_rst_n),
        .lcd_rgb(lcd_rgb_i),
        
        .lcd_id(lcd_id)
);
 
clk_div u_clk_div(
        .sys_clk(sys_clk),  //50MHz
        .sys_rst_n(sys_rst_n),
        .lcd_id(lcd_id),
        
        .lcd_pclk(lcd_pclk)
);
 
lcd_display u_lcd_display(
        .lcd_pclk(lcd_pclk),       //不同分辨率对应的时钟
        .sys_rst_n(sys_rst_n),      //系统复位信号
        .pixel_xpos(pixel_xpos),     //当前像素点横坐标  (像素点数最大为1440,11位)
        .pixel_ypos(pixel_ypos),     //当前像素点纵坐标
        .h_disp(h_disp),         //LCD屏水平分辨率
        .v_disp(v_disp),         //LCD屏垂直分辨率
        
        .pixel_data(pixel_data)  //像素数据
);
 
lcd_driver u_lcd_driver(
        .lcd_pclk(lcd_pclk),       //不同分辨率对应的时钟
        .sys_rst_n(sys_rst_n),      //系统复位信号
        .lcd_id(lcd_id),         //不同显示屏对应的ID
        .pixel_data(pixel_data),      //像素数据
        
        .pixel_xpos(pixel_xpos),     //当前像素点横坐标  (像素点数最大为1440,11位)
        .pixel_ypos(pixel_ypos),     //当前像素点纵坐标
        .h_disp(h_disp),         //LCD屏水平分辨率
        .v_disp(v_disp),         //LCD屏垂直分辨率
        
        .lcd_de(lcd_de),         //RGB LCD数据使能
        .lcd_hs(lcd_hs),         //RGB LCD行同步
        .lcd_vs(lcd_vs),         //RGB LCD场同步
        .lcd_clk(lcd_clk),       //RGB LCD像素时钟
        .lcd_bl(lcd_bl),         //RGB LCD背光控制
        .lcd_rst(lcd_rst),        //RGB LCD系统复位,低有效
        .lcd_rgb(lcd_rgb_o)         //RGB565颜色数据
);
 
endmodule

 

附一张野火画的框图

图片[7]-FPGA实现LCD显示屏显示彩条-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

请登录后发表评论