基于FPGA实现数码管显示

描述

引言:本文介绍数码管显示译码基本工作原理及Verilog HDL驱动代码编写,进一步熟练掌握FPGA入门基础知识。

1.概述

数码管是显示屏其中一类,通过对其不同的管脚输入相对的电流,会使其发亮,从而显示出数字能够显示 时间、日期、温度等所有可用数字表示的参数。

图1:数码管

由于它的价格便宜,使用简单,在电器特别是家电领域应用极为广泛,空调、热水器、冰箱等等。绝大多数热水器用的都是数码管,其他家电也用液晶屏与荧光屏。

2. 硬件原理

如图2所示,数码管按段数分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管单元(多一个小数点显示)。按能显示多少个“8”可分为1 位、2 位、4 位等等数码管。

如图2所示,按发光二极管单元连接方式分为共阳极数码管和共阴极数码管。

Verilog

图2:数码管共阴极(左侧)和共阳极(右侧)接法

共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴数码管在应用时应将公共极 COM接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮。

共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极COM 接到+3.3V(高电平),当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。

Verilog

图3:4位数码管共阳极接法

如图3所示,4位数码管的每段全部连接在一起,如果想动态控制每位数码管,就不能将四位数码管的COM端连接在一起,必须分别控制每位数码管的COM端,即增加位选择控制信号。8位数码管共阳极连接原理图如图4所示。

Verilog

图4:8位数码管原理图

如图4所示,8位数码管通过Sel[7..0]8个数据IO实现位选择,通过Disp[7..0]8个数据IO实现段选择。由于FPGA IO口驱动电流能力弱,增加三极管可提高驱动能力。

如图4所示,基极为低电平,三极管导通,数码管被选中,被选中的数码管相应的段发光显示。由于4位数码管的的数据端(ABCDEFG)是公用的,而每一位的数码管的公共阳极(COM)是单独,若某一位数码管显示字符,则这一位的公共阳极就要连接到低电平。

3. 软件实现

数码管显示有静态显示和动态显示两种方式。

在静态显示中,只考虑段选信号。在不同的时刻,各个位选信号保持不变,并根据真值表,选择要显示的数字或者字母。由于多位数码管段信号是连在一起的,所以这种情况下每位数码管显示的内容是想通过的。要想每位数码管显示不同内容,只能采用动态显示方式。

在动态显示中,需要将位选信号考虑进来。在不同的时刻,各个位的位选信号随时改变,并根据真值表,选择显示不同的数字或者字母。

3.1 数码管静态显示

根据图4硬件原理图,数码管位选择信号为低电平“0”时,同时数码管段为低电平“0”时,点亮数码管内部LED发光二极管。

数码管静态显示代码如下所示。

 

/*************************************************
数码管静态显示按键计数值。
*************************************************/
//位选信号     
assign segma_sel_o = 8'h00; //使能所有数码管位   
//段选信号,关闭DP点显示  
always @(posedge sys_clk_i or negedge rst_n_i) begin       
  if(!rst_n_i) begin  
      segma_disp_o  <= 8'b0000001_1;       
    end
  else begin
      case(cnt) //按键计数值  
        4'd0: segma_disp_o  <= 8'b0000001_1; //"0"  
        4'd1: segma_disp_o  <= 8'b1001111_1; //"1"  
        4'd2: segma_disp_o  <= 8'b0010010_1; //"2"
        4'd3: segma_disp_o  <= 8'b0000110_1; //"3"
        4'd4: segma_disp_o  <= 8'b1001100_1; //"4"
        4'd5: segma_disp_o  <= 8'b0100100_1; //"5"
        4'd6: segma_disp_o  <= 8'b0100000_1; //"6"
        4'd7: segma_disp_o  <= 8'b0001111_1; //"7"
        4'd8: segma_disp_o  <= 8'b0000000_1; //"8"
        4'd9: segma_disp_o  <= 8'b0000100_1; //"9"
        4'd10: segma_disp_o <= 8'b0001000_1; //"A"
        4'd11: segma_disp_o <= 8'b1100000_1; //"B"
        4'd12: segma_disp_o <= 8'b0110001_1; //"C"
        4'd13: segma_disp_o <= 8'b1000010_1; //"D"
        4'd14: segma_disp_o <= 8'b0110000_1; //"E"
        4'd15: segma_disp_o <= 8'b0111000_1; //"F"        
      endcase   
    end
end

 

测试结果如下:

3.2 数码管动态显示

数码管动态动态扫描显示,实际上是利用了两个现象:人眼的视觉暂留特性和数码管的余晖效应。人眼在观察景物时,光信号传入到大脑神经需要经过一段时间,光的作用结束之后我们的视觉影像并不会立刻的消失,这种残留的视觉被称为后像,这种现象就被称为视觉暂留;数码管的余晖效应是什么意思呢?当我们停止向发光二极管供电时,发光二极管的亮度仍能够维持一段时间。我们的动态扫描利用这两个特性就实现了数码管的动态显示。

(1)数码管位扫描控制代码:编写计数器cnt_scanf,周期性产生数码管选择信号seg_wei_num,根据seg_wei_num解码对应的需要使能数码管位segma_wei_o。

 

// 计数器,控制数码管位
reg [23:0] cnt_scanf;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i) begin
    cnt_scanf <= 24'd0;
  end
else begin 
    if(cnt_scanf == timer_scanf_i) 
      cnt_scanf <= 24'd0;
    else
      cnt_scanf <= cnt_scanf + 24'd1;
  end
end


//数码管扫描序号
reg [2:0] segma_wei_num; 
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i) begin
    segma_wei_num <= 3'd0;
  end
else begin
    if(cnt_scanf == timer_scanf_i)
      segma_wei_num <= segma_wei_num + 3'd1;
    else
      segma_wei_num <= segma_wei_num;
  end
end


//解码选中的数码管
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i) begin
    segma_wei_o <= 8'b1111_1111;
  end
else begin
    case(segma_wei_num)
      3'd0: segma_wei_o <= 8'b1110_1111; //选中第1个数码管
      3'd1: segma_wei_o <= 8'b1101_1111; //选中第2个数码管
      3'd2: segma_wei_o <= 8'b1011_1111; //选中第3个数码管
      3'd3: segma_wei_o <= 8'b0111_1111; //选中第4个数码管    
      3'd4: segma_wei_o <= 8'b1111_1110; //选中第5个数码管
      3'd5: segma_wei_o <= 8'b1111_1101; //选中第6个数码管
      3'd6: segma_wei_o <= 8'b1111_1011; //选中第7个数码管
      3'd7: segma_wei_o <= 8'b1111_0111; //选中第8个数码管
       default: segma_wei_o <= 8'b1111_1111;  
    endcase
  end
end

 

(2)数码管段显示控制代码:根据当前使能的数码管位segma_wei_num,获取当前数码管位需要显示的数据信息led_duan,然后根据led_duan数据解码映射到需要显示的每段发光二极管,数码管DP段单独控制。

 

//获取当前数码管位DP段
always @(*) begin
  case(segma_wei_num)
    3'd0: segma_duan_o[0] <= i_led0_data[4]; // 第1个数码管DP段数据
    3'd1: segma_duan_o[0] <= i_led1_data[4]; // 第2个数码管DP段数据    
    3'd2: segma_duan_o[0] <= i_led2_data[4]; // 第3个数码管DP段数据
    3'd3: segma_duan_o[0] <= i_led3_data[4]; // 第4个数码管DP段数据    
    3'd4: segma_duan_o[0] <= i_led4_data[4]; // 第5个数码管DP段数据
    3'd5: segma_duan_o[0] <= i_led5_data[4]; // 第6个数码管DP段数据    
    3'd6: segma_duan_o[0] <= i_led6_data[4]; // 第7个数码管DP段数据
    3'd7: segma_duan_o[0] <= i_led7_data[4]; // 第8个数码管DP段数据    
  endcase
end


//获取当前数码管位其他段数据
reg [3:0] led_duan;
always @(*) begin
  case(segma_wei_num)
    3'd0: led_duan <= i_led0_data[3:0]; // 第1个数码管其他段数据
    3'd1: led_duan <= i_led1_data[3:0]; // 第2个数码管其他段数据    
    3'd2: led_duan <= i_led2_data[3:0]; // 第3个数码管其他段数据
    3'd3: led_duan <= i_led3_data[3:0]; // 第4个数码管其他段数据    
    3'd4: led_duan <= i_led4_data[3:0]; // 第5个数码管其他段数据
    3'd5: led_duan <= i_led5_data[3:0]; // 第6个数码管其他段数据    
    3'd6: led_duan <= i_led6_data[3:0]; // 第7个数码管其他段数据
    3'd7: led_duan <= i_led7_data[3:0]; // 第8个数码管其他段数据    
  endcase
end


//解码数码管段数据
always @(*) begin
  case(led_duan)
      4'd0: segma_duan_o[7:1]  <= 7'b0000001; //"0" 
      4'd1: segma_duan_o[7:1]  <= 7'b1001111; //"1"
      4'd2: segma_duan_o[7:1]  <= 7'b0010010; //"2"
      4'd3: segma_duan_o[7:1]  <= 7'b0000110; //"3"
      4'd4: segma_duan_o[7:1]  <= 7'b1001100; //"4"
      4'd5: segma_duan_o[7:1]  <= 7'b0100100; //"5"
      4'd6: segma_duan_o[7:1]  <= 7'b0100000; //"6"
      4'd7: segma_duan_o[7:1]  <= 7'b0001111; //"7"
      4'd8: segma_duan_o[7:1]  <= 7'b0000000; //"8"
      4'd9: segma_duan_o[7:1]  <= 7'b0000100; //"9"
      4'd10: segma_duan_o[7:1] <= 7'b0001000; //"A"
      4'd11: segma_duan_o[7:1] <= 7'b1100000; //"B"
      4'd12: segma_duan_o[7:1] <= 7'b0110001; //"C"
      4'd13: segma_duan_o[7:1] <= 7'b1000010; //"D"
      4'd14: segma_duan_o[7:1] <= 7'b0110000; //"E"
      4'd15: segma_duan_o[7:1] <= 7'b0111000; //"F"    
  endcase
end
 

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分