最近接了一个项目吧,是我们学校物理院院长带的研究生搞的,小墨有幸跟他们合作,负责FPGA方面的工作,完成后据说还会申请国家专利,具体到什么时候完成,那可能就是猴年马月了,或者说我已经不在学校了。从今天开始,小墨将开始接触赛灵思公司的FPGA(老师提供的平台),用到的当然是SOPC。其实做做项目也好,让自己锻炼一下,我也好久没有做大一点的项目了,对我来说也是一个机会吧。
已经买好了去黄山的车票,这个季节的黄山应该很美吧,4月29号出发,又一次令人心潮澎湃的旅行~
信号发生器这个东西相信大家都知道,关于基于DDS信号发生器的技术文档网上也多的是,但是我还是想写一下这部分的教学,因为从我自身的学习来看,这部分内容并不是很难,也很容易实现,代码也就那几行。但是,我发现我当时学这部分的时候,从网上找资料,大部分都是基于原理的讲解,或者说只是做到仿真这一步,而且原理的讲解太过书面化,初学者不怎么好理解。我做了这么一个教程,全方面的做一个这样一个系统。其中包括信号发生器的原理部分,代码实现,仿真,按键控制频率、相位变换,包括正弦波、方波、锯齿波、三角波的产生,以及D/A转换芯片的操作,直到能够在示波器上观察到我们产生的波形,并通过按键控制为止。
这个系统的设计大概花了我三四天的样子,写的也是蛮用心的,工程不是很大,但还是有些细节需要注意的,下面我们就一步步的开始做。
一、整体框架
关于原理部分,书上讲的很多,但是总是感觉不尽人意,过于书面化的语言让人看着很头疼,下面就让小墨来给大家解释,希望给读者带来一种眼前一亮的感觉,让你再回去看书,看代码的时候觉得得心应手了。
首先我们应该先明确要做什么,我们要做的是一个频率,相位可调,任意波形信号发生器,也就是我们常见的正弦波,方波,锯齿波,三角波等。
其次,我们需要知道我们需要哪些参数。比如,我们要生成一个正弦波,它的初始频率是多少,相位是多少,步进频率、相位是多少,怎么计算这些参数?
然后我们需要知道如何生成一种波形,怎么样通过按键实现波形与波形之间的转换,例如我按下一个键生成正弦波,再按下一个键生成三角波,怎么实现?
再然后,我们需要把生成的数字信号送入D/A转换芯片,D/A转换的接口怎么写?怎么保证采集的数字信号完全正确?
最后,我们可以通过示波器观察我们生成的波形信号,验证我们的频率,相位是否符合我们的设计要求
下面是我简单的做的一个框架图
下面我们先来解释一下上面这张框架图。
首先,我们通过8位按键选择输出何种波形,这时候wave_select信号被赋予相应的值送给DDS模块,DDS模块由wave_select信号,从ROM中选择合适的地址,地址每变化一次,也就是数据每变化一次,DDS模块会告知DAC模块数据发生变化了,让它注意数据的采集,不要采错了!同时可以通过按键控制模块调节输出信号的频率,相位等,DAC模块将采集到的数字信号转化成模拟信号,送到示波器上显示。注意DAC芯片的采样频率,DDS信号的输出频率不能大于采集频率,否则数据就会出错。
二、DDS信号发生器原理详解
关于发生器原理这样部分有必要好好讲讲,很多人还是对某系问题不得其解,先来看这张图
DDS基本的结构组成,看图就知道了,包括相位累加器,相位调制器,波形数据表,和DA转换。我们还可以看到有两个输入,即频率控制字输入fword 和相位控制字输入 pword,这两个输入就是我们用来控制输出波形频率和相位的
1、相位累加器
相位累加器的原理,我们先假设频率控制字fword 为1。相位累加器的原理就是先将fword的值送到相位累加器,然后每来一个时钟,相位累加器的输出值,就跟相位累加器的新输入值相加,之后再送入相位累加器,再来一个时钟,再跟输入值相加送进去,如此循环。例如刚开始fword = 1 ,那么第一个时钟周期相位累加器的输出就是1,第二个时钟周期输出的就是2,第三个时钟周期输出的就是3。再例如,我们的频率控制字fword 刚开始等于2,那么相位累加器输出的就依次是0,2,4.....也就是说频率控制字fword 越大,相位累加器的输出值间隔也就越大,那么我们假设相位累加器的输出是32位的,如果fword越大,那么频率控制字记完到2^32 的时间就越短。这样的话,我们来算一下
我们使用的是50M的晶振,周期为20ns,假设fword为1,相位累加器的输出为N位的,那么每20ns,相位累加器加1,要加到2^N,需要20ns x 2^N 时间,这个时间就是输出一个完整信号的周期,那么我们可以知道,输出信号的频率为 Fout = Fclk /2^N,其中,Fclk为我们的晶振频率,再假如,fword = B 的时候,相位间隔提高 B 倍,因此计满一个周期的时间缩小了 B 倍,频率提高的 B 倍。综上所述,我们得出了输出信号的频率计算公式
有了这个公式,那么,如果我们把2^N看成是一个周期波形的相位,也就是说把一个波形的相位平均分成2^N个,每一个相位对应一个数字信号,将这些数字信号送到DA转换芯片,转化成模拟信号,不就是我们的信号发生器了吗?
好,这里我们假设N = 32 ,也就是把一个波形的相位分成了2^32个点,但是位数越多当然占用的资源也就越多,不能取那么多,怎么办呢,我们只取它的高8位即可
即取fre_add[31:24] ,将这8位数送给ROM的地址。很多人不明白了,为什么这样取8位,这样取8位不是把一个完整的信号给截断了吗?刚开始我也有这么想过,但是仔细算一下才知道自己是多虑了。用计算器算一下知道,2^32 = 4.3x 10^9 左右,而3^24 = 33 x 10^6 左右,前后差了几个数量级,所以,我们取fre_add[31:24],相当于把一个波形的相位分成了256个点,每个点对应一个数据。
二、ROM
上一部分说了,我们将一个波形分成了256个点,每个点对应一个数据,那么我们怎么实现呢,这里就要用到我们的只读存储器ROM了,我们可以先把波形的数据送到ROM里存起来,然后再从中取,取的地址就是我们的fre_add[31:24]这8位数,由此看来,fword越大,这256个地址取址的间隔就越大,相应的波形频率也就越大了,当然,我们的ROM不光存储一个波形,我们可以把ROM设大一点,里面放多种波形,再通过按键进行相应的选址,从而输出各种波形
说到这里有些人该问了,怎么将数据送到ROM里去呢?这个的话就设计到mif文件的制作,具体怎么做大家还是自己回去补课了,不一一介绍了,但是我这里有一个mif文件生成器,网上也有下载,可以生成各种波形,也可以进行相关参数的修改,注意保存的时候保存成.mif格式的文件即可,调用的时候在IP核 ROM的配置的时候调用就好了,但是要注意位宽。
三 、相位调制器
相位调制器部分就简单了,就是相位累加器取好的的8位ROM地址,我们可以通过按键进行加加减减,从而控制ROM取址的不同,从而控制波形的相位
四 、参数计算
由上面的公式我们知道了输出波形的频率计算公式,当fword =1的时候我们算一下
输出频率 Fout = 50_000000 / 2^32 = 0.01,就是个大约值了,也就是说,在fword =1的时候,我们的输出波形的频率为0.01HZ,假设我们让波形初始化的时候输出的是一个100HZ的正弦波,那么,我们应该设定fword = 10000,相位的话可以随便设置,我们就默认为0相位好了。如果要进行频率,相位可调,我们只需要让fword每次加100,就相当于频率步进为1HZ了,当然,由于我们的地址是8位的,那么,步进的相位就是 360 /256,大概等于1.4度吧。
还有,既然频率为100HZ,也就是周期为0.01s,ROM要在0.01s内要输出256个数,那么每输出一个数的时间为0.01 /256 大概为 39us的样子,因此,我们的DAC的采集频率不能比ROM的输出频率小了,否则的话采集到的数据是不准确的
综上所述,我们总结一下
输出频率: 100HZ
输出相位:0
步进频率:1HZ
步进相位:1.4度
DAC采集时间:小于39us
当然在一般情况下 DAC的采集频率是远大于ROM的输出频率的,只要信号输出频率不是特别快,采集正确还是有保证的。为了保证采集的正确性,我特别加了一个数据变化检测部分,一但数据发生变化,就告知DAC模块,做相应处理
一切准备就绪,我们可以做一下仿真,具体的仿真调试过程不做详解,还是大家自己动手去做的过程
五、DAC接口电路
DA芯片我用的是TLC5620这块芯片,这块芯片是四路输出,8位的的数模准换芯片,芯片的操作不是很难,我们还是先来看一下芯片的datasheet
DA芯片有这几个管脚需要用到,分别是
dac_clk,用于产生DAC的工作时钟,说明文档上说时钟最大为1M,而我们的晶振为50M因此我们需要做个分频,我做了个64分频,当然是为了稳定,如果可以大家也可以试试更高的频率
dac_data , 是串行输入的接口,用来接收数字信号
dac_load ,将接收来的数字信号开始转化成模拟信号的使能端,低电平有效,要持续最低250ns
dac_ldac , dac信号刷新控制端,当dac为低电平的时候一直有模拟信号输出,否则不再刷新
上面那个时序图大体解释一下,就是在load信号是高电平期间,每来一个DAC工作时钟的下降沿,就将1位数据通过dac_data端口送到DAC芯片内部的移位寄存器中,寄存器是11位的,前两位为输出模拟信号的通道号,第三位是用来计算输出电压用的一个参数吧,低8位是输入的数字信号,当移位寄存器存满之后,给一个load信号,工作时钟处于不工作状态,注意,这一点很重要,我当时就是不知道这一点,一直调不好,就是在转换期间,让工作时钟处于非工作状态,等待250ns之后,将load拉高,进行下一次采集。
关于代码的分析这里也不做详细分析了,大家还是自行消化,这里只传一部分,关于数据的发送部分的代码
每来一个时钟,送一位数据给dac_data端口,送满移位寄存器为止,当移位寄存器计满的时候,load拉低,同时时钟停止工作,bit_counter 清零,等待下一次采集。当然当数据发生变化的时候,也就是data_change 信号来的时候,bit_counter 也清零,重新采集,避免采集错误
全部模块编译完成之后,我们就可以用示波器来检测我们的结果了
由示波器的结果我们可以看到,输出电压峰值为2.5V,当然这是由芯片决定的,输出频率为116HZ,大体符合我们的要求,毕竟我们很多参数做了近似,再通过按键进行波形的变换,发现各波形输出正常,只有方波和锯齿波,在电压下降的时候变化有点缓慢,当然这也得考虑我们芯片的因素,毕竟电压不能变化的那么快,也属于正常情况
波形显示正常,下面通过按键来调节波形的频率和相位,可是实现实时显示的功能,发现频率,相位都有所改变
当然,这个工程也有做的不好的地方,例如,再进行相位调节的时候,由于各种波形的地址在ROM里面是挨在一起的,这样进行相位调节会侵犯其他波形的地址,这一点我当时也考虑过了,短时间内没有想出更好的办法,可以用多个ROM,其他的办法还请大家自己动脑子想一下吧,想出来了可以给我留言,大家一起交流嘛
好了,这一次主要还是讲了一下工程的全过程,没有仔细讲代码编写的具体过程,也没有讲仿真时候的操作流程,这些部分还是留给读者慢慢去摸索,毕竟这也是一个学习的过程,今天就到这里吧,由于小墨手上也有了项目,平时还要上课,更新速度方面应该不是很快!谢谢大家的支持~