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

前言

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

实验要求

完成一个计数值为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
);
Verilog

系统模块设计

系统模块总览

按键消抖模块

按键消抖模块

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

按键抖动波形

时序图

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

cnt_3 为什么要计数到3呢?因为 clk 频率是 100Hz ,频率是周期的倒数,所以简单换算如下:f=1TT=1f=1100s=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
Verilog

复位功能设计

同步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
Verilog

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

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

评论区
头像
  • 高兴
  • 小怒
  • 脸红
  • 内伤
  • 装大款
  • 赞一个
  • 害羞
  • 汗
  • 吐血倒地
  • 深思
  • 不高兴
  • 无语
  • 亲亲
  • 口水
  • 尴尬
  • 中指
  • 想一想
  • 哭泣
  • 便便
  • 献花
  • 皱眉
  • 傻笑
  • 狂汗
  • 吐
  • 喷水
  • 看不见
  • 鼓掌
  • 阴暗
  • 长草
  • 献黄瓜
  • 邪恶
  • 期待
  • 得意
  • 吐舌
  • 喷血
  • 无所谓
  • 观察
  • 暗地观察
  • 肿包
  • 中枪
  • 大囧
  • 呲牙
  • 抠鼻
  • 不说话
  • 咽气
  • 欢呼
  • 锁眉
  • 蜡烛
  • 坐等
  • 击掌
  • 惊喜
  • 喜极而泣
  • 抽烟
  • 不出所料
  • 愤怒
  • 无奈
  • 黑线
  • 投降
  • 看热闹
  • 扇耳光
  • 小眼睛
  • 中刀
  • 呵呵
  • 哈哈
  • 吐舌
  • 太开心
  • 笑眼
  • 花心
  • 小乖
  • 乖
  • 捂嘴笑
  • 滑稽
  • 你懂的
  • 不高兴
  • 怒
  • 汗
  • 黑线
  • 泪
  • 真棒
  • 喷
  • 惊哭
  • 阴险
  • 鄙视
  • 酷
  • 啊
  • 狂汗
  • what
  • 疑问
  • 酸爽
  • 呀咩爹
  • 委屈
  • 惊讶
  • 睡觉
  • 笑尿
  • 挖鼻
  • 吐
  • 犀利
  • 小红脸
  • 懒得理
  • 勉强
  • 爱心
  • 心碎
  • 玫瑰
  • 礼物
  • 彩虹
  • 太阳
  • 星星月亮
  • 钱币
  • 茶杯
  • 蛋糕
  • 大拇指
  • 胜利
  • haha
  • OK
  • 沙发
  • 手纸
  • 香蕉
  • 便便
  • 药丸
  • 红领巾
  • 蜡烛
  • 音乐
  • 灯泡
  • 开心
  • 钱
  • 咦
  • 呼
  • 冷
  • 生气
  • 弱
  • 阿鲁
  • 泡泡
    头像
    模电小菜鸡
      

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

      头像
      Nickwald
        
      @模电小菜鸡

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

    头像
    零信号
      

    赞(。^▽^)

      头像
      Nickwald
        
文章目录