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 

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

相关推荐
皇华ameya1 小时前
AMEYA360:村田电子更适合薄型设计应用场景的3.3V输入、12A输出的DCDC转换IC
fpga开发
cliffordl3 小时前
vscode 环境搭建
ide·vscode·编辑器
JANGHIGH3 小时前
VSCode引用Eigen库无法识别问题解决
ide·vscode·编辑器
千穹凌帝3 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
我就是全世界4 小时前
开源集成开发环境搭建之VSCode启动Jupyter Notebook
ide·vscode·jupyter
一口一口吃成大V9 小时前
FPGA随记——FPGA时序优化小经验
fpga开发
贾saisai10 小时前
Xilinx系FPGA学习笔记(九)DDR3学习
笔记·学习·fpga开发
redcocal15 小时前
地平线秋招
python·嵌入式硬件·算法·fpga开发·求职招聘
科研小白_d.s15 小时前
vscode配置c/c++环境
c语言·c++·vscode
程序猿进阶1 天前
如何在 Visual Studio Code 中反编译具有正确行号的 Java 类?
java·ide·vscode·算法·面试·职场和发展·架构