7.3 RGB接口LCD控制器
7.3.1 RGB接口LCD控制器框图
这里针对DemoSoC的需求,设计了连接到WISHBONE总线的RGB接口LCD控制器驱动L2F50090,其方框图如图7-5所示。
图7-5 RGB接口LCD控制器框图
7.3.2 寄存器定义
RGB接口LCD控制器的寄存器定义及说明如表7-1和表7-2所示。
表7-1 LCDC控制寄存器
表7-2 LCDC PIXEL START ADDRESS REGISTER
7.3.3 RGB接口LCD控制器设计文件列表
RGB接口LCD控制器设计文件列表如表7-3所示。
表7-3 RGB接口LCD控制器设计文件列表
下文将分别讲述各个设计文件的细节内容。从命名可以看出,本书的LCD控制器是由http://www.opencores.org的ssvga改进而来。
7.3.4 RGB接口LCD控制器WISHBONE从设备接口
RGB接口LCD控制器WISHBONE从设备接口实现主处理器对LCD控制器的控制。源代码如下:
`define SEL_PAL 10 //调色板选择信号,在本设计中未实现 `define SEL_ADDRESS 2 //调色板地址,在本设计中未实现 module ssvga_wbs_if(// WISHBONE从设备接口 wb_clk_i, wb_rst_i,wbs_cyc_i, wbs_stb_i, wbs_sel_i, wbs_we_i,wbs_adr_i, wbs_dat_i, wbs_cab_i,wbs_dat_o, wbs_ack_o, wbs_err_o, wbs_rty_o, //其他信号 ssvga_en, pal_wr_en, pal_rd_en, pal_dat,pix_start_addr,refresh_factor); ……//省略了部分输入输出信号 output ssvga_en; //LCD控制器使能信号,该信号由CPU控制,为1则LCD控制器开始工作调色板的作用是 //将输入的色彩调整到最适合屏幕显示的少量颜色,通常是256色。早期的CSTN显示器多需要调色板。最新的 //显示器多不需要,因此在本设计中未实现,只是预留了调色板操作接口,即调色板的读写 output pal_wr_en; //调色板写使能 output pal_rd_en; //调色板读使能 input [15:0] pal_dat;//调色板数据 output [31:2] pix_start_addr ;//主存储器中显存的起始地址 output [5:0] refresh_factor;//刷新分数,即列扫描多少个时钟周期更新一列 //LCDC控制寄存器实现 always @(posedge wb_clk_i or posedge wb_rst_i) if (wb_rst_i) ctrl_r <= #1 7'h0F;//默认情况下LCD控制器使能 else if (valid_access & wbs_we_i & !wbs_adr_i[`SEL_PAL] & !wbs_adr_i[`SEL_ADDRESS]) ctrl_r <= #1 wbs_dat_i[6:0]; //写操作 reg [31:2] pix_start_addr ;// 主存储器中显存的起始地址 always @(posedge wb_clk_i or posedge wb_rst_i) if (wb_rst_i) pix_start_addr <= #1 30'h0000_0000 ; else if (valid_access & wbs_we_i & !wbs_adr_i[`SEL_PAL] & wbs_adr_i[`SEL_ADDRESS] ) pix_start_addr <= #1 wbs_dat_i[31:2] ; //WISHBONE应答和错误信号 always @(posedge wb_clk_i or posedge wb_rst_i) if (wb_rst_i) begin wbs_ack_o <= #1 1'b0;wbs_err_o <= #1 1'b0;end else if (valid_access) begin wbs_ack_o <= #1 1'b1;wbs_err_o <= #1 1'b0; end else if (wbs_cyc_i & wbs_stb_i) begin wbs_ack_o <= #1 1'b0; wbs_err_o <= #1 1'b1;end else begin wbs_ack_o <= #1 1'b0;wbs_err_o <= #1 1'b0;end //WISHBONE输出信号 reg [31:0] wbs_dat_o ; always@(wbs_adr_i or pal_dat or ctrl_r or pix_start_addr) begin if ( wbs_adr_i[`SEL_PAL] ) wbs_dat_o = {16'h0000, pal_dat} ; //读取调色板 else if ( wbs_adr_i[`SEL_ADDRESS] ) wbs_dat_o = {pix_start_addr, 2'b00}; //读取主存储器中显存的起始地址 else wbs_dat_o = {{31{1'b0}}, ctrl_r};//读取控制寄存器 end assign wbs_rty_o = 1'b0; //其他控制信号 assign valid_access = wbs_cyc_i & wbs_stb_i & (wbs_sel_i == 4'b1111); assign ssvga_en = ctrl_r[0]; assign refresh_factor=ctrl_r[6:1]; assign pal_wr_en = valid_access & wbs_we_i & wbs_adr_i[`SEL_PAL]; assign pal_rd_en = valid_access & ~wbs_we_i & wbs_adr_i[`SEL_PAL]; endmodule
7.3.5 RGB接口LCD控制器WISHBONE主设备接口
RGB接口LCD控制器WISHBONE主设备接口从主存储器的显存读取数据并放入LCD控制器内部的FIFO(也称作像素缓存,英文为pixel buffer)等待显示,其源代码如下:
module ssvga_wbm_if(wb_clk_i, wb_rst_i,wbm_cyc_o, wbm_stb_o, wbm_sel_o, wbm_we_o, wbm_adr_o, wbm_dat_o, wbm_cab_o,wbm_dat_i, wbm_ack_i, wbm_err_i, wbm_rty_i, ssvga_en, fifo_full,fifo_wr_en, fifo_dat, pix_start_addr, resync); ……//忽略部分输入输出信号 input fifo_full; //FIFO满 output fifo_wr_en; // FIFO写使能 output [31:0] fifo_dat; // FIFO数据 input resync; // 像素缓存下溢出时,一个屏幕的扫描必须重新开始 //内部寄存器 reg [`SSVGA_VMCW-1:0] vmaddr_r; //视频像素地址计数器,Video memory address counter //一帧是指一个平面的数据,frame_read是一帧数据读取完毕指示 reg frame_read ; wire frame_read_in = ( vmaddr_r == `SSVGA_VMCW'h0_00_00 ) & wbm_ack_i & wbm_stb_o || ~ ssvga_en || resync;//LCD //控制器未使能或发生错误重新开始读取数据,或者正常开始新的一帧数据 always@(posedge wb_clk_i or posedge wb_rst_i) begin if (wb_rst_i) frame_read <= #1 1'b0 ; else frame_read <= #1 frame_read_in ; end //视频像素地址计数器,严格说,是剩余像素计数 always @(posedge wb_clk_i or posedge wb_rst_i) if (wb_rst_i) vmaddr_r <= #1 ((`PIXEL_NUM/4)-1) ;//起始值为最大值,每次读取4个像 //素,因此vmaddr_r为 ((`PIXEL_NUM/4)-1 else if (frame_read)vmaddr_r <= #1 ((`PIXEL_NUM/4)-1); else if (wbm_ack_i & wbm_stb_o) vmaddr_r <= #1 vmaddr_r -1;//递减 //像素读取地址,递增 reg [31:2] wbm_adr ; always@(posedge wb_clk_i or posedge wb_rst_i) begin if (wb_rst_i) wbm_adr <= #1 30'h0000_0000 ; else if (frame_read) wbm_adr <= #1 pix_start_addr ; else if (wbm_ack_i & wbm_stb_o) wbm_adr <= #1 wbm_adr + 1 ; end // WISHBONE输出信号 assign wbm_cyc_o = ssvga_en & !frame_read; assign wbm_stb_o = wbm_cyc_o & !fifo_full;//FIFO未满就可以读取新的数据 assign wbm_sel_o = 4'b1111; assign wbm_we_o = 1'b0; assign wbm_adr_o = {wbm_adr, 2'b00}; assign wbm_dat_o = 32'h0000_0000; assign wbm_cab_o = 1'b1; // Generate other signals assign fifo_wr_en = wbm_ack_i & wbm_stb_o ; assign fifo_dat = wbm_dat_i ; endmodule
7.3.6 LCD控制接口
LCD控制接口从像素缓存ssvga_fifo中读取像素数据,产生RGB接口LCD,源代码如下:
module ssvga_crtc(clk,rst,hsync,vsync,lcdck,lcdd,refresh_factor,wbm_restart, fifo_rd_en, fifo_data_in,fifo_empty); ……//此处忽略了输入输出信号定义 reg [`SSVGA_HCW-1:0]hcntr; //水平扫描计数 reg [`SSVGA_VCW-1:0]vcntr; //垂直扫描计数 reg [5:0] refresh_cnt;//刷新计数 always @(posedge clk or posedge rst) begin if(rst) refresh_cnt<=0; else if(refresh_cnt>=refresh_factor) refresh_cnt<=0; else refresh_cnt<=refresh_cnt+1; end wire dck_update=refresh_cnt>=refresh_factor;//像素时钟,refresh_factor+1个时钟周期将lcdck //取反一次 reg negedge_dck; always @(posedge clk or posedge rst) begin if(rst) begin lcdck<=1'b0;negedge_dck<=1'b0;end else if(dck_update) begin lcdck<=~lcdck; if(lcdck) negedge_dck<=1'b1; end else negedge_dck<=1'b0; end //生成水平和垂直扫描同步信号hsync和vsync,L2F50090显示器的像素阵列为240×176, //数据有效行列数要小于240×176,与显示器有关,这些参数实际上可以作为LCD控制器的 //寄存器动态改变。本设计中约束为L2F50090的技术参数 always @(posedge clk or posedge rst) begin if(rst) begin hcntr<=`SSVGA_HCW'h0; vcntr<=0; hsync<=1'b1; end else if(negedge_dck) begin if(hcntr<=`SSVGA_HCW'd207) hcntr<=hcntr+1; else hcntr<=0; hsync<=(hcntr>=`SSVGA_HCW'd10); if(hcntr==`SSVGA_HCW'd0) begin if(vcntr>=`SSVGA_VCW'd249) vcntr<=0; else vcntr<=vcntr+1; end end end assign vsync=(vcntr!=`SSVGA_VCW'd1); //一个帧正常结束或在一个帧的输出过程中FIFO为空,都要重新开始扫描整个帧 always @(posedge clk or posedge rst) begin if(rst) begin wbm_restart<=1'b0; fifo_rd_en<=1'b0; lcdd<=0; end else if(negedge_dck) begin lcdd<=fifo_data_in;// if(hcntr>=`SSVGA_HCW'd24 && hcntr<=`SSVGA_HCW'd199) begin if(vcntr>`SSVGA_VCW'd2 && vcntr<=`SSVGA_VCW'd242) begin if(!fifo_empty && !wbm_restart) fifo_rd_en<=1'b1; else begin fifo_rd_en<=1'b0;wbm_restart<=1'b1;end end end else begin //invalid period of a frame fifo_rd_en<=1'b0; if(vcntr<=`SSVGA_VCW'd2 && vcntr>`SSVGA_VCW'd242) begin wbm_restart<=1'b0; end end end else fifo_rd_en<=1'b0; end endmodule
7.3.7 LCD初始化接口
L2F50090的接口寄存器需要进行初始化,如第7.2.3节所述。本节内容只与L2F50090有关,写给对该LCD感兴趣的读者阅读。serial_interface使用一个状态机,按照如图7-3所示顺序初始化L2F50090的接口寄存器,初始化时序如图7-4所示。其源代码如下:
module serial_interface(clk,rstn,lcd_bk,lcdso,lcdncs,lcdsck,lcdnrst,lcdsi,lcda0); …..//此处忽略输入输出信号的定义 //寄存器初始值的定义 parameter DISCTRL1_CMD={8'hc9,8'hf4,8'h00,8'hf0,8'h00,8'h02}; parameter DISCTRL2_CMD={8'hca,8'hcf,8'h02,8'h06,8'h18,8'h04,8'h00}; parameter GCPSET_CMD={8'hcb,8'h41,8'h00,8'h81,8'h02,8'h01,8'h01,8'h02,8'h04, 8'h08,8'h08,8'h20,8'h81,8'h08,8'h42,8'h08,8'h89,8'h12,8'h25,8'h29,8'h55,8'h2a, 8'ha9,8'h2a,8'hab,8'hff,8'hfe,48'h000000000000}; parameter SLPOUT_CMD=8'h94; parameter DISPON_CMD=8'haf; parameter DISPOFF_CMD=8'hAE; parameter SLPIN_CMD=8'h95; parameter VOLCTL_CMD={8'hc6,8'h9f}; parameter MAX_WAIT=26'h0FFFFF; parameter RST_HOLD_CNT=16'd2000; reg lcd_bk; reg [12:0] bk_cnt; always @ (posedge clk or negedge rstn) begin if(~rstn) begin lcd_bk<=1'b1; bk_cnt<=0; end else begin bk_cnt<=bk_cnt+1;end end reg lcdso,lcdsck,lcdnrst,lcda0; //初始化状态机的状态定义 parameter RST_HOLD=5'd1,DISPOFF=5'd2,WAIT1=5'd3,SLPIN=5'd4, WAIT2=5'd5,DISPCTRL1=5'd6,WAIT3=5'd7,DISPCTRL2=5'd8, WAIT4=5'd9,GCPSET=5'd10,WAIT5=5'd11,SLPOUT=5'd12, WAIT6=5'd13,DISPON=5'd14,WAIT7=5'd15,VOLCTL=5'd16, DISPEND=5'd17; ……//忽略了部分寄存器的定义 always @ (posedge clk or negedge rstn) begin if(~rstn) begin dispon_cnt<=0;lcdsck<=1'b0; end else begin dispon_cnt<=dispon_cnt+1; if(dispon_cnt==6'h3f) lcdsck<=~lcdsck; end end wire negedge_sck= (dispon_cnt==6'h3f) & (lcdsck==1'b1); always @ (posedge clk or negedge rstn) begin if(~rstn) begin //初始化所有寄存器的初始值 state<=RST_HOLD; lcdnrst<=0; lcd_cnt<=0; lcda0<=1'b1; lcdso<=1'b1;lcdncs<=1'b1; GCPSET_reg<= GCPSET_CMD; DISCTRL2_reg<= DISCTRL2_CMD; DISCTRL1_reg<= DISCTRL1_CMD; DISPOFF_reg<=DISPOFF_CMD; SLPIN_reg<=SLPIN_CMD; SLPOUT_reg<=SLPOUT_CMD; VOLCTL_reg<=VOLCTL_CMD; DISPON_reg<=DISPON_CMD; wait_cnt<=0; end else begin case(state) RST_HOLD: begin //等待一段时间,让LCD复位稳定 lcdnrst<=1'b1; if(lcd_cnt>=RST_HOLD_CNT) begin param_cnt<=0; if(negedge_sck) state<=DISPOFF; lcd_cnt<=0; end else begin lcd_cnt<=lcd_cnt+1; end end DISPOFF:begin if(negedge_sck) begin //输出DISPOFF命令,按照图7-4的时序 DISPOFF_reg<={DISPOFF_reg[6:0],1'b0}; lcdso<=DISPOFF_reg[7]; param_cnt<=param_cnt+1; lcda0<=param_cnt>=8; if(param_cnt>=10'd8) begin lcda0<=1'b1; state<= WAIT1; wait_cnt<=0; lcdncs<=1'b1; lcd_cnt<=0; end else begin lcdncs<=1'b0; lcda0<=1'b0; end end end WAIT1:begin //等待一段时间 wait_cnt<=wait_cnt+1; if(wait_cnt>=MAX_WAIT) begin param_cnt<=0; state<=SLPIN; end end SLPIN:begin //输出SLPIN命令,按照图7-4的时序。SLPIN的下一个状态是WAIT2 if(negedge_sck) begin SLPIN_reg<={SLPIN_reg[6:0],1'b0}; …//此处省略代码类似DISPOFF状态的代码 end end WAIT2:……//等待一段时间,下一状态是DISPCTRL1 end DISPCTRL1:begin //输出DISPCTRL1命令,按照图7-4的时序 if(negedge_sck) begin DISCTRL1_reg<={DISCTRL1_reg[46:0],1'b0}; ……//下一状态是WAIT3 end end WAIT3:begin //等待一段时间进入DISPCTRL2 DISPCTRL2:begin //输出DISPCTRL2命令,按照图7-4的时序 if(negedge_sck) begin DISCTRL2_reg<={DISCTRL2_reg[54:0],1'b0}; ……//下一状态是WAIT4 end WAIT4: begin //按照用户手册要求等待一段时间,进入GCPSET GCPSET:begin //输出GCPSET命令后进入WAIT5 if(negedge_sck) begin GCPSET_reg<={GCPSET_reg[262:0],1'b0}; ……// WAIT5:begin ……//等待一段时间后进入SLPOUT状态 SLPOUT:begin //输出SLPOUT命令,按照图7-4的时序 if(negedge_sck) begin SLPOUT_reg<={SLPOUT_reg[6:0],1'b0}; ……//完成输出SLPOUT命令后进入WAIT6 end WAIT6://等待一段时间后进入DISPON状态 end DISPON:begin //输出DISPON命令,按照图7-4的时序 if(negedge_sck) begin DISPON_reg<={DISPON_reg[6:0],1'b0}; ……//完成输出DISPON命令后进入WAIT7 end WAIT7: //等待一段时间后进入VOLCTL状态 VOLCTL:begin if(negedge_sck) begin VOLCTL_reg<={VOLCTL_reg[14:0],1'b0}; ……//完成输出DISPON命令后进入DISPEND end DISPEND: begin lcdncs<=1'b1;end //初始化完毕后,永远停留在DISPEND状态 endcase end end endmodule
7.3.8 LCD控制器像素缓存FIFO
由于多个主个设备占用总线,LCD控制器主设备接口从主存储器读取像素数据的瞬时速率具有一定的波动,而LCD控制器输出像素速率是均匀的,因此需要LCD控制器内置一个像素缓存FIFO进行速率平滑。像素缓存FIFO的宽度为32位,与总线宽度相同,深度与设计者有关,典型的深度为32~256位。在FPGA实现中,由于片上的块RAM较大,可以设计的深一些,本设计中为256位。LCD控制器像素缓存FIFO的源代码如下:
module ssvga_fifo(clk, rst, dat_i, wr_en, rd_en,dat_o, full, empty, ssvga_en, wbm_restart); ……//忽略了输入输出信号 reg [2:0] state; parameter FIFO_EMPTY=3'd1,DATA_WAIT=3'd2,DATA_PREPARED=3'd3, DATA_NEXT=3'd4,NO_MORE_DATA=3'd5; reg [15:0] data_out; //从FIFO中一次读出32比特,高16位送入data_out缓存,低16位赋值//到dat_o always @(posedge clk or posedge rst) begin if(rst) begin empty<=1'b1; rdreq<=1'b0; state<=FIFO_EMPTY; end else if(ssvga_en) begin case (state) FIFO_EMPTY:begin //空闲状态下等待lcd_crtc的数据读取请求 if(!iempty) begin rdreq<=1'b1;state<=DATA_WAIT;end else begin rdreq<=1'b0; end end DATA_WAIT: begin empty<=1'b0; rdreq<=1'b0; state<=DATA_PREPARED;end //读 DATA_PREPARED:begin //将读取的数据复制到输出 data_out<=fifo_q[31:16]; dat_o<= fifo_q[15:0]; if(rd_en) begin state<=DATA_NEXT; end if(iempty && rd_en) begin rdreq<=1'b0;state<=NO_MORE_DATA;end else if(!iempty && rd_en) begin state<=DATA_NEXT; rdreq<=1'b1;end end NO_MORE_DATA:begin //FIFO中没有数据可读 if(rd_en) begin // dat_o<= data_out; empty<=1'b1; state<=FIFO_EMPTY; if(!iempty) begin rdreq<=1'b1;state<=DATA_WAIT;end else begin rdreq<=1'b0;state<=FIFO_EMPTY; end end else begin if(!iempty) begin rdreq<=1'b1;state<=DATA_NEXT;end end end DATA_NEXT:begin rdreq<=1'b0; if(rd_en) begin //连续读取FIFO的情况下送出数据 data_out<=fifo_q[31:16]; dat_o<= data_out; state<=DATA_PREPARED; end end endcase end else if(wbm_restart) state<=FIFO_EMPTY; end wire sclr=(~ssvga_en) | wbm_restart; //清除FIFO内容 wire ifull; assign full=ifull | rst;//复位或FIFO满 //像素缓存,在FPGA实现时使用Altera的MegaWizard或Xilinx的CoreGenerator生成 fifo256x32 fifo256x32(.aclr(rst),.clock(clk),.data(dat_i),.rdreq(rdreq),.sclr(sclr), .wrreq(wr_en),.empty(iempty),.full(ifull),.q(fifo_q)); endmodule//像素缓存模块结束
7.3.9 LCD控制器顶层模块
LCD控制器顶层模块将ssvga_top模块和serial_interface模块组合到一起。源代码如下:
module lcdc_top(lcdhsn, lcd_bk, lcdso, lcdncs, lcdsck, lcdck, lcdvsn, lcdnrst, lcdsi, lcda0, lcdd, wb_clk_i,wb_rst_i,wbm_cyc_o,wbm_stb_o,wbm_sel_o,wbm_we_o,wbm_adr_o,wbm_dat_o, wbm_cab_o,wbm_dat_i,wbm_ack_i,wbm_err_i,wbm_rty_i,wbs_cyc_i,wbs_stb_i,wbs_sel_i, wbs_we_i, wbs_adr_i, wbs_dat_i, wbs_cab_i, wbs_dat_o,wbs_ack_o, wbs_err_o,wbs_rty_o); //根据LCD屏幕说明书连接输出数据信号 assign lcdd[0]=lcd_out[4]; assign lcdd[11:1]=lcd_out[10:0]; assign lcdd[12]=lcd_out[15]; assign lcdd[17:13]=lcd_out[15:11]; //例化ssvga_top和serial_interface ssvga_top lcdc(……); serial_interface si(……); endmodule//lcd_top模块结束
7.3.10 RGB接口LCD控制器的FPGA验证
本章给出的RGB接口LCD控制器经过FPGA开发板的实际验证。
7.3.11 RGB接口LCD控制器的改进
本章的RGB接口LCD控制器是一个针对特定屏幕的设计,可改进的地方包括如下几点。
(1)通过增加寄存器支持多种不同的分辨率;
(2)支持黑白屏幕,支持帧率控制;
(3)支持调色板;
(4)支持Alpha-Blending,以产生多幅图片重叠的效果。
这些设计特征读者可以根据自己的需要自行添加。