打印
[FPGA]

信号发生器和DA转换 FPGA案例教程

[复制链接]
782|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
guyu_1|  楼主 | 2018-11-16 00:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

1     项目背景   源码下载技术交流群:544453837 暗号:fpga
1.1   信号发生器
      信号发生器又称信号源或振荡器,是一种能提供各种频率、波形和输出电平电信号的设备,在测量各种电信系统或电信设备的振幅特性、频率特性、传输特性及其它电参数时,以及测量元器件的特性与参数时,用作测试的信号源或激励源,在生产实践和科技领域中有着广泛的应用。

     直接数字式频率合成器(DDS)是将先进的数字处理理论与方法引入频率合成的一项新技术,它把一系列数字量形式的信号通过数/模转换器转换成模拟量形式的信号。


      上图是一个典型的DDS工程。DDS一般可分为相位累加器、信号转换器和DAC。

      DDS的输入是频率控制字,它用来控制相位累加器每次增加的相位值,相当于一个步进值。

      相位累加器:每来一个时钟脉冲,就会在原来相位值的基础上,加上频率控制字的值,得到最新的相位值,将相位值将输出给信号转换器。

      信号转换器:一般转换器内部有一片ROM,事先保存了要产生波形的幅度值。根据输入的的相位值,就能输出该相位值所对应的信号幅度值。例如将一个完整周期的正弦波等距离分成128份,并保存到转换器的ROM当中。当相位值为0时,就输出相位为0所应对的幅度值;当相位为100时,就输出相位为100所对应的幅度值。

       的具体工作过程是由N位相位累加器、N位加法器和N位累加寄存器组成。每来一个时钟脉冲,N位加法器将频率控制字K与N位累加寄存器输出的累加相位数据相加,并把相加后的结果送至累加寄存器的输入端。累加寄存器一方面将上一时钟周期作用后所产生的新的相位数据反馈到加法器的输入端,使加法器在下一时钟的作用下继续与频率控制字K相加;另一方面将这个值作为取样地址送入幅度/相位转换电路,幅度/相位转换电路根据这个地址输出相应的波形数据。最后经D/A转换器和 LPF将波形数据转换成所需要的模拟波形。





1.2 DA转换
         明德扬教学板板载双通道、125MHz 转换速率、8bi的高速DA芯片,满足常用信号发生器、滤波信号输出等需求。实际位置如下所示:


      芯片型号是AD9709,AD9709是一款双端口、高速、双通道、8位CMOS DAC,其中集成两个高品质8位TxDAC+®内核、一个基准电压源和数字接口电路,采用48引脚小型LQFP封装。它提供出色的交流和直流性能,同时支持最高125 MSPS的更新速率。



      与FPGA相连的信号有:DA_CLKA、DA_CLKB、DAC_DB7~0、DAC_DA7~0,DAC_MODE、DAC_SLEEP、DA_WRA和DA_WRB。


1.3    AD9709的时序
      AD9709的控制时序如下图


     在双通道模式中,通道A和通道B就如两个独立的DA芯片。其中DA_CLKA、DAC_DA7~0、DAC_WR_A用于控制通道A,DA_CLKB、DA_DB7~0、DA_WRB用于控制通道B。

      以控制通道A为例,时序图要求,要先将数据输出到DAC_DA7~0,然后经过ts时间后,将DAC_WRA和DA_CLKA变高,此时DAC就将数据锁住,经过一段时间后,就会输出数据所对应的电流,经过电路转换后就变成对应电压了。

      时序图中要注意几点(数据手册有详细说明)

     1. DA_CLKA并且超前于或者同时与DA_WRA由0变1。

     2. 图中tS(DAC_WRA上升沿前数据保持不变的时间)、tH(DAC_WRA上升沿后数据保值不变的时间)、tLPW(DAC_WRA的高电平时间)、tCPW(DAC_CLKA的高电平时间)等参数,查询数据手册,可以得到如下参数表。从表中可以看到tS的时间至少是2ns;tH时间至少是1.5ns;tLPW、tCPW时间至少是3.5ns。图中规定了至少,只要大于要求都是可以的。


      通道B的时序要求和通道A是相同的,仅是控制信号不同。

      明德扬教学板的AD9709的两个通道,均支持0.48~2.2V的电压输出,这个输出电压与输入数据的关系,可用下面的公式表示:

      通道A的输出电压 = -1.72 * (DAC_DA /255) + 2.2 V

     通道B的输出电压 = -1.72 * (DAC_DB/255) + 2.2 V


      由公式可见,输出电压与DAC_DA/B的值是成线性反比例关系,最低电压为0.48V,最高为2.2V。需要指出的是,由于电路原理图的原因才导致电压在此范围,不同电路实现是不相同的。

2   设计目标
      本次案例将使用到采样率大于100M的示波器。将示波器和教学板上的通道1连接,如下图所


      本案例是要让DA输出不同频率的正弦波。共输出方式如下:

      1.       连续输出2个周期为6.25MHz的正弦波,其中每个正弦波输出16个采样点;

      2.       连续输出2个周期为3.125MHz的正弦波,其中每个正弦波输出32个采样点;

      3.      连续输出2个周期为1.5625MHz的正弦波,其中每个正弦波输出128个采样点;

      4.       连续输出2个周期为781250Hz的正弦波,其中每个正弦波输出128个采样点;

      5.       连续输出2个周期为390625Hz的正弦波,其中每个正弦波输出128个采样点;

      6.      连续输出2个周期为195312.5Hz的正弦波,其中每个正弦波输出128个采样点。

     重复以上的1~7的步骤。

      正弦波的最高电压是2.2V,最低电压是0.48V

    示波器的显示结果如下图


上图是整体效果图,每种频率的正弦波连续出现2次,并且正弦波的周期越来越大。

下图是捕捉到的,频率为6.25MHz的正弦波,最高电压是2.2V,最低电压是0.48V。

下图是捕捉到的,频率为3.125MHz的正弦波,最高电压是2.2V,最低电压是0.48V。

下图是捕捉到的,频率为1.5625MHz的正弦波,最高电压是2.2V,最低电压是0.48V。

下图是捕捉到的,频率为390625Hz的正弦波,最高电压是2.2V,最低电压是0.48V。

下图是捕捉到的,频率为195312.5Hz的正弦波,最高电压是2.2V,最低电压是0.48V。

3   设计实现
3.1 顶层接口
       新建目录:D:\mdy_book\dds_da。在该目录中,新建一个名为dds_da.v的文件,并用GVIM打开,开始编写代码。



      我们要实现的功能,概括起来就是FPGA产生控制AD9709,让其中的通道A产生正弦波所对应的电压。为了控制AD9709的通道A,就需要控制AD9709的MODE、SLEEP、CLK1、WRT1、DB7~0P1管脚。根据设计目标的要求,整个工程需要以下信号:

1.       使用clk连接到晶振,表示50M时钟的输入。

2.       使用rst_n连接到按键,表示复位信号。

3.       使用dac_mode信号连接到AD9709的MODE管脚,用来控制其工作模式。

4.       使用dac_sleep信号连接到AD9709的SLEEP管脚,用来控制其睡眠模式。

5.       使用dac_clka信号连接到AD9709的CLK1管脚,用来控制通道A的时钟。

6.       使用dac_wra信号连接到AD9709的WRT1管脚,用来控制通道A的写使能。

7.       使用8位信号dac_da连接到AD9709的DB7~0P1管脚,用来控制通道A的写数据。

     综上所述,我们这个工程需要7个信号,时钟clk,复位rst_n,dac_mode、dac_sleep、dac_clka、dac_wra和dac_da,其中dac_da是8位信号,其他都是1位信号。     下面表格表示了硬件电路图的连接关系。


将module的名称定义为dds_da,代码如下:


     其中clk、rst_n是1位的输入信号,dac_da是8位的输出信号,dac_mode,dac_clka,dac_wra,dac_sleep是一位输出信号。


3.2   信号设计
      我们先分析下DAC的输出。以频率为195312.5Hz的正弦波为例,如下图。频率为195312.5Hz,也就是一个正弦波的周期是5120ns。案例要求一个周期要输出128个点,那就是每隔5120/128=40ns要输出一个点。考虑到工程输入的时钟是50MHz,周期是20ns,那就意味着每隔2个时钟就要输出一个点。

     综上所述,产生频率频率为195312.5Hz的正弦波,就是每隔2个时钟输出一个电压值,一共输出128个点,组成一个正弦波。我们要连续产生2个正弦波。

     现在进一步分析下,这128个点所对应电压值是多少?由于教学板的输出电压在0.48~2.2V之间,最低值是0.48V,最高值是2.2V。

      先将一个标准的正弦波向上平稳1个单位,使得范围变成0~2。然后等间隔取128个点(间隔为2*pi/128),获取其幅度值。我们再用8位信号sin_data表示这些幅度值,其表示方式为:

     sin_data = (sin(2*pi/128) + 1) * (255/2),i为0~127     (公式1)


      通道A的输出电压 = -1.72 * (DAC /255) + 2.2 V

      公式中可以看到,通道A的输出电压是与DAC_DA成线性反比例关系。为了让通道A的电压正确地展现出正弦波,我们还需要做如下调整。

      DAC_DA = 255 - sin_data

      上面的DAC_DA就是最终输出给DA芯片的数据值,即dac_da信号。

     综上所述,产生频率为195312.5Hz的正弦波,就是每隔2个时钟输出一个电压值dac_da。先按表XX每隔1个选出sin_data,再用(255-sin_data)得到dac_da。一共输出128个点,组成一个正弦波,并且连续输出2个正弦波。

      以相同的分析方法,分析频率为6.25MHz的正弦波。

     频率为6.25MHz,也就是一个正弦波的周期是160ns。案例要求一个周期要输出8个点,那就是每隔160/8=20ns要输出一个点。考虑到工程输入的时钟是50MHz,周期是20ns,那就意味着每隔1个时钟就要输出一个点。

     先将一个标准的正弦波向上平稳1个单位,使得范围变成0~2。然后等间隔取8个点(间隔为2*pi/8),获取其幅度值。我们再用8位信号sin_data表示这些幅度值,其表示方式为:

sin_data = (sin(2*pi/8) + 1) * (255/2),i为0~7   

        = (sin(2*pi*16/128) + 1) * (255/2),i为0~7   (公式2)

     对比公式1和公式2,发现同样可以由表XX得到相应的sin_data,只是此时间隔16个点取一个值,一共取8个。

      综上所述,产生频率为6.25MHz的正弦波,就是每隔1个时钟输出一个电压值dac_da,按表XX中每隔16个点输出一个值,再用(255-sin_data)得到dac_da。一共输出8个点,组成一个正弦波,并且连续产生2个正弦波 .

     按同样的分析方法,分析其他频率。最终总结如下:

   1.       连续输出2个周期为6.25MHz的正弦波,其中每个正弦波输出8个采样点。

      等价于:每隔1个时钟输出一个电压值dac_da,一共输出8个点,组成一个正弦波,连续产生2个正弦波。

dac_da的产生方式:表XXX每隔16个选出得到sin_data,通过(255-sin_data)得到dac_da。

      2.       连续输出2个周期为3.125MHz的正弦波,其中每个正弦波输出16个采样点。

等价于:每隔1个时钟输出一个电压值dac_da,一共输出16个,组成一个正弦波,连续产生2个正弦波。

dac_da的产生方式:表XXX每隔8个选出得到sin_data,通过(255-sin_data)得到dac_da。



     3.       连续输出2个周期为1.5625MHz的正弦波,其中每个正弦波输出32个采样点。

等价于:每隔1个时钟输出一个电压值dac_da,一共输出32个,组成一个正弦波,连续产生2个正弦波。

     dac_da的产生方式:表XXX每隔4个选出得到sin_data,通过(255-sin_data)得到dac_da。

     4.      连续输出2个周期为781250Hz的正弦波,其中每个正弦波输出64个采样点。

等价于:每隔1个时钟输出一个电压值dac_da,一共输出64个点,组成一个正弦波,连续产生2个正弦波。

dac_da的产生方式:表XXX每隔2个选出得到sin_data,通过(255-sin_data)得到dac_da。

5.       连续输出2个周期为390625Hz的正弦波,其中每个正弦波输出128个采样点。

等价于:每隔1个时钟输出一个电压值dac_da,一共输出128个点,组成一个正弦波,连续产生2个正弦波。

dac_da的产生方式:表XXX每隔1个选出得到sin_data,通过(255-sin_data)得到dac_da。

      6.       连续输出2个周期为195312.5Hz的正弦波,其中每个正弦波输出128个采样点。

等价于:每隔2个时钟输出一个电压值dac_da,一共输出128个点,组成一个正弦波,连续产生2个正弦波。

dac_da的产生方式:表XXX每隔1个选出得到sin_data,通过(255-sin_data)得到dac_da。

      按照至简设计法中的变量法思想,那么可以概括上面的功能:每隔x个时钟输出一个电压值,一共输出y个点,组成一个正弦波,每个要产生要连续产生2个正弦波。由于一共要产生6种不同频率的正弦波,所以还需要一个计数器来数6个。

     总结出上面的内容后,我们开始设计代码。

“每隔x个时钟输出一个电压值”,所以这需要一个计数器cnt0,加1条件是“1”,结束条件是数到x个,可以得到cnt0的代码。


     “一共输出y个点”,这同样需要一个计数器cnt1。注意的是,由于每个点维持x个时钟,也就是cnt1的加1条件是“数到x个时钟”,即end_cnt0。结束条件是:数到y下。可以得到cnt1的代码。


      “每个要产生要连续产生2个正弦波”,这也需要一个计数器cnt2。一个正弦波由y个点组成,所以cnt2的加1条件是“数到y个”,即end_cnt1,结束条件是“数到2个”。可以得到cnt2的代码:


       由于一共要产生6种不同频率的正弦波,所以还需要一个计数器cnt3来数6个。这个cnt3的加1条件是“产生完2个正弦波”,即end_cnt2,结束条件是“数到6个”。可以得到cnt3的代码。


      我们定义了变量x和y,其中x表示相隔的时钟数,y表示一个正弦波的采样点数。具体的x和y是与正弦波的不同频率相关的,也就是与cnt3相关。根据题意和至简设计法中的变量设计方法,可以得到x和y的代码。


      有了计数器之后,其他信号就可以根据计数器设计出来了。

      首先看信号dac_da。dac_da都是按(255-sin_data)得到。那么可以写出dac_da的代码


      接下来看sin_data信号。sin_data是从表XX中选择出来的值,不同的频率,选择的方式不同。那么很自然是定义一个选择信号addr。我们只要控制好addr,就能方便得到sin_data。







      接下来设计信号addr。addr是用来控制选择数据的地址,不同频率的正弦波要求地址控制方式不同。频率为6.25MHz(cnt3=0)是每隔16个选择一个;频率为3.125MHz(cnt3=1)是每隔8个点选择一个;频率为1.5625MHz(cnt3=2)是每隔4个点选择一个;频率为781250Hz(cnt3=3)是每隔2个选择一个;频率为390625Hz(cnt3=4)是每隔1个点选择一个;频率为195312.5Hz(cnt3=5)是每隔1个选择一个,一共发送128个。

     我们用cnt1表示发送的第几个数。

     cnt3==0 时,addr = cnt1*16;

     cnt3==1时,addr = cnt1*8;

     cnt3==2时,addr = cnt1*4;

     cnt3==3时,addr = cnt1*2;

     cnt3==4时,addr = cnt1*1;

     cnt3==5时,addr = cnt1*1。

     因此,可以写得addr的代码


       接下来是信号dac_sleep,AD是一直工作的,所以要让dac_sleep一直为0。

     dac_clka为了满足tS的时间要求,可以让dac_clka = ~clk。

      dac_wra可以与dac_clka相同。

      dac_mode是控制AD9709的模式,当为高电平时,表示双通道模式,此时通过DA、DB两组信号分别独立控制两个通道。在能实现功能的前提下,越简单越好,就使用双通道模式,因此令dac_mode一直为1。


至此,主体程序已经完成。接下来是将module补充完整。

3.3  信号定义
     接下来定义信号类型。

     cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为15,需要用5根线表示,即位宽是5位。add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


     cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为127,需要用8根线表示,即位宽是8位。add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:


     cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为7,需要用3根线表示,即位宽是8位。add_cnt2和end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:


      cnt3是用always产生的信号,因此类型为reg。cnt3计数的最大值为5,需要用3根线表示,即位宽是3位。add_cnt3和end_cnt3都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:


     变量x,y是用always方式设计的,因此类型为reg,x最大值为2,要有2位来表示,y最大值为128,要有8根线表示,即位宽为8,因此代码如下,


      addr是用always设计的,因此类型为reg。其值最大为127,一共有7根线,位宽为7,故而代码如下


     sin_data是用always设计的,因此类型为reg。其最大值为255,要有8根线表示,位宽为8,故而代码如下:


      dac_da是用always设计的,因此类型为reg。其位宽为8;dac_sleep是用assign设计的,因此类型为wire,位宽为1;dac_wra是用assign设计的,因此类型为wire,位宽为1;dac_clka是用assign设计的,因此类型为wire,位宽为1;dac_mode是用assign设计的,因此类型为wire,位宽为1。故而代码如下:


       在代码的最后一行写下endmodule.


4    综合工程和上板
4.1 新建工程

   1.)打开quartus,点击File 在File菜单中选择New ProjectWizard.... 。


       2.弹出Introduction界面选择Next。


      (3)设置工程目录,工程名,顶层模块名

      工程目录设置为:D:\mdy_book\dds_da

      工程名:dds_da

      顶层模块名:dds_da

     填写完毕后,点击next之后进入下一界面。


     工程类型界面,Project Type选择Empty project,选择空白工程。点Next进入下一个界面。


       (3.)在文件添加界面,点击右上角的,在弹出来的窗口中,双击选择D:\mdy_book\dds_da目录下的dds_da.v文件。


      点击右上角的add按键,将文件添加进工程。


      在主窗口中会显示将dds_da.v加入了工程。点击Next,进入下一个界面。


4.2   综合

      在菜单栏中,选中Processing,然后选择Start Compilation,开始对整个工程进行编译和综合。


      出现上面的界面,就说明编译综合成功。


4.3 配置管脚

      在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。


      在配置窗口中的location一列,可以填写每个管脚所对应的FPGA管脚号。

      按上面配置好每个信号的管脚,其最终效果如下图。

4.4 再次综合

    在菜单栏中,选中Processing,然后选择Start Compilation,开始对整个工程进行编译和综合。


    出现上面的界面,就说明编译综合成功.

4.5   连接开发板

      连接示意如上图所示。将电源接上开发板;USBBLASTER一端连接到JTAG插口,另一端连到PC的USB接口;将开发板上的P7接口与示波器相连。最后再将电源打开。

4.6  上板

      在quartus的Task窗口中,右键Program Device 选择Open 进入烧录界面。


在上面的界面中,默认会选中文件output/dds_da.sof,如果没有生成请看XXXX。


点击statr,在progress这一条显示100%即表示成功,此时可以看FPGA输出效果了。

<p>

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

53

主题

61

帖子

2

粉丝