FPGA开发——按键控制数码管的设计

一、概述

按键控制数码管是一种常见的电子显示技术,它结合了按键输入与数码管显示的功能。在这一设计中,用户通过按下不同的按键来发送指令,这些指令随后被处理并转换为数码管上显示的数字或字符。按键通常作为输入设备,通过电路连接到微控制器(如FPGA、单片机等)的输入引脚,而数码管则作为输出设备,其显示内容由微控制器控制。

按键控制数码管的设计可以包括以下几个步骤:

  1. 按键输入处理:微控制器不断扫描按键的状态,当检测到按键被按下时,根据按键的编号或功能执行相应的操作。

  2. 指令转换:将按键的输入转换为数码管能够理解的显示指令。这通常涉及将按键编号或功能映射到特定的数字或字符编码。

  3. 数码管显示控制:微控制器根据转换后的显示指令,通过控制数码管的驱动电路来点亮或熄灭数码管中的不同段,从而显示出所需的数字或字符。

  4. 循环扫描与更新:为了保持数码管显示内容的实时性,微控制器需要不断重复上述步骤,形成一个循环扫描和更新的过程。

二、工程实现

1、基本思路构建

在本篇文章中我们以按键控制单个数码管为例讲解通过按键改变数码管显示的值的设计,基本思路如下:

在默认情况下数码管正常显示,当按下按键1时,数码管从正常显示的状态跳转到设置的状态,接着通过按键二队数码管显示的值进行一个改变,最后在按下按键1使得数码管从设置状态有重新回到正常显示状态,如此往复设计。

2、设计文件的编写

新建一个seg0.v文件

cpp 复制代码
module seg0(
  input  clk,
  input  rst_n,
  input  [1:0] key_in,
  input  seg_sel,//位选
  output reg [7:0] seg_dual//段选
);
localparam  ZERO  = 8'b1100_0000, //共阳极段码
            ONE   = 8'b1111_1001,
            TWO   = 8'b1010_0100,
            THREE = 8'b1011_0000,
            FOUR  = 8'b1001_1001,
            FIVE  = 8'b1001_0010,
            SIX   = 8'b1000_0010,
            SEVEN = 8'b1111_1000,
            EIGHT = 8'b1000_0000,
            NINE  = 8'b1001_0000,
            A     = 8'b1000_1000,
            b     = 8'b1000_0011,
            c     = 8'b1100_0110,
            d     = 8'b1010_0001,
            E     = 8'b1000_0110,
            f     = 8'b1000_1110;
reg [26:0] cnt;
wire       add_cnt;
wire       end_cnt;
reg     [4:0]   flag;
wire            add_flag;
wire            end_flag;

reg    seg_flag;//数码管状态切换标志位
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
      cnt<=0;
    else if(add_cnt)begin
      if(end_cnt)
        cnt<=0;
      else
        cnt<=cnt+1'b1;
    end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt && (cnt==50_000_000-1);

always @(posedge clk or negedge rst_n)begin
  if(!rst_n)
    flag<=0;
  else if(seg_flag==1 && key_in[1])
        flag<=flag+1'b1;
  else if(add_flag)begin
    if(end_flag)
      flag<=0;
    else
      flag<=flag+1'b1;
  end
end
assign add_flag=end_cnt;
assign end_flag=add_flag && (flag==16-1);

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        seg_flag<=0;
    else if(key_in[0])
        seg_flag<=~seg_flag;
    else 
        seg_flag<=seg_flag;
end


always @(posedge clk or negedge rst_n)begin
  if(!rst_n)
    seg_dual<=8'b1111_1111;
  else if(seg_flag==0)begin
    case (flag)
      4'd0:seg_dual <=ZERO ;
      4'd1:seg_dual <=ONE  ;
      4'd2:seg_dual <=TWO  ;
      4'd3:seg_dual <=THREE; 
      4'd4:seg_dual <=FOUR ;
      4'd5:seg_dual <=FIVE ;
      4'd6:seg_dual <=SIX  ;
      4'd7:seg_dual <=SEVEN;
      4'd8:seg_dual <=EIGHT;
      4'd9:seg_dual <=NINE ;
      4'd10:seg_dual<=A    ;
      4'd11:seg_dual<=b    ;
      4'd12:seg_dual<=c    ;
      4'd13:seg_dual<=d    ;
      4'd14:seg_dual<=E    ;
      4'd15:seg_dual<=f    ;
      default: ;
    endcase
  end 
end
endmodule 

新建一个key.v文件

cpp 复制代码
//状态机实现
module key (
    input           clk     ,
    input           rst_n   ,
    input        [1:0]   key_in  ,   //输入原始的按键信号
    output  reg  [1:0]   key_out     //输出处理之后的按键信号
);
//参数定义
    localparam  IDLE        = 4'b0001,//空闲
                JITTLE0     = 4'b0010,//滤除第一次抖动
                DOWN        = 4'b0100,//稳定
                JITTLE1     = 4'b1000;//滤除第二次抖动

    parameter   TIME_20MS = 1_000_000;//需要计数的值,20ms

//内部信号
    reg  [3:0]   state_c;//现态
    reg  [3:0]   state_n;//次态

    reg  [19:0] cnt_20ms;//计数20ms
    wire        add_cnt_20ms;
    wire        end_cnt_20ms;

    wire  [1:0]      nedge   ;//下降沿信号
    wire  [1:0]      pedge   ;//上升沿信号
    reg   [1:0]      key_in_r1  ;//打两拍  同步打拍
    reg   [1:0]      key_in_r2  ;


//状态转移 同步时序逻辑描述状态转移
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            state_c <= IDLE;
        else
            state_c <= state_n;
    end

//状态转移条件 组合逻辑
    always @(*) begin
        case (state_c)//一定是case  现态
            IDLE:begin
                if(nedge)
                    state_n = JITTLE0;
                else
                    state_n = state_c;
            end
            JITTLE0:begin
                if(end_cnt_20ms)
                    state_n = DOWN;
                else
                    state_n = state_c; 
            end
            DOWN:begin
                if(pedge)
                    state_n = JITTLE1;
                else
                    state_n = state_c;
            end
            JITTLE1  :begin
                if(end_cnt_20ms)
                    state_n = IDLE;
                else
                    state_n = state_c;  
            end   
            default: state_n = IDLE;
        endcase
    end

//20ms计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_20ms <= 0;
    else if(add_cnt_20ms)begin
        if(end_cnt_20ms)
            cnt_20ms <= 0;
        else
            cnt_20ms <= cnt_20ms + 1;
    end
end

assign add_cnt_20ms = (state_c == JITTLE0) || (state_c == JITTLE1);
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;

//下降沿  上升沿
//同步  打拍
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        key_in_r1 <= 2'b11;
        key_in_r2 <= 2'b11;
       
    end 
    else begin 
        key_in_r1 <= key_in;      //同步按键输入信号
        key_in_r2 <= key_in_r1;   //打拍
         
    end 
end
    //r1当前状态,r2上一个状态
assign nedge = ~key_in_r1 && key_in_r2;
assign pedge = key_in_r1 && ~key_in_r2;

//key_out
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            key_out <= 0;
        else if(end_cnt_20ms &&(state_c== JITTLE1))
            key_out <= ~key_in_r2;//有效脉冲 20ns
        else 
            key_out <= 0;
    end

endmodule

3、新建一个顶层文件top.v,用于将数码管和按键连接起来。

cpp 复制代码
module top(
    input  clk,
    input  rst_n,
    input [1:0] key_in,
    output  seg_sel,
    output [7:0] seg_dual

);
wire [1:0] key_flag;
key key_inst(
    /*input   */  .clk     (clk        ),
    /*input   */  .rst_n   (rst_n      ),
    /*input   */  .key_in  (key_in     ),   //输入原始的按键信号
    /*output  */  .key_out (key_flag    )     //输出处理之后的按键信号
);

seg0 seg_inst(
  /*input */ .clk       (clk         ),
  /*input */ .rst_n     (rst_n       ),
             .key_in    (key_flag), //
  /*input */ .seg_sel   (seg_sel     ),//位选
  /*output*/ .seg_dual (seg_dual   )//段选
);

endmodule

4、测试文件的编写

其实这里我不推荐进行波形图仿真的,因为仿真需要编写的条件非常多,相比之下使用在线调试工具进行实时调试更好。这里我也给一个测试文件,需要的小伙伴可以加以完善。

cpp 复制代码
//定义时间尺度
`timescale 1ns/1ns
module top_tb ;

//输入信号定义
reg  clk;
reg rst_n;
reg  [1:0] key_in;
wire [7:0] seg_dual;
wire  seg_sel;
defparam  top_inst.cnt_inst.TIME_1s=500;
//模块例化
top top_inst(
    /*input */.clk      (clk     ),
    /*input */.rst_n    (rst_n   ),
    /*input */.key_in   (key_in  ),
    /*output*/.seg_sel  (seg_sel ),
    /*output*/.seg_dual (seg_dual)

);
//激励信号产生
parameter CLK_CLY = 20;
//时钟
initial clk=1;
always #(CLK_CLY/2)clk=~clk;

//复位
initial begin
    rst_n= 1'b0;
    #(CLK_CLY*3);
    #5;//复位结束避开时钟上升沿
    rst_n= 1'b1;
end

//激励
//激励
integer i;
initial repeat(5)begin
    key_in[1] = 1;//模拟按键未按下
    i ={$random}%6;//给i赋值0-5
    #(CLK_CLY*500);//等待复位时间结束
    #3;
    key_in[1] = 0;//前按键抖动开始
    #(CLK_CLY*1);
    //一个5-10ms的抖动时间
    repeat ((i+5)*50)begin
        key_in[1] = $random;
        #(CLK_CLY*1);
    end 
    key_in[1] = 0;//按键稳定
    #(CLK_CLY*100*500);
  
    //后抖动开始
    key_in[1] = 1;
    #(CLK_CLY*1);
    repeat ((i+5)*50)begin
        key_in[1] = $random;
        #(CLK_CLY*1);
    end 
    key_in[1] = 1;//按键稳定
    #(CLK_CLY*10*5000);

    key_in[2] = 1;//模拟按键未按下
    i ={$random}%6;//给i赋值0-5
    #(CLK_CLY*500);//等待复位时间结束
    #3;
    key_in[2] = 0;//前按键抖动开始
    #(CLK_CLY*1);
    //一个5-10ms的抖动时间
    repeat ((i+5)*50)begin
        key_in[2] = $random;
        #(CLK_CLY*1);
    end 
    key_in[2] = 0;//按键稳定
    #(CLK_CLY*100*500);
  
    //后抖动开始
    key_in[2] = 1;
    #(CLK_CLY*1);
    repeat ((i+5)*50)begin
        key_in[2] = $random;
        #(CLK_CLY*1);
    end 
    key_in[2] = 1;//按键稳定
    #(CLK_CLY*10*5000);

    key_in[0] = 1;//模拟按键未按下
    i ={$random}%6;//给i赋值0-5
    #(CLK_CLY*500);//等待复位时间结束
    #3;
    key_in[0] = 0;//前按键抖动开始
    #(CLK_CLY*1);
    //一个5-10ms的抖动时间
    repeat ((i+5)*50)begin
        key_in[0] = $random;
        #(CLK_CLY*1);
    end 
    key_in[0] = 0;//按键稳定
    #(CLK_CLY*100*500);
  
    //后抖动开始
    key_in[0] = 1;
    #(CLK_CLY*1);
    repeat ((i+5)*50)begin
        key_in[0] = $random;
        #(CLK_CLY*1);
    end 
    key_in[0] = 1;//按键稳定
    #(CLK_CLY*10*5000);

    key_in[0] = 1;//模拟按键未按下
    i ={$random}%6;//给i赋值0-5
    #(CLK_CLY*500);//等待复位时间结束
    #3;
    key_in[0] = 0;//前按键抖动开始
    #(CLK_CLY*1);
    //一个5-10ms的抖动时间
    repeat ((i+5)*50)begin
        key_in[0] = $random;
        #(CLK_CLY*1);
    end 
    key_in[0] = 0;//按键稳定
    #(CLK_CLY*100*500);
  
    //后抖动开始
    key_in[0] = 1;
    #(CLK_CLY*1);
    repeat ((i+5)*50)begin
        key_in[0] = $random;
        #(CLK_CLY*1);
    end 
    key_in[0] = 1;//按键稳定
    #(CLK_CLY*10*5000);

    key_in[1] = 1;//模拟按键未按下
    i ={$random}%6;//给i赋值0-5
    #(CLK_CLY*500);//等待复位时间结束
    #3;
    key_in[1] = 0;//前按键抖动开始
    #(CLK_CLY*1);
    //一个5-10ms的抖动时间
    repeat ((i+5)*50)begin
        key_in[1] = $random;
        #(CLK_CLY*1);
    end 
    key_in[1] = 0;//按键稳定
    #(CLK_CLY*100*500);
  
    //后抖动开始
    key_in[1] = 1;
    #(CLK_CLY*1);
    repeat ((i+5)*50)begin
        key_in[1] = $random;
        #(CLK_CLY*1);
    end 
    key_in[1] = 1;//按键稳定
    #(CLK_CLY*10*5000);


    //模拟意外抖动
    repeat (3)begin 
        repeat ((i+5)*50)begin
            key_in = $random;
            #(CLK_CLY*1);
        end 
        key_in = 1;//按键稳定
        #(CLK_CLY*5000);

    end 
    $stop;
end
endmodule

5、下板验证

在进行下板验证之后最终实现的效果和我们所改造的预期结果一致,这里格式限制上传不了视频,就不展示了。在下板验证时设计的引脚配置等由用户自己根据需求去设置。

三、不足之处

经过最后的检查之后,发现在开发板上进行验证时,在使用按键进行数码管显示数值的改变的这个过程当中我们看不到,因为是在标志位位1时计数里面进行改变,而我们的输出是在标志位为0时进行显示的,所以看不到,这里我们对seg0设计进行一个最简单粗暴的方法进行修改。这里修改的方法很low了,在后面的数字时钟的文章中我会加以完善,不足之处多多包涵。

修改之后的seg0文件:

cpp 复制代码
//分频器
module seg0(
  input  clk,
  input  rst_n,
  input  [1:0] key_in,
  input  seg_sel,//位选
  output reg [7:0] seg_dual//段选
);
localparam  ZERO  = 8'b1100_0000, //共阳极段码
            ONE   = 8'b1111_1001,
            TWO   = 8'b1010_0100,
            THREE = 8'b1011_0000,
            FOUR  = 8'b1001_1001,
            FIVE  = 8'b1001_0010,
            SIX   = 8'b1000_0010,
            SEVEN = 8'b1111_1000,
            EIGHT = 8'b1000_0000,
            NINE  = 8'b1001_0000,
            A     = 8'b1000_1000,
            b     = 8'b1000_0011,
            c     = 8'b1100_0110,
            d     = 8'b1010_0001,
            E     = 8'b1000_0110,
            f     = 8'b1000_1110;
reg [26:0] cnt;
wire       add_cnt;
wire       end_cnt;
reg     [4:0]   flag;
wire            add_flag;
wire            end_flag;

reg    seg_flag;//数码管状态切换标志位
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
      cnt<=0;
    else if(add_cnt)begin
      if(end_cnt)
        cnt<=0;
      else
        cnt<=cnt+1'b1;
    end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt && (cnt==50_000_000-1);

always @(posedge clk or negedge rst_n)begin
  if(!rst_n)
    flag<=0;
  else if(add_flag)begin
    if(end_flag)
      flag<=0;
    else
      flag<=flag+1'b1;
  end
end
assign add_flag=end_cnt;
assign end_flag=add_flag && (flag==16-1);

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        seg_flag<=0;
    else if(key_in[0])
        seg_flag<=~seg_flag;
    else 
        seg_flag<=seg_flag;
end


always @(posedge clk or negedge rst_n)begin
  if(!rst_n)
    seg_dual<=8'b1111_1111;
  else if(seg_flag==0)begin
    case (flag)
      4'd0:seg_dual <=ZERO ;
      4'd1:seg_dual <=ONE  ;
      4'd2:seg_dual <=TWO  ;
      4'd3:seg_dual <=THREE; 
      4'd4:seg_dual <=FOUR ;
      4'd5:seg_dual <=FIVE ;
      4'd6:seg_dual <=SIX  ;
      4'd7:seg_dual <=SEVEN;
      4'd8:seg_dual <=EIGHT;
      4'd9:seg_dual <=NINE ;
      4'd10:seg_dual<=A    ;
      4'd11:seg_dual<=b    ;
      4'd12:seg_dual<=c    ;
      4'd13:seg_dual<=d    ;
      4'd14:seg_dual<=E    ;
      4'd15:seg_dual<=f    ;
      default: ;
    endcase
  end 
    else if(seg_flag==1)begin
        if(key_in[1])begin
            case (flag)
              4'd0:seg_dual <=ZERO ;
              4'd1:seg_dual <=ONE  ;
              4'd2:seg_dual <=TWO  ;
              4'd3:seg_dual <=THREE; 
              4'd4:seg_dual <=FOUR ;
              4'd5:seg_dual <=FIVE ;
              4'd6:seg_dual <=SIX  ;
              4'd7:seg_dual <=SEVEN;
              4'd8:seg_dual <=EIGHT;
              4'd9:seg_dual <=NINE ;
              4'd10:seg_dual<=A    ;
              4'd11:seg_dual<=b    ;
              4'd12:seg_dual<=c    ;
              4'd13:seg_dual<=d    ;
              4'd14:seg_dual<=E    ;
              4'd15:seg_dual<=f    ;
              default: ;
            endcase
        end
  end 
end
endmodule 

经过本次修改之后就可以观察得到数码管显示数值的一个修改过程,到这里本次设计也就结束了。

相关推荐
9527华安2 小时前
FPGA实现MIPI转FPD-Link车载同轴视频传输方案,基于IMX327+FPD953架构,提供工程源码和技术支持
fpga开发·架构·mipi·imx327·fpd-link·fpd953
热爱学习地派大星3 小时前
FPGA远程升级 -- FLASH控制
fpga开发
带电的小王6 小时前
VSCode:VSCode安装 -- 最简洁的VSCode安装教程
ide·vscode·编辑器
sg_knight10 小时前
VSCode如何修改默认扩展路径和用户文件夹目录到D盘
前端·ide·vscode·编辑器·web
szxinmai主板定制专家11 小时前
【国产NI替代】基于国产FPGA+兆易创新GD32F450的全国产16振动+2转速(24bits)高精度终端采集板卡
fpga开发
GPT祖弘11 小时前
【VScode】第三方GPT编程工具-CodeMoss安装教程
ide·vscode·gpt
乐闻x11 小时前
VSCode 插件开发实战(五):实现新语言支持和语法高亮
ide·vscode·编辑器
Dontla11 小时前
vscode怎么设置anaconda python解释器(anaconda解释器、vscode解释器)
ide·vscode·python
乐闻x11 小时前
VSCode 插件开发实战(六):配置自定义状态栏
ide·vscode·编辑器
漫天转悠11 小时前
VScode中配置ESlint+Prettier详细步骤(图文详情)
vscode·vue