当前位置:   article > 正文

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

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

一、概述

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

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

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

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

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

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

二、工程实现

1、基本思路构建

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

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

2、设计文件的编写

新建一个seg0.v文件

  1. module seg0(
  2. input clk,
  3. input rst_n,
  4. input [1:0] key_in,
  5. input seg_sel,//位选
  6. output reg [7:0] seg_dual//段选
  7. );
  8. localparam ZERO = 8'b1100_0000, //共阳极段码
  9. ONE = 8'b1111_1001,
  10. TWO = 8'b1010_0100,
  11. THREE = 8'b1011_0000,
  12. FOUR = 8'b1001_1001,
  13. FIVE = 8'b1001_0010,
  14. SIX = 8'b1000_0010,
  15. SEVEN = 8'b1111_1000,
  16. EIGHT = 8'b1000_0000,
  17. NINE = 8'b1001_0000,
  18. A = 8'b1000_1000,
  19. b = 8'b1000_0011,
  20. c = 8'b1100_0110,
  21. d = 8'b1010_0001,
  22. E = 8'b1000_0110,
  23. f = 8'b1000_1110;
  24. reg [26:0] cnt;
  25. wire add_cnt;
  26. wire end_cnt;
  27. reg [4:0] flag;
  28. wire add_flag;
  29. wire end_flag;
  30. reg seg_flag;//数码管状态切换标志位
  31. always @(posedge clk or negedge rst_n)begin
  32. if(!rst_n)
  33. cnt<=0;
  34. else if(add_cnt)begin
  35. if(end_cnt)
  36. cnt<=0;
  37. else
  38. cnt<=cnt+1'b1;
  39. end
  40. end
  41. assign add_cnt=1'b1;
  42. assign end_cnt=add_cnt && (cnt==50_000_000-1);
  43. always @(posedge clk or negedge rst_n)begin
  44. if(!rst_n)
  45. flag<=0;
  46. else if(seg_flag==1 && key_in[1])
  47. flag<=flag+1'b1;
  48. else if(add_flag)begin
  49. if(end_flag)
  50. flag<=0;
  51. else
  52. flag<=flag+1'b1;
  53. end
  54. end
  55. assign add_flag=end_cnt;
  56. assign end_flag=add_flag && (flag==16-1);
  57. always @(posedge clk or negedge rst_n)begin
  58. if(!rst_n)
  59. seg_flag<=0;
  60. else if(key_in[0])
  61. seg_flag<=~seg_flag;
  62. else
  63. seg_flag<=seg_flag;
  64. end
  65. always @(posedge clk or negedge rst_n)begin
  66. if(!rst_n)
  67. seg_dual<=8'b1111_1111;
  68. else if(seg_flag==0)begin
  69. case (flag)
  70. 4'd0:seg_dual <=ZERO ;
  71. 4'd1:seg_dual <=ONE ;
  72. 4'd2:seg_dual <=TWO ;
  73. 4'd3:seg_dual <=THREE;
  74. 4'd4:seg_dual <=FOUR ;
  75. 4'd5:seg_dual <=FIVE ;
  76. 4'd6:seg_dual <=SIX ;
  77. 4'd7:seg_dual <=SEVEN;
  78. 4'd8:seg_dual <=EIGHT;
  79. 4'd9:seg_dual <=NINE ;
  80. 4'd10:seg_dual<=A ;
  81. 4'd11:seg_dual<=b ;
  82. 4'd12:seg_dual<=c ;
  83. 4'd13:seg_dual<=d ;
  84. 4'd14:seg_dual<=E ;
  85. 4'd15:seg_dual<=f ;
  86. default: ;
  87. endcase
  88. end
  89. end
  90. endmodule

新建一个key.v文件

  1. //状态机实现
  2. module key (
  3. input clk ,
  4. input rst_n ,
  5. input [1:0] key_in , //输入原始的按键信号
  6. output reg [1:0] key_out //输出处理之后的按键信号
  7. );
  8. //参数定义
  9. localparam IDLE = 4'b0001,//空闲
  10. JITTLE0 = 4'b0010,//滤除第一次抖动
  11. DOWN = 4'b0100,//稳定
  12. JITTLE1 = 4'b1000;//滤除第二次抖动
  13. parameter TIME_20MS = 1_000_000;//需要计数的值,20ms
  14. //内部信号
  15. reg [3:0] state_c;//现态
  16. reg [3:0] state_n;//次态
  17. reg [19:0] cnt_20ms;//计数20ms
  18. wire add_cnt_20ms;
  19. wire end_cnt_20ms;
  20. wire [1:0] nedge ;//下降沿信号
  21. wire [1:0] pedge ;//上升沿信号
  22. reg [1:0] key_in_r1 ;//打两拍 同步打拍
  23. reg [1:0] key_in_r2 ;
  24. //状态转移 同步时序逻辑描述状态转移
  25. always @(posedge clk or negedge rst_n) begin
  26. if(!rst_n)
  27. state_c <= IDLE;
  28. else
  29. state_c <= state_n;
  30. end
  31. //状态转移条件 组合逻辑
  32. always @(*) begin
  33. case (state_c)//一定是case 现态
  34. IDLE:begin
  35. if(nedge)
  36. state_n = JITTLE0;
  37. else
  38. state_n = state_c;
  39. end
  40. JITTLE0:begin
  41. if(end_cnt_20ms)
  42. state_n = DOWN;
  43. else
  44. state_n = state_c;
  45. end
  46. DOWN:begin
  47. if(pedge)
  48. state_n = JITTLE1;
  49. else
  50. state_n = state_c;
  51. end
  52. JITTLE1 :begin
  53. if(end_cnt_20ms)
  54. state_n = IDLE;
  55. else
  56. state_n = state_c;
  57. end
  58. default: state_n = IDLE;
  59. endcase
  60. end
  61. //20ms计数器
  62. always @(posedge clk or negedge rst_n) begin
  63. if(!rst_n)
  64. cnt_20ms <= 0;
  65. else if(add_cnt_20ms)begin
  66. if(end_cnt_20ms)
  67. cnt_20ms <= 0;
  68. else
  69. cnt_20ms <= cnt_20ms + 1;
  70. end
  71. end
  72. assign add_cnt_20ms = (state_c == JITTLE0) || (state_c == JITTLE1);
  73. assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
  74. //下降沿 上升沿
  75. //同步 打拍
  76. always @(posedge clk or negedge rst_n)begin
  77. if(!rst_n)begin
  78. key_in_r1 <= 2'b11;
  79. key_in_r2 <= 2'b11;
  80. end
  81. else begin
  82. key_in_r1 <= key_in; //同步按键输入信号
  83. key_in_r2 <= key_in_r1; //打拍
  84. end
  85. end
  86. //r1当前状态,r2上一个状态
  87. assign nedge = ~key_in_r1 && key_in_r2;
  88. assign pedge = key_in_r1 && ~key_in_r2;
  89. //key_out
  90. always @(posedge clk or negedge rst_n) begin
  91. if(!rst_n)
  92. key_out <= 0;
  93. else if(end_cnt_20ms &&(state_c== JITTLE1))
  94. key_out <= ~key_in_r2;//有效脉冲 20ns
  95. else
  96. key_out <= 0;
  97. end
  98. endmodule

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

  1. module top(
  2. input clk,
  3. input rst_n,
  4. input [1:0] key_in,
  5. output seg_sel,
  6. output [7:0] seg_dual
  7. );
  8. wire [1:0] key_flag;
  9. key key_inst(
  10. /*input */ .clk (clk ),
  11. /*input */ .rst_n (rst_n ),
  12. /*input */ .key_in (key_in ), //输入原始的按键信号
  13. /*output */ .key_out (key_flag ) //输出处理之后的按键信号
  14. );
  15. seg0 seg_inst(
  16. /*input */ .clk (clk ),
  17. /*input */ .rst_n (rst_n ),
  18. .key_in (key_flag), //
  19. /*input */ .seg_sel (seg_sel ),//位选
  20. /*output*/ .seg_dual (seg_dual )//段选
  21. );
  22. endmodule

4、测试文件的编写

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

  1. //定义时间尺度
  2. `timescale 1ns/1ns
  3. module top_tb ;
  4. //输入信号定义
  5. reg clk;
  6. reg rst_n;
  7. reg [1:0] key_in;
  8. wire [7:0] seg_dual;
  9. wire seg_sel;
  10. defparam top_inst.cnt_inst.TIME_1s=500;
  11. //模块例化
  12. top top_inst(
  13. /*input */.clk (clk ),
  14. /*input */.rst_n (rst_n ),
  15. /*input */.key_in (key_in ),
  16. /*output*/.seg_sel (seg_sel ),
  17. /*output*/.seg_dual (seg_dual)
  18. );
  19. //激励信号产生
  20. parameter CLK_CLY = 20;
  21. //时钟
  22. initial clk=1;
  23. always #(CLK_CLY/2)clk=~clk;
  24. //复位
  25. initial begin
  26. rst_n= 1'b0;
  27. #(CLK_CLY*3);
  28. #5;//复位结束避开时钟上升沿
  29. rst_n= 1'b1;
  30. end
  31. //激励
  32. //激励
  33. integer i;
  34. initial repeat(5)begin
  35. key_in[1] = 1;//模拟按键未按下
  36. i ={$random}%6;//给i赋值0-5
  37. #(CLK_CLY*500);//等待复位时间结束
  38. #3;
  39. key_in[1] = 0;//前按键抖动开始
  40. #(CLK_CLY*1);
  41. //一个5-10ms的抖动时间
  42. repeat ((i+5)*50)begin
  43. key_in[1] = $random;
  44. #(CLK_CLY*1);
  45. end
  46. key_in[1] = 0;//按键稳定
  47. #(CLK_CLY*100*500);
  48. //后抖动开始
  49. key_in[1] = 1;
  50. #(CLK_CLY*1);
  51. repeat ((i+5)*50)begin
  52. key_in[1] = $random;
  53. #(CLK_CLY*1);
  54. end
  55. key_in[1] = 1;//按键稳定
  56. #(CLK_CLY*10*5000);
  57. key_in[2] = 1;//模拟按键未按下
  58. i ={$random}%6;//给i赋值0-5
  59. #(CLK_CLY*500);//等待复位时间结束
  60. #3;
  61. key_in[2] = 0;//前按键抖动开始
  62. #(CLK_CLY*1);
  63. //一个5-10ms的抖动时间
  64. repeat ((i+5)*50)begin
  65. key_in[2] = $random;
  66. #(CLK_CLY*1);
  67. end
  68. key_in[2] = 0;//按键稳定
  69. #(CLK_CLY*100*500);
  70. //后抖动开始
  71. key_in[2] = 1;
  72. #(CLK_CLY*1);
  73. repeat ((i+5)*50)begin
  74. key_in[2] = $random;
  75. #(CLK_CLY*1);
  76. end
  77. key_in[2] = 1;//按键稳定
  78. #(CLK_CLY*10*5000);
  79. key_in[0] = 1;//模拟按键未按下
  80. i ={$random}%6;//给i赋值0-5
  81. #(CLK_CLY*500);//等待复位时间结束
  82. #3;
  83. key_in[0] = 0;//前按键抖动开始
  84. #(CLK_CLY*1);
  85. //一个5-10ms的抖动时间
  86. repeat ((i+5)*50)begin
  87. key_in[0] = $random;
  88. #(CLK_CLY*1);
  89. end
  90. key_in[0] = 0;//按键稳定
  91. #(CLK_CLY*100*500);
  92. //后抖动开始
  93. key_in[0] = 1;
  94. #(CLK_CLY*1);
  95. repeat ((i+5)*50)begin
  96. key_in[0] = $random;
  97. #(CLK_CLY*1);
  98. end
  99. key_in[0] = 1;//按键稳定
  100. #(CLK_CLY*10*5000);
  101. key_in[0] = 1;//模拟按键未按下
  102. i ={$random}%6;//给i赋值0-5
  103. #(CLK_CLY*500);//等待复位时间结束
  104. #3;
  105. key_in[0] = 0;//前按键抖动开始
  106. #(CLK_CLY*1);
  107. //一个5-10ms的抖动时间
  108. repeat ((i+5)*50)begin
  109. key_in[0] = $random;
  110. #(CLK_CLY*1);
  111. end
  112. key_in[0] = 0;//按键稳定
  113. #(CLK_CLY*100*500);
  114. //后抖动开始
  115. key_in[0] = 1;
  116. #(CLK_CLY*1);
  117. repeat ((i+5)*50)begin
  118. key_in[0] = $random;
  119. #(CLK_CLY*1);
  120. end
  121. key_in[0] = 1;//按键稳定
  122. #(CLK_CLY*10*5000);
  123. key_in[1] = 1;//模拟按键未按下
  124. i ={$random}%6;//给i赋值0-5
  125. #(CLK_CLY*500);//等待复位时间结束
  126. #3;
  127. key_in[1] = 0;//前按键抖动开始
  128. #(CLK_CLY*1);
  129. //一个5-10ms的抖动时间
  130. repeat ((i+5)*50)begin
  131. key_in[1] = $random;
  132. #(CLK_CLY*1);
  133. end
  134. key_in[1] = 0;//按键稳定
  135. #(CLK_CLY*100*500);
  136. //后抖动开始
  137. key_in[1] = 1;
  138. #(CLK_CLY*1);
  139. repeat ((i+5)*50)begin
  140. key_in[1] = $random;
  141. #(CLK_CLY*1);
  142. end
  143. key_in[1] = 1;//按键稳定
  144. #(CLK_CLY*10*5000);
  145. //模拟意外抖动
  146. repeat (3)begin
  147. repeat ((i+5)*50)begin
  148. key_in = $random;
  149. #(CLK_CLY*1);
  150. end
  151. key_in = 1;//按键稳定
  152. #(CLK_CLY*5000);
  153. end
  154. $stop;
  155. end
  156. endmodule

5、下板验证

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

三、不足之处

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

修改之后的seg0文件:

  1. //分频器
  2. module seg0(
  3. input clk,
  4. input rst_n,
  5. input [1:0] key_in,
  6. input seg_sel,//位选
  7. output reg [7:0] seg_dual//段选
  8. );
  9. localparam ZERO = 8'b1100_0000, //共阳极段码
  10. ONE = 8'b1111_1001,
  11. TWO = 8'b1010_0100,
  12. THREE = 8'b1011_0000,
  13. FOUR = 8'b1001_1001,
  14. FIVE = 8'b1001_0010,
  15. SIX = 8'b1000_0010,
  16. SEVEN = 8'b1111_1000,
  17. EIGHT = 8'b1000_0000,
  18. NINE = 8'b1001_0000,
  19. A = 8'b1000_1000,
  20. b = 8'b1000_0011,
  21. c = 8'b1100_0110,
  22. d = 8'b1010_0001,
  23. E = 8'b1000_0110,
  24. f = 8'b1000_1110;
  25. reg [26:0] cnt;
  26. wire add_cnt;
  27. wire end_cnt;
  28. reg [4:0] flag;
  29. wire add_flag;
  30. wire end_flag;
  31. reg seg_flag;//数码管状态切换标志位
  32. always @(posedge clk or negedge rst_n)begin
  33. if(!rst_n)
  34. cnt<=0;
  35. else if(add_cnt)begin
  36. if(end_cnt)
  37. cnt<=0;
  38. else
  39. cnt<=cnt+1'b1;
  40. end
  41. end
  42. assign add_cnt=1'b1;
  43. assign end_cnt=add_cnt && (cnt==50_000_000-1);
  44. always @(posedge clk or negedge rst_n)begin
  45. if(!rst_n)
  46. flag<=0;
  47. else if(add_flag)begin
  48. if(end_flag)
  49. flag<=0;
  50. else
  51. flag<=flag+1'b1;
  52. end
  53. end
  54. assign add_flag=end_cnt;
  55. assign end_flag=add_flag && (flag==16-1);
  56. always @(posedge clk or negedge rst_n)begin
  57. if(!rst_n)
  58. seg_flag<=0;
  59. else if(key_in[0])
  60. seg_flag<=~seg_flag;
  61. else
  62. seg_flag<=seg_flag;
  63. end
  64. always @(posedge clk or negedge rst_n)begin
  65. if(!rst_n)
  66. seg_dual<=8'b1111_1111;
  67. else if(seg_flag==0)begin
  68. case (flag)
  69. 4'd0:seg_dual <=ZERO ;
  70. 4'd1:seg_dual <=ONE ;
  71. 4'd2:seg_dual <=TWO ;
  72. 4'd3:seg_dual <=THREE;
  73. 4'd4:seg_dual <=FOUR ;
  74. 4'd5:seg_dual <=FIVE ;
  75. 4'd6:seg_dual <=SIX ;
  76. 4'd7:seg_dual <=SEVEN;
  77. 4'd8:seg_dual <=EIGHT;
  78. 4'd9:seg_dual <=NINE ;
  79. 4'd10:seg_dual<=A ;
  80. 4'd11:seg_dual<=b ;
  81. 4'd12:seg_dual<=c ;
  82. 4'd13:seg_dual<=d ;
  83. 4'd14:seg_dual<=E ;
  84. 4'd15:seg_dual<=f ;
  85. default: ;
  86. endcase
  87. end
  88. else if(seg_flag==1)begin
  89. if(key_in[1])begin
  90. case (flag)
  91. 4'd0:seg_dual <=ZERO ;
  92. 4'd1:seg_dual <=ONE ;
  93. 4'd2:seg_dual <=TWO ;
  94. 4'd3:seg_dual <=THREE;
  95. 4'd4:seg_dual <=FOUR ;
  96. 4'd5:seg_dual <=FIVE ;
  97. 4'd6:seg_dual <=SIX ;
  98. 4'd7:seg_dual <=SEVEN;
  99. 4'd8:seg_dual <=EIGHT;
  100. 4'd9:seg_dual <=NINE ;
  101. 4'd10:seg_dual<=A ;
  102. 4'd11:seg_dual<=b ;
  103. 4'd12:seg_dual<=c ;
  104. 4'd13:seg_dual<=d ;
  105. 4'd14:seg_dual<=E ;
  106. 4'd15:seg_dual<=f ;
  107. default: ;
  108. endcase
  109. end
  110. end
  111. end
  112. endmodule

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/915470
推荐阅读
相关标签
  

闽ICP备14008679号