草庐IT

FPGA之VGA/LCD数字时钟显示

萧长生 2023-04-12 原文

文章目录


前言

软件实现了在4.3寸LCD左上角显示一个数字时钟,效果如下图所示。本文针对VGA/LCD控制时许有一定基础的人群,博主的开发环境为Quartus13.1和一个随便哪家的开发板,使用4.3寸LCD(RGB565接口),兼容VGA,但是相关参数需要更改。软件中部分代码模块借用野火电子的软件,感谢。后文以LCD进行说明。
获取源代码、字模软件、rom初始化文件等点击此处


一、LCD显示控制

1.LCD显示一个字符

LCD在显示时是从左到右、从上到下进行刷新显示,4.3寸lcd显示区域大小为480x272,软件在坐标(24,32)处划定了一个长24宽32的显示区域,将此区域命名为square1,显示的内容为时的十位(22:15中左边第一个2),如下图所示。

在显示一个字符1时先进行取模,如下图所示,点阵大小也为24x32,与上图square1对应,因此lcd在square1中一行一行刷新时也判断字模的值是否为1,是则点亮lcd对应位置,反之则变暗。下面举例进行说明。square1在扫描前三行时,字模为0,第四行的第13个像素点为1,那么此时lcd对应的位置点亮。软件中lcd点亮即将该点像素变红。
软件实现时,将字模存储在宽度为24bit,深度为32的rom中,square1的32行读出rom地址0的值,在本行的第24列开始判断rom地址0读出值rom_dat的最高位bit23,然后判断bit23值是否为1,是则将lcd该点变红,反之变黑,之后继续判断bit30、bit29、bit28…bit0,一行判断完成后则判断下一行。square1的第33行读出rom的地址1的数据,然后进行判断。重复以上过程直到square1最后一行,此时对应rom地址31的数据。由此完成一个字符的显示。

部分代码如下:

parameter 	POINTX   ='d24, //squr1
			POINTY   ='d32;
			
parameter 	WIDTH	 ='d24,
			HEIGH	 ='d32;			

else if(squr1_rden)begin
		if(squrrom_dat[WIDTH+POINTX-pix_x])
			o_tft_dat<=RED ;
		else
			o_tft_dat<=0;//BLUE;
	end 

squrrom_rdaddr =(pix_y-HEIGH)

2.LCD显示多个字符

完成一个字符显示后多个字符显示就很简单,继续划定剩下3个字符和冒号区域,4个字符的点阵大小都是24x32,冒号点阵大小为16x32。软件使用两个rom存放字模,一个存储0-9字符,地址0-31为字符“0”,32-63为字符“1”,后续依次递增;另一个存储“:”。4个字符共用一个rom,字符寻址、显示部分代码如下图:

always@(*)begin
	if(!rst_n)
		squrrom_rdaddr='d0;
	else if(squr1_rden)
		squrrom_rdaddr=suqr1_addr;
	else if(squr2_rden)
		squrrom_rdaddr=suqr2_addr;
	else if(squr3_rden)
		squrrom_rdaddr=suqr3_addr;
	else if(squr4_rden)
		squrrom_rdaddr=suqr4_addr;	
end	

assign suqr1_addr=(pix_y-HEIGH)+ squr1*32;
assign suqr2_addr=(pix_y-HEIGH)+ squr2*32;
assign suqr3_addr=(pix_y-HEIGH)+ squr3*32;
assign suqr4_addr=(pix_y-HEIGH)+ squr4*32;

always@(posedge clk,negedge rst_n)
	if(!rst_n)
		o_tft_dat<='d0;
	else if(squr1_rden)begin
		if(squrrom_dat[WIDTH+POINTX-pix_x])
			o_tft_dat<=RED ;
		else
			o_tft_dat<=0;//BLUE;
	end else if(squr2_rden)begin
		if(squrrom_dat[WIDTH+POINTX2-pix_x])
			o_tft_dat<=RED ;
		else
			o_tft_dat<=0;//YELLOW;		
	end else if(dot_rden)begin
		if(dotrom_dat[WIDTHDOT+POINTXDOT-pix_x] && dot_vld)
			o_tft_dat<=RED ;
		else
			o_tft_dat<=0;//CYAN;
	end else if(squr3_rden)begin
		if(squrrom_dat[WIDTH+POINTX3-pix_x])
			o_tft_dat<=RED ;
		else
			o_tft_dat<=0;//PURPPLE;	
	end else if(squr4_rden)begin
		if(squrrom_dat[WIDTH+POINTX4-pix_x])
			o_tft_dat<=RED ;
		else
			o_tft_dat<=0;//GRAY;		
	end else 
		o_tft_dat<='d0;


二、数字时钟输出

1.数字时钟

软件输出小时、分钟两个数值,直接对时钟计数产生,部分代码如下:


always@(posedge clk,negedge rst_n)
	if(!rst_n)
		cnt<='d0;
	else if(cnt==MS-1)
		cnt<='d0;
	else
		cnt<=cnt+'d1;
		
always@(posedge clk,negedge rst_n)
	if(!rst_n)
		cntms<='d0;
	else if(cnt==MS-1)begin
		if(cntms=='d1000-1)
			cntms<='d0;
		else
			cntms<=cntms+'d1;
	end else 
		cntms<=cntms;

always@(posedge clk,negedge rst_n)
	if(!rst_n)
		cnts<='d0;
	else if((cnt==MS-1) && (cntms=='d1000-1))begin
		if(cnts=='d60-1)
			cnts<='d0;
		else 
			cnts<=cnts+'d1;
	end else 
		cnts<=cnts;
		
assign o_sec_fg =	(cnt==MS-1) && (cntms=='d1000-1);

always@(posedge clk,negedge rst_n)
	if(!rst_n)
		cntmin<='d0;
	else if(min_ctl) begin
		if(cntmin>='d60-1)
			cntmin<='d0;
		else
			cntmin<=cntmin+'d1;
	end else if(o_sec_fg && (cnts=='d60-1))	begin
		if(cntmin=='d60-1)
			cntmin<='d0;
		else
			cntmin<=cntmin+'d1;
	end else 
		cntmin<=cntmin;
		
always@(posedge clk,negedge rst_n)
	if(!rst_n)
		cnthour<='d0;
	else if(hour_ctl)begin
		if(cnthour>='d24-1)
			cnthour<='d0;
		else
			cnthour<=cnthour+'d1;
	end else if(o_sec_fg && (cnts=='d60-1) &&(cntmin=='d60-1))begin
		if(cnthour=='d24-1)
			cnthour<='d0;
		else 
			cnthour<=cnthour+'d1;
	end else 
		cnthour<=cnthour;		

2.十进制数据拆分BCD码

对于一个两位数的数字,常用的提取个位和十位的方法是除以10得到十位,除以10取余数得到个位,但是对于FPGA来说以上方法将使用更多资源,并且不符合FPGA编程习惯,因此一般使用加3移位法实现二进制转BCD码,部分代码如下:

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_shift   <=  5'd0;
    else    if((cnt_shift == 5'd7) && (shift_flag == 1'b1))
        cnt_shift   <=  5'd0;
    else    if(shift_flag == 1'b1)
        cnt_shift   <=  cnt_shift + 1'b1;
    else
        cnt_shift   <=  cnt_shift;
       
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_shift  <=  14'b0;
    else    if(cnt_shift == 5'd0)
        data_shift  <=  {8'b0,data};
    else    if((cnt_shift <= 6) && (shift_flag == 1'b0))
        begin
            data_shift[09:06]   <=  (data_shift[09:06] > 4) ? (data_shift[09:06] + 2'd3) : (data_shift[09:06]);
            data_shift[13:10]   <=  (data_shift[13:10] > 4) ? (data_shift[13:10] + 2'd3) : (data_shift[13:10]);
        end
    else    if((cnt_shift <= 6) && (shift_flag == 1'b1))
        data_shift  <=  data_shift << 1;
    else
        data_shift  <=  data_shift;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        shift_flag  <=  1'b0;
    else
        shift_flag  <=  ~shift_flag;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            unit    <=  4'b0;
            ten     <=  4'b0;
        end
    else    if(cnt_shift == 5'd7)
        begin
            unit    <=  data_shift[09:06];
            ten     <=  data_shift[13:10];

        end

三、按键检测及LCD驱动

1.按键检测

软件使用两个按键对小时、分钟进行控制,功能比较简单,只能进行加一调整,即按下某个按键对应数值加一。因为轻触按键在按下时存在机械抖动,所以不能简单判断下降沿就认为按键按下,需要在下降沿后延时20ms在判断按键情况,部分代码如下:

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else    if(key_in == 1'b1)
        cnt_20ms <= 20'b0;
    else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else    if(cnt_20ms == CNT_MAX - 1'b1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

2.LCD驱动

LCD驱动时序与VGA类似,因此在使用VGA是只需要更改时钟、行列计数等参数,驱动代码如下:


//parameter define
parameter H_SYNC    =   10'd41  ,   //行同步
          H_BACK    =   10'd2   ,   //行时序后沿
          H_VALID   =   10'd480 ,   //行有效数据
          H_FRONT   =   10'd2   ,   //行时序前沿
          H_TOTAL   =   10'd525 ;   //行扫描周期
parameter V_SYNC    =   10'd10  ,   //场同步
          V_BACK    =   10'd2   ,   //场时序后沿
          V_VALID   =   10'd272 ,   //场有效数据
          V_FRONT   =   10'd2   ,   //场时序前沿
          V_TOTAL   =   10'd286 ;   //场扫描周期

//wire  define
wire            rgb_valid       ;   //VGA有效显示区域
wire            pix_data_req    ;   //像素点色彩信息请求信号

//reg   define
reg     [9:0]   cnt_h   ;   //行扫描计数器
reg     [9:0]   cnt_v   ;   //场扫描计数器

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//o_tft_clk,o_tft_de,o_tft_bl:TFT像素时钟、数据使能、背光信号
assign  o_tft_clk = tft_clk_9m    ;
assign  o_tft_de  = rgb_valid     ;
assign  o_tft_bl  = sys_rst_n     ;

//cnt_h:行同步信号计数器
always@(posedge tft_clk_9m or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_h   <=  10'd0   ;
    else    if(cnt_h == H_TOTAL - 1'd1)
        cnt_h   <=  10'd0   ;
    else
        cnt_h   <=  cnt_h + 1'd1   ;

//o_hsync:行同步信号
assign  o_hsync = (cnt_h  <=  H_SYNC - 1'd1) ? 1'b1 : 1'b0  ;

//cnt_v:场同步信号计数器
always@(posedge tft_clk_9m or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_v   <=  10'd0 ;
    else    if((cnt_v == V_TOTAL - 1'd1) &&  (cnt_h == H_TOTAL-1'd1))
        cnt_v   <=  10'd0 ;
    else    if(cnt_h == H_TOTAL - 1'd1)
        cnt_v   <=  cnt_v + 1'd1 ;
    else
        cnt_v   <=  cnt_v ;

//o_vsync:场同步信号
assign  o_vsync = (cnt_v  <=  V_SYNC - 1'd1) ? 1'b1 : 1'b0  ;

//rgb_valid:VGA有效显示区域
assign  rgb_valid = (((cnt_h >= H_SYNC + H_BACK)
                    && (cnt_h < H_SYNC + H_BACK + H_VALID))
                    &&((cnt_v >= V_SYNC + V_BACK)
                    && (cnt_v < V_SYNC + V_BACK + V_VALID)))
                    ? 1'b1 : 1'b0;

//pix_data_req:像素点色彩信息请求信号,超前rgb_valid信号一个时钟周期
assign  pix_data_req = (((cnt_h >= H_SYNC + H_BACK - 1'b1)
                    && (cnt_h < H_SYNC + H_BACK + H_VALID - 1'b1))
                    &&((cnt_v >= V_SYNC + V_BACK)
                    && (cnt_v < V_SYNC + V_BACK + V_VALID)))
                    ? 1'b1 : 1'b0;

//o_pix_x,o_pix_y:VGA有效显示区域像素点坐标
assign  o_pix_x = (pix_data_req == 1'b1)
                ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'h3ff;
assign  o_pix_y = (pix_data_req == 1'b1)
                ? (cnt_v - (V_SYNC + V_BACK )) : 10'h3ff;

//o_rgb_tft:输出像素点色彩信息
assign  o_rgb_tft = (rgb_valid == 1'b1) ? pix_data : 16'b0 ;

四、总结

整个软件不复杂,RTL视图如下,主要由timer产生小时、分钟数值,经过BCD转换后输入到pic_char模块,然后将rgb输出到tft_ctl模块,最终实现上图效果,软件工程如下:源代码、字模软件、rom初始化文件等

有关FPGA之VGA/LCD数字时钟显示的更多相关文章

  1. ruby-on-rails - Rails 编辑表单不显示嵌套项 - 2

    我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - 使用 Sublime Text 3 突出显示 HTML 背景语法中的 ERB? - 2

    所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择

  4. ruby-on-rails - link_to 不显示任何 rails - 2

    我试图在索引页中创建一个超链接,但它没有显示,也没有给出任何错误。这是我的index.html.erb代码。ListingarticlesTitleTextssss我检查了我的路线,我认为它们也没有问题。PrefixVerbURIPatternController#Actionwelcome_indexGET/welcome/index(.:format)welcome#indexarticlesGET/articles(.:format)articles#indexPOST/articles(.:format)articles#createnew_articleGET/article

  5. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  6. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  7. ruby-on-rails - 复数 for fields_for has_many 关联未显示在 View 中 - 2

    目前,Itembelongs_toCompany和has_manyItemVariants。我正在尝试使用嵌套的fields_for通过Item表单添加ItemVariant字段,但是使用:item_variants不显示该表单。只有当我使用单数时才会显示。我检查了我的关联,它们似乎是正确的,这可能与嵌套在公司下的项目有关,还是我遗漏了其他东西?提前致谢。注意:下面的代码片段中省略了不相关的代码。编辑:不知道这是否相关,但我正在使用CanCan进行身份验证。routes.rbresources:companiesdoresources:itemsenditem.rbclassItemi

  8. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  9. ruby-on-rails - 在 Flash 警报 Rails 3 中显示错误消息 - 2

    如果我在模型中设置验证消息validates:name,:presence=>{:message=>'Thenamecantbeblank.'}我如何让该消息显示在闪光警报中,这是我迄今为止尝试过的方法defcreate@message=Message.new(params[:message])if@message.valid?ContactMailer.send_mail(@message).deliverredirect_to(root_path,:notice=>"Thanksforyourmessage,Iwillbeintouchsoon")elseflash[:error]

  10. ruby-on-rails - Rails 4 WYSIWYG Bootsy 不显示格式 - 2

    我刚刚按照thebootsygempage上的安装说明进行操作在我保存并查看帖子内容之前,一切看起来都不错。这是输出在View中的样子:HeaderSubhead:似乎没有呈现任何html格式,因为它被引号或类似的东西转义了-其他人有这个问题吗?我没有在github页面或SO上看到任何问题来指出我正确的方向。除了遵循gem安装说明之外,我还没有做任何事情,但也许我错过了什么或者只是犯了一个愚蠢的错误。如果你还有什么想知道的,请尽管问。干杯 最佳答案 你需要有这样的东西,转义html: 关

随机推荐