定时器设计与层次化设计(驱动蜂鸣器)
作者:互联网
一、定时器设计
1.定时器简介
定时器即计数器,可以循环定时,也可以单次定时。
2.定时器代码
module timer(
Clk, //时钟信号
Rst_n, //复位信号
CNT_ARR, //定时预设值
MODE, //该信号决定定时模式:1为循环定时;0为单次定时
Cnt_Go, /* 循环定时模式:高电平使能计时,低电平停止计时。
单次计数模式:该信号的一个单基准时钟周期的脉冲使能一次定时。*/
CNT_NOW, //当前定时值
Full_Flag //定时满信号
);
input Clk;
input Rst_n;
input [31:0]CNT_ARR;
input MODE;
input Cnt_Go;
output [31:0] CNT_NOW;
output reg Full_Flag;
reg [31:0] cnt; //定义一个32位的寄存器用作计数器
reg oneshot; //单次定时的内部使能信号
wire Full_Flag_r;
assign CNT_NOW = cnt; //输出当前定时值
assign Full_Flag_r = (cnt == CNT_ARR - 1)?1'b1:1'b0;
always@(posedge Clk)
Full_Flag <= Full_Flag_r; //这里设置了一个Full_Flag_r,用于将Full_Flag信号延后一个周期,使其与记满时的值对齐
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_ARR)) //当计数使能且当前计数值小于预设值时,计数器+1
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
else if(!MODE)begin //单次定时模式
if(oneshot)
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
//下面这个always块是针对单次计时设计的
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;//允许计数时,将oneshot置1
else if(Full_Flag_r)
oneshot <= 1'b0;//如果计满,则将oneshot信号置0,结束计数
else
oneshot <= oneshot;
end
else
oneshot <= 1'b0;
endmodule
3.定时器解读
①功能概述
输入端口:时钟端口,异步清零端口,预设值端口,计数使能端口,计数模式端口
输出端口:当前计数值端口,计满标志端口
②关于信号“Full_Flag_r”
如果将信号“Full_Flag_r”直接接到输出端口,经过仿真会发现,Full_Flag_r会比计满时刻的时钟上升沿提前一个时钟周期拉高,因此需要在此信号后连接一个D触发器,使计满信号的跳变沿延后一个周期到来,如下:
output reg Full_Flag;
……
always@(posedge Clk)
Full_Flag <= Full_Flag_r;
即定义一个寄存器类型的变量,将其连到需要延迟的信号后即可。
③关于信号“oneshot”
“oneshot”是模块内部标志定时一次的信号,当输入信号MODE为0时,oneshot置1,两者具体逻辑关系如下:
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; //允许计数时,将oneshot置1
else if(Full_Flag_r)
oneshot <= 1'b0; //如果计满,则将oneshot信号置0,结束计数
else
oneshot <= oneshot; //这条可以省略,为了直观上逻辑清楚的话可以写出来
end
else
oneshot <= 1'b0; //MODE为1时为循环计时,oneshot置0
二、层次化设计
我们本次目的是利用计数器模块来驱动开发板上的蜂鸣器,我们可以将刚才编写的timer模块重新命名为beep_test。但是仅有一个计数器模块是不够的,这是因为我们要控制蜂鸣器发出不同频率的声音,就要将各音调的数据事先存储在某个文件中,这样就又有一个需要使用的模块,于是需要一个顶层模块来调用它们,我们将顶层模块命名为beep_top。而将存储音调数据的模块命名为sound_lut(即查找表)
1.流程图
2.声音频率数据表(sound_lut)
调整定时设定参数输入端口,即调整预重装值,预重装值与对应频率的关系为:counter_arr=fclk/fpwm-1。
下图为各声音频率的对照表:
根据这个表,我们可以写出查找表模块(时钟频率为50MHz)
module sound_lut(
Clk, //查找数据的频率
Rst_n, //复位信号
ARR //输出频率信息
);
input Clk;
input Rst_n;
output reg[31:0]ARR; //所谓的频率信息实际上就是计数器的模
reg [4:0]index; //index可以理解为每个音调数据的地址
//always块中,循环逐个取出音调信息
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;//一定要有default语句,否则在复杂工程中可能带来毁灭性灾难
endcase
end
endmodule
查找表模块比较简单,不再赘述。
3.顶层模块(beep_top)
顶层模块负责调用所有需要用到的模块,如下:
module beep_top(
Clk,
Rst_n,
Cnt_Go,//循环定时模式:高电平使能计时,低电平停止计时。单次计数模式:该信号的一个单基准时钟周期的脉冲使能一次定时。
beep
);
input Clk; //接开发板上的时钟
input Rst_n; //接开发板上的复位按键
input Cnt_Go; //接开发板上的计数允许信号接控制按键
output beep; //输出端beep接开发板上的蜂鸣器
wire [31:0] CNT_NOW;
wire Full_Flag1;
wire [31:0] CNT_ARR;
//第一个定时器用来驱动蜂鸣器
beep_test beep_test0(
.Clk(Clk),
.Rst_n(Rst_n),
.CNT_ARR(CNT_ARR), //CNT_ARR信号来自查找表模块
.MODE(1'b1), //设置为循环计数模式
.Cnt_Go(Cnt_Go),
.CNT_NOW(CNT_NOW), //
.Full_Flag()
);
sound_lut sound_lut(
.Clk(Full_Flag1), //此处不推荐这种异步时序逻辑的写法
.Rst_n(Rst_n),
.ARR(CNT_ARR) //输出音调数据,即计数周期
);
//250ms定时,用于切换音调
beep_test beep_test1(
.Clk(Clk),
.Rst_n(Rst_n),
.CNT_ARR(12500000), //设定初值为12500000,由于时钟周期为1/50M=20ns,故定时为250ms
.MODE(1'b1), //循环计时
.Cnt_Go(1), //计数使能
.CNT_NOW(), //输出端悬空
.Full_Flag(Full_Flag1) //记满信号连到查找表的时钟端
);
assign beep = (CNT_NOW >= 24999)?1'b1:1'b0;
endmodule
①always@(*)用法
在查找表模块中我们用到了“always@(*)”语句。always@(*) 描述组合逻辑,括号中直接填入敏感信号。在always语句中,如果我们对敏感信号不清楚,或者敏感信号不重要,直接填入*,这样块内语句会对所有涉及到的信号敏感。(always块中无论描述的是组合逻辑还是时序逻辑,均只能对reg型变量进行赋值,否则会报错)
②不推荐.Clk(Full_Flag1)写法
在查找表的时钟端口,我们直接连上了第二个定时器的计满信号端。这样的操作属于异步时序逻辑,在小工程(比如本次的蜂鸣器实验)中使用并无大碍,但在今后的学习中应尽量避免异步时序逻辑的出现。
③beep_test0与beep_test1
在顶层模块中我们例化了两个定时器,分别命名为beep_test0和beep_test1,这是本次实验的一个亮点。beep_test0负责输出PWM波,用于驱动蜂鸣器,而beep_test1负责控制音调切换的时间间隔。打个比方,战场上两名战士操控重机枪,一个负责开火,一个负责装子弹。beep_test0就是我们负责开火的战士,beep_test1则我们负责装子弹的战士,而sound_lut则可以看作子弹箱,为机枪源源不断地提供子弹。
三、总结
本次实验标志着正式开始系统学习FPGA,几个重要的知识点都很典型,当然本次工程也留下了一些不足的地方,后面的会不断深入学习,掌握合理完整的开发方法。
标签:层次化,CNT,定时器,蜂鸣器,Clk,beep,Full,信号,Rst 来源: https://blog.csdn.net/m0_51261356/article/details/112725266