【FPGA零基础学习之旅#4】定时器设计与蜂鸣器驱动

文章介绍了如何使用FPGA设计定时器和驱动蜂鸣器,通过实验过程解释了计数器与定时器的关系,展示了代码逻辑的重要性,并解决了时序延迟问题。同时,提供了使用D触发器延迟信号的方法,并给出了驱动蜂鸣器的顶层文件示例。
摘要由CSDN通过智能技术生成

🎉欢迎来到FPGA专栏~定时器设计与蜂鸣器驱动


  • ☆* o(≧▽≦)o *☆~我是小夏与酒🍹
  • 博客主页:小夏与酒的博客
  • 🎈该系列文章专栏:FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️
    FPGQ2

CSDN

遇见未来

一、前言

🥝实现效果

✨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、循环定时模式:
11
12

2、单次定时模式:
21
22

📜小技巧~快速分组操作
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;

仿真结果:
oneshot
在结尾部分,发现结果多记了一次数:
多记一次数
我们更改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

观察仿真结果,实现了一拍的延迟效果
D

🍍驱动蜂鸣器

添加顶层文件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

引脚分配:
pin
效果展示:
烧写完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视图:
RTL

🍋效果展示

FPGA蜂鸣器电子琴

在演示中出现的延长音就是default语句的作用结果,default: ARR = 191130;。将之前的语句改为:else if(index >= 5'd20)之后,蜂鸣器将会正常连续播放。

csdn

🧸结尾


评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夏与酒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值