🎉欢迎来到FPGA专栏~定时器设计与蜂鸣器驱动
一、前言
🥝实现效果
✨AC620驱动板载蜂鸣器:
FPGA驱动蜂鸣器
✨蜂鸣器电子琴:
FPGA-简单蜂鸣器电子琴
🥝定时器与计数器
从一定程度上讲,定时器就是计数器,计数器就是定时器。
关于计数器的设计和使用可以参考上一篇文章:【FPGA零基础学习之旅#3】时序逻辑电路设计-计数器设计和闪烁LED灯
定时器:核心单元本质也是一个计数器,设置一个定时值,启动定时器后,计数器开始计数,计数满后产生计数满标志信号,提示设定的定时时间到达。
计数器:对脉冲信号进行计数,统计一确定时间段内该脉冲信号出现的次数,或者等待指定次数的脉冲信号出现后,产生相应标志。
🥝实验原理图
AC620开发板上蜂鸣器对应的原理图:
二、实验过程
🍍注意代码的逻辑性
观察以下代码,并进行仿真:
beep.v文件:
module beep(
//端口列表
Clk,
Rst_n,
CNT_ACC,//定时预设值
MODE,//设置一个计数模式控制信号,当该信号为1时,设置为循环定时模式,当该信号为0时,设置为单次定时模式。
CNT_GO,
//在循环定时模式下,该信号(CNT_GO信号)为高电平使能计时,为低电平则停止计时。
//在单次计数模式下,该信号(oneshot信号)的一个单基准时钟周期的脉冲使能一次定时。
CNT_NOW,//输出计数器实时计数值,该值将用于产生特定占空比的方波。
Full_Flag
);
input Clk;
input Rst_n;
input [31:0]CNT_ACC;
input MODE;
input CNT_GO;
output [31:0]CNT_NOW;
output Full_Flag;
//定义一个寄存器
//定义一个32位的寄存器用于计数器
reg [31:0]cnt;
assign CNT_NOW = cnt;
assign Full_Flag = (cnt >= CNT_ACC);
//完整格式:assign Full_Flag = (cnt >= CNT_ACC)?1'b1:1'b0
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt <= 0;
else if(MODE == 1'b1)begin
if((CNT_GO == 1'b1) && (cnt < CNT_ACC))
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
else if(!MODE)begin
if(oneshot)
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
reg oneshot;//单次定时的内部使能信号。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
oneshot <= 1'b0;
else if(CNT_GO == 1'b1)
oneshot <= 1'b1;
else if(Full_Flag)//cnt >= CNT_ACC
oneshot <= 1'b0;
else
oneshot <= oneshot;//不写该条语句也行,else默认oneshot <= oneshot。
endmodule
beep_tb.v文件:
`timescale 1ns/1ns
`define Clk_period 20
module beep_tb();
reg Clk;
reg Rst_n;
reg [31:0]CNT_ACC;
reg MODE;
reg CNT_GO;
wire [31:0]CNT_NOW;
wire Full_Flag;
beep beep0(
.Clk(Clk),
.Rst_n(Rst_n),
.CNT_ACC(CNT_ACC),
.MODE(MODE),
.CNT_GO(CNT_GO),
.CNT_NOW(CNT_NOW),
.Full_Flag(Full_Flag)
);
initial Clk = 1;
always #(`Clk_period/2) Clk = ~Clk;
initial begin
Rst_n=0;
CNT_ACC=0;
MODE=0;
CNT_GO=0;
#(`Clk_period*20 + 1);
Rst_n=1;
#(`Clk_period*20);
//预设值为1000,循环定时模式
CNT_ACC=1000;
MODE=1;
#(`Clk_period*20);
CNT_GO=1;
#(`Clk_period*12000);
CNT_GO=0;
#(`Clk_period*20);
//预设值为600,循环定时模式
CNT_ACC=600;
MODE=1;
#(`Clk_period*20);
CNT_GO=1;
#(`Clk_period*8000);
CNT_GO=0;
#(`Clk_period*20);
#(`Clk_period*20 + 1);
Rst_n=1;
#(`Clk_period*20);
//预设值为1000,单次定时模式
CNT_ACC=1000;
MODE=0;
#(`Clk_period*20);
CNT_GO=1;
#(`Clk_period);
CNT_GO=0;
#(`Clk_period*1200);
//预设值为600,单次定时模式
CNT_ACC=600;
MODE=0;
#(`Clk_period*20);
CNT_GO=1;
#(`Clk_period);
CNT_GO=0;
#(`Clk_period*1200);
$stop;
end
endmodule
✨使用modelsim进行功能仿真时,会报如下错误:
在编写代码时,我们可以以并行的思维写代码,同时quartus软件也能正常编译。但是对于modelsim仿真来说,它是从上到下进行执行的。因此,将该句代码reg oneshot;//单次定时的内部使能信号。
放到第一次使用前:
output [31:0]CNT_NOW;
output Full_Flag;
//定义一个寄存器
//定义一个32位的寄存器用于计数器
reg [31:0]cnt;
reg oneshot;//单次定时的内部使能信号。
assign CNT_NOW = cnt;
assign Full_Flag = (cnt >= CNT_ACC);
//完整格式:assign Full_Flag = (cnt >= CNT_ACC)?1'b1:1'b0
仿真结果部分截图:
1、循环定时模式:
2、单次定时模式:
📜小技巧~快速分组操作
Ctrl+A全选,Ctrl+G分组
:
更改oneshot部分的代码,以保证单次循环定时的逻辑正确:
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
oneshot <= 1'b0;
else if(!MODE)begin
if(CNT_GO == 1'b1)
oneshot <= 1'b1;
else if(Full_Flag)//cnt >= CNT_ACC
oneshot <= 1'b0;
else
oneshot <= oneshot;//不写该条语句也行,else默认oneshot <= oneshot。
end
else
oneshot <= 1'b0;
仿真结果:
在结尾部分,发现结果多记了一次数:
我们更改Full_Flag部分的代码,将原来的判断部分改为:
assign Full_Flag = (cnt >= CNT_ACC - 1)?1'b1:1'b0;
再次仿真,结果正确:
当然,也可以更改为这样:
assign Full_Flag = (cnt == CNT_ACC - 1)?1'b1:1'b0;
结果如下:
🍍解决时序延迟的问题
📜D触发器的妙用:
使用D触发器使我们期望的信号延迟,使用一个D触发器就可以使信号延迟一拍。
更改部分代码后:
module beep(
//端口列表
Clk,
Rst_n,
CNT_ACC,//定时预设值
MODE,//设置一个计数模式控制信号,当该信号为1时,设置为循环定时模式,当该信号为0时,设置为单次定时模式。
CNT_GO,
//在循环定时模式下,该信号(CNT_GO信号)为高电平使能计时,为低电平则停止计时。
//在单次计数模式下,该信号(oneshot信号)的一个单基准时钟周期的脉冲使能一次定时。
CNT_NOW,//输出计数器实时计数值,该值将用于产生特定占空比的方波。
Full_Flag
);
input Clk;
input Rst_n;
input [31:0]CNT_ACC;
input MODE;
input CNT_GO;
output [31:0]CNT_NOW;
output reg Full_Flag;
//定义一个寄存器
//定义一个32位的寄存器用于计数器
reg [31:0]cnt;
reg oneshot;//单次定时的内部使能信号。
wire Full_Flag_r;
assign CNT_NOW = cnt;
assign Full_Flag_r = (cnt == CNT_ACC - 1)?1'b1:1'b0;
always@(posedge Clk)
Full_Flag <= Full_Flag_r;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt <= 0;
else if(MODE == 1'b1)begin
if((CNT_GO == 1'b1) && (cnt < CNT_ACC))
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
else if(!MODE)begin
if(oneshot)
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
oneshot <= 1'b0;
else if(!MODE)begin
if(CNT_GO == 1'b1)
oneshot <= 1'b1;
else if(Full_Flag_r)//cnt >= CNT_ACC - 1
oneshot <= 1'b0;
else
oneshot <= oneshot;//不写该条语句也行,else默认oneshot <= oneshot。
end
else
oneshot <= 1'b0;
endmodule
观察仿真结果,实现了一拍的延迟效果:
🍍驱动蜂鸣器
添加顶层文件beep_top:
module beep_top(
Clk,
Rst_n,
CNT_GO,
beep
);
input Clk;
input Rst_n;
input CNT_GO;
output beep;
wire [31:0]CNT_NOW;
beep beep0(
.Clk(Clk),
.Rst_n(Rst_n),
.CNT_ACC(32'd49999),
.MODE(1'b1),
.CNT_GO(CNT_GO),
.CNT_NOW(CNT_NOW),
.Full_Flag()
);
assign beep = (CNT_NOW >= 24999)?1'b1:1'b0;
endmodule
引脚分配:
效果展示:
烧写完jic文件之后,重新上电。由于按键端默认输出高电平,上电后蜂鸣器便开始发出声音;按下S0或者S2按键,蜂鸣器停止发出声音。
FPGA驱动蜂鸣器
三、扩展学习
📜使用定时器设计一个变音蜂鸣器。
🍋代码编写
新建sound_lut.v文件:
module sound_lut(
Clk,
Rst_n,
ARR
);
input Clk;
input Rst_n;
output reg[31:0]ARR;
reg [4:0]index;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
index <= 0;
else if(index >= 5'd20)
index <= 0;
else
index <= index + 1'b1;
always@(*)begin
case(index)
0 : ARR = 191130;
1 : ARR = 170241;
2 : ARR = 151689;
3 : ARR = 143183;
4 : ARR = 127550;
5 : ARR = 113635;
6 : ARR = 101234;
7 : ARR = 95546 ;
8 : ARR = 85134 ;
9 : ARR = 75837 ;
10: ARR = 71581 ;
11: ARR = 63775 ;
12: ARR = 56817 ;
13: ARR = 50617 ;
14: ARR = 47823 ;
15: ARR = 42563 ;
16: ARR = 37921 ;
17: ARR = 35793 ;
18: ARR = 31887 ;
19: ARR = 28408 ;
20: ARR = 25309 ;
default: ARR = 191130;
endcase
end
endmodule
在顶层中进行调用:
module beep_top(
Clk,
Rst_n,
CNT_GO,
beep
);
input Clk;
input Rst_n;
input CNT_GO;
output beep;
//内部连线的信号定义为wire类型
wire [31:0]CNT_NOW;
wire Full_Flag1;
wire [31:0]CNT_ARR;
beep beep0(
.Clk(Clk),
.Rst_n(Rst_n),
.CNT_ARR(CNT_ARR),
.MODE(1'b1),
.CNT_GO(CNT_GO),
.CNT_NOW(CNT_NOW),
.Full_Flag()
);
sound_lut sound_lut0(
.clk(Full_Flag1),
.Rst_n(Rst_n),
.ARR(CNT_ARR)
);
//250ms定时,用于切换音调
beep beep1(
.Clk(Clk),
.Rst_n(Rst_n),
.CNT_ARR(6250000),
.MODE(1'b1),
.CNT_GO(1),
.CNT_NOW(),
.Full_Flag(Full_Flag1)
);
assign beep = (CNT_NOW >= 24999)?1'b1:1'b0;
endmodule
RTL视图:
🍋效果展示
FPGA蜂鸣器电子琴
在演示中出现的延长音就是default语句的作用结果,default: ARR = 191130;
。将之前的语句改为:else if(index >= 5'd20)
之后,蜂鸣器将会正常连续播放。
🧸结尾