【FPGA工程(1)-------点灯大师(1)led灯闪烁流水跑马+按键】
FPGA实战
前言
用verliog语言点亮FPGA开发板上的led灯是最最最最最基础的操作。
这里用的EP4CE6F17C8开发板,上边一共有四个led灯珠,可以实现简单的例如4个全亮,流水灯,跑马灯以及相比之下难了一点的呼吸灯等等等等效果。
本文最终目的就是实现用按键切换led灯的运动模式。做一个真正的“点灯大师1.0”。
一、认识led灯珠
四个led灯共阴极接地,所以需要高电平导通led灯。也就是代码中led=1,运用到电路中led才会亮。
二、工程模块设计
1.系统架构图
图中,时钟、复位、按键为输入信号,led为输出信号,这个图看起来简单没什么难度。
2.led闪烁
在上边已经说过了:
四个led灯共阴极接地,所以需要高电平导通led灯。也就是代码中led=1,运用到电路中led才会亮
所以对于代码中的led:1亮0灭。
*************************<led>**********************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 4'b0001 ; //复位时亮一个
end
-----------------------------<闪烁>-----------------------------------------
else if(key_in[1]==0 && end_cnt_300ms)begin //按下key_in[1]
led <= ~led ; //led[3:1]和led[0]分开闪烁
end
复位时亮一个为了之后的流水灯设计方便,后边认真看就看懂了。
这里长按key[1]时四个led灯珠会在计数器每次结束计数后变成与当前亮灭状态相反的状态。从而实现了闪烁。
3.流水灯实现-----计数器
这里实现一个从左向右流动的流水灯。如图,四个灯珠将从左向右依次亮灭。而实现这一效果的方法需要在代码中用到“计数器”这样的东西。
计数是一种最简单基本的运算,计数器就是实现这种运算的逻辑电路,计数器在数字系统中主要是对脉冲的个数进行计数,以实现测量、计数和计时的功能。
计数器也是在 FPGA 设计中最常用的一种时序逻辑电路,根据计数器的计数值我们可以精确的计算出 FPGA 内部各种信号之间的时间关系,每个信号何时拉高、何时拉低、拉高多久、拉低多久都可以由计数器实现精确的控制。而让计数器计数的是由外部晶振产生的时钟,所以可以比较精准的控制具体需要计数的时间。计数器一般都是从 0 开始计数,计数到我们需要的值或者计数满溢出后清零,并可以进行不断的循环。
下边是本篇代码中的计数器模版。
//------------------------<计时器>---------------------------
parameter MAX_300MS = 24'd15_000_000 ;
reg [23:0] cnt_300ms ;//300ms计数器
wire add_cnt_300ms ;
wire end_cnt_300ms ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_300ms <= 'd0 ;
end
else if(add_cnt_300ms)begin
if(end_cnt_300ms)begin
cnt_300ms <= 'd0 ;
end
else begin
cnt_300ms <= cnt_300ms + 1'b1 ;
end
end
end
assign add_cnt_300ms = key_in[1]==0 || key_in[2]==0 || key_in[3]==0 ;//按下三个按键开始计时
assign end_cnt_300ms = add_cnt_300ms && cnt_300ms == MAX_300MS - 1;
在这里 add_cnt_300ms 和 end_cnt_300ms 分别是这个计数器的开始条件和结束条件。这两个条件相当的重要。决定了这个计数器是否能用。
当长按三个按键时计数器会开始计数,且每经过一个周期就加一;当计数到MAX_300MS - 1时,计数器归零。
我们计数的时钟就用系统时钟50MHz,换算成时间为**[1/(50_000_000)Hz]s = 0.000_000_02s**,也就是说50MHz频率的时钟一个周期的时间为20ns。
所以代码中计数器的最大值是14,999,999(因为从0开始计数,所以0也算一位)。
而最大值的位宽看图中的二进制有24位,所以MAX_300MS=24'd14,999,999
(代码块end_cnt_300ms定义中用了“-1”,都一样,个人习惯) 。
以上是计数器的运用,知道了这些,接下来就是形成流水灯了。
*************************<led>**********************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 4'b0001 ;//复位时亮一个
end
-----------------------------<流水灯>-----------------------------------------
else if(key_in[2]==0 && end_cnt_300ms)begin
led <= {led[0],led[3:1]};//长按key[2]从左到右的流水灯
end
上述代码是工程中形成流水灯的代码,不要震惊,对,就是这么短。
代码中四个led在复位的情况下让其亮一个就是为了让流水灯的代码可以不那么繁琐。不亮也可以做出来,就是用case语句,不过下面的跑马灯中用了case代码,这里就不用了。
代码中“{led[0],led[3:1]}”运用了“拼接符{}”,这样写相当于让led[0]移到了led[3]的左边,led[0]变成了新的led[3],led[1]顶替了原来的led[0],而在换位过程中,led[0]一直亮着,所以就呈现出了流水的效果。
4.跑马灯实现------case语句
其实我也不知道跑马灯是啥,大体也就下面那么个跑法,俺也不知道为啥叫跑马灯,只是都这么叫(-_-')(图画得不好,献丑了)。
要实现跑马灯,在这里使用了一个“case语句”
case语句检查给定的表达式是否与列表中的其他表达式之一相匹配,并据此进行分支。它通常用于实现一个多路复用器。
简单来说,就是定义一个数据,有一些不一样的数值,让led(需要改变的数)在这个数据相应的数值去改变灯珠的亮灭。也可以说随着这个数据的变化,去改变led,这个数据是什么值的时候,led必须是一个相对应的亮灭。
case(控制表达式/值)
分支表达式:执行语句
default:执行语句
endcase
以case关键字开始,以endcase关键字结束。在括弧内的表达式将被精确地评估一次,并按其编写顺序与备选方案列表进行比较,与给定表达式匹配的备选方案的语句将被执行。
//------------------------<state>---------------------------
reg [2:0] state ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= 0 ;
end
else if(key_in[3]==0 && end_cnt_300ms)begin
state <= state + 1 ;
end
else begin
state <= state ;
end
end
***********************<led跑马>**********************************
else if(key_in[3]==0)begin//长按key[3]跑马灯
case (state)
3'd0 : led <= 4'b0001 ;
3'd1 : led <= 4'b0011 ;
3'd2 : led <= 4'b0111 ;
3'd3 : led <= 4'b1111 ;
3'd4 : led <= 4'b0111 ;
3'd5 : led <= 4'b0011 ;
default: led <= 4'b0001 ;
endcase
end
在代码中,state就是case中的“控制表达式/值”,将led亮灭情况(执行语句)与state的编写顺序一一对应并执行。
依靠这些代码,实现led跑马灯。
总体代码
/*
* @Projcet: led
* @Author: Yang.
* @Date: 2023-08-22 18:59:46
* @LastEditors: Yang.
* @LastEditTime: 2023-09-29 21:58:06
*/
//---------<模块及端口声名>------------------------------------------------------
module led(
input clk ,
input rst_n ,
input [3:1] key_in , //key_in[0]给到E15引脚复位
output reg [3:0] led
);
//------------------------<参数定义>---------------------------
reg [2:0] state ;
parameter MAX_300MS = 24'd15_000_000 ;
//------------------------<计时器>---------------------------
reg [23:0] cnt_300ms ;//300ms计数器
wire add_cnt_300ms ;
wire end_cnt_300ms ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_300ms <= 'd0 ;
end
else if(add_cnt_300ms)begin
if(end_cnt_300ms)begin //计满归零
cnt_300ms <= 'd0 ;
end
else begin
cnt_300ms <= cnt_300ms + 1'b1 ;
end
end
end
assign add_cnt_300ms = key_in[1]==0 || key_in[2]==0 || key_in[3]==0 ;//按下三个按键开始计时
assign end_cnt_300ms = add_cnt_300ms && cnt_300ms == MAX_300MS - 1;//计满数
//------------------------<state>---------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= 0 ;
end
else if(key_in[3]==0 && end_cnt_300ms)begin
state <= state + 1 ;
end
else begin
state <= state ;
end
end
//****************************************************************
//--按键点灯
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 4'b0001 ;//复位时亮一个
end
//闪烁
else if(key_in[1]==0 && end_cnt_300ms)begin//按下key_in[1]
led <= ~led ;//led[3:1]和led[0]分开闪烁
end
//流水
else if(key_in[2]==0 && end_cnt_300ms)begin
led <= {led[0],led[3:1]};//长按key[2]从左到右的流水灯
end
//跑马
else if(key_in[3]==0)begin//长按key[3]跑马灯
case (state)
3'd0 : led <= 4'b0001 ;
3'd1 : led <= 4'b0011 ;
3'd2 : led <= 4'b0111 ;
3'd3 : led <= 4'b1111 ;
3'd4 : led <= 4'b0111 ;
3'd5 : led <= 4'b0011 ;
default: led <= 4'b0001 ;
endcase
end
//初始
else if(key_in[1] != 0 && key_in[2] != 0 && key_in[3] != 0)begin//没有按键按下的时候led回初始状态
led <= 4'b0001 ;
end
end
endmodule
总结
这就是上板效果,仿真懒得写了,有空再填。
点灯大师(1)------按键控制led
博客上传的工程都在百度网盘,有压缩包也有整个文件,可以自行下载。
链接:https://pan.baidu.com/s/1boY0JLOdO53gO1Mu1kg1Yg?pwd=aysz
提取码:aysz文章来源:https://www.toymoban.com/news/detail-764430.html
点灯虽然简单也有意思,下一章进阶到点灯大师2.0,各位,来做呼吸灯。文章来源地址https://www.toymoban.com/news/detail-764430.html
到了这里,关于FPGA实战-----点灯大师(1)led灯闪烁流水跑马+按键的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!