FPGA实战——按键控制LED灯
实验任务
无按键按下时,LED 灯全灭;
按键 key0 按下时,LED 灯显示自左往右的流水;
按键 key1 按下时,LED 灯显示自右往左的流水;
按键 key2 按下时,四个 LED 灯同时闪烁;
按键 key3 按下时,LED 灯全亮。
硬件电路
程序设计
1.RTL文件编写
- 流水灯是需要计数器的,所以我们先定义计数器。这个计数器以0.2s为周期。
- 如果key0被按下,是需要流水灯的,我们就先设置一个led_ctrl变量,来为led目前的状态编码,这个状态每计数器周期就变化一次(也就是一直在流水,就是我们在硬件电路是否调用的问题,调用它就显示出流水)
- 我们检测按键是否被按下,当key0被按下的时候,我们就对led_ctrl进行判断,对4个状态进行赋值(led_ctrl有4个状态,分别对应4个流水灯亮的状态),一直按着key0就可以实现流水
- 同理,剩余3个按键的就很简单了。
- 总结来就是,我们需要一个计数器(一个always块),一个控制led状态的程序(一个always块),一个检测按键,控制led(一个always块)
程序代码如下:
module key_led( input sys_clk, input sys_rst_n, input [3:0] key, output reg [3:0] led ); reg [23:0] cnt; //计数器 reg [1:0] led_ctrl; //led状态变量 //0.2秒计数器 always @ (posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) cnt <= 24'd0; else if(cnt < 24'd1000_0000) cnt <= cnt + 1'b1; else cnt <= 24'd0; //这是已经数到最大值了,就赋值0 end //led状态赋值 ,这是控制led的状态的,只有流水的时候有用 always @ (posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) led_ctrl <= 2'b00; else if(cnt == 24'd1000_0000) //计数器周期0.2s,所以led每0.2s状态加一 led_ctrl <= led_ctrl + 1'b1; else led_ctrl <= led_ctrl; end //按键控制led状态 always @ (posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) led <= 4'b0000; else if(key[0] == 0) //如果key0按下,实现从左向右的流水灯 case(led_ctrl) 2'b00 : led <= 4'b0001; 2'b01 : led <= 4'b0010; 2'b10 : led <= 4'b0100; 2'b11 : led <= 4'b1000; default: led <= 4'b0000; endcase else if(key[1] == 0) //如果key1按下,实现从右向左的流水灯 case(led_ctrl) 2'b11 : led <= 4'b0001; 2'b10 : led <= 4'b0010; 2'b01 : led <= 4'b0100; 2'b00 : led <= 4'b1000; default: led <= 4'b0000; endcase else if(key[2] == 0) //如果key2按下,所有灯同时闪烁 case(led_ctrl) 2'b00 : led <= 4'b1111; 2'b01 : led <= 4'b0000; 2'b10 : led <= 4'b1111; 2'b11 : led <= 4'b0000; default: led <= 4'b0000; endcase else if(key[3] == 0) //如果key3按下,所有灯都亮。 led <= 4'b1111; else led <= 4'b0000; end endmodule
讯享网
2.约束文件ucf
讯享网NET sys_clk TNM_NET = sys_clk_pin; TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 20ns HIGH 50%; NET sys_clk LOC = T8 | IOSTANDARD = LVCMOS33; NET sys_rst_n LOC = L3 | IOSTANDARD = LVCMOS33; NET led<0> LOC = P4 | IOSTANDARD = LVCMOS33; NET led<1> LOC = N5 | IOSTANDARD = LVCMOS33; NET led<2> LOC = P5 | IOSTANDARD = LVCMOS33; NET led<3> LOC = M6 | IOSTANDARD = LVCMOS33; NET key<0> LOC = C3 | IOSTANDARD = LVCMOS33; NET key<1> LOC = D3 | IOSTANDARD = LVCMOS33; NET key<2> LOC = E4 | IOSTANDARD = LVCMOS33; NET key<3> LOC = E3 | IOSTANDARD = LVCMOS33;
实际上,不用看后面的 ;提示,问题出在,没有加等号=
TNM_NET = sys_clk_pin; 被写成了
TNM_NET sys_clk_pin; 就报错了

而且有意思的是,这个错误两次都犯过,都是刚开始没意识到的时候,第一次编译总是没有报错,第二次编译才没通过,很奇怪,如果有大佬看见,希望告诉一下原因。
3.下载程序
编译完成之后,就可以生成bit流文件然后下载到开发板了。
RTL图

综合之后就可以看,但是实验来看,需要编译成功,才能看到我们期望看到的那个图。如下图设置好端口,点击右下角的Create Schematic即可。

生成RTL图如下

仿真tb文件编写
因为按键检测,硬件上需要按键去按下松开,是我们人的操作,那tb文件去仿真的时候如何实现呢。我们的思路就是每延时几秒,通过程序让key[i] = 0,通过软件赋值来实现。
//注意,我们要写的操作是在initial中就完成,而不是在always这里,always这是生成时钟。
//新建文件后, 从头开始修改、编写代码!!!修改模块名,initial语句块的赋值等。。。。
`timescale 1ns / 1ns module tb_key_led(); // Inputs reg sys_clk; reg sys_rst_n; reg [3:0] key; // Outputs wire [3:0] led; // Instantiate the Unit Under Test (UUT) key_led u_key_led ( .sys_clk(sys_clk), .sys_rst_n(sys_rst_n), .key(key), .led(led) ); initial begin // Initialize Inputs sys_clk <= 1'b0; sys_rst_n <= 1'b0; key <= 4'b1111; // Wait 100 ns for global reset to finish #100; sys_rst_n <= 1'b1; // Add stimulus here // 1ns * 800_000_000 = 0.8s #2000_0000 key[0] <= 0; #2000_0000 key[0] <= 1; key[1] <= 0; #2000_0000 key[1] <= 1; key[2] <= 0; #2000_0000 key[2] <= 1; key[3] <= 0; #2000_0000 key[3] <= 1; end //注意,我们要写的操作是在initial中就完成,而不是在always这里,always这是生成时钟 always #10 sys_clk = ~sys_clk; endmodule
出现了一个错误
(vopt-2133) Instantiating ‘u_key_led’ has exceeded the recursion depth limit of 200.

就是这里重名了,导致嵌套死循环。修改如下
最终1s时间内的仿真如图:

以上就是按键控制LED闪烁的工程,包括例化、按键消抖等还未设计,将在之后的工程练习中学习。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/57278.html