FPGA学习-RGB LCD显示实验-通信协议-显示原理-仿真验证上板显示
前言
板子:正点原子达芬奇的FPGA开发板,后续FPGA学习硬件都是基于该开发板,软件环境基于vavido和modelsim完成
显示屏算是一个比较常见的功能模块,最开始接触是一种长方形的模块,比较low像外面超市那种循环播放的红色字幕的屏幕。后来在接触就是oled屏幕,当时32学习这个屏幕也是主要围绕通信协议以及封装函数,显示都是直接调用封装好的函数,本身没有涉及太多的逻辑。
今天来看看这个板卡配套的大屏幕的工作流程吧,了解屏幕的基本参数,通信原理以及图形显示等等
一、屏幕基本参数
1.分辨率
720P、1080P、2K、4K这样的分辨率参数,分别对应了1280×720、19201080、25601440、38402160的像素阵列。
图中是1080P的像素阵列示意图,19201080,代表着共有1920列,每一列上是1080个像素点。
2.像素格式
一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就可以显示出各种各样的色彩。那该如何控制 R、G、B 这三种颜色的显示亮度呢?一般一个 R、G、B 这三部分分别使用8bit 的数据,那么一个像素点就是 8bit*3=24bit,也就是说一个像素点 3 个字节,这种像素格式称为RGB888。当然常用的像素点格式还有 RGB565,只需要两个字节,但在色彩鲜艳度上较差一些。我们开发板上的 RGB TFT-LCD 接口采用的 RGB888 的像素格式,共需要 24 位,每一位对应 RGB 的颜色分量如下图所示:
相当于一个像素点是24位的,相当于有A88 * A88 *A88种亮度情况
比如纯红色:24‘H0000FF 纯绿色:24‘H00FF00 纯蓝色:24‘HFF00000 具体的数值可以通过调色板获取
3.屏幕接口
LCD 屏幕或者说显示器有很多种接口,比如在显示器上常见的 VGA、HDMI、DP 等等,开发板支持RGB 接口的 LCD 和 HDMI 接口的显示器。本章我们介绍的是 RGB LCD 接口,RGB LCD 接口的信号线如下表所示:
上表中就是 RGB LCD 的信号线,R[7:0]、G[7:0]和 B[7:0]是 24 位数据,DE、VSYNC、HSYNC 和PCLK 是四个控制信号。
4.时间参数
按照从左至右、从上到下的顺序扫描每个像素点,并且在像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了。以1024*600为例;
HSYNC 是水平同步信号,也叫做行同步信号,当产生此信号的话就表示开始显示新的一行了,所以此信号都是在图的最左边。VSYNC 信号是垂直同步信号,也叫做帧同步信号,当产生此信号的话就表示开始显示新的一帧图像了,所以此信号在图的左上角
5.时序分析
拉宽示波器时钟线后的波形:
二、编写模块
1.ID读取模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/10/14 15:09:03
// Design Name:
// Module Name: get_id
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module get_id(
input clk,
input rst_n,
input [23:0] lcd_rgb,
output reg [15:0] lcd_id,
output reg rd_flag
);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
lcd_id <=16'h00_00;
rd_flag <= 1'b0;
end
else begin
if(rd_flag == 1'b0)begin
rd_flag <= 1'b1;
case({lcd_rgb[7],lcd_rgb[13],lcd_rgb[23]})
3'b000:lcd_id <=16'h43_42;
3'b001:lcd_id <=16'h70_84;
3'b010:lcd_id <=16'h70_16;
3'b100:lcd_id <=16'h43_84;
3'b101:lcd_id <=16'h10_18;
default:lcd_id <=16'h00_00;
endcase
end else begin
lcd_id <=lcd_id;
end
end
end
endmodule
2.时钟分频模块
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: clk_div
// Last modified Date: 2019/8/7 10:41:06
// Last Version: V1.0
// Descriptions: 时钟分频模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/8/7 10:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module clk_div(
input clk, //50Mhz
input rst_n,
input [15:0] lcd_id,
output reg lcd_pclk
);
//reg define
reg clk_25m;
reg clk_12_5m;
reg div_4_cnt;
//*****************************************************
//** main code
//*****************************************************
//时钟2分频 输出25MHz时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
clk_25m <= 1'b0;
else
clk_25m <= ~clk_25m;
end
//时钟4分频 输出12.5MHz时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
div_4_cnt <= 1'b0;
clk_12_5m <= 1'b0;
end
else begin
div_4_cnt <= div_4_cnt + 1'b1;
if(div_4_cnt == 1'b1)
clk_12_5m <= ~clk_12_5m;
else
clk_12_5m <= clk_12_5m;
end
end
always @(*) begin
case(lcd_id)
16'h4342 : lcd_pclk = clk_12_5m;
16'h7084 : lcd_pclk = clk_25m;
16'h7016 : lcd_pclk = clk;
16'h4384 : lcd_pclk = clk_25m;
16'h1018 : lcd_pclk = clk;
default : lcd_pclk = 1'b0;
endcase
end
endmodule
3.LCD驱动模块
这个模块主要围绕800*480进行信号的状态切换,比如行同步信号,列同步信号,分别是计数到480和800,按照上文的时序进行写就可以了。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: lcd_driver
// Last modified Date: 2019/8/07 10:41:06
// Last Version: V1.0
// Descriptions: LCD驱动模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/8/07 10:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module lcd_driver(
input lcd_pclk, //时钟
input rst_n, //复位,低电平有效
input [15:0] lcd_id, //LCD屏ID
input [23:0] pixel_data, //像素数据
output reg [10:0] pixel_xpos, //当前像素点横坐标
output reg [10:0] pixel_ypos, //当前像素点纵坐标
output reg [10:0] h_disp, //LCD屏水平分辨率
output reg [10:0] v_disp, //LCD屏垂直分辨率
output reg data_req, //数据请求信号
//RGB LCD接口
output reg lcd_de, //LCD 数据使能信号
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_bl, //LCD 背光控制信号
output lcd_clk, //LCD 像素时钟
output lcd_rst, //LCD复位
output [23:0] lcd_rgb //LCD RGB888颜色数据
);
//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; //场扫描周期
//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 ;
//*****************************************************
//** main code
//*****************************************************
//RGB LCD 采用DE模式时,行场同步信号需要拉高
assign lcd_hs = 1'b1; //LCD行同步信号
assign lcd_vs = 1'b1; //LCD场同步信号
assign lcd_bl = 1'b1; //LCD背光控制信号
assign lcd_clk = lcd_pclk; //LCD像素时钟
assign lcd_rst= 1'b1; //LCD复位
//RGB888数据输出
assign lcd_rgb = lcd_de ? pixel_data : 24'd0;
//像素点x坐标
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
pixel_xpos <= 11'd0;
else if(data_req)
pixel_xpos <= h_cnt + 2'd2 - h_sync - h_back ;
else
pixel_xpos <= 11'd0;
end
//像素点y坐标
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
pixel_ypos <= 11'd0;
else if(v_cnt >= (v_sync + v_back)&&v_cnt < (v_sync + v_back + v_disp))
pixel_ypos <= v_cnt + 1'b1 - (v_sync + v_back) ;
else
pixel_ypos <= 11'd0;
end
//行场时序参数
always @(*) 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 rst_n) begin
if(!rst_n)
lcd_de <= 1'b0;
else
lcd_de <= data_req;
end
//请求像素点颜色数据输入
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
data_req<=1'b0;
else if((h_cnt >= h_sync + h_back - 2'd2) && (h_cnt < h_sync + h_back + h_disp - 2'd2)
&& (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp))
data_req <= 1'b1;
else
data_req <= 1'b0;
end
//行计数器对像素时钟计数
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
h_cnt <= 11'd0;
else begin
if(h_cnt == h_total - 1'b1)
h_cnt <= 11'd0;
else
h_cnt <= h_cnt + 1'b1;
end
end
//场计数器对行计数
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
v_cnt <= 11'd0;
else begin
if(h_cnt == h_total - 1'b1) begin
if(v_cnt == v_total - 1'b1)
v_cnt <= 11'd0;
else
v_cnt <= v_cnt + 1'b1;
end
end
end
endmodule
3.LCD驱动模块
这个模块最关键的就是获取你想要描点的位置,其实就是这个点的x和y,x其实一直是递增,你只需要关注随着列同步信号递增,你的输入信号幅值是多少?根据你输入信号的幅值确定你描点的高度。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: lcd_display
// Last modified Date: 2019/8/07 10:41:06
// Last Version: V1.0
// Descriptions: LCD显示模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/8/07 10:41:06
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module lcd_display(
input lcd_pclk, //时钟
input rst_n, //复位,低电平有效
input lcd_de,
input uart_rxd,
input [10:0] pixel_xpos, //当前像素点横坐标
input [10:0] pixel_ypos, //当前像素点纵坐标
input [10:0] h_disp, //LCD屏水平分辨率
input [10:0] v_disp, //LCD屏垂直分辨率
output reg [23:0] pixel_data //像素数据
);
//parameter define
parameter WHITE = 24'hFFFFFF; //白色
parameter BLACK = 24'h000000; //黑色
parameter RED = 24'hFF0000; //红色
parameter GREEN = 24'h00FF00; //绿色
parameter BLUE = 24'h0000FF; //蓝色
//*****************************************************
//** main code
//*****************************************************
//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条
//always @(posedge lcd_pclk or negedge rst_n) begin
// if(!rst_n)
// pixel_data <= BLACK;
// else begin
// if((pixel_xpos >= 11'd0) && (pixel_xpos < h_disp/5*1))
// pixel_data <= WHITE;
// else if((pixel_xpos >= h_disp/5*1) && (pixel_xpos < h_disp/5*2))
// pixel_data <= BLACK;
// else if((pixel_xpos >= h_disp/5*2) && (pixel_xpos < h_disp/5*3))
// pixel_data <= RED;
// else if((pixel_xpos >= h_disp/5*3) && (pixel_xpos < h_disp/5*4))
// pixel_data <= GREEN;
// else
// pixel_data <= BLUE;
// end
//end
wire [7:0] data;
reg [6:0] addra;
wire [7:0] douta;
reg [6:0] addrb;
wire [7:0] doutb;
reg [9:0] addr;
reg [15:0] flag_count;
reg flag;
reg display;
assign data =(flag==1'b1)?doutb:douta;
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)begin
display <= 1'b0;
end
else if(uart_rxd==1'b0)begin
display <= ~display;
end
else begin
display <= display;
end
end
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)begin
addra <= 7'd0;
end
else if(lcd_de)begin
addra <= addra +1'b1;
end
else begin
addra <= 7'd0;
end
end
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)begin
addrb <= 7'd0;
end
else if(lcd_de)begin
addrb <= addrb +1'b1;
end
else begin
addrb <= 7'd0;
end
end
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(flag_count>100 && flag_count<3000)begin
if(addr==224 ) begin
flag <= 1'b1;
end
else if(addr==608 ) begin
flag <= 1'b0;
end
end
else if(flag_count>=3000 && flag_count<6000)begin
if(addr==352 ) begin
flag <= 1'b1;
end
else if(addr==480 ) begin
flag <= 1'b0;
end
end
else if(flag_count>=6000 && flag_count<9000)begin
if(addr==96 ) begin
flag <= 1'b1;
end
else if(addr==480 ) begin
flag <= 1'b0;
end
end
else if(flag_count>=6000 && flag_count<9000)begin
if(addr==1 ) begin
flag <= 1'b0;
end
else if(addr==352 ) begin
flag <= 1'b1;
end
end
else if(flag_count>=9000 && flag_count<12000)begin
if(addr==0 ) begin
flag <= 1'b1;
end
else if(addr==352 ) begin
flag <= 1'b0;
end
end
else if(flag_count>=12000 && flag_count<15000)begin
if(addr==96 ) begin
flag <= 1'b0;
end
else if(addr==480 ) begin
flag <= 1'b1;
end
end
else if(flag_count>=15000 && flag_count<18000)begin
if(addr==224 ) begin
flag <= 1'b0;
end
else if(addr==608 ) begin
flag <= 1'b0;
end
end
else if(flag_count>=18000 && flag_count<21000)begin
if(addr==96 ) begin
flag <= 1'b0;
end
else if(addr==480 ) begin
flag <= 1'b1;
end
end
else begin
flag <= flag;
end
end
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)begin
flag_count <= 16'd0;
end
else if(addr==799 &&flag_count<24000)begin
flag_count <= flag_count +1'b1;
end
else if(addr==799 &&flag_count==24000)begin
flag_count <= 16'd0;
end
end
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)begin
addr <= 10'd0;
end
else if(lcd_de && addr <800)begin
addr <= addr +1'b1;
end
else if(addr==800)begin
addr <= 10'd0;
end else begin
addr <= 10'd0;
end
end
blk_mem_gen_0 u_sin (
.clka(lcd_pclk), // input wire clka
.addra(addra), // input wire [5 : 0] addra
.douta(douta) // output wire [7 : 0] douta
);
blk_mem_gen_1 u_sin2k (
.clka(lcd_pclk), // input wire clka
.addra(addrb+16), // input wire [6 : 0] addra
.douta(doutb) // output wire [7 : 0] douta
);
reg [2:0] x_count;
reg [4:0] y_count;
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)begin
x_count <= 3'd0;
y_count <= 5'd0;
end
else if(pixel_xpos==800 && pixel_ypos==480)begin
x_count<=x_count+1'b1;
y_count<=y_count+x_count;
end
end
//always @(posedge lcd_pclk or negedge rst_n) begin
// if(!rst_n)
// pixel_data <= BLACK;
// else begin
// if((pixel_xpos >= 10+x_count) && (pixel_xpos < 15+x_count) && (pixel_ypos >= 20+y_count))
// pixel_data <= RED;
// else
// pixel_data <= WHITE;
// end
//end
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
pixel_data <= BLACK;
else if(display==1'b0)begin
if((pixel_xpos >= addr) && (pixel_xpos < addr+10) && (pixel_ypos >= data)&& (pixel_ypos < data+10))
pixel_data <= RED;
else
pixel_data <= WHITE;
end
else begin
if((pixel_xpos >= 20) && (pixel_xpos < 25) && (pixel_ypos >= 20+y_count))
pixel_data <= RED;
else if((pixel_xpos >= 80) && (pixel_xpos < 85) && (pixel_ypos >= 250+x_count))
pixel_data <= RED;
else
pixel_data <= WHITE;
end
end
endmodule
效果展示
基于ROM产生正弦波,获取幅值和rom地址,确定位置,画出来点。