使用像素:480*272的一块RGB565屏幕。
像素时钟:9Mhz
接口:
lcd_bl:lcd背光
lcd_rgb[15:0]:色彩值
lcd_de:当计数器处于valid(有效)区域时应将其拉高,此时输出lcd_rgb[15:0]的值到屏幕的对应像素点上。
lcd_hs:可一直将其拉高没有影响
lcd_vs:可一直将其拉高没有影响
lcd_pclk:9M时钟
lcd_rst:lcd单独复位低电平有效
1、利用ip核倍分频获得9M时钟。
2、使用子母两个计数器对驱动时钟进行计数。
3、子计数器的计数范围为0-524,所代表一个行扫描周期,满载时在下一个时钟周期清零,并使母计数器+1。
4、母计数器的计数范围为0-285,所代表一个场扫描周期,满载时在下一个时钟周期清零。
5、利用组合逻辑将子母俩个计数的计数值做范围限定,确定真正valid的范围。
6、当计数值处于真正valid的范围时,需要回传当前的坐标值,用户根据回传的坐标值设置RGB565的色彩值。
module pll_9m (
areset,//注意:其为高电平复位
inclk0,//输入时钟
c0, //输出时钟
locked //稳定锁:当c0输出稳定时locked会从低电平变为高电平
);
module lcd_driver(
input lcd_clk, //lcd模块驱动时钟
input sys_rst_n, //复位信号
//下列信号与RGBLCD屏幕相连接
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_de, //LCD 数据使能
output [15:0] lcd_rgb, //LCD RGB565颜色数据
output lcd_bl, //LCD 背光控制信号
output lcd_rst, //LCD 复位信号
output lcd_pclk, //LCD 采样时钟
//接口信号,此模块推出valid坐标值,并接收用户发来的RGB565的像素点颜色值
input [15:0] pixel_data, //像素点数据
output [10:0] pixel_xpos, //像素点横坐标
output [10:0] pixel_ypos //像素点纵坐标
);
//参数列表
parameter H_SYNC = 11'd41; //行同步
parameter H_BACK = 11'd2; //行显示后沿
parameter H_DISP = 11'd480; //行有效数据
parameter H_FRONT = 11'd2; //行显示前沿
parameter H_TOTAL = 11'd525; //行扫描周期
parameter V_SYNC = 11'd10; //场同步
parameter V_BACK = 11'd2; //场显示后沿
parameter V_DISP = 11'd272; //场有效数据
parameter V_FRONT = 11'd2; //场显示前沿
parameter V_TOTAL = 11'd286; //场扫描周期
//子母两个计数器
reg [10:0] cnt_h;
reg [10:0] cnt_v;
//...
wire lcd_en;
wire data_req;
//对lcd屏幕的某些信号加以固定
assign lcd_bl = 1'b1;
assign lcd_rst = 1'b1;
assign lcd_pclk = lcd_clk;
assign lcd_hs = 1'b1;
assign lcd_vs = 1'b1;
//当子母计数器的值处于valid范围内时,将lcd_de拉高,此时输出lcd_rgb[15:0]的值到屏幕的对应像素点上。
assign lcd_de = lcd_en;
assign lcd_en = (((cnt_h > H_SYNC+H_BACK) && (cnt_h <= H_SYNC+H_BACK+H_DISP))
&&((cnt_v > V_SYNC+V_BACK) && (cnt_v <= V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//lcd_rgb[15:0]的值由用户(input)给予即为:pixel_data
assign lcd_rgb = lcd_en ? pixel_data : 16'd0;
//...此处做一个简单的分析,lcd_de拉高,lcd_rgb[15:0]的值瞬间到达屏幕的对应像素点上。
// 我们最开始的分析是:cnt值->产生坐标->根据坐标确认有效范围->再往范围里放颜色值
// 所以说我们在上面写的那个就是有效范围,由于触发器的特性,所以要提前一个时钟周期将坐标值送出,才来的及在lcd_en为1时候准确的送出颜色值
// 而一场是由很多行组成的,母计数器,要比子计数器慢。子计数器变很多次,母计数器才变一次。所以对子计数器需要提前一个时钟周期发送出坐标。
// PS:如果没懂的话也没关系,因为我也比较晕。但只要时序对,屏幕就能出现颜色然后进行simulation或者实物调试
assign data_req = (((cnt_h > H_SYNC+H_BACK-1'b1) && (cnt_h <= H_SYNC+H_BACK+H_DISP-1'b1)) //对子计数器需要提前一个时钟周期发送出坐标。
&& ((cnt_v > V_SYNC+V_BACK) && (cnt_v <= V_SYNC+V_BACK+V_DISP))) //母计数器不变
? 1'b1 : 1'b0;
//当前像素点坐标
assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 11'd0;
assign pixel_ypos = data_req ? ((cnt_v - (V_SYNC + V_BACK - 1'b1)) - 1'b1) : 11'd0;
//这块为什么有个-1 在最下面的测试图片中可以看到解释
//行计数器对像素时钟计数(子)
always @(posedge lcd_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_h <= 11'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 11'd0;
end
end
//场计数器对行计数(母)
always @(posedge lcd_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_v <= 11'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 11'd0;
end
end
endmodule
module lcd_display(
input lcd_clk, //lcd驱动时钟
input sys_rst_n, //复位信号
input [10:0] pixel_xpos, //像素点横坐标
input [10:0] pixel_ypos, //像素点纵坐标
output reg [15:0] pixel_data //像素点数据
);
localparam WHITE = 16'b11111_111111_11111; //RGB565 白色
localparam BLACK = 16'b00000_000000_00000; //RGB565 黑色
localparam RED = 16'b11111_000000_00000; //RGB565 红色
localparam GREEN = 16'b00000_111111_00000; //RGB565 绿色
localparam BLUE = 16'b00000_000000_11111; //RGB565 蓝色
localparam BROWN = 16'h7800; //RGB565 棕色
//测试边框
always @(posedge lcd_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
pixel_data <= 16'hffff;
else begin
if(pixel_xpos == 11'd1)
pixel_data <= GREEN;
else if(pixel_xpos == 11'd480)
pixel_data <= GREEN;
else if(pixel_xpos == 11'd240)
pixel_data <= GREEN;
else if(pixel_ypos == 11'd1)
pixel_data <= GREEN;
else if(pixel_ypos == 11'd272)
pixel_data <= GREEN;
else if(pixel_ypos == 11'd136)
pixel_data <= GREEN;
else
pixel_data <= BROWN;
end
end
//彩条
// always @(posedge lcd_clk or negedge sys_rst_n) begin
// if (!sys_rst_n)
// pixel_data <= 16'hffff;
// else begin
// if((pixel_xpos >= 1) && (pixel_xpos < (11'd480/5)*1))
// pixel_data <= GREEN;
// else if((pixel_xpos >= (11'd480/5)*1) && (pixel_xpos < (11'd480/5)*2))
// pixel_data <= BLACK;
// else if((pixel_xpos >= (11'd480/5)*2) && (pixel_xpos < (11'd480/5)*3))
// pixel_data <= RED;
// else if((pixel_xpos >= (11'd480/5)*3) && (pixel_xpos < (11'd480/5)*4))
// pixel_data <= BLUE;
// else
// pixel_data <= BROWN;
// end
// end
endmodule
module lcd_rgb_colorbar(
input sys_clk,
input sys_rst_n,
output lcd_hs,
output lcd_vs,
output lcd_de,
output [15:0] lcd_rgb,
output lcd_bl,
output lcd_rst,
output lcd_pclk
);
wire lcd_clk_w;
wire locked_w;
wire rst_n_w;
wire [15:0] pixel_data_w;
wire [ 9:0] pixel_xpos_w;
wire [ 9:0] pixel_ypos_w;
//待PLL输出稳定之后,停止复位
assign rst_n_w = sys_rst_n & locked_w;
pll_9m u_lcd_pll( //时钟分频模块
.inclk0 (sys_clk),
.areset (~sys_rst_n),
.c0 (lcd_clk_w), //lcd驱动时钟
.locked (locked_w)
);
lcd_driver u_lcd_driver( //lcd驱动模块
.lcd_clk (lcd_clk_w),
.sys_rst_n (rst_n_w),
.lcd_hs (lcd_hs),
.lcd_vs (lcd_vs),
.lcd_de (lcd_de),
.lcd_rgb (lcd_rgb),
.lcd_bl (lcd_bl),
.lcd_rst (lcd_rst),
.lcd_pclk (lcd_pclk),
.pixel_data (pixel_data_w),
.pixel_xpos (pixel_xpos_w),
.pixel_ypos (pixel_ypos_w)
);
lcd_display u_lcd_display( //lcd显示模块
.lcd_clk (lcd_clk_w),
.sys_rst_n (rst_n_w),
.pixel_xpos (pixel_xpos_w),
.pixel_ypos (pixel_ypos_w),
.pixel_data (pixel_data_w)
);
endmodule
可以看到,测试中出现了列偏移(1-480)*(0~271),所以对代码进行修整
见上面代码中的这一句:
给予时钟激励,观察信号关系即可。