Contents
1 实验目的
2 实验仪器
3 子任务
3.1 消抖电路
- 实验原理
- 实验步骤
- 具体实现
- 问题解决
3.2 简易篮球比赛计分器
- 实验原理
- 实验步骤
- 具体实现
- 问题解决
4 结论心得
1 实验目的
- 掌握时序逻辑电路的一般设计方法。
- 掌握消抖电路的设计方法。
- 通过 V e r i l o g Verilog Verilog 语言实现一个简单篮球记分器。
2 实验仪器
- F P G A FPGA FPGA 开发板 D E 1 − S o C DE1-SoC DE1−SoC,如图所示。
3 子任务
3.1 消抖电路
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。按键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
实验原理
抖动时间的长短由按键的机械特性决定,一般为
5
m
s
~
10
m
s
5ms~10ms
5ms~10ms 。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。单片机一般采用延迟重采样的方式进行消抖。当检测到信号为低时,延迟一段时间(一般为
20
m
s
20ms
20ms ),再次检测信号是否为低,如果为低,则证明按键按下,否则认为按键没有按下,继续下一次检查。
最后,通过实现如下功能的电路来验证消抖是否成功。
- 消抖脉冲为 500 H z 500Hz 500Hz。
- 用按键作为消抖电路的输入,消抖结果控制一个十进制计数器,用 1 1 1 位数码管显示计数结果。
计数器迭代的流程如图所示:
实验步骤
- 根据消抖脉冲要求设计分频计数器。
- 设计数码管显示电路。
- 编写 V e r i l o g Verilog Verilog 程序并进行调试。
- 下载到 F P G A FPGA FPGA 开发板上验证程序相关功能。
具体实现
在 Q u a r t u s Quartus Quartus 中先编写消抖电路程序并封装成一个模块,程序如下所示:
Codes
module xiaodou
(
input clk , //输入时钟信号,开发板上是50MHz
input rst_n , //复位键(低电平触发)
input key_in, //对应的机械按键
output reg clk_500hz, //分频出的500Hz时钟脉冲信号
output key_done //按键按下动作完成标志
);
reg [25:0]div_cnt; //分频计数器
always@(posedge clk or negedge rst_n) //获得500Hz时钟脉冲信号
begin
if(!rst_n)
begin
div_cnt <= 0;
clk_500hz <= 0;
end
else if(div_cnt == 99999) //计数十万次反转状态
begin
div_cnt <= 0;
clk_500hz <= ~clk_500hz;
end
else
begin
div_cnt <= div_cnt + 1;
clk_500hz <= clk_500hz;
end
end
reg qout;
reg key_tmp1,key_tmp2;
parameter n = 10;
reg [25:0] cnt;
always@(posedge clk_500hz or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 0;
qout <= 0;
end
else if(key_in == 0) //按键按下
begin
if(cnt == n-1) //持续2ms的话判定按下
begin
cnt <= cnt;
qout <= 1;
end
else
begin
cnt <= cnt+1;
qout <= 0;
end
end
else
begin
qout <= 0;
cnt <= 0;
end
end
/*提取前后按键信号*/
always@(posedge clk_500hz or negedge rst_n)
begin
if(!rst_n)
begin
key_tmp1 <= 0;
key_tmp2 <= 0;
end
else
begin
key_tmp1 <= qout;
key_tmp2 <= key_tmp1;
end
end
assign key_done = key_tmp1 & (~ key_tmp2);
endmodule
消抖模块电路逻辑图
然后编写十进制计数器程序,代码如下:
Codes
module count
(
input clk,
input rst_n,
input key,
output reg[6:0] seg
);
reg[3:0]cnt;
wire key_done;
xiaodou u1
(
.clk (clk ), //需要分频的信号为50Mhz时钟脉冲信号
.rst_n (rst_n ), //复位键
.key_in (key ), //机械按键
.clk_500hz (clk_500hz), //分频得到的500Hz时钟脉冲信号
.key_done (key_done) //按键动作完成标志
);
always@(posedge clk_500hz or negedge rst_n)
begin
if(!rst_n)
cnt <= 0;
else if(key_done) //按下动作完成
begin
if(cnt == 9) //计数到9后需要清零
cnt <= 0;
else
cnt <= cnt + 1;
end
end
always@(cnt) //数码管显示模块
begin
case(cnt)
0:seg = 7'b0000001;
1:seg = 7'b1001111;
2:seg = 7'b0010010;
3:seg = 7'b0000110;
4:seg = 7'b1001100;
5:seg = 7'b0100100;
6:seg = 7'b0100000;
7:seg = 7'b0001111;
8:seg = 7'b0000000;
9:seg = 7'b0000100;
endcase
end
endmodule
十进制计数器电路逻辑图
十进制计数器引脚分配方案
下载验证
问题解决
个人认为,这个实验的主要难点在于如何分频出 500 H z 500Hz 500Hz 的时钟脉冲信号。由于临近期末时间紧迫,我没法系统地深入学习 V e r i l o g Verilog Verilog 语言,因此消抖模块的程序也是找的现成的程序进行学习理解。在最初编写的程序里,我的代码是没有以下这段的。
always@(posedge clk_500hz or negedge rst_n)
begin
if(!rst_n)
begin
key_tmp1 <= 0;
key_tmp2 <= 0;
end
else
begin
key_tmp1 <= qout;
key_tmp2 <= key_tmp1;
end
end
我以为,只要计数够 2 m s 2ms 2ms (就是稳定状态)还是低电平就可以了,这段代码没啥存在的必要,但实际验证貌似还是会有不成功的情况。后来才发现,从原理上来说,机械按键按下存在前沿抖动和后沿抖动,因此需要取前后按键信号的按下状态来进行强判定是否按下。
3.2 简易篮球比赛计分器
利用开发板上现有的按键、开关和数码管资源实现一个简易篮球比赛计分器。
实验原理
简易篮球比赛计分器的基本功能要求如下:
- 甲乙两队的得分分别用两个数码管显示
- 每次可以给甲队或乙队加上 1 1 1 分, 2 2 2 分或 3 3 3 分,比分显示范围 00 00 00 到 99 99 99 。
篮球计分器主要由以下模块构成
实验步骤
- 根据功能要求设计各模块电路。
- 对各模块进行逻辑和算法分析。
- 编写 V e r i l o g Verilog Verilog 程序并进行调试。
- 下载到 F P G A FPGA FPGA 开发板上验证程序相关功能。
具体实现
下面以甲队计分的过程为例描述计分器的设计过程:
在 Q u a r t u s Quartus Quartus 中编写程序,总代码如下:
Codes
module ball
(
input [2:0] o, //表示对应加1,2,3分按键的状态
input m, //甲队加分控制端
input m1, //乙队加分控制端
input clk, //50MHz时钟脉冲信号
input rst_n, //复位键
input key,
output reg [6:0] ans1,
output reg [6:0] ans2,
output reg [6:0] ans3,
output reg [6:0] ans4
);
integer i;
reg [3:0] l; //甲队分数的十位
reg [3:0] r; //甲队分数的个位
reg [3:0] l1; //乙队分数的十位
reg [3:0] r1; //乙队分数的个位
//reg [3:0] key1;
//reg [3:0] key2;
//reg [3:0] key3;
wire key_done; //有按键按下
xiaodou u1 //消抖模块,代码与子任务3.1中略有不同
(
.clk (clk ),
.rst_n (rst_n ),
.key_in1 (o[0] ),
.key_in2 (o[1] ),
.key_in3 (o[2] ),
.clk_500hz (clk_500hz),
.key_done (key_done)
);
always@(posedge clk_500hz or negedge rst_n)
begin
if (!rst_n) begin //复位
l=0; r=0; l1=0; r1=0;
end
else begin
if (key_done) begin //当有加分按键按下时
for (i=0; i<=2; i=i+1)
begin
if (!o[i]) begin //判断是哪个按键按下
case (i)
0:begin
if (m) begin //给甲队加分
r=r+1;
if (r==10) begin
r=0;
l=l+1;
end
end
if (m1) begin //给乙队加分
r1=r1+1;
if (r1==10) begin
r1=0;
l1=l1+1;
end
end
end
1:begin
if (m) begin
r=r+2;
if (r>=10) begin
r=r-10;
l=l+1;
end
end
if (m1) begin
r1=r1+2;
if (r1>=10) begin
r1=r1-10;
l1=l1+1;
end
end
end
2:begin
if (m) begin
r=r+3;
if (r>=10) begin
r=r-10;
l=l+1;
end
end
if (m1) begin
r1=r1+3;
if (r1>=10) begin
r1=r1-10;
l1=l1+1;
end
end
end
endcase
end
end
end
end
end
always @ (l)
begin
case(l)
0:ans1 = 7'b0000001;
1:ans1 = 7'b1001111;
2:ans1 = 7'b0010010;
3:ans1 = 7'b0000110;
4:ans1 = 7'b1001100;
5:ans1 = 7'b0100100;
6:ans1 = 7'b0100000;
7:ans1 = 7'b0001111;
8:ans1 = 7'b0000000;
9:ans1 = 7'b0000100;
endcase
end
always @ (r)
begin
case(r)
0:ans2 = 7'b0000001;
1:ans2 = 7'b1001111;
2:ans2 = 7'b0010010;
3:ans2 = 7'b0000110;
4:ans2 = 7'b1001100;
5:ans2 = 7'b0100100;
6:ans2 = 7'b0100000;
7:ans2 = 7'b0001111;
8:ans2 = 7'b0000000;
9:ans2 = 7'b0000100;
endcase
end
always @ (l1)
begin
case(l1)
0:ans3 = 7'b0000001;
1:ans3 = 7'b1001111;
2:ans3 = 7'b0010010;
3:ans3 = 7'b0000110;
4:ans3 = 7'b1001100;
5:ans3 = 7'b0100100;
6:ans3 = 7'b0100000;
7:ans3 = 7'b0001111;
8:ans3 = 7'b0000000;
9:ans3 = 7'b0000100;
endcase
end
always @ (r1)
begin
case(r1)
0:ans4 = 7'b0000001;
1:ans4 = 7'b1001111;
2:ans4 = 7'b0010010;
3:ans4 = 7'b0000110;
4:ans4 = 7'b1001100;
5:ans4 = 7'b0100100;
6:ans4 = 7'b0100000;
7:ans4 = 7'b0001111;
8:ans4 = 7'b0000000;
9:ans4 = 7'b0000100;
endcase
end
endmodule
其中,消抖电路模块代码如下:
Codes
module xiaodou
(
input clk ,
input rst_n ,
input key_in1,
input key_in2,
input key_in3,
output reg clk_500hz,
output key_done
);
reg [25:0]div_cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
div_cnt <= 0;
clk_500hz <= 0;
end
else if(div_cnt == 99999)
begin
div_cnt <= 0;
clk_500hz <= ~clk_500hz;
end
else
begin
div_cnt <= div_cnt + 1;
clk_500hz <= clk_500hz;
end
end
reg qout;
reg key_tmp1,key_tmp2;
parameter n = 10;
reg [25:0] cnt;
always@(posedge clk_500hz or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 0;
qout <= 0;
end
else if(key_in1==0 || key_in2==0 || key_in3==0) //此处与子任务3.1中的条件不同
begin
if(cnt == n-1)
begin
cnt <= cnt;
qout <= 1;
end
else
begin
cnt <= cnt+1;
qout <= 0;
end
end
else
begin
qout <= 0;
cnt <= 0;
end
end
always@(posedge clk_500hz or negedge rst_n)
begin
if(!rst_n)
begin
key_tmp1 <= 0;
key_tmp2 <= 0;
end
else
begin
key_tmp1 <= qout;
key_tmp2 <= key_tmp1;
end
end
assign key_done = key_tmp1 & (~ key_tmp2);
endmodule
简易篮球计分器电路逻辑图
简易篮球计分器引脚分配方案
下载验证
问题解决
这个实验是一个综合度较高的基础实验,我个人也是遇到了不少的问题,经过不断修改调试,总结了以下几个主要的问题:
- V e r i l o g Verilog Verilog 语言中一个寄存器变量不能在两个 a l w a y s always always 语句(有限状态机)中赋值。
- V e r i l o g Verilog Verilog 语言虽然为硬件程序设计语言,但是不同于 C C C 语言等传统软件编程语言,有些复杂逻辑(比如多重 i f if if 语句嵌套,或者是上面那个程序中 if (!rst_n) 改成 if (rst_n)),虽然语法正确,但是系统综合分析就是会失败,报错提示顶层的模块名要跟工程文件名一致。
- 在主程序向消抖模块传递参数时,不能只把按键存储的寄存器变量 o o o 直接传递,这样它会默认只判断寄存器最低位的状态,不管高位的状态了。
主要的解决方法就是:
- 从硬件原理上来说,如果一个变量能在两个有限状态机中赋值,那么这个逻辑其实不是那么牢靠的,可能存在冲突现象,因此要避免。
- 降低算法复杂度,尽量避免多重 i f if if 嵌套。
- 将某一按键动作判断归一化为只要所有按键中有一个按下就判定按下成立(或逻辑),然后通过循环扫描哪一个按键的电平是低电平(低电平触发)选择具体的按键。