简单抢答器的实现
开发环境
AX301黑金开发版,编译软件quartus
主要功能
第一个文件
主要功能:
1.被动
正向计时,每5秒进行一次蜂鸣
2.主动
KEY2和KEY3同时按下,开始计时
KEY1 KEY2 KEY3分别按下对应LED亮起 且他人的灯不能再点亮,同时计时暂停
RESET重置LED 数码管
第二个文件
1.KEY2和KEY3同时按下,开始9秒倒计时,点亮LED0
2.KEY1 KEY2 KEY3开始作答,一人答题后其他人不可作答
3.如果LED0未亮,有人作答,蜂鸣器响
4.RESET键 一切重置
基本原理
1.按键消抖
问题描述
一个按键按下的理论状态是,立马发生电位变化,瞬间由最高点到底最低点,但实际上不可能实现,而是有略微抖动的变换.
实验中设计了一个计数器,当按键输入有变化时,
计时器清零,否则就累加,直到加到一个预定值(例如 10ms),就认为按键稳定,输出按键值,
这样就得到以后没有抖动的按键值。
代码展示
always @(posedge clk or negedge rst_n)
if (!rst_n) key_rst <= 1'b1;
else key_rst <= sw3_n&sw2_n&sw1_n;
reg key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( posedge clk or negedge rst_n )
if (!rst_n) key_rst_r <= 1'b1;
else key_rst_r <= key_rst;
//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期
wire key_an = key_rst_r & (~key_rst);
reg[19:0] cnt; //计数寄存器
always @ (posedge clk or negedge rst_n)
if (!rst_n) cnt <= 20'd0; //异步复位
else if(key_an) cnt <=20'd0;
else cnt <= cnt + 1'b1;
reg[2:0] low_sw;
always @(posedge clk or negedge rst_n)
if (!rst_n) low_sw <= 3'b111;
else if (cnt == 20'hfffff) //满20ms,将按键值锁存到寄存器low_sw中 cnt == 20'hfffff
low_sw <= {sw3_n,sw2_n,sw1_n};
reg [2:0] low_sw_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( posedge clk or negedge rst_n )
if (!rst_n) low_sw_r <= 3'b111;
else low_sw_r <= low_sw;
//当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期
wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);
reg d1;
reg d2;
reg d3;
always @ (posedge clk or negedge rst_n)
if (!rst_n) begin
d1 <= 1'b0;
d2 <= 1'b0;
d3 <= 1'b0;
end
else begin //某个按键值变化时,LED将做亮灭翻转
if ( led_ctrl[0]&led_d2==1'b0&led_d3==1'b0 ) d1 <= ~d1;
if ( led_ctrl[1]&led_d1==1'b0&led_d3==1'b0 ) d2 <= ~d2;
if ( led_ctrl[2]&led_d1==1'b0&led_d2==1'b0 ) d3 <= ~d3;
end
assign led_d1 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d3 = d3 ? 1'b1 : 1'b0;
2.6位数码管显示
使用方法
- 电路用 PNP 管来反向驱动并且控制列扫描信号(SEL0_T~SEL5_T)来选择哪个数码管。而且所有的 6 个数码管的“段选信号”(LEDA … LEDH)都共用驱动引脚(LED_A~LEDH)。数码管的所有驱动信号都是“低电平有效”。
- 单个数码管可以采用静态显示方式,如图所示,数码管被分成 a、b、c、d、e、f、g 和小数点,每段可以单独控制亮灭,通过点亮不同的段显示不同的数字和字符。
- 对于多位数码管,利用视觉暂留原理,快速交替显示,让眼睛看上去是多个数码管同时显示的。
代码展示
译码模块
always@(*)
begin
case(bin_data)
4'd0:seg_data <= 7'b100_0000;
4'd1:seg_data <= 7'b111_1001;
4'd2:seg_data <= 7'b010_0100;
4'd3:seg_data <= 7'b011_0000;
… … … …
default:seg_data <= 7'b111_1111;
endcase
end
endmodule
//主要为了将二进制信号转化为段控制信号
计数器
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
scan_timer <= 32'd0;
scan_sel <= 4'd0;
end
else if(scan_timer >= SCAN_COUNT)
begin
scan_timer <= 32'd0;
if(scan_sel == 4'd5)
scan_sel <= 4'd0;
else
scan_sel <= scan_sel + 4'd1;
end
else
begin
scan_timer <= scan_timer + 32'd1;
end
end
always@(posedge clk or negedge rst_n)
begin if(rst_n == 1'b0)
begin
seg_sel <= 6'b111111;
seg_data <= 8'hff;
end
else
begin
case(scan_sel)
4'd0:
begin seg_sel <= 6'b11_1110;
seg_data <= seg_data_0;
end
… …
endcase
end
end
模十计数器
always@(posedge clk or negedge rst_n)
begin
if(rst_n==0)
begin
data <= 4'd0;
t <= 1'd0;
end
else if(clr)
begin
data <= 4'd0;
t <= 1'd0;
end
else if(en&led_d1==led_d2==led_d3==0)
begin
if(data==4'd9)
begin
t<= 1'b1; //Counter to 9 to generate carry
data <= 4'd0;
end//Counter to 9 reset
else
begin
t <= 1'b0;
data <= data + 4'd1;
end
end
else
t <= 1'b0;
end
5秒一蜂鸣
always @(posedge clk)
if(data==4'd5)
buzzer <= 1'b0;
else if(data==4'd0)
buzzer <= 1'b0;
else
buzzer <= 1'b1;
问题总结
- 同一信号不能在两个always里改变,但是可以在两个always里分别用作条件
- output从模块调用到主程序时,模块中要设置成reg形式;
- 逻辑层面要设计好,比如倒计时;比如信号到底在哪里修改;比如灵活性,简洁性
- 空的引脚可以不用分配,默认低电平;运行前一定要把电压设置好,还有芯片型号
给读者的问题
1.设置消抖的锁存器的时间,应该设置多少秒合适?
2.数码管交替的频率设置为多少较为合适?
3:请问单个数码管有几个控制段?是否全部可以同时点亮
4:两个always里,可以同时用一个信号作为判断条件么?
1.20ms左右,可以稍微加长;
2.1ms,实际20ms以内就行,太慢会闪烁,太快会导致亮度不够
3.8个,包含小数点;不可以同时点亮,6个通用一个电位
4.可以,只是不能在两处改变
整体代码(觉得麻烦可以直接在我资源里下载)
第一个文件
module seg_test(
input clk, //主时钟信号,50MHz
input rst_n, //复位信号,低有效
input sw1_n,sw2_n,sw3_n, //三个独立按键,低表示按下
output led_d0=0,led_d1,led_d2,led_d3, //发光二极管,分别由按键控制
output buzzer,
output[5:0] seg_sel,
output[7:0] seg_data
);
reg EnFlat=1'b0;//锁存器,一人答题后阻止他人答题
reg key_rst;
always @(posedge clk or negedge rst_n)
if (!rst_n) key_rst <= 1'b1;
else key_rst <= sw3_n&sw2_n&sw1_n;
reg key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( posedge clk or negedge rst_n )
if (!rst_n) key_rst_r <= 1'b1;
else key_rst_r <= key_rst;
//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期
wire key_an = key_rst_r & (~key_rst);
//---------------------------------------------------------------------------
reg[19:0] cnt; //计数寄存器
always @ (posedge clk or negedge rst_n)
if (!rst_n) cnt <= 20'd0; //异步复位
else if(key_an) cnt <=20'd0;
else cnt <= cnt + 1'b1;
reg