数逻实验2——实现数码管动态控制显示器
数码管闪烁的原理
七段数码管是一种常见的数显装置,由7个独立的发光二极管组成,用来显示数字0到9。每个数码管通常有7个段(a到g),通过控制每一段的点亮或熄灭,可以组合成不同的数字。
数码管的段位命名
- a: 顶部
- b: 右上
- c: 右下
- d: 底部
- e: 左下
- f: 左上
- g: 中间
这7段可以组合成不同的图形来显示数字0到9。
每段可以点亮(1)或熄灭(0)。
一个8位的二进制数用于控制7段数码管的显示(通常第8位用作小数点控制)。
设计文件led_display_ctrl.v
完整代码
1 | `timescale 1ns/1ps |
第一行
1 | timescale 1ns/1ps |
作用:这个语句用来定义仿真的时间单位。
模块定义
1 | module led_display_ctrl #( |
这段代码定义了一个名为
led_display_ctrl
的模块。模块是 Verilog 中的基本单元,相当于其他语言中的函数或类。这个模块有参数、输入信号、输出信号。#(...)
部分:这是模块的参数列表。参数是可以传入模块的一些常量,用于调整模块的行为。这里的参数包括:ZERO
到NINE
:这些参数定义了七段数码管显示数字0到9的编码。(详情了解数码管闪烁的原理)FREQ_CLK_DIV
:定义了时钟分频器的值,用来生成2毫秒的时钟信号。为了控制数码管的刷新频率。FREQ_S2
:定义了生成0.1秒时钟间隔的分频器值。
input wire ...
:这些是输入信号,表示模块可以接受哪些信号输入。clk
:时钟信号,这是一个周期性高低变化的信号,电路的所有行为都会同步到它。rst
:复位信号,当它为高电平时,模块会复位所有状态。s_3
、s_2
:按钮或开关的输入信号。dip_switch
:8位拨码开关的输入,用于控制底下的8个开关。
output reg
…:这些是输出信号,表示模块向外部输出什么数据。led_en
:控制哪一个数码管被启用的信号。led_cx
:数码管上显示的内容。
内部信号和寄存器定义
1 | wire pos_edge_s3_debounce, pos_edge_s2; |
wire
:这是定义的组合逻辑信号。它们用于计算逻辑结果并不能存储值。pos_edge_s3_debounce
:用来检测 s_3 信号经过去抖动后的上升沿。pos_edge_s2
:用来检测 s_2 信号的上升沿。s3_stable
:表示 s_3 信号在经过15毫秒后是否稳定。
reg
:这是定义的寄存器(存储单元),可以存储信号的值,并用于时序逻辑。clk_div
:这是时钟分频器的信号,用来生成2ms的时钟。cnt
:这是时钟的计数器,用于控制分频。cnt_s2
、cnt_s3
:分别为s_2
和s_3
按键计数的寄存器。cnt_s2_d1
:记录从1到20的计数。display_digits [0:4]
:存储要在数码管上显示的5个数字的寄存器。shift_reg_s2
、shift_reg_s3
:这是用于s_2
和s_3
信号的移位寄存器,用来进行边沿检测。s3_counter_stable
:这是一个用于s_3
信号去抖动的计数器。
num_ones
:这是一个组合逻辑的输出信号,用来计算拨码开关dip_switch
中有多少个开关是打开(‘1’)的。
上升沿检测和防抖处理
s2的上升沿检测和防抖设计
1 | always @(posedge clk or posedge rst) begin |
- 这个块用于检测
s2
的上升沿。shift_reg_s2
是一个移位寄存器,shift_reg_s2 <= {shift_reg_s2[1:0], s_2}
的意思是把寄存器中的每个值往前移一位,同时将最新的s_2
信号放入最低位。pos_edge_s2
是一个组合逻辑,用于检测上升沿,只有在shift_reg_s2[2]
是0,shift_reg_s2[1]
是1时,才会产生上升沿信号。
s3的上升沿检测和防抖设计
1 | always @(posedge clk or posedge rst) begin |
这里是
s3
按键的防抖处理,防止一次按键会被多次识别。shift_reg_s3
用来记录s3
的上升沿变化,如果s3
保持不变,计数器s3_counter_stable
会递增,当超过150000
时,表示 s3 信号已经稳定超过15ms
,不再有抖动。pos_edge_s3_debounce
是通过s3_sampled
来捕获防抖处理后的 s3 上升沿信号。
实现0.1S周期从1到20的计数器
1 | /* Count for 0.1s interval (s2) */ |
手动计数器
1 | always @(posedge clk or posedge rst) begin |
数字转换为数码管格式
1 | /* Convert digit value to 7-segment display format */ |
这个函数的作用是将输入的4位二进制数字转换为7段显示器的对应格式,方便在实际的数字电路设计中显示相应的数字。通过case
语句,可以清晰地映射输入和输出,从而实现简洁的逻辑设计。
更新显示数字
1 | always @(posedge clk or posedge rst) begin |
时间分频
1 | /* Clock divider (2ms) */ |
更新数码管的显示和使能信号
1 | /* Update led_cx and led_en for 7-segment display */ |
设计文件代码勘误
勘误后完整代码
1 | module led_display_ctrl #( |
勘误逻辑分析———基于延时法
在经过了最终实际的上板修改后,发现之前的代码还是存在一定的问题的。就是上板后手动计数器会不工作,一直显示为0。查找原因之后发现了还是对s3按键的防抖动处理这里逻辑存在问题。故修改防抖动逻辑设计(我自己的代码最终烧下来还是存在一定的抖动,但是效果还行…)
我这里采用的是延时法的一个代码实现:
1 | /* Debounce logic for s3 */ |
第一个if
语句块是一个复位逻辑,当rst被激活的时候,所有的状态变量都置为0。
debounce_counter
:计数器,用于计时。s3_last
:上一个输入信号的状态。cnt_s3
:有效按下的计数器。debounce_active
:标志,指示防抖动逻辑是否处于激活状态。
如果rst在低电平,那就正常工作。我们判断if (s_3 && !s3_last && !debounce_active) begin
这个条件,决定是否激活防抖动。
如果防抖动逻辑处于活动状态(debounce_active
为1),则增加计数器 debounce_counter
的值,用于计时。
检查计数器是否达到了设定值(10000000),如果是:
将 debounce_active
设置为0,表示防抖动结束。
如果 s_3
仍为高电平,增加 cnt_s3
的值,记录有效按下的次数。
每个时钟周期结束时,将 s3_last
更新为当前的 s_3
状态,以便在下一次判断时使用。
进一步优化设计文件——基于移位寄存器和计数器
用这种方法来进行消抖设计,会更稳定,效果很好。
完整代码
感谢舍友提供的这一份代码:
1 | always @(posedge clk or posedge rst) |
逐步逻辑分析
整体逻辑总结
- 历史状态存储:移位寄存器有效地保留了最近的按键状态,为上升沿检测提供了准确的依据。
- 稳定性判断:通过设置多周期状态判断(连续高电平)与计时器的结合,提升了对信号稳定性的检测能力。
- 避免误触发:只有在信号稳定并满足时间条件后,才会更新有效的按下标志,确保系统响应的可靠性。
仿真代码testbench.v
1 | `timescale 1ns / 1ps |
约束文件
1 | set_property PACKAGE_PIN Y18 [get_ports clk] |
总结
最后跑了一下,仿真还是有点问题,上板没问题。
再更(应该是最后一更了)
问题找到了,之前仿真跑出来有问题是因为这个计数器的debounce_count的值设置得太大了,导致仿真跑下来没有办法满足条件使得debounce_count>10000,仿真的时候把这个参数改成10就没问题了。按钮的时间适当加长一些,不然cnt_s3计不上数。
真是计算机学生枯燥又痛苦的一天啊。
-------------本文结束感谢您的阅读-------------