1. led流水灯的实现
硬件说明**
方法一:模块化设计:在之前的实验中我们做了3-8译码器和时钟分频,如果把这两个结合起来,我们就能搭建一个自动操作的流水LED显示。框图如下:
方法二:循环赋值:这是一种很简洁的实现流水灯效果逻辑,就是定义一个8位的变量,在每个时钟上升沿将最低位赋值给最高位,其他位右移一位,这就实现了循环赋值。这8位输出到LED就能实现流水灯。
verilog的实现
方法一实现:
module flashled (clk,rst,led);
input clk,rst;
output [7:0] led;
reg [2:0] cnt ; //定义了一个3位的计数器,输出可以作为3-8译码器的输入
wire clk1h; //定义一个中间变量,表示分频得到的时钟,用作计数器的触发
//例化module decode38,相当于调用
decode38 u1 (
.sw(cnt), //例化的输入端口连接到cnt,输出端口连接到led
.led(led)
);
//例化分频器模块,产生一个1Hz时钟信号
divide #(.WIDTH(32),.N(12000000)) u2 ( //传递参数
.clk(clk),
.rst_n(rst), //例化的端口信号都连接到定义好的信号
.clkout(clk1h)
);
//1Hz时钟上升沿触发计数器,循环计数
always @(posedge clk1h or negedge rst)
if (!rst)
cnt <= 0;
else
cnt <= cnt +1;
endmodule
方法二实现:
module flashled (clk,rst,led);
input clk,rst;
output [7:0] led;
wire clk1h; //定义一个中间变量,表示分频得到的时钟,用作计数器的触发
//例化分频器模块,产生一个1Hz时钟信号
divide #(.WIDTH(32),.N(12000000)) u2 ( //传递参数
.clk(clk),
.rst_n(rst), //例化的端口信号都连接到定义好的信号
.clkout(clk1h)
);
//1Hz时钟上升沿触发循环赋值
reg [7:0] led;
always@(posedge clk1h or negedge rst)
begin
if(!rst)
led <= 8'b11111110; // <=为非阻塞赋值
else
led <= {led[0],led[7:1]}; //当时钟上升沿来一次,执行一次赋值,赋值内容是led[0]与led[7:1]重新拼接成8位赋给led,相当于循环右移
end
endmodule
引脚分配
按照下面表格定义输入输出信号
信号 | 引脚 | 信号 | 引脚 |
---|---|---|---|
clk | C1 | led[3] | M11 |
rst | L14 | led[4] | P11 |
led[0] | N13 | led[5] | N10 |
led[1] | M12 | led[6] | N9 |
led[2] | P12 | led[7] | p9 |
2.按键消抖
硬件说明
按键抖动的原理
抖动的产生 :通常的按键所用的开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
消除抖动的措施:一般我们采用软件方法消抖。即检测到按键按下动作之后进行10ms-20ms左右的延时,当前沿的抖动消失之后再一次检测按键的状态。如果仍然是按下的电平状态,则认为这是一次真正的按键按下;同样检测到按键释放,也要做10ms~20ms延时,检测到后沿抖动消失后认为是一个完整的按键弹起过程。
消抖的好处:执行按键消抖有两个好处,
消除误触发:我们想通过按键来翻转信号(例如按下一次led亮,在按一次led灭),如果没有进行消抖,则会产生很多误触发造成不必要的翻转。
记录按键次数:执行按键消抖可以让我们记录按键动作的次数,在很多应用里这非常有用。
-
[1 ] 要消除按键的抖动,我们需要去扫描按键,也就是不断的去采集按键的状态。软件消抖时我们一般只考虑按键按下时的抖动,而放弃对释放时抖动的消除。用系统时钟(频率较高)去采集按键状态,当检测到按下时用计数器延时20ms,再去检测按键状态,如果这时仍为按下状态,确认是一次按下动作,否侧的话认为无按键按下。如何检测按键状态变化就需要用到脉冲边沿检测的方法。
脉冲边沿检测 -
[ 1] 检测按键按下时要用到脉冲边沿检测的方法,捕捉信号的突变、捕捉时钟的上升下降沿等经常会用到这种方法。简单地说就是用一个频率更高的时钟去触发要检测的信号,用两个寄存器去储存相邻两个时钟采集到的值,然后进行异或运算,如果不为零,代表发生了上升沿或者下降沿。
-[2] 在按键消抖的过程中,同样运用了脉冲边沿检测。用两个寄存器储存相邻时钟采集的值(例如datapre,data),然后将data取反与前一个值相与(state=datapre&(~data)),如果为1,则判断有下降沿既按键按下由高到低;否则无变化。
将一个信号由连续时钟采集,相邻两个钟触发的值存入两个寄存器
Verilog代码
当按下一次led变亮,再按下一次led变暗。首先做个试验,对按键不做处理通过按键来控制led翻转。module top( key, //按键输入 rst, //复位输入 led //led输出 ); input key,rst; output reg led; always @(key or rst) if (!rst) //复位时led熄灭 led = 1; else if(key == 0) led = ~led; //按键按下时led翻转 else led = led; endmodule
未经过消抖的程序下载到小脚丫上会发现按键有时不能够控制led翻转,这是因为按键的抖动造成了led状态变化不可控,所以我们必须将抖动消除。下面是一种延时去抖的程序
module debounce (clk,rst,key,key_pulse);
parameter N = 1; //要消除的按键的数量
input clk;
input rst;
input [N-1:0] key; //输入的按键
output [N-1:0] key_pulse; //按键动作产生的脉冲
reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值
reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值
wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲
//利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
always @(posedge clk or negedge rst)
begin
if (!rst) begin
key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1
key_rst_pre <= {N{1'b1}};
end
else begin
key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
end
end
assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
reg [17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器
//产生20ms延时,当检测到key_edge有效是计数器清零开始计数
always @(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 18'h0;
else if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end
reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量
reg [N-1:0] key_sec;
//延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec <= {N{1'b1}};
else if (cnt==18'h3ffff)
key_sec <= key;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec_pre <= {N{1'b1}};
else
key_sec_pre <= key_sec;
end
assign key_pulse = key_sec_pre & (~key_sec);
endmodule
以上就是一个N位按键的消抖程序,如果有按键按下会输出一个时钟周期的高脉冲。下面我们可以试试用这个按键消抖的输出来触发LED的显示,既按键一次LED翻转。你也可以不加按键消抖试试用按键来控制LED(按一次变亮,再按一次灭掉)。
下面的程序是例化调用debounce模块来控制LED
module top (clk,rst,key,led);
input clk;
input rst;
input key;
output reg led;
wire key_pulse;
//当按键按下时产生一个高脉冲,翻转一次led
always @(posedge clk or negedge rst)
begin
if (!rst)
led <= 1'b1;
else if (key_pulse)
led <= ~led;
else
led <= led;
end
//例化消抖module,这里没有传递参数N,采用了默认的N=1
debounce u1 (
.clk (clk),
.rst (rst),
.key (key),
.key_pulse (key_pulse)
);
endmodule
引脚分配
3.计时控制
实现一个篮球赛场上常见的24秒计时器
硬件说明
实现由数码管作为显示模块,按键作为控制信号的输入(包含复位信号和暂停信号),Lattice MXO2 4000HC作为控制核心的篮球读秒系统,实现框图如下:
Verilog代码
module counter
(
clk , //时钟
rst , //复位
hold , //启动暂停按键
seg_led_1 , //数码管1
seg_led_2 , //数码管2
led //led
);
input clk,rst;
input hold;
output [8:0] seg_led_1,seg_led_2;
output reg [7:0] led;
wire clk1h; //1Hz时钟
wire hold_pulse; //按键消抖后信号
reg hold_flag; //按键标志位
reg back_to_zero_flag ; //计时完成信号
reg [6:0] seg [9:0];
reg [3:0] cnt_ge; //个位
reg [3:0] cnt_shi; //十位
initial
begin
seg[0] = 7'h3f; // 0
seg[1] = 7'h06; // 1
seg[2] = 7'h5b; // 2
seg[3] = 7'h4f; // 3
seg[4] = 7'h66; // 4
seg[5] = 7'h6d; // 5
seg[6] = 7'h7d; // 6
seg[7] = 7'h07; // 7
seg[8] = 7'h7f; // 8
seg[9] = 7'h6f; // 9
/*若需要显示A-F,
seg[10]= 7'hf7; // A
seg[11]= 7'h7c; // b
seg[12]= 7'h39; // C
seg[13]= 7'h5e; // d
seg[14]= 7'h79; // E
seg[15]= 7'h71; // F
end
// 启动/暂停按键进行消抖
debounce U2 (
.clk(clk),
.rst(rst),
.key(hold),
.key_pulse(hold_pulse)
);
// 用于分出一个1Hz的频率
divide #(.WIDTH(32),.N(12000000)) U1 (
.clk(clk),
.rst_n(rst),
.clkout(clk1h)
);
//按键动作标志信号产生
always @ (posedge hold_pulse)
if(!rst==1)
hold_flag <= 0;
else
hold_flag <= ~hold_flag;
//计时完成标志信号产生
always @ (*)
if(!rst == 1)
back_to_zero_flag <= 0;
else if(cnt_shi==0 && cnt_ge==0)
back_to_zero_flag <= 1;
else
back_to_zero_flag <= 0;
//24秒倒计时控制
always @ (posedge clk1h or negedge rst) begin
if (!rst == 1) begin
cnt_ge <= 4'd4;
cnt_shi <= 4'd2;
end
else if(hold_flag == 1)begin
cnt_ge <= cnt_ge;
cnt_shi <= cnt_shi;
end
else if(cnt_shi==0 && cnt_ge==0) begin
cnt_shi <= cnt_shi;
cnt_ge <= cnt_ge;
end
else if(cnt_ge==0)begin
cnt_ge <= 4'd9;
cnt_shi <= cnt_shi-1;end
else
cnt_ge <= cnt_ge -1;
end
//计时完成点亮led
always @ ( back_to_zero_flag)begin
if (back_to_zero_flag==1)
led = 8'b0;
else
led = 8'b11111111;
end
assign seg_led_1[8:0] = {2'b00,seg[cnt_ge]};
assign seg_led_2[8:0] = {2'b00,seg[cnt_shi]};
endmodule
引脚分配