对于FPGA几个与LED相关的实验(包括按键点灯、流水灯、呼吸灯等)的记录,方便日后查看。这世界上就又多了一个FPGA点灯工程师了
按键点灯程序比较简单,就不搞仿真了,直接上机
module led(input wire key_1,
output reg led_1);
always@*
led_1=!key_1;
endmodule
由程序得到的RTL图:
(1)引脚分配与接线
引脚分配如下,记得引脚分配后再编译一次,不然可能没有现象。
上述引脚分配对应的接线:JX22连接到JP5,JX5连接到JP1。
为什么要这样接线可以参考《【FPGA实验2】二进制转为格雷码》中的【三、实验箱实验】➡️【3、引脚分配】。
(2)实验现象
具体的实验现象可观看下方的视频
学了点状态机的内容,决定用状态机来写一下这个流水灯的代码。
感谢正点原子的视频,用了一个很好理解的例子讲了状态机是怎么样的一个东西,并总结了写状态机主要有四个步骤(也称四段论):
(1)状态空间定义(定义各个状态)
(2)状态跳转(告诉FPGA:你要跳转。让FPGA知道在什么条件下,你要从现在状态跳转到下一个状态)
(3)下个状态的判断(告诉FPGA:你要怎么跳。给FPGA一个地图,判断现在的情况是什么,然后根据地图确定下一个要跳转的状态)
(4)各个状态下的动作(我理解为状态对应信息的输出)
module WaterLED(input wire clk,
input wire rst_n,
output wire [7:0]led_data);
//空间状态定义
parameter S1=8'b10000000;
parameter S2=8'b01000000;
parameter S3=8'b00100000;
parameter S4=8'b00010000;
parameter S5=8'b00001000;
parameter S6=8'b00000100;
parameter S7=8'b00000010;
parameter S8=8'b00000001;
reg [7:0]current_state;
reg[7:0]next_state;
//状态跳转
always @(posedge clk or negedge rst_n) begin
if(! rst_n) current_state<=S1; //复位,从状态S1开始
else current_state<=next_state; //不是复位的情况下,到达时钟的上升沿就转到下一个状态
end
//状态判断
always @(current_state) begin
case(current_state)
S1:next_state=S2;
S2:next_state=S3;
S3:next_state=S4;
S4:next_state=S5;
S5:next_state=S6;
S6:next_state=S7;
S7:next_state=S8;
default:next_state=S1;
endcase
end
//各个状态下的动作
assign led_data=current_state;
endmodule
流水灯的仿真程序如下:
`timescale 1ns/100ps
module tb_WaterLED;
reg clk_1;
reg rst_n_1;
wire [7:0] led_data_1;
parameter PERIOD=10;
always #(PERIOD/2) clk_1=~clk_1;
initial begin
clk_1=0;
#200 $stop;
end
task task_rst;
begin
rst_n_1=0;
repeat(2) @(negedge clk_1);//两个时钟负跳变之后
rst_n_1=1;
end
endtask
WaterLED WD_1(.clk(clk_1),.rst_n(rst_n_1),.led_data(led_data_1));
initial begin
task_rst;
$display("task_rst ok!!!");
end
endmodule
【如何将仿真程序加入到工程中以及如何仿真可以看上一篇《【FPGA实验0】Quartus建立工程文件以及仿真》)】
仿真结果如下:
在两个时钟下降沿之后,复位线RST置高;之后随着每一个时钟上升沿的到来,开始状态的转换。
在实际的硬件实现中,需要注意两个点:
(1)时钟信号
在仿真中,两个状态之间的切换是在时钟信号的上升沿,因而,每个状态保持的时间是一个时钟信号周期(在上的仿真中,一个时钟信号的周期是10ns)。而如果在实际中要实现流水灯的效果,这样的间隔太小了,由于眼睛的暂存作用,我们看懂的现象是8个LED灯一直在同时亮着,没有流水灯的效果,因而我们必须将时钟周期改得大一点。
选择实验箱最大时钟12MHz(接线的话将左侧12M对应的时钟和右侧的任意一个引脚接起来即可),定义一个计数器,计数2400000个时钟周期,一个时钟频率为12MHz,一个时钟周期为83.33ns,2400000个时钟周期就是0.2秒。
reg [23:0] counter;
//计数器对系统时钟计数,计时0.2秒
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
counter <= 24'd0;
else if (counter < 24'd2400_000) //仿真的时候可以改为 24'd0000_0010,下同
counter <= counter + 1'b1;
else
counter <= 24'd0;
end
同时,状态跳变部分增加一个条件,修改为:
//状态跳转
always @(posedge clk or negedge rst_n) begin
if(! rst_n) current_state<=S1; //复位,从状态S1开始
else if (counter==24'd2400_000) current_state<=next_state; //不是复位的情况下,到达时钟的上升沿就转到下一个状态
else ;
end
(2)复位键如何使用
复位键一开始需要置1(打到开的位置);
之后置0(下降沿产生出发条件;同时!rst=0,状态为初始状态S1);
完成复位后置1(使此刻的状态在触发之后能跳转到下一个状态)。
整个硬件实现的程序:
module WaterLED(input wire clk,
input wire rst_n,
output wire [7:0]led_data);
//空间状态定义
parameter S1=8'b10000000;
parameter S2=8'b01000000;
parameter S3=8'b00100000;
parameter S4=8'b00010000;
parameter S5=8'b00001000;
parameter S6=8'b00000100;
parameter S7=8'b00000010;
parameter S8=8'b00000001;
reg [7:0]current_state;
reg[7:0]next_state;
reg [23:0] counter;
//计数器对系统时钟计数,计时0.2秒
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
counter <= 24'd0;
else if (counter < 24'd2400_000) //仿真的时候可以改为 24'd0000_0010,下同
counter <= counter + 1'b1;
else
counter <= 24'd0;
end
//状态跳转
always @(posedge clk or negedge rst_n) begin
if(! rst_n) current_state<=S1; //复位,从状态S1开始
else if (counter==24'd2400_000) current_state<=next_state; //不是复位的情况下,到达时钟的上升沿就转到下一个状态
else ;
end
//状态判断
always @(current_state) begin
case(current_state)
S1:next_state=S2;
S2:next_state=S3;
S3:next_state=S4;
S4:next_state=S5;
S5:next_state=S6;
S6:next_state=S7;
S7:next_state=S8;
default:next_state=S1;
endcase
end
//各个状态下的动作
assign led_data=~current_state;//灯为0时亮起,为1时变暗
endmodule
按之前的接线即可,key8为复位键。
上述引脚分配对应的接线:JX22连接到JP5,JX5连接到JP1。
为什么要这样接线可以参考《【FPGA实验2】二进制转为格雷码》中的【三、实验箱实验】➡️【3、引脚分配】。
时钟引脚:左侧的12M时钟引脚接到右侧任意一个引脚即可。总的接线图如下:
具体现象可以看文末的视频
呼吸灯,就是灯的亮度由暗变亮再由亮变暗,像人的呼吸一样。
(1)代码
module PWM(
input wire clk,
output wire[9:0] pwma,
output wire pwmb);
reg [3:0]counter1=0,counter2=0;
reg [9:0]pwm_1=10'b0000_0000_00;
reg[0:0] pwm_2;
always@(posedge clk) begin
if(counter2==4'd9) begin
if(counter1==4'd9) begin
counter1=0;
pwm_1=10'b0000_0000_00;
end
else counter1=counter1+1;
pwm_1[counter1]=1;
counter2=0;
end
else counter2=counter2+1;
pwm_2=pwm_1[counter2];
end
assign pwma=pwm_1;
assign pwmb=pwm_2;
endmodule
(2)仿真文件:
`timescale 1ns/100ps
module tb_PWM;
reg clk;
wire [9:0]pwma;
wire [0:0]pwmb;
parameter PERIOD=10;
always #(PERIOD/2) clk=~clk;
initial begin
clk=0;
# 3000 $stop;
end
PWM PWM_1(.clk(clk),.pwma(pwma), .pwmb(pwmb));
endmodule
(1)代码
module PWM(
input wire clk,
output wire[9:0] pwma,
output wire pwmb);
reg [4:0]counter1=0;
reg [3:0]counter2=0;
reg [9:0]pwm_1=10'b0000_0000_00;
reg[0:0] pwm_2;
always@(posedge clk) begin
if(counter2==4'd9) begin
if(counter1==5'd17) counter1=0;
else counter1=counter1+1;
$display("counter1: ",counter1);
case(counter1)
5'd0 : pwm_1=10'b0000_0000_01;
5'd1 : pwm_1=10'b0000_0000_11;
5'd2 : pwm_1=10'b0000_0001_11;
5'd3 : pwm_1=10'b0000_0011_11;
5'd4 : pwm_1=10'b0000_0111_11;
5'd5 : pwm_1=10'b0000_1111_11;
5'd6 : pwm_1=10'b0001_1111_11;
5'd7 : pwm_1=10'b0011_1111_11;
5'd8 : pwm_1=10'b0111_1111_11;
5'd9 : pwm_1=10'b1111_1111_11;
5'd10 : pwm_1=10'b0111_1111_11;
5'd11 : pwm_1=10'b0011_1111_11;
5'd12 : pwm_1=10'b0001_1111_11;
5'd13 : pwm_1=10'b0000_1111_11;
5'd14 : pwm_1=10'b0000_0111_11;
5'd15 : pwm_1=10'b0000_0011_11;
5'd16 : pwm_1=10'b0000_0001_11;
5'd17 : pwm_1=10'b0000_0000_11;
endcase
$display("pwm_1: ",pwm_1);
counter2=0;
end
else counter2=counter2+1;
pwm_2=pwm_1[counter2];
end
assign pwma=pwm_1;
assign pwmb=pwm_2;
endmodule
(2)tb文件:
`timescale 1ns/100ps
module tb_PWM;
reg clk;
wire [9:0]pwma;
wire [0:0]pwmb;
parameter PERIOD=10;
always #(PERIOD/2) clk=~clk;
initial begin
clk=0;
# 3000 $stop;
end
PWM PWM_1(.clk(clk),.pwma(pwma), .pwmb(pwmb));
endmodule
(3)仿真结果:
呼吸灯的硬件实现,和流水灯的硬件实现一样原理,如果每个亮度的时间跟仿真的时候一样的话,那我们是看不出亮度变化,所以在硬件实现的时候需要在每一个亮度延长一定的时间。具体的实现如下:
(1)代码:
module PWM(
input wire clk,
output wire led0,
output wire led1,
output wire led2,
output wire led3,
output wire led4,
output wire led5,
output wire led6,
output wire led7
);
reg [4:0]counter1=0;
reg [3:0]counter2=0;
reg [9:0]pwm_1=10'b0000_0000_00;
reg[0:0] pwm_2;
reg [20:0] cnt_base;
parameter T_6ms = 21'd2000_000;
always @(posedge clk ) begin
if(cnt_base < T_6ms - 1'b1)begin
cnt_base <= cnt_base + 1'b1;
pwm_2<=pwm_1[cnt_base%10];
end
else begin
cnt_base <= 21'd0;
if(counter1==5'd17) counter1=0;
else counter1=counter1+1;
case(counter1)
5'd0 : pwm_1=10'b0000_0000_01;
5'd1 : pwm_1=10'b0000_0000_11;
5'd2 : pwm_1=10'b0000_0001_11;
5'd3 : pwm_1=10'b0000_0011_11;
5'd4 : pwm_1=10'b0000_0111_11;
5'd5 : pwm_1=10'b0000_1111_11;
5'd6 : pwm_1=10'b0001_1111_11;
5'd7 : pwm_1=10'b0011_1111_11;
5'd8 : pwm_1=10'b0111_1111_11;
5'd9 : pwm_1=10'b1111_1111_11;
5'd10 : pwm_1=10'b0111_1111_11;
5'd11 : pwm_1=10'b0011_1111_11;
5'd12 : pwm_1=10'b0001_1111_11;
5'd13 : pwm_1=10'b0000_1111_11;
5'd14 : pwm_1=10'b0000_0111_11;
5'd15 : pwm_1=10'b0000_0011_11;
5'd16 : pwm_1=10'b0000_0001_11;
5'd17 : pwm_1=10'b0000_0000_11;
endcase
end
end
assign led0=pwm_2;
assign led1=pwm_2;
assign led2=pwm_2;
assign led3=pwm_2;
assign led4=pwm_2;
assign led5=pwm_2;
assign led6=pwm_2;
assign led7=pwm_2;
endmodule
(2)对应的RTL图:
(3)引脚分配与接线
接线同上流水灯的接线。
(4)实验现象:
具体实验现象可以看文末
FPGA点灯工程师养成记
Forever young,always tearful.