本次学习的是设计定时器来驱动蜂鸣器,并拓展一下电子琴的设计。定时器的核心单元本质就是一个计数器,设定一个预设值然后计数器开始计数,当计数达到我们想要的预设值时,启动标志位提示达到设定的时间。在本设计中,使用计数器来产生我们需要的PWM波,然后使用PWM波便能驱动蜂鸣器按照既定的频率鸣叫。
01
有源蜂鸣器与无源蜂鸣器
蜂鸣器是一种产生声音的电子元器件,采用DC直流稳压电源供电,广泛用于计算机、报警器、玩具车等电子产品中。蜂鸣器按照产品分类可分为两种,一种是有源蜂鸣器一种是无源蜂鸣器。
有源蜂鸣器:对于有源蜂鸣器其内部含有震荡源,对于有源蜂鸣器的驱动直接接上额定电源便可连续发声。
有源蜂鸣器的优点:程序控制简单方便,只需一个电平即可。
无源蜂鸣器:无源蜂鸣器内部不含震荡源,需要接在音频输出电路才能发声,所以用直流信号无法令其鸣叫,需用2K-5Khz的方波去驱动它。
无源蜂鸣器的优点:1、便宜;2、声音频率可控,不同的频率可以令蜂鸣器发出不同的声音,如电子琴就是使用的这一功能;
02
定时器的实现
首先第一步仍然是定义端口,以及定义端口类型。
首先我们得定义一个时钟信号clk,在AC620开发板上有一路板载50MHz的有源晶振提供一路时钟源。另外还需定义一路复位信号Rst_n。然后就是CNT_ACC用来设置目标值也就是期望值,期望计数多长。MODE用来设置计数模式,分为循环计数模式和单次计数模式。另外还需要一路启动信号Cnt_Go,当Cnt_Go为1时开始计时,当Cnt_Go为0时停止计时。CNT_NOW为当前的计数值。Full_Flag为标记信号,当一次计数满后,Full_Flag则置1。
对于这些端口类型,因为clk外接FPGA上的50M时钟源,所以定义为外部输入input型。对于复位信号Rst_n、计数启动信号Cnt_Go都由外部按键控制,所以也定义为input型。预设值信号CNT_ACC和模式选择信号MODE都由外部输入,所以都为input型。
对于当前计数值CNT_NOW以及标志信号Full_Flag则是由系统写入,所以定义为output输出类型。
接下来就要写逻辑时序。
首先看第33行,总是关注clk时钟的上升沿和复位信号Rst_n的下降沿,当复位信号为0时if(!Rst_n)将计数寄存器cnt清零,如果复位信号不为零,则判断当前计数的模式,如果MODE为1循环模式,则继续判断计数信号Cnt_Go是否为1开始计数以及计数寄存器cnt是否计满,如果Cnt_Go为1且cnt小于目标值CNT_ACC则继续计数cnt加1,否则cnt清零。若计数模式MODE为0,则为单次计数模式,此时需要再定义一个标志位oneshot,用来标记单次计数的开始和结束,如第49行开始就是写标记信号oneshot的逻辑,当复位信号为0时,oneshot清零,复位信号不为0时且为单次计数模式则继续判断是否开始计数,若Cnt_Go为1,则将oneshot置1开始计数,当一次计满后cnt==CNT_ACC-1则将oneshot清零停止计数,其余时刻oneshot不变。这边是oneshot标记信号的逻辑。然后将oneshot标志信号写入第43行mode 0 的单次计数逻辑中,当oneshot为1时开始计数cnt自加否则cnt清零。
另外在这些时序逻辑的书写过程中还需要注意各端口的定义类型,在always@中运用的需要定义为reg型,比如cnt和oneshot。
cnt为计数的存储计数器,将cnt实时计数的值赋给定义的端口CNT_NOW(第27行)。然后就是标记信号(第28行)Full_Flag_r = (cnt == CNT_ACC-1)?1'b1:1'b0;这种写法是判断cnt是否等于CNT_ACC-1,是,则Full_Flag_r等于1'b1,否则Full_Flag_r等于1'b0(b表示2进制,最前面的1表示该变量为几位)。
到此基本的计时器逻辑就书写完了,然后可以书写仿真文件对其进行仿真。同样建立一个.v文件,
定义仿真的时间间隔周期为1ns,第二行相当于c语言中的宏定义,让clk——period为20。然后定义仿真的端口与上一个.v文件里的端口连线进行仿真。
这一步是仿真时钟信号,开始时让时钟信号clk为高电平1,然后每隔10ns让时钟信号clk翻转一次。这里的10ns是因为AC620 FPGA上的时钟源是50MHz换算成周期就是20ns,这样每一个时钟周期就是10ns的高电平和10ns的低电平。
然后就是对其功能进行仿真,开始时为了方便观察仿真现象,让其复位信号为0,预设值为0,模式为0,开始计数信号为0,延时20个周期,然后将复位信号置1,再延时20个周期。然后便可以设置想要的预设值和模式,并开始观察仿真现象。
下面几个为分别设置不同的预设值与循环模式来观察仿真现象。设置预设值为1000,模式为循环定时模式,延时12000个周期计数10次观察现象;设置预设值600,计数10次,模式为循环定时模式,延时8000个周期计数10次来观察现象;然后就是单次计数模式,同样分别设置预设值为1000和600。
接下来看一下仿真现象。
这是预设值1000,模式为循环计数模式,计满1000次处标记信号呈现了一次高电平,随后cnt计数信号又重新从0开始计数。
这是预设值600,模式为循环计数模式,计满600次处标记信号呈现了一次高电平,随后cnt计数信号又重新从0开始计数。
这是预设值1000,模式为单次计数模式,计满1000次处标记信号呈现了一次高电平,随后cnt清零并不再变化。
这是预设值600,模式为单次计数模式,计满600次处标记信号呈现了一次高电平,随后cnt清零并不再变化。
在上述图中一直有一个问题,那就是标记信号并不是在cnt计满处上升,而总是提前一个周期到来,为此解决方案为设置一个变量过度一下。
也就是这里为什么会突然增加一个Full_Flag_r的标记信号。然后再将Full_Flag_r的值赋给Full_Flag。
此处的Full_Flag_r因为不是在时序逻辑中使用所以定义为wire型。接下来看一下仿真结果。
这是预设值1000,循环模式,设置一个过度后发现Full_Flag在计数达到1000时才升为高电平,在1000次计满之后降为低电平,实现标志提醒功能。
下面为600循环、1000单次和600单次的仿真图。
03
触发蜂鸣器
对于定义端口这些此处就不再详细阐述,此处只需新加一个beep端口用来控制蜂鸣器,端口类型为output型。
然后就是引用前面的.v文件里的程序,然后和此处定义的端口进行连线。
对于预设值CNT_ACC处的32'd49999,此处是用来设置驱动蜂鸣器的频率,这里我给的是1KHz的频率,1KHz转化成周期就是1ms,而计数器每一个周期是20ns(FPGA上的50M时钟源对应周期是20ns),所以1ms=1000000ns,然后1000000ns/20ns=50000次[0:49999]。模式设置为循环模式,启动计数Cnt_Go连接到FPGA的按键上,用来控制蜂鸣器的鸣叫。
最后一步就是形成PWM波。
上面设置的是计数50000次,那就在他的中间值也就是25000次的时候做一个分界线,当当前计数值CNT_NOW大于25000时输出高电平,小于25000时输出低电平,从而形成方波。这就是这行代码的意思。
04
电子琴的设计
再建一个.v文件,同样的还是要先定义端口。
然后再定义一个计数模块,就像前面的cnt一样,只不过这里的计数模块是用来切换频率的,不同的频率可以使蜂鸣器发出不同声音的鸣叫。
index是设置用来计数的,每计20次循环一次,然后通过类似于c语言中的switch函数来给ACC赋值,当index取不同的值时便给ACC赋不同的值。也就是设置不同的频率,然后去驱动蜂鸣器。
在驱动蜂鸣器的.v文件中引用并连线,此处需要注意的是需要再调用一个延时计数函数,因为原本的计数是用来计数频率的,此处电子琴的计数是需要计数周期,每隔一段时间切换一个频率,两者互不相同。
因此需要再写一个计数值,用来计时改变音调即计时改变频率,如此便实现了电子琴的功能。