这篇文章主要讲解两个知识点:驱动蜂鸣器(定时器设计)、变音蜂鸣器(层次化系统设计)。
定时器与计数器的区别不大。
定时器:核心单元本质上是个计数器,设置一个定时值,启动定时器后,计数器开始计数,计数满后产生计数满标志信号,提示设定的定时时间到达。
计数器:对脉冲信号进行计数,统计一确定时间段内该脉冲信号出现的次数,或者等待指定次数的脉冲信号出现后,产生相应标志。
定时时间可修改的定时器需要满足以下要求:
(1)定时器在不同的运行时段,可能需要的定时时间不同;
(2)定时器可以循环定时,也可以单次定时;
(3)定时时间到达时,需要产生标志信号。
(4)必要时,需要对外提供实时计数值。
一、定时器设计
本节设计一个定时器,能够支持以下功能:
该定时器的定时时间参数可以通过该模块的一个端口输入,通过调节端口上输入数据的值,就能修改其定时时间;
设置一个计数模式控制信号,当该信号为1时,设置为循环定时模式,当该信号为0时,设置为单次定时模式;
设置一个计数启动信号,在循环定时模式下,该信号为高电平使能计时,为低电平则停止计时。在单次计数模式下,该信号的一个单基准时钟周期的脉冲使能一次定时。
输出计时器实时计数值,该值将用于产生特定占空比的方波。
我们结合AC620开发板上的蜂鸣器,完成定时器的验证实验。
蜂鸣器按照驱动电路得不同可以分为有源蜂鸣器与无源蜂鸣器。有源蜂鸣器内部带震荡源,所以只要通电就会鸣叫;而无源蜂鸣器内部不带震荡源,因此如果用直流信号无法令其鸣叫,这就需要用200-5K的方波(声音频率)去驱动。
创建工程
![](https://img-blog.csdnimg.cn/img_convert/dff9c863cced0d1cb9fda1491178de6b.png)
编写verilog文件
我们要先想清楚要设计哪几个module。由于时序逻辑电路,所以要有clk和Rst_n。根据设计要求,我们要设置时间参数输入端CNT_ACC,计数模式控制端MODE,计数启动端Cnt_GO,输出实时计数端CNT_NOW,计时完成标志端Full_Flag。
![](https://img-blog.csdnimg.cn/img_convert/44694f202641b7736be488dd4f3e4c2f.png)
设置好输入和输出
![](https://img-blog.csdnimg.cn/img_convert/7cdc92b6ec48781526117bb4e0627ebf.png)
接下来就是根据要求设计敏感事件了,
复位端为0时置0
复位端为1且计数模式控制端为1时:循环计数。如果计数启动端为1且当前计数小于设定值时,计数+1,否则计数置0
复位端为1且计数模式控制端为0时:单次计数。而对于计数启动端,在单次计数模式下,该信号的一个单基准时钟周期的脉冲使能一次定时。这里就产生一个问题,单基准时间周期的脉冲怎么表示?
![](https://img-blog.csdnimg.cn/img_convert/a6001c423f0073946416cb1b3c869079.png)
定义一个oneshot信号
![](https://img-blog.csdnimg.cn/img_convert/c0c1ffe9490ee683b24184e8910a6508.png)
再引入Full_Flag,整体代码如下
![](https://img-blog.csdnimg.cn/img_convert/1576e5f3299209bbcc0bda73abfe2d51.png)
注意逗号,分号,下划线都要用英文格式,如果错用中文格式就会报错。
![](https://img-blog.csdnimg.cn/img_convert/f8708f1a0e45ee5f23b404822bcd39e7.png)
![](https://img-blog.csdnimg.cn/img_convert/0b2afa137cf5986094a912d76d933da5.png)
使用了66个逻辑单元,33个寄存器,69个引脚
![](https://img-blog.csdnimg.cn/img_convert/c7ab8447371c1ca962ce6b2cd30e107a.png)
编写testbench
先写timescale和module
![](https://img-blog.csdnimg.cn/img_convert/fa75fd9d9a78ebb782fb25afc5f81fb3.png)
拷贝并粘贴beep_test中的module部分,并起个新名字beep_test0
![](https://img-blog.csdnimg.cn/img_convert/4e987f6bd66b5162f3db12d8d49a958b.png)
在这段代码上面拷贝粘贴输入输出部分,并将input替换成reg,将output替换成wire
按Ctrl+H可以进行查找和替换
![](https://img-blog.csdnimg.cn/img_convert/958a6c14572662ba8951a8ba0ed79962.png)
![](https://img-blog.csdnimg.cn/img_convert/af383951b349666ffc6b042cf3ead7af.png)
例化
![](https://img-blog.csdnimg.cn/img_convert/c64ee1535a6a6956c1960f5401eb2027.png)
设置仿真流程
![](https://img-blog.csdnimg.cn/img_convert/f15008137c7638762eadbdf0811b2fea.png)
仿真与调试
RTL仿真出现了如下错误
![](https://img-blog.csdnimg.cn/img_convert/e32ce0e987c2576fc3f5095569d72e9d.png)
我们双击错误提示,找到错误出现的位置
![](https://img-blog.csdnimg.cn/img_convert/eef2b614f90a0a01d32938024206e6ae.png)
原来是因为出现了对oneshot先使用后定义的情况,但是在Quartus里这不会产生错误,因为Quartus是并行执行,ModelSim是顺序执行,这就出现问题了。所以我们把oneshot的定义挪到上面
![](https://img-blog.csdnimg.cn/img_convert/4cd54b5b0d0627551491d731e06b2bfb.png)
改好了记得保存,编辑好的testbench文件别忘记导入,之前的博客中有介绍,否则不会出波形的。
![](https://img-blog.csdnimg.cn/img_convert/6bc3540f8dbfbbab8e18a0019423dfeb.png)
转成十进制
![](https://img-blog.csdnimg.cn/img_convert/4485c11a8eefb2a8005dc4dc457db9bb.png)
我们发现了一个重要问题:输出计数端CNT_NOW没有计数,一直保持0。这说明我们的代码逻辑在某些地方是存在问题的,那么根据现象我们去回头找错误在哪。
![](https://img-blog.csdnimg.cn/img_convert/1555b7988fca822fbe301b57d234bfb2.png)
目前cnt一直为0,最有可能的就是定时器一直处于置0状态,那么就要去看tb文件中与Rst_n有关的语句,看看是不是使它一直保持0。
![](https://img-blog.csdnimg.cn/img_convert/31e12670e9bc02e1835b05b8f8047e43.png)
这里是有问题的,跳出复位后,Rst_n应该等于1,因为复位端是低电平有效,保持低电平会使输出一直是0,继续检查,oneshot信号没有被设置为必须仅在MODE等于0时才变化。所以这样改
![](https://img-blog.csdnimg.cn/img_convert/479e42bf86880a13596ac1946c5cd06a.png)
再跑一次波形就正常了,但是出现一个问题,单次计数时,多计了一个
![](https://img-blog.csdnimg.cn/img_convert/13c3a93d9e3f8bc7bf42b4e8a80440e1.png)
![](https://img-blog.csdnimg.cn/img_convert/d7f64d0a91af1438d5097b9f8568b77a.png)
这是因为Full_flag占了两个时钟周期,所以我们要让Full_flag提前一个时钟周期触发高电平
![](https://img-blog.csdnimg.cn/img_convert/cd1525226494022c5d319e01d32e3ece.png)
![](https://img-blog.csdnimg.cn/img_convert/23d29e552b888640590d95bc15cf4ccc.png)
这回就没问题了。如果我们想让Full_Flag只占一个时钟周期,那就将>=改成==
![](https://img-blog.csdnimg.cn/img_convert/ef608d668f073c83a8ee71994148c87f.png)
![](https://img-blog.csdnimg.cn/img_convert/16e020cc2f09b9c85e183e8e619bd820.png)
现在的情况属于超前进位,如果我们想在此基础上将Full_Flag向后移位一个时钟周期呢?也就是从599挪到600。如果只是单纯的把-1去掉,那就会出现跟之前一样的问题(多计一位)
![](https://img-blog.csdnimg.cn/img_convert/de04a7f3bafd89a49276885ecc7e8e90.png)
所以我们需要更加复杂的手段,那就是串联一个D触发器,使Full_Flag延迟一拍。
![](https://img-blog.csdnimg.cn/img_convert/b8369b73be3456c958cf50906c4c0099.png)
把原来的Full_Flag设为Full_Flag_r,把Full_Flag定义为寄存器类型,令Full_Flag等于Full_Flag_r,刚好可以占一个时钟周期,达到延迟的效果。
![](https://img-blog.csdnimg.cn/img_convert/26fd99347ea5697e306d53b6ee10baea.png)
至此,定时器设计完成。接下来我们进行蜂鸣器测试
二、蜂鸣器驱动
设计顶层文件
设计一个顶层文件,命名为beep_top
![](https://img-blog.csdnimg.cn/img_convert/40370ed2403124cadd0f8cdc3e71b622.png)
保存到rtl文件夹中,进行分析和综合
![](https://img-blog.csdnimg.cn/img_convert/7fc5f113e4f27d677e8d2828731d2ed5.png)
接着,切换工程顶层文件为beep_top
![](https://img-blog.csdnimg.cn/img_convert/66a9077e4d9f5d17126f9a19c8b0e286.png)
![](https://img-blog.csdnimg.cn/img_convert/04132bcaca3e1f36b5707f980d80bc05.png)
再次进行分析和综合
![](https://img-blog.csdnimg.cn/img_convert/7eae2a9868a5361e15cf764a4b46dedf.png)
我们发现逻辑单元为0,这是因为top文件中,我们只例化,而没调用,只有输入没有输出。重新编写一下beep_top,再进行分析和综合
![](https://img-blog.csdnimg.cn/img_convert/e05a68336fd238d8b52bfed72fc0b93e.png)
![](https://img-blog.csdnimg.cn/img_convert/1ad366cf6dec51e08a40f86a6d482067.png)
引脚分配
先把I/O 标准都换成3.3V。
![](https://img-blog.csdnimg.cn/img_convert/bd83a8f12257dfedbaf9ec92b9050054.png)
然后去查用户手册,分配个端口的引脚
clk是时钟,我们用的是50MHz的,对应的引脚是E1
![](https://img-blog.csdnimg.cn/img_convert/f25bea763c5ac60b18ec6bb7f446c20a.png)
Cnt_GO是计数启动端,对应的是按键,我们选择S0,也就是M16引脚。Rst_n连S2,E16引脚
![](https://img-blog.csdnimg.cn/img_convert/7c2c2690199fa172bac057a63aa33aec.png)
蜂鸣器连L16
![](https://img-blog.csdnimg.cn/img_convert/f0605ddb01d493c8f3e16b93e8c1f887.png)
![](https://img-blog.csdnimg.cn/img_convert/58382c720004f01ed3c383cbb44ae8f3.png)
接着进行全编译,无问题
![](https://img-blog.csdnimg.cn/img_convert/77440c00346b3d366458821a08d48103.png)
最后连上板子,进行板机验证
板机验证
![](https://img-blog.csdnimg.cn/img_convert/22c61e1aa326021f5b60db6b903f61d2.png)
点击start之后,板子的蜂鸣器会发出声音,按住S0会停止发声,松开就会继续发声。
这是因为按键默认高电平,按下S0就会使Cnt_GO低电平,使CNT_NOW=cnt=0,蜂鸣器低电平。
那么,这个简单的蜂鸣器驱动实验就完成了。接下来可以做一个比较复杂的实验
三、变音蜂鸣器(电子琴)
设计要求
设计一个能够变频率驱动蜂鸣器的系统,该系统包括蜂鸣器定频驱动(定时器)和蜂鸣器频率设定电路。
每个音调的的频率如下表所示
![](https://img-blog.csdnimg.cn/img_convert/8a957d4fcf6ec3c9f6a675b0fcc8f55a.png)
定时器的定时设定参数输入端口,一般称作预重装值(𝑐𝑜𝑢𝑛𝑡𝑒𝑟_𝑎𝑟𝑟)。
预重装值与对应频率(计数周期的倒数)的关系为:
𝑐𝑜𝑢𝑛𝑡𝑒𝑟_𝑎𝑟𝑟=𝑓_𝑐𝑙𝑘/𝑓_𝑝𝑤𝑚 −1=50000000/5000−1=9999
调整蜂鸣器的驱动频率,实质就是调整该参数的值,下表为当计数基准时钟为50MHz时,期望频率值与预重装值的关系。
![](https://img-blog.csdnimg.cn/img_convert/cdca08391c366e88f90dbdff4ba443b3.png)
预重装值就是我们设的CNT_ACC,之后我们将其都修正为CNT_ARR (Ctrl+F)
编写verilog文件
我们写一个查找表(Look-Up-Table)的代码,使用了case语句。ARR作为被赋值的对象,一定要定义成reg类型。
![](https://img-blog.csdnimg.cn/img_convert/0bf9872a20c312fec92457a32213425d.png)
分析和综合没问题,然后我们在beep_top里调用它,我们是用层次化系统设计,在顶层文件调用多个模块。设计了两个定时器,第一个用于驱动蜂鸣器,第二个用于音调切换。
![](https://img-blog.csdnimg.cn/img_convert/768f7ce67f37d105a9a2991e196c8981.png)
先分析和综合,然后来看一下RTL视图
![](https://img-blog.csdnimg.cn/img_convert/fd85f5d3417bb90743a884f05aae6ddc.png)
结构清晰,通过beep_test1定时器驱动sound_lut以生成相应的预重装值(CNT_ARR),传输到beep_test0定时器来改变蜂鸣器的音调。
板机验证
首先进行全编译
![](https://img-blog.csdnimg.cn/img_convert/6c86d8844af6e3c77c8a8825ca122982.png)
直接双击Assembler也可以,因为最后两步我们用不到,可以节约时间
![](https://img-blog.csdnimg.cn/img_convert/10c187ed1b907d393ef24abe20366acd.png)
接下来运行,确实可以听到蜂鸣器在发出哆来咪发梭拉西哆,实验成功。