【数电】计数器实验小结与启发

前言

最近在北邮沉迷学习数电,第一个验收的实验是实现循环自动计数的计数器。就这次实验写一个小结回顾,也希望能给后来人启发。

实验要求

完成一个计数值为00~19的计数器的设计和仿真,并下载到实验板上验证。要求如下:

  1. 计数值每秒加1,加到19后回0
  2. BTN0 为暂停键,按一下计时停止,再按一下计时继续,要求为 BTN0 设计防抖电路
  3. 在数码管 DISP1DISP0 上显示计数结果
  4. BTN7 为复位键,无论何时按下计数值都回到 00
  5. 实验板上时钟选择 100Hz

系统分析

输入输出端口设计

首先,我们先设计系统的输入和输出。

输入显然有两个:暂停按钮 btn0 和复位按钮 reset

输出是数码管,但是我们发现数码管共用一个输入端口,所以为了实现多位数字显示,我们需要用 扫描 的方式显示。于是我们定义输出为 seg ,位选为 cat

我们可以定义整个顶层模块如下:

module segCounter (
    input wire clk,

    input wire btn0,
    reset,
    output reg [6:0] seg,
    output reg [7:0] cat
);

系统模块设计

系统模块总览

按键消抖模块

按键消抖模块

由于按键动作的时刻和按下时间长短随机,并且存在开关簧片反弹导致电平抖动,我们需要设计一个按键消抖模块。一般抖动时间小于 20ms ,所以我们只需要检测高电平持续的长度大于20ms(实验里取30ms为检测时间)时再置高电平即可。

按键抖动波形

时序图

可见,btn0 是原始的按键输入波形, cnt_3 的作用是基于 clk 实现计数,每当 clk 到上升沿时,若高电平则+1,计数到3后则 stable_flag 置高电平,同时 press 产生一个周期的高电平。

cnt_3 为什么要计数到3呢?因为 clk 频率是 100Hz ,频率是周期的倒数,所以简单换算如下:$$ f=\frac{1}{T} \Rightarrow T=\frac{1}{f}=\frac{1}{100}s=10ms $$每隔10ms clk 来一个上升沿, cnt_3 +1,则当 cnt_3 为3时显然时间就过去了30ms。

消抖模块对应代码如下:

  // 消抖
  always @(posedge clk or posedge reset) begin
    if (reset == 1'b1) begin
      cnt_3 <= 3'b000;
    end else if (cnt_3 == 3'b011) begin
      cnt_3 <= 3'b000;
    end else if (btn0 == 1'b1) begin
      cnt_3 <= cnt_3 + 3'b001;
    end else begin
      cnt_3 <= 3'b000;
    end
  end

  always @(posedge clk or posedge reset) begin
    if (reset == 1'b1) begin
      stable_flag <= 1'b0;
    end else if (btn0 == 1'b1 && cnt_3 == 'd3) begin
      stable_flag <= 1'b1;
    end else if (btn0 == 1'b0) begin
      stable_flag <= 1'b0;
    end
  end

  always @(posedge clk or posedge reset) begin
    if (reset == 1'b1) begin
      press <= 1'b0;
    end else if (stable_flag == 1'b0 && cnt_3 == 'd3) begin
      press <= 1'b1;
    end else begin
      press <= 1'b0;
    end
  end

复位功能设计

同步vs复位

我们采用异步复位( posedge clk or posedge reset 里面用 or ),当reset按下时就立刻执行复位操作。因为复位功能显然优先级最高,所以所有 always 块内我们都先验证 reset 的状态。

数码管的显示

共阴极数码管

如图,要使得共阴极数码管点亮,需要改变的是阳极的状态。而阳极是多个数码管共用的,所以我们借助人 视觉暂留 的特点,用扫描的方式显示所有数字(类似显示屏的原理)。

数码管的控制
扫描
扫描内部的实现

可见,我们使用 select 区分当前显示的是十位还是个位。译码器负责转换数字到数码管阳极状态的映射关系,扫描控制电路负责控制不同位数数码管的点亮与否。

代码实现

最后,整个代码如下:

module segCounter (
    input wire clk,

    input wire btn0,
    reset,
    output reg [6:0] seg,
    output reg [7:0] cat
);
  reg [2:0] cnt_3;
  reg [6:0] cnt_100;
  reg stable_flag, press, ctr, select;
  reg [3:0] cnt1;
  reg [3:0] cnt2;
  reg [3:0] bin;
  // 消抖
  always @(posedge clk or posedge reset) begin
    if (reset == 1'b1) begin
      cnt_3 <= 3'b000;
    end else if (cnt_3 == 3'b011) begin
      cnt_3 <= 3'b000;
    end else if (btn0 == 1'b1) begin
      cnt_3 <= cnt_3 + 3'b001;
    end else begin
      cnt_3 <= 3'b000;
    end
  end

  always @(posedge clk or posedge reset) begin
    if (reset == 1'b1) begin
      stable_flag <= 1'b0;
    end else if (btn0 == 1'b1 && cnt_3 == 'd3) begin
      stable_flag <= 1'b1;
    end else if (btn0 == 1'b0) begin
      stable_flag <= 1'b0;
    end
  end

  always @(posedge clk or posedge reset) begin
    if (reset == 1'b1) begin
      press <= 1'b0;
    end else if (stable_flag == 1'b0 && cnt_3 == 'd3) begin
      press <= 1'b1;
    end else begin
      press <= 1'b0;
    end
  end
  // 暂停标志
  always @(posedge press or posedge reset) begin
    if (reset == 1'b1) begin
      ctr <= 1'b0;
    end else begin
      ctr <= ~ctr;
    end
  end
  // 时钟倍频(cnt_100 clk计数)
  always @(posedge clk or posedge reset) begin
    if (reset == 1'b1) begin
      cnt_100 <= 7'b000_0000;
    end else if (cnt_100 == 7'b110_0100 && ctr == 1'b0) begin
      cnt_100 <= 7'b000_0000;
    end else if (ctr == 1'b0) begin
      cnt_100 <= cnt_100 + 7'b000_0001;
    end
  end
  // 显示(cnt1 个位,cnt2 十位)
  always @(posedge clk, posedge reset) begin
    if (reset == 1'b1) begin
      cnt1 <= 0;
      cnt2 <= 0;
    end else if (cnt_100 == 7'b110_0100 && ctr == 1'b0) begin
      if (cnt1 == 4'b1001) begin
        cnt1 <= 0;
        if (cnt2 == 0) cnt2 <= cnt2 + 1;
        else cnt2 <= 0;
      end else begin
        cnt1 <= cnt1 + 1;
      end
    end
  end
  // 扫描
  always @(posedge clk, posedge reset) begin
    if (reset == 1'b1) begin
      cat <= 8'b1111_1111;
      bin <= 0;
    end else begin
      select <= ~select;
      if (select == 1'b0) begin
        cat <= 8'b1111_1110;
        bin <= cnt1;
      end else begin
        cat <= 8'b1111_1101;
        bin <= cnt2;
      end
    end
  end
  // 译码
  always @(bin) begin
    case (bin)
      4'd0: seg = 7'b1111110;
      4'd1: seg = 7'b0110000;
      4'd2: seg = 7'b1101101;
      4'd3: seg = 7'b1111001;
      4'd4: seg = 7'b0110011;
      4'd5: seg = 7'b1011011;
      4'd6: seg = 7'b1011111;
      4'd7: seg = 7'b1110000;
      4'd8: seg = 7'b1111111;
      4'd9: seg = 7'b1111011;
      default: seg = 7'b0000000;
    endcase
  end

endmodule

实验用板如图,引脚对应如下。

实验板
7段数码管模块
独立按键模块

评论区
头像
    头像
    模电小菜鸡
      

    大佬!能不能请教一下你的博客搭建使用的是什么工具呀!你的博客很丝滑欸!!!

      头像
      Nickwald
        
      @模电小菜鸡

      用的Typecho哦,比wordpress轻量些
      大部分丝滑动画是前端做的

    头像
    零信号
      

    赞(。^▽^)

      头像
      Nickwald
        
文章目录