• 国产CPLD(AGM1280)试用记录——做个SPI接口的任意波形DDS [原创www.cnblogs.com/helesheng]


    我之前用过的CPLD有Altera公司的MAX和MAX-II系列,主要有两个优点:1、程序存储在片上Flash,上电即行,保密性高。2、CPLD器件规模小,成本和功耗低,时序不收敛情况也不容易出现。缺点也很明显:1、没有片上RAM,无法对数据进行高速暂存和处理;2、没有PLL,使用一个以上的高频时钟非常不方便;3、没有小封装产品,MAX-II最小的EPM240也是LQFP100封装。近年来,随着Altera被Intel收购,对MAX-II的支持力度不断降低,当前EPM240的价格也达到了百元左右。

    最近在B站关注到一种国产CPLD——AG1280Q48,几乎满足了我对CPLD的一切幻想:1、片上Flash,上电即行。2、有1280个LUT和触发器,资源足以媲美小型FPGA,而工作电流仅为几个mA——MAX-II的几分之一。3、成本低到数元。3、QFN48小封装,节约嵌入式系统空间,焊接又相对BGA封装简单。4、拥有和FPGA类似(包括PLL和片上RAM块)的资源,可用于完成以往CPLD无法完成的任务,如信号缓冲,高速通信等。

    AG1280和STM32、GD32等低成本MCU联合使用时,能将只有专用解决方案才能完成的功能带给通用嵌入式系统。个人感觉,AG1280的最佳应用场景是在低成本应用领域和MCU协同工作, MCU+AG1280能部分替代Xilinx的Zynq方案。(注:AGM公司也有类似Zynq的ARM Cortex-M3 + FPGA方案,但我个人不看好这些方案,原因在于嵌入式工程师使用这些新MCU的开发环境和固件库的学习成本过高,没有学习动力。)

    为验证这一设计思路,我自己动手做了一个MCU+AG1280+DAC的DDS(直接频率合成器)系统,跑通了STM32+AG1280的开发过程。其中AG1280除了完成DDS算法所需的地址累加、数据表格存储和查询之外,还实现了与STM32之间的同步串行数据接收和波形表格存入。现将开发全过程分享给各位网友,相信会对大家有一定的参考价值,也供大神批评指正。

    有网友可能会较真质疑方案的意义:STM32有片上DAC,且还支持DMA,可以构成任意型号发生器,为什么还要用附加的可编程逻辑器件和DAC芯片?其原因在于STM32的DMA不支持存储器的地址递增值变化,因此不用中断无法实现DDS算法——而这也正好体现了AG1280在系统中的价值。

    以下原创内容欢迎网友转载,但请注明出处: https://www.cnblogs.com/helesheng

    一、硬件电路

    MCU开发板很多,这里就不“重复发明轮子”了——直接用手头STM32开发板上的PMOD接口来和AG1280子板连接。系统硬件方案框图如下图所示。

    注1:PMOD接口是由Xilinx官方定义的一种用于其FPGA开发板的低速接口,现在很多FPGA和嵌入式处理器开发板上都有这种接口。PMOD仅使用12芯2.54mm间距的两排普通连接器,其中含有8个GPIO以及电源、地。

    注2:有PMOD接口的单片机开发板可以自行搜索购买,我们教研室自己也开发过自编教材的配套Innovation-STM32开发板。

     

     

    图1 系统硬件框图

    用STM32开发板的PMOD连接一块自制的,具有一主一从两个PMOD接口的AG1280板子,该板子的另一个PMOD接口用于连接DAC板子。至于DAC我选用了常见的低成本同步串行芯片DAC7512。AG1280板子和DAC板子的电路图如下所示。

    图2  两个PMOD接口的AG1280电路原理图

    图3  PMOD接口的DAC7512电路原理图

    图2是AG1280基本电路,值得注意的有几点:

    1、IO_GLOBE_S1(位于第9脚)、IO_GLOBE_S2(位于第13脚)、IO_GLOBE_S3(位于第15脚)、IO_GLOBE_S4(位于第19脚);IO_GLOBE_N1(位于第41脚) 、IO_GLOBE_N2(位于第44脚) 、IO_GLOBE_N3(位于第46脚)可以作为全局时钟输入管脚,可用于输入全局时钟。但若要使用PLL,则只能从13、15和19管脚输入

    2、图2电路板载一个20MHz有源晶振,另外还可以通过PMOD接口从STM32的MCO时钟输出管脚获得时钟,它们被连接到具有PLL输入功能的管脚13、15上。

    3、AG1280的GPIO分为North和South两组,可以使用不同IO电平,以实现不同电平逻辑的转换。另外AG1280还需要3.3V电源作为片上Flash电源,且该电源域North组的IO电源共用,因此North组也只能使用3.3V的IO电源电压。South组却可以任选电源电压

    4、AG1280还需要1.2V内核电源电压,且该电源应略迟于Flash电源上电,以方便Flash加载程序。我的图2电路通过PMOD接口从STM32开发板获得3.3V电源,再用LDO芯片XC6206P122MR从3.3V向下稳压到1.2V内核电源,LDO后带有100uF电容,1.2V上电时间自然要落后于3.3V上电。

    二、基于Supera和Quartus II的AG1280开发流程

    AG1280的开发EDA软件Supera还不具备分析和综合电路的能力,但能实现其特有的PLL和片上RAM的IP核打包、综合后的布局布线、下载文件打包及下载等功能。我计划完成的DDS系统,完整的包含了PLL、片上RAM以及全部开发流程。

    1、开发平台搭建

    到百度网盘http://pan.baidu.com/s/1eQxc6XG 提取密码:q59e下载AGM公司EDA开发软件Supra(网盘上有多个版本的Supra,选择需要的一种即可)。Supra无需安装,下载后将其放置在不含中文的路径下,直接运行Bin目录下的Supra.exe即可。

    目前版本的Supra还无法进行硬件描述语言及原理框图的开发和电路综合,用户只能在Supra下创建工程并完成AGM公司特有IP(包括PLL和RAM)的配置,再通过Supra创建Quartus-II工程文件,在转到Quartus-II下完成硬件描述语言和原理框图开发和电路综合,最后再回到Supra中完成器件内部的布局布线、下载文件的打包和器件烧写(具体流程在后续会详细介绍)。

    综上,进行AG1280的开发一定需要安装一个顺手的Quartus-II。这里特别提醒网友注意,Supra只支持Quartus-II 13.0以上,且不支持Web与Lite版本,必须安装Full或Standard版本(本人掉到过坑里,因此特别提醒大家注意)至于Quartus-II的安装方法,网上资料较多,这里不再赘述。

    AG1280可以使用Intel的USB-Blaster进行下载和软件调试,但淘宝网上USB-Blaster版本较多,价格差异较大。据网传,有的版本USB-Blaster不支持AG1280的Flash下载,大家可自行注意避坑。

    2、开发流程

    1)新建Supra工程

    运行 Supra,选择 File - Project - New Project。

    图4 新建Supra工程

    随后选择工程存放路径并填入工程名称,注意不要使用中文路径、国产路径,也不要在路径中使用空格。

    2)配置AGM自有硬件IP

    AGM公司自有的硬件及其相关电路的IP只能在Supra中配置。先配置PLL:选择Supra中Tools - CreateIP – Create Pll菜单,在弹出的下列界面中配置PLL。

    图5 配置PLL IP

    分别配置模块名称、输入频率、PLL类型、反馈模式、输出时钟路数、输出频率后,单击Generate按钮产生IP和顶层封装HDL文件(模块名称.v和模块名称.ip)。

    其中值得注意的是:AG1280有两种时钟源模式:片内RC振荡器模式和片外有源振荡器模式。可以在反馈模式(Feedback mode)选项中选择EXT_FEEDBACK,以选择外部有源振荡器模式;选择NO_REFERENCE,以选择片上RC振荡器模式。片上RC振荡器振荡频率不会太准确,供不需要精确定时的系统使用。若选择片上振荡器则应在输入频率(Input frequency)处输入8MHz,否则真实输出频率将与你输入的频率成比例变化

    另外,Supra中AG1280的PLL配置中的PLL类型(PLL type),只能选择PLLX。而输出路数(PLL output count),相位移动(Phase shift)等配置参照字面意思理解即可。

    继续配置AG1280的片上存储器RAM IP:选择Supra中Tools - CreateIP – Create memory菜单,在弹出的下列界面中配置片上BRAM。

    图6 配置片上存储器IP

    根据我的DDS系统设计思路,AG1280实现的DDS控制器能从MCU接收波形数据,同时向DAC输出实时波形数据,因此,需要实现一个双口RAM。如图6所示,将存储器IP配置为2个端口,每个端口的数据宽度都是12bits(DAC7512的分辨率为12位),存储器深度为256(DDS算法要求波形表深度为2的整数次幂)。AG1280片上RAM较多,也可以选择更大的存储器深度,以获得更高信噪比。

    另外,我还选择了复位后从Flash向双口RAM内部加载初始数据,因此指定了初始化数据文件(Select init file)。根据DAC7512对数据数据格式的要求,这些数据是0-4095之间的无符号数,你可以通过MATLAB或Python等工具计算产生。注意,Supra数据文件的格式与Quartus-II的MIF、HEX都不一样;Supra要求数据文件中,每个数据占一行,且采用ASCII码表示十六进制数。例如下面一样。

    831

    862
    893
    8C4
    8F5
    925
    956
    986
    9B6

    完成IP配置后,单击Generate按钮,Supra将自动生成可在Quartus-II中调用的IP文件和Verilog HDL文件。

    3)在Quartus-II中完成功能模块开发

    单击Supra菜单中Tools – Migrate,将工程移植为Quartus-II工程。在下图所示的配置窗口中输入工程名称、器件、需要使用的之前创建的IP等信息后,单击Next按钮。

    注:最下面一个对话栏中,需要输入所有需要转换到Quartus-II工程的IP名称,如果有多余一个IP需要转换,可以在两个路径之间用半角逗号分隔开来,也可以点击Browse按钮后同时多选多个IP文件

    图7 移植为Quartus-II工程

    Supra弹出如下界面,可转入Quartus-II进行开发和综合电路。

    图8 移植为Quartus-II工程

    此时Supra已经在工程目录下建了与其工程同名的Quartus-II工程(xxxxx.qpf),以及顶层Verilog HDL文件,下一步需要在Quartus-II中打工程完成所有电路模块代码或原理图的开发。下图是我创建的DDS系统工程——DDS_SPI_dualRAM下的各个模块层次关系。

    图9 Quartus-II工程

    可以看到,在缺省情况下Quartus-II工程使用的器件是Cyclone IV系列的EP4CE75F29C8,我们不需要修改该工程使用的器件。但可以限定Quartus-II通过增量编译模式不编译Supra已经生成的IP:右键单击PLL IP和memory IP,并将其设置为Design Partition(该功能只有Prime和Standard版本的Quartus-II才有)。

    图10 设置IP为Design Partition

    在Quartus-II中运行Supra生成的脚本文件af_quartus.tcl:选择Quartus-II的Tools菜单下的Tcl scripts,并在弹出的窗口中运行工程目录下的af_quartus.tcl文件。

    图11 Quartus-II运行Tcl脚本

    Quartus-II将弹出Windows命令行窗口,等待综合工程中的所有模块,综合成功后Quartus-II的任务就完成了(这里可能需要几分钟)。

    此时Supra已经自动生成了一个文件名与工程名相同,使用后缀asf的管教约束文件。该文件目前还是空的,可以其中添加约束器件管脚的脚本。asf文件的语法与Quartus-II的tcl脚本管脚约束语法相同,例如:

    set_location_assignment -to cs_host1 PIN_43

    set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to cs_host1

    set_global_assignment -name RESERVE_ALL_UNUSED_PINS "AS INPUT TRI-STATED"

    上面的脚本将cs_host1约束到了AG1280的43脚,并将其配置成了弱上拉模式,以防止干扰拉低cs_host1,造成数据误传输

    第三句将所有未用到的管脚约束为三态输入管脚。这是Altera初学者容易犯的一个错误,缺省情况下Quartus会将所有未用到的管脚连接到地,以降低干扰,但这会造成未用到的管脚将外围电路短路到地。不知道AGM有没有这个问题——无论如何还是把它们先配置为高阻态安全一些。

    4)在Supra中完成AG1280的布局布线和程序下载烧写

    根据图2的硬件电路编辑完asf文件,即可返回Supra的图8所示界面,单击Next按钮。(注:若你已经关闭了Supra也不要紧,再次打开Supra工程后,单击Migrate按钮重新进入图8所示界面即可)。在随后的界面中检查配置,并单击Finish按钮,Supra将完成布局布线。

    若一切顺利,在Supra下部的Message窗口中间看到编译成功的提示信息,即可进入下载文件打包和USB Blaster下载配置文件阶段:选择Supra中的Tools – Program,连接电脑、USB Blaster以及AG1280板子的JTAG接口,在弹出的窗口中单击Query Device ID来查询Supra是否连接到AG1280。若正确连接了硬件,AG1280的ID应该是0x00120010。随后,可以在Program from file中选择需要下载的文件,单击Browse按钮选择所需的下载文件。其中xxxxx_sram.prg是直接烧写到AG1280的SRAM中;xxxxx_hybird.prg是烧写到Flash中,可以实现程序掉电不丢程序,上电即行。(文件名中的xxxxx是Supra工程的名称)。

    图12 程序下载烧写界面

    单击Program按钮,即可烧写刚才完成的程序。

    三、在AG1280上实现基于DDS算法的任意波形发生器

    为验证和测试AG1280的功能,我设计了基于DDS算法的任意波形发生器。AG1280内部的存储器RAM刚好可以用于存储MCU下载的DDS任意波形数据;另一方面,AG1280还定时地从RAM中读取波形数据用于DAC播放。为节约MCU和AG1280的管脚,它们之间的通信使用同步串行SPI接口,DAC7512也使用SPI兼容的同步串行接口。其中用于控制任意波形频率的DDS算法是AG1280要实现的核心算法,它的基本原理如下。

    1、DDS原理

    DDS算法的功能特点是可以获得非常高的频率分辨率,以取得近乎连续的频率调节效果。下面简述一下DDS算法的核心思想,帮助读者理解AG1280上的硬件模块的原理和相互关系。一时无法理解的初学者,可以进一步阅读其他介绍DDS原理的书籍和帖子。

    假设存储器中有个长度为N=2^n的波形数据表格,这些数据为1个周期的波形。若用频率fc从该表格中每间隔K个点取出一个放入DAC中变为模拟信号,则DAC输出模拟信号的频率将是:

    fo = fc * K / N              (1)

    从(1)式中可以看出输出模拟信号的频率fo和每次在数据表格中跳跃的地址数K成正比:K每增加1,输出频率fo就增加fc/N,因此K被称为"频率控制字"。为获得足够搞得频率分辨率,即使得fo输出的频率能够接近连续的调节,N的数值应该越大越好。一些ADI公司的商业DDS芯片,N甚至达到了2^40 ≈ 1T,这样大的表格显然是无法直接存储在AG1280中的。好在用1T个点来存储一个周期的信号其实并没有多大意义:在表格中相临的很多点大概率都是相同的内容。既然相邻点的数据都差不多,还不如不存储——在波形表格中每相临2^p个点,只存储第一个点的数值,如果用到后续2^p - 1个点,则都用第一个点的数值来代替。则AG1280所需的数据表格长度仅为2^(N-p) = 2^n(其中,设n = N-p),这样在AG1280中只需要长度N位的累加器和频率控制字,他们在时钟控制下不断相加新的表格地址(也是N位),但在读波形表时,只使用这个N位地址的高n位,低p位地址则直接舍弃。

    我们的系统使用的DAC是DAC7512,其输出模拟信号的刷新率fc为100KSPS,精度为12bits,取N为16,n为8。即所需的存储器为256(2^8)个12位(具体配置参见图6所示),地址累加器和频率控制字为16位。获得的频率分辨率为100K / 2^16 ≈ 1.53Hz,理论上频率调节范围上限为:100K / 2 = 50KHz(奈奎斯特频率),频率下限为:100K / 2^16 ≈ 1.53Hz。

    2、硬件总体设计

    AG1280中的整个电路以双口RAM(Dual Port RAM)为核心,其左侧接口与MCU相连,用于接收MCU下发的波形数据和频率控制字K的数值;右侧接口和DAC7512相连,用于依次输出DDS算法输出的数据。

    双口RAM左侧地址由一个每次加1的计数器产生,以遍历所有的存储单元;右侧地址由一个每次加K(频率控制字)的加法器产生,以实现DDS算法的频率控制。由于双口RAM的深度256刚好是2的整数次幂,所以无需特意处理计数器和加法器的溢出问题,溢出后自然返回地址开头即可。

    AG1280中还应实现两个SPI接口:一个SPI从机接口(通过左侧的PMOD接口)用于接收来自MCU的数据;另一个SPI主机接口(通过右侧的PMOD接口)用于向同步串行接口的DAC7512下发数据。

    左侧与MCU的接口除了需要接收波形数据之外,还需要接收频率控制字K。我通过两个管脚分别输出两个低电平选通信号cs1和cs2来区分当前下发的数据是波形数据还是频率控制字。

    3、硬件实现

    3.1 双口RAM右侧与DAC接口的电路部分

    1)DAC输出同步时钟生成电路

    DAC7512需要使用100KSPS的刷新率,需要25MHz的主时钟分配产生输出同步时钟信号sap_syc_sig,该信号还将同步双口RAM右侧的读取和DDS地址累加器的不断叠加。

     1 module samp_syc_sig(
     2     input clk_25m,
     3     input nrst,
     4     output samp_syc_sig
     5     );
     6 reg[9:0] time_cnt;    
     7 wire comp;
     8 reg samp_syc_sig_reg;
     9 assign samp_syc_sig = samp_syc_sig_reg;
    10 always @ (negedge clk_25m or negedge nrst) 
    11 begin
    12     if(!nrst)
    13         time_cnt[9:0] <= 10'd0;
    14     else
    15         if(time_cnt[9:0] < 10'd249)
    16             time_cnt[9:0] <= time_cnt[9:0] + 10'd1;
    17         else
    18             time_cnt[9:0] <= 10'd0;
    19 end
    20     //以下组合逻辑用于产生start信号
    21 assign comp =  (time_cnt[9:0] < 10'd20) ? 1'b1 : 1'b0;    
    22 always @ (negedge clk_25m or negedge nrst) 
    23 begin
    24     if(!nrst)
    25         samp_syc_sig_reg <= 1'b0;
    26     else
    27         samp_syc_sig_reg <= comp;
    28 end
    29 endmodule
    同步时钟生成电路

    2)与DAC7512通信的SPI主机电路

     1 module DAC7512(
     2     input clk_25m,//25MHz左右的时钟信号
     3     input nrst,//低电平复位
     4     input syc_sig,//外部产生的同步输出信号,当其出现上升沿时刷新输出电压
     5     output cs_da,
     6     output mosi_da,
     7     output sck_da,
     8     input [11:0] data_in//待转换的数据,在每次开始转换之前被锁存到模块中,只有低12位有效
     9     );
    10 reg[7:0] cnt_cs;
    11 reg cs_da_reg;
    12 reg sck_da_reg;
    13 reg[2:0] sck_timer;//用于测量单个DAC通信时钟长度的计数器
    14 /////////以下用25MHz时钟,产生片选信号////////
    15 assign cs_da = cs_da_reg;
    16 always @ (posedge clk_25m or negedge nrst)
    17 begin 
    18     if(!nrst)
    19     begin
    20         cnt_cs[7:0] <= 8'd0;
    21         cs_da_reg <= 1'b1;
    22     end
    23     else begin
    24         if(syc_sig == 1'b1)//同步信号高电平时,回复到计数初始状态
    25         begin
    26           cnt_cs[7:0] <= 8'd0;
    27           cs_da_reg <= 1'b1;   
    28         end
    29         else
    30         begin
    31             if(cnt_cs[7:0] < 8'd98) //98个脉冲,少一个防止竞争现象
    32             begin
    33                 cnt_cs[7:0] <= cnt_cs[7:0] + 8'd1; 
    34                 cs_da_reg <= 1'b0;
    35             end
    36             else begin
    37                 cnt_cs[7:0] <= cnt_cs[7:0];
    38                 cs_da_reg <= 1'b1;
    39             end
    40         end
    41      end
    42 end    
    43 ////////以下产生串行同步输出时钟////////////
    44 assign sck_da = sck_da_reg;
    45 always @(posedge clk_25m or posedge cs_da)
    46 begin
    47     if(cs_da)
    48     begin
    49         sck_timer[2:0] <= 3'd0;
    50         sck_da_reg <= 1'b0;
    51     end
    52     else begin
    53         if(sck_timer[2:0] < 3'd2)
    54         begin
    55             sck_timer[2:0] <= sck_timer[2:0] + 3'd1;
    56             sck_da_reg <= sck_da_reg;
    57         end
    58         else begin
    59             sck_timer[2:0] <= 3'd0;
    60             sck_da_reg <= !sck_da_reg;
    61         end
    62     end
    63 end    
    64 ///////////产生串行输出信号,需要串行输出的数据在CS下降沿在并行输入口被锁存/////////  
    65 reg[16:0] shift_reg;
    66 //注意这里多一个位,是因为DAC7512在下降沿读取数据,而我们的数据在第一个SCK脉冲上升沿就开始移出,因此会多移出一个位(第一位)
    67 assign mosi_da = shift_reg[16];
    68 always @ (posedge sck_da or posedge cs_da)
    69 begin
    70     if(cs_da)
    71         shift_reg[16:0] <= {5'b00000,data_in[11:0]};//对于dac7512而言,最高四位为0000,表示输出使能,且连接1KΩ负载
    72     else
    73        shift_reg[16:0] <= shift_reg[16:0]<<1;
    74 end
    75 endmodule
    DAC7512驱动电路

    上面的代码,使用独立的计数器分别产生SCK和CS信号,避免了由于组合逻辑信号链过长可能造成的建立时间不收敛问题。另外输入数据data_in只有12位,其内容就是模拟信号大小。DAC7512的SPI通信中需要使用16位数据,其高四位被上面的模块自动补充为控制字4’b0000,需要使用其他控制模式的网友,可以根据DAC7512手册自行修改。

    3)DDS地址累加器电路 

     1 module addr_adder(
     2                             input clk,
     3                             input nrst,
     4                             input[15:0] delta_addr,
     5                             output[7:0] high_byte_addr
     6                             );
     7 reg[15:0] inc_addr_reg;
     8 assign high_byte_addr[7:0] = inc_addr_reg[15:8];
     9 always @(posedge clk or negedge nrst)
    10 begin
    11     if(!nrst)
    12         inc_addr_reg[15:0] <= 16'H0;
    13     else
    14         inc_addr_reg[15:0] <= inc_addr_reg[15:0] + delta_addr[15:0];
    15 end
    16 endmodule
    DDS累加器代码

    注意,根据DDS算法,16位的累加器每次增加的值也是16位的频率控制字delta_addr(相当于公式(1)中的K),但每次只输出累加器的高8位作为双口RAM右侧的地址。

    3.2 双口RAM左侧与MCU接口的电路部分。

    4)根据不同片选信号,将MCU的SPI信号分解为波形数据和频率控制字的数据多路器电路

     1 module mux_HostSpi(
     2                     input mosi,
     3                     input sck,
     4                     input cs1,
     5                     input cs2,
     6                     output mosi_ram,
     7                     output mosi_DdsDelta,
     8                     output sck_ram,
     9                     output sck_DdsDelta
    10                     );
    11 assign mosi_ram = mosi & (!cs1);//输入CS1为低电平时,spi信号被送到双口ram一侧
    12 assign mosi_DdsDelta = mosi & (!cs2);//输入CS2为低电平时,spi信号被送到DDS频率控制字
    13 assign sck_ram = sck & (!cs1);//输入CS1为低电平时,spi信号被送到双口ram一侧
    14 assign sck_DdsDelta = sck & (!cs2);//输入CS2为低电平时,spi信号被送到DDS频率控制字
    15 endmodule
    主机SPI信号多路器电路

    上述信号可以根据MCU输出的两个不同的片选信号cs1和cs2将MCU输出的mosi、sck信号分解为连接波形数据双口RAM的mosi_ram、sck_ram,以及与频率控制字向量的mosi_DdsDelta和sck_DdsDelta。

    5)SPI从机接收寄存器电路

     1 module shift_reg(
     2                         input mosi,
     3                         input sck,
     4                         input nrst,//低电平复位
     5                         input[15:0] ini_data,//复位后的初始化值
     6                         output[15:0] data_out
     7                         );
     8 reg[15:0] shft_r;
     9 assign data_out[15:0] = shft_r[15:0];            
    10 always @ ( posedge sck or negedge nrst)
    11 begin
    12     if(!nrst)//复位后初始化为初始值
    13         shft_r[15:0] <= ini_data[15:0];
    14     else
    15         shft_r[15:0] <= {shft_r[14:0],mosi};
    16 end                         
    17 endmodule
    SPI数据接收移位寄存器

    这个电路被例化为两个实例,分别用于接收波形数据或频率控制字。读者也可以只例化一个SPI从机移位寄存器,但这样做数据多路器就要放在移位寄存器并行化之后。这一点可以根据器件资源消耗情况灵活决定。

    6)双口RAM左侧地址生成电路

     1 module addr_generator(
     2                             input clk,
     3                             input nrst,
     4                             output[7:0] inc_addr
     5                             );
     6 reg[7:0] inc_addr_reg;
     7 assign inc_addr[7:0] = inc_addr_reg[7:0];
     8 always @(posedge clk or negedge nrst)
     9 begin
    10     if(!nrst)
    11         inc_addr_reg[7:0] <= 8'd0;
    12     else
    13         inc_addr_reg[7:0] <= inc_addr_reg[7:0] +1'b1;
    14 end
    15 endmodule
    双口RAM存储地址生成电路

    这个地址生成电路用于产生将接收到的波形数据依次存入双口RAM所需的地址,是一个简单的带异步复位信号的加1增计数器。

    注意:1、该计数器的时钟应使用MCU产生的波形数据片选信号cs1,这样在每次一个地址单元的数据传输后,可以产生下一个单元的地址。2、该电路需要一个异步复位,以实现双方地址的同步,MCU可以在每次开始一个新的波形数据传输之前,输出该复位信号,以实现MCU和AG1280之间的地址同步。

    7)LED闪烁电路

    LED闪烁电路并不是必须的,加上该电路是为了监测系统时钟是否正常工作。读者可以根据实际情况自信选择,代码不再给出。

    8)顶层例化文件

    顶层例化文件用于连接上述各个模块电路

      1 module DDS_SPI_dualRAM(
      2                 input clk,
      3                 input nrst,
      4                 output led,
      5                 output samp_syc_sig,
      6                 output mosi,
      7                 output sck,
      8                 output cs,
      9                 //以下连接主机接收波形数据的SPI线
     10                 input sck_host,
     11                 input mosi_host,
     12                 input cs_host1,//主机输出的第一个cs,用于选通和数据RAM通信
     13                 input cs_host2,//主机输出的第而个cs,用于选通和DDS累加器增加值寄存器
     14                 //以下测试管脚
     15                 output test_pin
     16                 );
     17 //wire samp_syc_sig;
     18 wire[7:0] dds_addr;
     19 wire[11:0] tab_data;
     20 wire[15:0] delta_addr;
     21 wire clk_25m;
     22 assign test_pin = inc_addr_host[0];
     23 //assign clk_25m = clk;
     24 //////////////从板载20MHz有源振荡通过PLL产生25MHz时钟/////////
     25 expll_2_25M i_ex_pll_25M(
     26    .clkin(clk),
     27    .clkfb(clk_25m),
     28    .pllen(1'b1),
     29    .resetn(nrst),
     30    .clkout0en(1'b1),
     31    .clkout1en(1'b0),
     32    .clkout2en(1'b0),
     33    .clkout3en(1'b0),
     34    .clkout0(clk_25m),
     35    .clkout1(),
     36    .clkout2(),
     37    .clkout3(),
     38    .lock()
     39 );
     40 //////////////以下DAC侧的电路描述/////////////
     41 samp_syc_sig i_syc_sig(//产生DAC输出的时钟
     42                 .clk_25m(clk_25m),
     43                 .nrst(nrst),
     44                 .samp_syc_sig(samp_syc_sig)
     45                 );
     46                 
     47 run_led i_led(
     48                     .clk(clk_25m),
     49                     .nrst(nrst),
     50                     .led(led)
     51                     );
     52 DAC7512 iDAC7512(
     53                     .clk_25m(clk_25m),
     54                     .nrst(nrst),
     55                     .syc_sig(samp_syc_sig),
     56                     .cs_da(cs),
     57                     .mosi_da(mosi),
     58                     .sck_da(sck),
     59                     .data_in(tab_data[11:0])
     60                     );
     61 //////////////以下双口RAM侧的电路描述/////////////
     62 wire[15:0] data_host;//从主机接收的数据的并行接口
     63 wire[7:0] inc_addr_host;//连接到主机端存储器端口的地址线,由计数器自动产生
     64 wire mosi_ram_host,mosi_DdsDelta_host,sck_ram_host,sck_DdsDelta_host;
     65 wire[15:0] delta_val;
     66 dualRAM_DATA i_dualRAM_DATA(
     67    .Clk0(cs_host1),//用接收端串行接收选通信号作为存储信号的时钟
     68    .Clk1(samp_syc_sig),
     69    .ClkEn0(1'b1),
     70    .ClkEn1(1'b1),//时钟有效信号,可以一直为高
     71    .AsyncReset0(1'b0),//复位信号,高有效,可以一直不复位
     72    .AsyncReset1(1'b0),//复位信号,高有效,可以一直不复位
     73    .WeRenA(1'b1),//写数据有效信号,高有效
     74    .ReB(1'b1 ), //端口1读数据有效信号,高有效
     75    .DataInA(data_host[11:0]),//只使用接收数据的低12位
     76    .AddressA(inc_addr_host[7:0]),
     77    .AddressB(dds_addr[7:0]),
     78    .DataOutB(tab_data[11:0])
     79 );
     80 //////////////以下主控MCU侧的电路描述/////////////
     81 mux_HostSpi i_mux_HostSpi(//数据多路器,用于选择SPI口初始化的是RAM还是DDS累加地址
     82                                     .mosi(mosi_host),
     83                                     .sck(sck_host),
     84                                     .cs1(cs_host1),
     85                                     .cs2(cs_host2),
     86                                     .mosi_ram(mosi_ram_host),
     87                                     .mosi_DdsDelta(mosi_DdsDelta_host),
     88                                     .sck_ram(sck_ram_host),
     89                                     .sck_DdsDelta(sck_DdsDelta_host)
     90                                     );
     91 //以下移位寄存器用于通过CS1选通后接收主机发来的波形数据                
     92 shift_reg i_ram_shift_reg_host(
     93     .mosi(mosi_ram_host),
     94     .sck(sck_ram_host),
     95     .nrst(nrst),//低电平复位
     96     .ini_data(16'd0),
     97     .data_out(data_host[15:0])
     98 );
     99 addr_generator i_addr_generator_host(//每次加一的方式遍历所有地址
    100                     .clk(cs_host1),
    101                     .nrst(nrst),
    102                     .inc_addr(inc_addr_host[7:0])
    103                     );
    104 //以下移位寄存器用于通过CS2选通后接收主机发来的地址累加值                        
    105 shift_reg i_delta_shift_reg_host(
    106     .mosi(mosi_DdsDelta_host),
    107     .sck(sck_DdsDelta_host),
    108     .nrst(nrst),//低电平复位
    109     .ini_data(16'd328),//复位后的初始化为500Hz输出
    110     .data_out(delta_val[15:0])
    111 );
    112 addr_adder i_dds_addr_adder(//DDS算法产生下一个需要DAC数据数据的地址
    113                     .clk(samp_syc_sig),
    114                     .nrst(nrst),
    115                     .delta_addr(delta_val[15:0]),
    116                     .high_byte_addr(dds_addr[7:0])
    117                     );                    
    118 endmodule
    顶层例化电路

    4、MCU软件实现

    MCU软件通过SPI口完成波形数据和频率控制字的下载。SPI应配置为时钟空闲时低电平(CPOL=0),第一个脉冲边沿读取数据(CPHA=0)的模式。此处不再给出MCU初始化代码,仅给出下载波形表格和频率控制字函数的代码。

     1 //配置波形函数
     2 void set_wave(unsigned char wave_type)
     3     //波形类型0:正弦;1:三角;2:墨西哥草帽
     4 {
     5     unsigned short i;
     6     switch(wave_type)
     7     {
     8         case 0://选择正弦波
     9             for(i = 0; i<256 ; i++)
    10             {
    11                 CS_wave_reg = 0;
    12                 SPIx_ReadWrite16bit(sin_tl[i]);
    13                 CS_wave_reg = 1;
    14                 delay_us(2);
    15             }    
    16             break;            
    17         case 1://选择三角波
    18             for(i = 0; i<256 ; i++)
    19             {
    20                 CS_wave_reg = 0;
    21                 SPIx_ReadWrite16bit(trg_tl[i]);
    22                 CS_wave_reg = 1;
    23                 delay_us(2);
    24             }    
    25             break;            
    26         case 2://选择墨西哥草帽小波基
    27             for(i = 0; i<256 ; i++)
    28             {
    29                 CS_wave_reg = 0;
    30                 SPIx_ReadWrite16bit(mexhat_tl[i]);
    31                 CS_wave_reg = 1;
    32                 delay_us(2);
    33             }            
    34             break;
    35         default:
    36             break;
    37     }
    38 }
    39 
    40 //配置频率控制字函数
    41 #define DAC_CLK 100000    //DAC的输出频率对DAC7512为100K
    42 //设置输出频率
    43 void set_frq(unsigned short frq)
    44 //输出频率范围必须在1-50_000Hz
    45 {
    46     unsigned short temp_short;
    47     temp_short = (float)65536/DAC_CLK * frq + 0.5;//加0.5是为了消除舍弃误差
    48     CS_delta_reg = 0;//选通累加器增加值配置寄存器
    49     SPIx_ReadWrite16bit(temp_short);
    50     CS_delta_reg = 1;
    51 }
    MCU主机配置函数代码

    这里我做实验的几种波形的数据也罗列一下,以方便网友验证和测试。

     1 const unsigned short trg_tl[256]=
     2     //以下三角波
     3     {
     4         1000,1020,1040,1060,1080,1100,1120,1140,1160,1180,1200,1220,1240,1260,1280,1300,1320,1340,1360,1380,
     5         1400,1420,1440,1460,1480,1500,1520,1540,1560,1580,1600,1620,1640,1660,1680,1700,1720,1740,1760,1780,
     6         1800,1820,1840,1860,1880,1900,1920,1940,1960,1980,2000,2020,2040,2060,2080,2100,2120,2140,2160,2180,
     7         2200,2220,2240,2260,2280,2300,2320,2340,2360,2380,2400,2420,2440,2460,2480,2500,2520,2540,2560,2580,
     8         2600,2620,2640,2660,2680,2700,2720,2740,2760,2780,2800,2820,2840,2860,2880,2900,2920,2940,2960,2980,
     9         3000,3020,3040,3060,3080,3100,3120,3140,3160,3180,3200,3220,3240,3260,3280,3300,3320,3340,3360,3380,
    10         3400,3420,3440,3460,3480,3500,3520,3540,3540,3520,3500,3480,3460,3440,3420,3400,3380,3360,3340,3320,
    11         3300,3280,3260,3240,3220,3200,3180,3160,3140,3120,3100,3080,3060,3040,3020,3000,2980,2960,2940,2920,
    12         2900,2880,2860,2840,2820,2800,2780,2760,2740,2720,2700,2680,2660,2640,2620,2600,2580,2560,2540,2520,
    13         2500,2480,2460,2440,2420,2400,2380,2360,2340,2320,2300,2280,2260,2240,2220,2200,2180,2160,2140,2120,
    14         2100,2080,2060,2040,2020,2000,1980,1960,1940,1920,1900,1880,1860,1840,1820,1800,1780,1760,1740,1720,
    15         1700,1680,1660,1640,1620,1600,1580,1560,1540,1520,1500,1480,1460,1440,1420,1400,1380,1360,1340,1320,
    16         1300,1280,1260,1240,1220,1200,1180,1160,1140,1120,1100,1080,1060,1040,1020,1000
    17     };
    18     //以下正弦波
    19     const unsigned short sin_tl[256]=
    20                                                                     {
    21                                                                     2048,2097,2146,2195,2244,2293,2341,2390,2438,2486,2534,2581,
    22                                                                     2629,2675,2722,2768,2813,2858,2903,2947,2991,3034,3076,3118,3159,3200,
    23                                                                     3239,3278,3317,3354,3391,3427,3462,3496,3530,3562,3594,3625,3654,3683,
    24                                                                     3711,3738,3763,3788,3812,3834,3856,3876,3896,3914,3931,3947,3962,3976,
    25                                                                     3988,3999,4010,4019,4026,4033,4038,4043,4046,4047,4048,4047,4046,4043,
    26                                                                     4038,4033,4026,4019,4010,3999,3988,3976,3962,3947,3931,3914,3896,3876,
    27                                                                     3856,3834,3812,3788,3763,3738,3711,3683,3654,3625,3594,3562,3530,3496,
    28                                                                     3462,3427,3391,3354,3317,3278,3239,3200,3159,3118,3076,3034,2991,2947,
    29                                                                     2903,2858,2813,2768,2722,2675,2629,2581,2534,2486,2438,2390,2341,2293,
    30                                                                     2244,2195,2146,2097,2048,1999,1950,1901,1852,1803,1755,1706,1658,1610,
    31                                                                     1562,1515,1467,1421,1374,1328,1283,1238,1193,1149,1105,1062,1020,978,
    32                                                                     937,896,857,818,779,742,705,669,634,600,566,534,502,471,442,413,385,
    33                                                                     358,333,308,284,262,240,220,200,182,165,149,134,120,108,97,86,77,70,
    34                                                                     63,58,53,50,49,48,49,50,53,58,63,70,77,86,97,108,120,134,149,165,182,
    35                                                                     200,220,240,262,284,308,333,358,385,413,442,471,502,534,566,600,634,
    36                                                                     669,705,742,779,818,857,896,937,978,1020,1062,1105,1149,1193,1238,
    37                                                                     1283,1328,1374,1421,1467,1515,1562,1610,1658,1706,1755,1803,1852,
    38                                                                     1901,1950,1999
    39                                                                 };
    40 //以下墨西哥草帽小波基
    41     const unsigned short mexhat_tl[256]=
    42                                                                 {
    43                                                                     1221,1219,1217,1215,1213,1211,1208,1205,1202,1199,1195,1191,1187,1182,
    44                                                                     1177,1172,1166,1160,1153,1145,1137,1129,1119,1109,1099,1088,1076,1063,
    45                                                                     1049,1035,1020,1003,986,968,950,930,909,887,865,841,817,791,765,738,
    46                                                                     710,681,651,621,590,559,527,495,463,430,398,365,333,302,270,240,210,
    47                                                                     182,155,129,105,83,63,46,31,18,9,3,0,1,5,14,27,44,65,91,122,158,198,
    48                                                                     244,294,350,411,476,547,622,703,787,877,970,1068,1169,1274,1382,1493,
    49                                                                     1606,1722,1839,1958,2077,2197,2316,2436,2554,2670,2785,2897,3006,3111,
    50                                                                     3213,3310,3402,3489,3571,3646,3715,3777,3831,3879,3919,3951,3976,3992,
    51                                                                     4000,4000,3992,3976,3951,3919,3879,3831,3777,3715,3646,3571,3489,3402,
    52                                                                     3310,3213,3111,3006,2897,2785,2670,2554,2436,2316,2197,2077,1958,1839,
    53                                                                     1722,1606,1493,1382,1274,1169,1068,970,877,787,703,622,547,476,411,350,
    54                                                                     294,244,198,158,122,91,65,44,27,14,5,1,0,3,9,18,31,46,63,83,105,129,155,
    55                                                                     182,210,240,270,302,333,365,398,430,463,495,527,559,590,621,651,681,710,
    56                                                                     738,765,791,817,841,865,887,909,930,950,968,986,1003,1020,1035,1049,1063,
    57                                                                     1076,1088,1099,1109,1119,1129,1137,1145,1153,1160,1166,1172,1177,1182,1187,
    58                                                                     1191,1195,1199,1202,1205,1208,1211,1213,1215,1217,1219,1221
    59                                                                 };
    60     const unsigned short test_tl[256]=                //用于测试的不完整锯齿波                                                
    61                                                                 {
    62                                                                     10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180,190,200,210,
    63                                                                     220,230,240,250,260,270,280,290,300,310,320,330,340,350,360,370,380,390,400,
    64                                                                     410,420,430,440,450,460,470,480,490,500,510,520,530,540,550,560,570,580,590,
    65                                                                     600,610,620,630,640,650,660,670,680,690,700,710,720,730,740,750,760,770,780,
    66                                                                     790,800,810,820,830,840,850,860,870,880,890,900,910,920,930,940,950,960,970,
    67                                                                     980,990,1000,1010,1020,1030,1040,1050,1060,1070,1080,1090,1100,1110,1120,1130,
    68                                                                     1140,1150,1160,1170,1180,1190,1200,1210,1220,1230,1240,1250,1260,1270,1280,
    69                                                                     1290,1300,1310,1320,1330,1340,1350,1360,1370,1380,1390,1400,1410,1420,1430,
    70                                                                     1440,1450,1460,1470,1480,1490,1500,1510,1520,1530,1540,1550,1560,1570,1580,
    71                                                                     1590,1600,1610,1620,1630,1640,1650,1660,1670,1680,1690,1700,1710,1720,1730,
    72                                                                     1740,1750,1760,1770,1780,1790,1800,1810,1820,1830,1840,1850,1860,1870,1880,
    73                                                                     1890,1900,1910,1920,1930,1940,1950,1960,1970,1980,1990,2000,10,20,30,40,50,
    74                                                                     60,70,80,90,100,110,120,130,140,150,160,170,180,190,200,210,220,230,240,250,
    75                                                                     260,270,280,290,300,310,320,330,340,350,360,370,380,390,400,410,420,430,440,
    76                                                                     450,460,470,480,490,500,510,520,530,540,550,560
    77                                                                 };
    波形数据

    三、总结

    1、实际测试

    1)系统

    下图是本文使用国产CPLD器件AG1280设计的DDS系统。实验系统看起来有点丑,请大家海涵。

    图13 实际制作的DDS系统

    2)功能实测

    本文设计的DDS系统相比于ADI公司商业化的DDS芯片,最大的特点在于可以自己下载所需的波形,我测试了一个信号处理领域常用的小波基——墨西哥草帽小波基作。需要使用下面的MATLAB代码产生所需的数据。

    1 y = mexihat(-4,4,256);%调用matlab小波函数
    2 %%%%以下代码将浮点结果标准化为12位的DAC7512可以接收的数据
    3 y = y - min(y);
    4 y = y / max(y);
    5 y=round(4000*y);
    墨西哥草帽小波基--MATLAB

    用STM32的SPI1口把上面MATLAB脚本产生的数据下发到AG1280中的DDS系统后得到频率/周期可以调节的墨西哥草帽函数周期波形,用示波器观察1.5KHz的波形如下图所示。

    图14 用本DDS系统产生的频率可调墨西哥草帽小波基函数

    可以看到不论是频率的准确性,还是波形的完整性均达到了设计预期。

    3)AG1280占用资源、功耗和上电运行时间实测

    本文设计的DDS系统占用AG1280资源如下:

    图15 AG1280资源占用情况

    可以看到逻辑资源仅使用了10-20%,而存储器资源仅使用了10%左右(当然有可能随着DDS波形表格增长而变大)。可以想见AG1280的资源能够满足一般的MCU+FPGA的嵌入式系统需要。

    另外,据实测本系统中的AG1280在25MHz工作频率,PLL打开的情况下,功耗约为5mA左右。

    本次测试中唯一让我觉得意外的是AG1280的上电配置时间——可达100ms-300ms。仔细想来也可以理解:AG1280需要从其片上Flash中读取程序,在配置到内部的查找表中方能正常使用,肯定要比STM32一类直接在Flash中运行程序的器件要慢。但在使用中就需要如果与STM32等MCU配合,在MCU上电后延时一段时间再和AG1280通信

    2、AG1280和Supra使用感慨

    国产PLD器件一路走来不容易,虽然存在套用国外大厂EDA软件,支持文档不足,工具不够专业等等问题——但瑕不掩瑜,AG1280已经具有相当的可用性和市场竞争能力。

     

  • 相关阅读:
    Win11 22000.918(KB5016691)正式版发布,解决一系列问题!
    不知道10年老电脑如何重装系统?其实很简单
    2023_Spark_实验五:Scala面向对象部分演示(一)(IDEA开发)
    工业智能仓储货架|HEGERLS供应电动移动式货架Electric mobile shelf自动立体化仓库货架
    java Flink(四十一)Flink+avro+广播流broadcast实现流量的清洗
    【网络协议】Http-下
    站在巨人肩上!阿里内部流传的Kubernetes实战手册,让你事半功倍
    Nacos+openfeign
    12 - Spring AOP介绍与使用3 - AOP的xml配置文件方式
    第三章 简单静态网页爬取
  • 原文地址:https://www.cnblogs.com/helesheng/p/16692320.html