ZYNQ学习笔记(三):PL与PS数据交互—— UART串口+AXI GPIO控制DDS IP核输出实验

目录

前言

实验环境:

一、设计需求

二、硬件设计

2.1 系统框图

2.2 模块配置

2.2.1 PS与GPIO核配置

2.2.2 信号生成与输出相关IP核配置

2.2.3 其余模块以及约束文件

三、软件设计

四、实验现象

五、补充

总结



前言

一个月没有继续更新是因为入手了一块新的板子——redpitaya。
自上一个实验完成后,在罗德频谱仪上观测信号,发现信号质量不够好,毕竟黑金开发板配套的AN108模块,它的ADDA芯片都是8位的,使我们输出DDS信号的SDRF最多是48,所以信号观测起来体验感很差,而火龙果板拥有支持最高125Mhz,14位的ADDA芯片,能很大程度上提供更精确和稳定的模拟信号输出,所以以后的实验就在这块板子上进行~


实验环境:

火龙果(redpitaya)开发板(SOC型号zynq7010),2018.3 Vivado,2018.3 SDK, 示波器。

工程下载地址:https://download.csdn.net/download/weixin_44852755/88580848?spm=1001.2014.3001.5503 

一、设计需求

1.学会利用redpitaya板输出波形,来测试DAAD这样的一个回环
2.学会利用PS端串口来控制AXI GPIO核的输出
3.最终实现能够通过串口输入来控制AXI GPIO核的输出,进而控制DDS IP核输出不同频率信号。

二、硬件设计

2.1 系统框图

根据实验任务我大致画出本次实验的系统框图 ~  如图所示:

PS 端的 M_AXI_GP0 作为主端口,与 PL 端的 AXI GPIO IP 核以 AXI4-Lite 总线相连接。
其中,AXI Interconnect 用于连接 AXI 存储器映射( memory-mapped)的主器件和从器件,用于PS,PL端数据的传输

2.2 模块配置

2.2.1 PS与GPIO核配置

创建VIVADO工程之前我们要看一下火龙果板的原理图:要注意它内部各器件的型号~

首先ZYNQ7 Processing System 模块配置:
点击PS-PL Configuration配置 PS-PL 接口,包括 AXI 接口, 在右侧展开 General 下的 Enable Clock Resets ,勾选其中的 FCLK_RESET0_N 。 另外在当前界面中展开 AXI Non Secure Enablement 下的 GP Master AXI Interface,勾选其中的 MAXI GP0 interface。
点击Peripheral IO Pins , 这里我们只需要配置UART0与GPIO MIO。

 其余配置如下:

注意DDR3型号 

 添加AXI-GPIO模块:AXI GPIO用于控制和读取GPIO引脚的状态。它可以配置GPIO引脚的输入/输出模式、电平状态和中断触发方式,并可以读取GPIO引脚的当前状态。

我们也可以通过勾选图中的“All Inputs”或者“All Outputs”将 GPIO 指定为输入或者输出接口。这两个选项默认是没有勾选的,这样我们可以在程序中将其动态地配置成输入或者输出接口,(注意如果在这里设置了All Input/Output,那么在PS程序中就无需再设置GPIO方向,设置了也无效,建议不要在这里设置,通过PS程序去设置,保持灵活性)。

GPIO 接口的位宽“GPIO Width”, 最大可以支持 32 位,意味着一个通道最多可以控制32个GPIO引脚。这里我们需要控制8种不同频率,因此将其设置为 4。

参数“ Default Tri State Value ”,它配置 GPIO 默认情况下的输入输出模式,当其为 0xFFFFFFFF时,表明GPIO所有的位默认为输入模式。参数“Default Output value ”:指的是默认的输出值,意味着当该GPIO核被初始化或复位时,它会将输出端口设置为指定的默认值。(三态寄存器设置)

 “Enable Dual Channel”可以使能 GPIO 通道 2GPIO 2 的配置与 GPIO 完全相同。该选项默认没有勾选,即该IP工作在单通道模式下。

进一步详细解释,AXI GPIO内核的顶层框图如图所示:

S_AXI中S代表slave是从端口,处理器作为Master可以控制AXI GPIO这个从器件。

AXI4-Lite作为轻量级的接口可以实现简单寄存器的配置和少量的数据传输,READ_REG是读寄存器,Interrupt Registers是中断寄存器,AXI4-Lite可以实现对这两个寄存器的配置。三态缓冲器不包含在AXI GPIO里,是工具在顶层文件里自动添加的。

GPIO内核包含寄存器(输入输出和中断)和多路复用器(选择两个GPIO的通道),GPIO_DATA是数据寄存器(Data Register),读数据和写数据都是通过这个寄存器。

GPIO_TRI是三态控制寄存器——逻辑高1、逻辑低0或高阻抗(高阻态),用来控制GPIO引脚作为输入还是输出,GPIO_T是三态的使能信号,GPIO_T=1配置为输入模式(in),GPIO_T=0配置为输出模式(out)(对应前面IP核窗口的两个参数设置)。GPIO_I 是用于输入,GPIO_O 是用于输出。GPIO_WIDTH是用户指定的位宽,可以配置在1位到32位之间。

GPIO位宽分别是1跟4时:

此外来自PL端的输入数据一方面进入READ_REG,一方面进入中断检测模块Interrupt Detection。只要输入端口数据发生改变就可以生成中断,中断检测实现该功能并通过ip2intc_irpt输出一个中断。

添加配置完这两个IP核后,点击Diagram 窗口的1、2:

连接完成后如图:

2.2.2 信号生成与输出相关IP核配置

接下来我们配置PL端DDS IP核与ADDA部分~

DDS ip核:用了PLL进行时钟倍频,就要用到system parameters模式,原因看上一篇学习笔记。

看到配置完成的DDS IP核你可能会有疑惑,为什么m_axis_data_tdata是32位的?我设置的SFDR为84,那么对应的位宽应该是14位,而且选了sin and cos输出后,m_axis_data_tdata不应该是28位吗?

数据手册给出解释:

因为选的是正交输出,所以最后输出端口是32位的,由于输出采用的是axi总线,因此输出数据位于M_AXIS_DATA_TDATA中,那么正余弦输出结果是如何组合成M_AXIS_DATA_TDATA的呢?

输出DATA通道TDATA结构将正弦和余弦输出字段符号扩展到下一个字节边界,然后以最低有效部分的余弦进行连接,以创建m_axis_data_tdata。如果仅选择正弦或余弦之一,则将其符号扩展并放入m_axis_data_tdata的最低有效部分。

下图显示了这三种配置的TDATA的内部结构。正交输出,仅余弦和仅正弦。例如,在图中显示了11位输出,符号扩展到16位。 <<<表示符号扩展名:

返回到工程实际来看,高29-16位是带符号的正弦信号,低13-0位是带符号的余弦信号,也就是说这32位的信号其中30、31、15、14 为符号扩展位。

所以我引入了两个Slice模块,把高位正弦信号与低位余弦信号分离的同时,剔除掉符号扩展位:生成了有符号的14位正弦信号与有符号的14位余弦信号

 经符号转化后,送入ADDA模块~

当然你也可以DDS IP核设置SFDR为96,直接输出16位的正弦(31-16)、余弦信号(15-0),不存在符号扩展位。同样的步骤在你利用Slice IP核分离完正余弦信号后,经过有符号到无符号数的转化、再利用Slice IP核去除这16位无符号信号的最低两位(范围改成15-2),这样做的代价可能会些影响信号的精度~(记得修改signed_to_unsigned模块)

自定义ADDA ip核


module red_ADDA_shell (

  input             [0:0]    fclk          , //[0]-125MHz
  input             [0:0]    frstn         ,
  // ADC
  input   [14-1:0] adc_dat_0_i     , // ADC data
  input   [14-1:0] adc_dat_1_i     , // ADC data
  input   [ 2-1:0] adc_clk_i     , // ADC clock {p,n}
  output  [ 2-1:0] adc_clk_o     , // optional ADC clock source (unused)
  output           adc_cdcs_o    , // ADC clock duty cycle stabilizer
  output  [14-1:0] rtl_adc_0_o   , // rtl side adc 0 output
  output  [14-1:0] rtl_adc_1_o   , // rtl side adc 1 output
  output           rtl_adc_clk_o , // rtl side adc clk output
  // DAC
  output  [14-1:0] dac_dat_o  ,  // DAC combined data
  output           dac_wrt_o  ,  // DAC write
  output           dac_sel_o  ,  // DAC channel select
  output           dac_clk_o  ,  // DAC clock
  output           dac_rst_o  ,  // DAC reset
  input   [14-1:0] rtl_dac_0_i,
  input   [14-1:0] rtl_dac_1_i,
  output           rtl_dac_clk );



red_pitaya_ADDA U_adda(
  .fclk          (fclk          ), //[0]-125MHz
  .frstn         (frstn         ),
  .adc_dat_0_i   (adc_dat_0_i   ), // ADC data
  .adc_dat_1_i   (adc_dat_1_i   ), // ADC data
  .adc_clk_i     (adc_clk_i     ), // ADC clock {p,n}
  .adc_clk_o     (adc_clk_o     ), // optional ADC clock source (unused)
  .adc_cdcs_o    (adc_cdcs_o    ), // ADC clock duty cycle stabilizer
  .rtl_adc_0_o   (rtl_adc_0_o   ), // rtl side adc 0 output
  .rtl_adc_1_o   (rtl_adc_1_o   ), // rtl side adc 1 output
  .rtl_adc_clk_o (rtl_adc_clk_o ), // rtl side adc clk output
  .dac_dat_o     (dac_dat_o     ),  // DAC combined data
  .dac_wrt_o     (dac_wrt_o     ),  // DAC write
  .dac_sel_o     (dac_sel_o     ),  // DAC channel select
  .dac_clk_o     (dac_clk_o     ),  // DAC clock
  .dac_rst_o     (dac_rst_o     ),  // DAC reset
  .rtl_dac_0_i   (rtl_dac_0_i   ),
  .rtl_dac_1_i   (rtl_dac_1_i   ),
  .rtl_dac_clk   (rtl_dac_clk   ));

endmodule
module red_pitaya_ADDA (

  input  logic           [0:0]    fclk          , //[0]-125MHz
  input  logic           [0:0]    frstn         ,
  // ADC
  input  logic           [14-1:0] adc_dat_0_i     , // ADC data
  input  logic           [14-1:0] adc_dat_1_i     , // ADC data
  input  logic           [ 2-1:0] adc_clk_i     , // ADC clock {p,n}
  output logic           [ 2-1:0] adc_clk_o     , // optional ADC clock source (unused)
  output logic                    adc_cdcs_o    , // ADC clock duty cycle stabilizer
  output logic           [14-1:0] rtl_adc_0_o   , // rtl side adc 0 output
  output logic           [14-1:0] rtl_adc_1_o   , // rtl side adc 1 output
  output logic                    rtl_adc_clk_o , // rtl side adc clk output
  // DAC
  output logic [14-1:0] dac_dat_o  ,  // DAC combined data
  output logic          dac_wrt_o  ,  // DAC write
  output logic          dac_sel_o  ,  // DAC channel select
  output logic          dac_clk_o  ,  // DAC clock
  output logic          dac_rst_o  ,  // DAC reset
  input  logic [14-1:0] rtl_dac_0_i,
  input  logic [14-1:0] rtl_dac_1_i,
  output logic          rtl_dac_clk

);

// PLL signals
logic                 adc_clk_in;         //ADC 时钟输入信号。
logic                 pll_adc_clk;        //ADC 时钟的锁相环信号。
logic                 pll_dac_clk_1x;     //DAC 时钟的锁相环信号
logic                 pll_dac_clk_2x;     //DAC 时钟的锁相环信号,频率是 pll_dac_clk_1x 的两倍。
logic                 pll_dac_clk_2p;     //DAC 时钟的锁相环信号,相位是 pll_dac_clk_1x 的两倍。
logic                 pll_ser_clk;        //串行时钟的锁相环信号。
logic                 pll_locked;      


// ADC clock/reset
logic                 adc_clk,adc_rstn;

// stream bus type
localparam type SBA_T = logic signed [14-1:0];  // 表示采集的数据流总线类型,具有 14 位带符号的数据。

SBA_T [  2-1:0]          adc_dat;     //一个大小为 2 的数据流数组,用于表示 ADC 数据。adc_dat 包含两个元素,每个元素都是带有 14 位符号的数据。

// DAC 时钟信号
logic                    dac_clk_1x,dac_clk_2x,dac_clk_2p,dac_rst;

//DAC数据信号
logic        [14-1:0] dac_dat_a, dac_dat_b;   //14 位的 DAC 数据信号 A 和 B。

// PLL (clock and reset)

// diferential clock input
IBUFDS i_clk (.I (adc_clk_i[1]), .IB (adc_clk_i[0]), .O (adc_clk_in)); 
 // IBUFDS: 差分输入缓冲器,用于处理差分信号。adc_clk_i[1] 和 adc_clk_i[0] 是差分时钟信号的两个分量。IBUFDS 模块接收这两个差分信号作为输入,并将它们转换为单端信号 adc_clk_in,以便后续的逻辑电路可以使用这个单端信号进行处理。

red_pitaya_pll pll (
  // inputs
  .clk         (adc_clk_in),  // clock
  .rstn        (frstn[0]  ),  // reset - active low,将输入时钟 adc_clk_in 和复位信号 frstn[0] 作为输入,为不同的模块提供多个时钟输出和锁定状态输出。
  // output clocks
  .clk_adc     (pll_adc_clk   ),  // ADC clock
  .clk_dac_1x  (pll_dac_clk_1x),  // DAC clock 125MHz
  .clk_dac_2x  (pll_dac_clk_2x),  // DAC clock 250MHz
  .clk_dac_2p  (pll_dac_clk_2p),  // DAC clock 250MHz -45DGR
  .clk_ser     (pll_ser_clk   ),  // fast serial clock

  // status outputs
  .pll_locked  (pll_locked)
);


logic                 ser_clk ;//用于串行通信的时钟信号。
BUFG bufg_adc_clk    (.O (adc_clk   ), .I (pll_adc_clk   ));
BUFG bufg_dac_clk_1x (.O (dac_clk_1x), .I (pll_dac_clk_1x));
BUFG bufg_dac_clk_2x (.O (dac_clk_2x), .I (pll_dac_clk_2x));
BUFG bufg_dac_clk_2p (.O (dac_clk_2p), .I (pll_dac_clk_2p));
BUFG bufg_ser_clk    (.O (ser_clk   ), .I (pll_ser_clk   ));


//复位逻辑,adc_rstn: ADC 复位信号,与 frstn[0] 和 pll_locked 有关。dac_rst: 
// ADC reset (active low)
always @(posedge adc_clk)
adc_rstn <=  frstn[0] &  pll_locked;
// DAC reset (active high),DAC 复位信号,与 frstn[0] 和 pll_locked 有关。
always @(posedge dac_clk_1x)
dac_rst  <= ~frstn[0] | ~pll_locked;


// ADC IO


// generating ADC clock is disabled,adc_clk_o: 这个信号被设定为固定的值 2'b10,意味着 ADC 时钟生成被禁用。系统中已经存在一个稳定的外部时钟源,因此不需要通过逻辑电路生成 ADC 时钟。在这种情况下,禁用内部时钟生成可以节省资源并避免冲突。
assign adc_clk_o = 2'b10;

// ADC clock duty cycle stabilizer is enabled,表明 ADC 时钟占空比稳定器被启用,并且将信号 adc_cdcs_o 赋值为 1。ADC 时钟占空比稳定器(Clock Duty Cycle Stabilizer,CDCS)用于确保 ADC 时钟的占空比保持稳定,以提高系统的稳定性和性能。
assign adc_cdcs_o = 1'b1 ;

logic [2-1:0] [14-1:0] adc_dat_raw;//一个 2x14 的二维数组,用于存储 ADC 采集到的原始数据。

// IO block registers should be used here
always @(posedge adc_clk)
begin
  adc_dat_raw[0] <= adc_dat_0_i[13:0];
  adc_dat_raw[1] <= adc_dat_1_i[13:0];
end
//通过输出 ADC 数据到 rtl_adc_0_o 和 rtl_adc_1_o,可以方便地对数据进行监控和调试,以确保 ADC 模块正常工作。这些输出端口可以连接到外部设备或接口,以便将 ADC 数据导出到外部系统或外部显示设备进行显示或记录。
always @(posedge adc_clk)
begin
  rtl_adc_0_o <= adc_dat_0_i;
  rtl_adc_1_o <= adc_dat_1_i;
end
assign rtl_adc_clk_o = adc_clk;
    
// transform into 2's complement (negative slope),adc_dat[0] 和 adc_dat[1] 用于将 ADC 数据转换为 2 的补码形式,以便后续处理或存储。
assign adc_dat[0] = {adc_dat_raw[0][14-1], ~adc_dat_raw[0][14-2:0]};
assign adc_dat[1] = {adc_dat_raw[1][14-1], ~adc_dat_raw[1][14-2:0]};

// DAC IO


assign rtl_dac_clk = dac_clk_1x;
always @(posedge dac_clk_1x)
begin
  dac_dat_a <=  rtl_dac_0_i;
  dac_dat_b <=  rtl_dac_1_i;
end

// DDR outputs
ODDR oddr_dac_clk          (.Q(dac_clk_o), .D1(1'b0     ), .D2(1'b1     ), .C(dac_clk_2p), .CE(1'b1), .R(1'b0   ), .S(1'b0));  
ODDR oddr_dac_wrt          (.Q(dac_wrt_o), .D1(1'b0     ), .D2(1'b1     ), .C(dac_clk_2x), .CE(1'b1), .R(1'b0   ), .S(1'b0)); 
ODDR oddr_dac_sel          (.Q(dac_sel_o), .D1(1'b1     ), .D2(1'b0     ), .C(dac_clk_1x), .CE(1'b1), .R(dac_rst), .S(1'b0));
ODDR oddr_dac_rst          (.Q(dac_rst_o), .D1(dac_rst  ), .D2(dac_rst  ), .C(dac_clk_1x), .CE(1'b1), .R(1'b0   ), .S(1'b0));
ODDR oddr_dac_dat [14-1:0] (.Q(dac_dat_o), .D1(dac_dat_b), .D2(dac_dat_a), .C(dac_clk_1x), .CE(1'b1), .R(dac_rst), .S(1'b0));


endmodule:red_pitaya_ADDA


module red_pitaya_pll (
  // inputs
  input  logic clk       ,  // clock
  input  logic rstn      ,  // reset - active low
  // output clocks
  output logic clk_adc   ,  // ADC clock
  output logic clk_dac_1x,  // DAC clock
  output logic clk_dac_2x,  // DAC clock
  output logic clk_dac_2p,  // DAC clock
  output logic clk_ser   ,  // fast serial clock

  // status outputs
  output logic pll_locked
);

logic clk_fb;

PLLE2_ADV #(
   .BANDWIDTH            ("OPTIMIZED"),
   .COMPENSATION         ("ZHOLD"    ),
   .DIVCLK_DIVIDE        ( 1         ),
   .CLKFBOUT_MULT        ( 8         ),
   .CLKFBOUT_PHASE       ( 0.000     ),
   .CLKOUT0_DIVIDE       ( 8         ),
   .CLKOUT0_PHASE        ( 0.000     ),
   .CLKOUT0_DUTY_CYCLE   ( 0.5       ),
   .CLKOUT1_DIVIDE       ( 8         ),
   .CLKOUT1_PHASE        ( 0.000     ),
   .CLKOUT1_DUTY_CYCLE   ( 0.5       ),
   .CLKOUT2_DIVIDE       ( 4         ),
   .CLKOUT2_PHASE        ( 0.000     ),
   .CLKOUT2_DUTY_CYCLE   ( 0.5       ),
   .CLKOUT3_DIVIDE       ( 4         ),
   .CLKOUT3_PHASE        (-45.000    ),
   .CLKOUT3_DUTY_CYCLE   ( 0.5       ),
   .CLKOUT4_DIVIDE       ( 4         ),  // 4->250MHz, 2->500MHz
   .CLKOUT4_PHASE        ( 0.000     ),
   .CLKOUT4_DUTY_CYCLE   ( 0.5       ),
   .CLKOUT5_DIVIDE       ( 4         ),
   .CLKOUT5_PHASE        ( 0.000     ),
   .CLKOUT5_DUTY_CYCLE   ( 0.5       ),
   .CLKIN1_PERIOD        ( 8.000     ),
   .REF_JITTER1          ( 0.010     )
) pll (
   // Output clocks
   .CLKFBOUT     (clk_fb    ),
   .CLKOUT0      (clk_adc   ),
   .CLKOUT1      (clk_dac_1x),
   .CLKOUT2      (clk_dac_2x),
   .CLKOUT3      (clk_dac_2p),
   .CLKOUT4      (clk_ser   ),
  
   // Input clock control
   .CLKFBIN      (clk_fb    ),
   .CLKIN1       (clk       ),
   .CLKIN2       (1'b0      ),
   // Tied to always select the primary input clock
   .CLKINSEL     (1'b1 ),
   // Ports for dynamic reconfiguration
   .DADDR        (7'h0 ),
   .DCLK         (1'b0 ),
   .DEN          (1'b0 ),
   .DI           (16'h0),
   .DO           (     ),
   .DRDY         (     ),
   .DWE          (1'b0 ),
   // Other control and status signals
   .LOCKED       (pll_locked),
   .PWRDWN       (1'b0      ),
   .RST          (!rstn     )
);

endmodule: red_pitaya_pll

解释一下red_pitaya_ADDA 模块的代码逻辑。

red_pitaya_pll 模块:这是一个锁相环模块,用于生成各种时钟信号,包括 ADC 时钟、DAC 时钟和其他内部时钟信号。它的输入是外部信号 clk 和 rstn,输出包括各种时钟信号如 clk_adc、clk_dac_1x、clk_dac_2x、clk_dac_2p、clk_ser 和 clk_pdm。它通过 PLL 控制时钟的频率和相位,并提供了锁定状态的输出信号 pll_locked。

ADC 逻辑部分:ADC 通过时钟信号 adc_clk 对输入的模拟数据进行采样。采样后的数据经过一定的处理后输出到 rtl_adc_0_o 和 rtl_adc_1_o 输出端口。这部分代码包括了时钟同步和数据处理逻辑。adc_dat_raw 数组用于存储采样后的原始数据。

DAC 逻辑部分:DAC 的逻辑包括了一些处理步骤。模块中,dac_dat_a 和 dac_dat_b 存储要输出的 DAC 数据,而 oddr_dac_clk、oddr_dac_wrt、oddr_dac_sel 和 oddr_dac_rst 控制 DAC 的时钟、写入、选择和复位操作。oddr_dac_dat 则负责输出 DAC 数据。这些部分一起工作以确保 DAC 正确运行并输出正确的模拟信号。

其中代码:

ODDR oddr_dac_clk          (.Q(dac_clk_o), .D1(1'b0     ), .D2(1'b1     ), .C(dac_clk_2p), .CE(1'b1), .R(1'b0   ), .S(1'b0));
ODDR oddr_dac_wrt          (.Q(dac_wrt_o), .D1(1'b0     ), .D2(1'b1     ), .C(dac_clk_2x), .CE(1'b1), .R(1'b0   ), .S(1'b0));
ODDR oddr_dac_sel          (.Q(dac_sel_o), .D1(1'b1     ), .D2(1'b0     ), .C(dac_clk_1x), .CE(1'b1), .R(dac_rst), .S(1'b0));
ODDR oddr_dac_rst          (.Q(dac_rst_o), .D1(dac_rst  ), .D2(dac_rst  ), .C(dac_clk_1x), .CE(1'b1), .R(1'b0   ), .S(1'b0));
ODDR oddr_dac_dat [14-1:0] (.Q(dac_dat_o), .D1(dac_dat_b), .D2(dac_dat_a), .C(dac_clk_1x), .CE(1'b1), .R(dac_rst), .S(1'b0));

oddr_dac_clk: 这个实例用于生成 DAC 的时钟信号。它有两个输入数据位,分别是1'b0和1'b1,这意味着在时钟信号满足特定条件时,dac_clk_o会在不同的时钟周期交替为0和1。.C(dac_clk_2p)表示该时钟的控制信号,.CE(1'b1)表示使能控制信号,.R(1'b0)和.S(1'b0)分别表示异步复位和同步置位的控制信号。
同样地,oddr_dac_wrt: 这个实例用于生成 DAC 的写入控制信号。oddr_dac_sel: 这个实例用于生成 DAC 的通道选择信号。oddr_dac_rst: 这个实例用于生成 DAC 的复位信号。oddr_dac_dat [14-1:0]: 这个实例用于生成 DAC 的数据信号,dac_dat_b 和 dac_dat_a 是作为两个独立的输入数据传递给了模块的 D1 和 D2 端口,而 dac_clk_1x 是时钟信号传递给了 C 端口。这意味着这两个输入数据 dac_dat_b 和 dac_dat_a 通过时钟信号 dac_clk_1x 被输入到了模块中,并且经过模块内部的逻辑处理后,最终的输出结果会出现在 dac_dat_o 端口上。

自定义dds_contral ip核

module dds_contral(
    input           clk,
    input           rst_n,
    input [3 : 0]   key_PINC,
    output reg [23 : 0] Fword
 
     );
always@(*)
begin
    case(key_PINC)
        4'b0001:  Fword <= 'h20c4;      
        4'b0010:  Fword <= 'h624c;    
        4'b0011:  Fword <= 'ha3d4;     
        4'b0100:  Fword <= 'h147a8;
        4'b0101:  Fword <= 'h1EB85;
        4'b0110:  Fword <= 'h28f5c;
        4'b0111:  Fword <= 'h33333;
        4'b1000:  Fword <= 'h3d70a; 
        default: Fword <= 'h1EB85;
 
    endcase
  end
endmodule

频率字的计算参考第一篇文章~不再细说。

自定义signed_to_unsigned模块:目的是为了在示波器上显示

module signed_to_unsigned(
DIN  ,
DOUT 
     );

parameter DWL  = 14;

input [DWL-1:0] DIN;
output[DWL-1:0] DOUT;
assign DOUT[DWL-1]   = ~DIN[DWL-1];
assign DOUT[DWL-2:0] =  DIN[DWL-2:0];

endmodule

2.2.3 其余模块以及约束文件

因为ADDA芯片可以支持时钟频率为125MHZ,所以利用一个锁相环把PS端引出的时钟频率倍频到125Mhz后输出给ADDA模块~

red_ADDA_shell IP核输出 ADC 数据到 rtl_adc_0_o 和 rtl_adc_1_o,这里添加ILA_1咱们只观测rtl_adc_0_o,时钟由rtl_adc_clk_o提供。

 此外添加ILA_2观测DDS IP输出的信号,最后我们可以与ILA_1观测的信号进行对比~

约束文件:

查看原理图可以很清晰的看到各个引脚位置~

# ADC data
set_property IOSTANDARD LVCMOS18 [get_ports {adc_dat_*_i[*]}]
set_property IOB        TRUE     [get_ports {adc_dat_*_i[*]}]

# ADC 0 data
set_property PACKAGE_PIN Y17     [get_ports {adc_dat_0_i[0]}]
set_property PACKAGE_PIN U17     [get_ports {adc_dat_0_i[1]}]
set_property PACKAGE_PIN Y16     [get_ports {adc_dat_0_i[2]}]
set_property PACKAGE_PIN W15     [get_ports {adc_dat_0_i[3]}]
set_property PACKAGE_PIN W14     [get_ports {adc_dat_0_i[4]}]
set_property PACKAGE_PIN Y14     [get_ports {adc_dat_0_i[5]}]
set_property PACKAGE_PIN W13     [get_ports {adc_dat_0_i[6]}]
set_property PACKAGE_PIN V12     [get_ports {adc_dat_0_i[7]}]
set_property PACKAGE_PIN V13     [get_ports {adc_dat_0_i[8]}]
set_property PACKAGE_PIN T14     [get_ports {adc_dat_0_i[9]}]
set_property PACKAGE_PIN T15     [get_ports {adc_dat_0_i[10]}]
set_property PACKAGE_PIN V15     [get_ports {adc_dat_0_i[11]}]
set_property PACKAGE_PIN T16     [get_ports {adc_dat_0_i[12]}]
set_property PACKAGE_PIN V16     [get_ports {adc_dat_0_i[13]}]


# ADC 1 data
set_property PACKAGE_PIN R18     [get_ports {adc_dat_1_i[0]}]
set_property PACKAGE_PIN R16     [get_ports {adc_dat_1_i[1]}]
set_property PACKAGE_PIN P18     [get_ports {adc_dat_1_i[2]}]
set_property PACKAGE_PIN N17     [get_ports {adc_dat_1_i[3]}]
set_property PACKAGE_PIN R19     [get_ports {adc_dat_1_i[4]}]
set_property PACKAGE_PIN T20     [get_ports {adc_dat_1_i[5]}]
set_property PACKAGE_PIN T19     [get_ports {adc_dat_1_i[6]}]
set_property PACKAGE_PIN U20     [get_ports {adc_dat_1_i[7]}]
set_property PACKAGE_PIN V20     [get_ports {adc_dat_1_i[8]}]
set_property PACKAGE_PIN W20     [get_ports {adc_dat_1_i[9]}]
set_property PACKAGE_PIN W19     [get_ports {adc_dat_1_i[10]}]
set_property PACKAGE_PIN Y19     [get_ports {adc_dat_1_i[11]}]
set_property PACKAGE_PIN W18     [get_ports {adc_dat_1_i[12]}]
set_property PACKAGE_PIN Y18     [get_ports {adc_dat_1_i[13]}]


set_property IOSTANDARD DIFF_HSTL_I_18 [get_ports adc_clk_i[*]]
set_property PACKAGE_PIN U18           [get_ports adc_clk_i[1]]
set_property PACKAGE_PIN U19           [get_ports adc_clk_i[0]]

# Output ADC clock
set_property IOSTANDARD LVCMOS18 [get_ports {adc_clk_o[*]}]
set_property SLEW       FAST     [get_ports {adc_clk_o[*]}]
set_property DRIVE      8        [get_ports {adc_clk_o[*]}]
#set_property IOB        TRUE     [get_ports {adc_clk_o[*]}]

set_property PACKAGE_PIN N20 [get_ports {adc_clk_o[0]}]
set_property PACKAGE_PIN P20 [get_ports {adc_clk_o[1]}]

# ADC clock stabilizer
set_property IOSTANDARD  LVCMOS18 [get_ports adc_cdcs_o]
set_property PACKAGE_PIN V18     [get_ports adc_cdcs_o]
set_property SLEW        FAST     [get_ports adc_cdcs_o]
set_property DRIVE       8        [get_ports adc_cdcs_o]

### DAC

# data
set_property IOSTANDARD LVCMOS33 [get_ports {dac_dat_o[*]}]
set_property SLEW       SLOW     [get_ports {dac_dat_o[*]}]
set_property DRIVE      8        [get_ports {dac_dat_o[*]}]
#set_property IOB        TRUE     [get_ports {dac_dat_o[*]}]

set_property PACKAGE_PIN M19 [get_ports {dac_dat_o[0]}]
set_property PACKAGE_PIN M20 [get_ports {dac_dat_o[1]}]
set_property PACKAGE_PIN L19 [get_ports {dac_dat_o[2]}]
set_property PACKAGE_PIN L20 [get_ports {dac_dat_o[3]}]
set_property PACKAGE_PIN K19 [get_ports {dac_dat_o[4]}]
set_property PACKAGE_PIN J19 [get_ports {dac_dat_o[5]}]
set_property PACKAGE_PIN J20 [get_ports {dac_dat_o[6]}]
set_property PACKAGE_PIN H20 [get_ports {dac_dat_o[7]}]
set_property PACKAGE_PIN G19 [get_ports {dac_dat_o[8]}]
set_property PACKAGE_PIN G20 [get_ports {dac_dat_o[9]}]
set_property PACKAGE_PIN F19 [get_ports {dac_dat_o[10]}]
set_property PACKAGE_PIN F20 [get_ports {dac_dat_o[11]}]
set_property PACKAGE_PIN D20 [get_ports {dac_dat_o[12]}]
set_property PACKAGE_PIN D19 [get_ports {dac_dat_o[13]}]

# control
set_property IOSTANDARD LVCMOS33 [get_ports dac_*_o]
set_property SLEW       FAST     [get_ports dac_*_o]
set_property DRIVE      8        [get_ports dac_*_o]
#set_property IOB        TRUE     [get_ports dac_*_o]

set_property PACKAGE_PIN M17 [get_ports dac_wrt_o]
set_property PACKAGE_PIN N16 [get_ports dac_sel_o]
set_property PACKAGE_PIN M18 [get_ports dac_clk_o]
set_property PACKAGE_PIN N15 [get_ports dac_rst_o]




三、软件设计

导出硬件后,Launch SDK~ 在 SDK 软件中新建一个 BSP 工程和一个空的应用工程,应用工程名为“redptiaya ”。然后为应用工程新建一个源文件“main.c ”,我们在新建的 main.c 文件中输入本次实验的代码。

 

代码的主体部分如下所示:
这段代码的功能是读取用户输入的数字,并根据数字控制 GPIO 输出信号的状态。然后从 GPIO 读取输出状态并通过 UART 发送回去,以检测GPIO输出是否符合自己预期。
#include <stdio.h>
#include <string.h>
#include "xparameters.h"
#include "xgpio.h"
#include "xuartps.h"

#define GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define UART_DEVICE_ID XPAR_XUARTPS_0_DEVICE_ID
#define CHANNEL 1

XGpio Gpio;
XUartPs Uart_Ps;

int main() {
    XUartPs_Config *Config;
    Config = XUartPs_LookupConfig(UART_DEVICE_ID);
    if (Config == NULL) {
        printf("无法找到UART配置。\n");
        return 1;
    }

    XUartPs *Uart_PsPtr = &Uart_Ps;
    XUartPs_CfgInitialize(Uart_PsPtr, Config, Config->BaseAddress);
    XUartPs_SetBaudRate(Uart_PsPtr, 115200);

    if (XGpio_Initialize(&Gpio, GPIO_DEVICE_ID) != XST_SUCCESS) {
        printf("GPIO初始化失败。\n");
        return 1;
    }

    XGpio_SetDataDirection(&Gpio, CHANNEL, 0x0);

    int input;
    int output;
    int gpio_output;
    int in_num; // 用整数存储输入

    while (1) {
        printf("输入一个数字频率模式:\n ");
        scanf("%d", &in_num); // 直接读取整数

        if (in_num >= 1 && in_num <= 8) {
            input = in_num;

            // 将输入转换为4位二进制数
            output = input & 0xF;

            // 将4位二进制数输出到GPIO
            XGpio_DiscreteWrite(&Gpio, CHANNEL, output);

            // 从GPIO读取值
            gpio_output = XGpio_DiscreteRead(&Gpio, CHANNEL);

            // 将从GPIO读取的值发送到串口
            char buffer[15];
            sprintf(buffer, "频率%d", gpio_output);
            XUartPs_Send(Uart_PsPtr, (u8 *)buffer, strlen(buffer)); // 发送整个字符串
            XUartPs_Send(Uart_PsPtr, (u8 *)"\n", 1); // 发送换行符
        } else {
            printf("输入错误。请确保输入一个1到8之间的数字。\n");
        }
    }
    return 0;
}

代码通过 XUartPs_LookupConfig 函数查找 UART 的配置信息,如果找不到,则打印一条错误消息。然后通过 XUartPs_CfgInitialize 函数进行初始化,并设置波特率为 115200。然后,通过 XGpio_Initialize 函数初始化 GPIO 设备,并设置通道的数据方向为输出。接下来进入一个无限循环,循环中首先提示用户输入一个数字频率模式。

输入一个整数作为频率模式,如果用户输入的数字在 1 到 8 之间,代码将读取输入的数字并将其转换为一个 4 位的二进制数,接着,代码将这个 4 位二进制数写入到 GPIO 设备中,然后再从 GPIO 读取值。接着将从 GPIO 读取的值发送到 UART,以便通过串口发送出去。这部分代码使用了 sprintf 函数将读取的 GPIO 值转换为字符串,然后通过 XUartPs_Send 函数将字符串发送到串口。

CLTA+S保存代码并编译~

电脑与开发板用USB、JATG线相连,连接上示波器

选择自己电脑的串口号连接串口。

下载程序与硬件到板子里~

四、实验现象

程序下载完成后,目光移至SDK Terminal

发送数字后,观察示波器

我们可以再回到VIVADO,把线从板子的DA输出连接到AD输入,观测这个回环~

 

 

由此可以看出,比较符合自己一开始的预期~ 

五、补充

2023年11月9日补充:

我在redpitaya教程里找到了关于官方给的DA模块,首先你要有他们提供的FPGA例程文件夹:

我在这个例程里找到了他们定义的DA模块: 


`timescale 1 ns / 1 ps

module axis_red_pitaya_dac #
(
  parameter integer DAC_DATA_WIDTH = 14,
  parameter integer AXIS_TDATA_WIDTH = 32
)
(
  // PLL signals
  input  wire                        aclk,
  input  wire                        ddr_clk,
  input  wire                        locked,

  // DAC signals
  output wire                        dac_clk,
  output wire                        dac_rst,
  output wire                        dac_sel,
  output wire                        dac_wrt,
  output wire [DAC_DATA_WIDTH-1:0]   dac_dat,

  // Slave side
  output wire                        s_axis_tready,
  input  wire [AXIS_TDATA_WIDTH-1:0] s_axis_tdata,
  input  wire                        s_axis_tvalid
);

  reg [DAC_DATA_WIDTH-1:0] int_dat_a_reg;
  reg [DAC_DATA_WIDTH-1:0] int_dat_b_reg;
  reg int_rst_reg;

  wire [DAC_DATA_WIDTH-1:0] int_dat_a_wire;
  wire [DAC_DATA_WIDTH-1:0] int_dat_b_wire;

  assign int_dat_a_wire = s_axis_tdata[DAC_DATA_WIDTH-1:0];
  assign int_dat_b_wire = s_axis_tdata[AXIS_TDATA_WIDTH/2+DAC_DATA_WIDTH-1:AXIS_TDATA_WIDTH/2];

  genvar j;

  always @(posedge aclk)
  begin
    if(~locked | ~s_axis_tvalid)
    begin
      int_dat_a_reg <= {(DAC_DATA_WIDTH){1'b0}};
      int_dat_b_reg <= {(DAC_DATA_WIDTH){1'b0}};
    end
    else
    begin
      int_dat_a_reg <= {int_dat_a_wire[DAC_DATA_WIDTH-1], ~int_dat_a_wire[DAC_DATA_WIDTH-2:0]};
      int_dat_b_reg <= {int_dat_b_wire[DAC_DATA_WIDTH-1], ~int_dat_b_wire[DAC_DATA_WIDTH-2:0]};
    end
    int_rst_reg <= ~locked | ~s_axis_tvalid;
  end

  ODDR ODDR_rst(.Q(dac_rst), .D1(int_rst_reg), .D2(int_rst_reg), .C(aclk), .CE(1'b1), .R(1'b0), .S(1'b0));
  ODDR ODDR_sel(.Q(dac_sel), .D1(1'b0), .D2(1'b1), .C(aclk), .CE(1'b1), .R(1'b0), .S(1'b0));
  ODDR ODDR_wrt(.Q(dac_wrt), .D1(1'b0), .D2(1'b1), .C(ddr_clk), .CE(1'b1), .R(1'b0), .S(1'b0));
  ODDR ODDR_clk(.Q(dac_clk), .D1(1'b0), .D2(1'b1), .C(ddr_clk), .CE(1'b1), .R(1'b0), .S(1'b0));

  generate
    for(j = 0; j < DAC_DATA_WIDTH; j = j + 1)
    begin : DAC_DAT
      ODDR ODDR_inst(
        .Q(dac_dat[j]),
        .D1(int_dat_a_reg[j]),
        .D2(int_dat_b_reg[j]),
        .C(aclk),
        .CE(1'b1),
        .R(1'b0),
        .S(1'b0)
      );
    end
  endgenerate

  assign s_axis_tready = 1'b1;

endmodule

如何使用?

dds_contral模块的频率字要更改~计算方法看ZYNQ学习笔记(一):基于ZYNQ7020、AN108的DDS实验(VIO可控频率字) 

xdc文件只保留DAC就可:


### DAC

# data
set_property IOSTANDARD LVCMOS33 [get_ports {dac_dat[*]}]
set_property SLEW       SLOW     [get_ports {dac_dat[*]}]
set_property DRIVE      8        [get_ports {dac_dat[*]}]
#set_property IOB        TRUE     [get_ports {dac_dat_o[*]}]

set_property PACKAGE_PIN M19 [get_ports {dac_dat[0]}]
set_property PACKAGE_PIN M20 [get_ports {dac_dat[1]}]
set_property PACKAGE_PIN L19 [get_ports {dac_dat[2]}]
set_property PACKAGE_PIN L20 [get_ports {dac_dat[3]}]
set_property PACKAGE_PIN K19 [get_ports {dac_dat[4]}]
set_property PACKAGE_PIN J19 [get_ports {dac_dat[5]}]
set_property PACKAGE_PIN J20 [get_ports {dac_dat[6]}]
set_property PACKAGE_PIN H20 [get_ports {dac_dat[7]}]
set_property PACKAGE_PIN G19 [get_ports {dac_dat[8]}]
set_property PACKAGE_PIN G20 [get_ports {dac_dat[9]}]
set_property PACKAGE_PIN F19 [get_ports {dac_dat[10]}]
set_property PACKAGE_PIN F20 [get_ports {dac_dat[11]}]
set_property PACKAGE_PIN D20 [get_ports {dac_dat[12]}]
set_property PACKAGE_PIN D19 [get_ports {dac_dat[13]}]

# control
set_property IOSTANDARD LVCMOS33 [get_ports dac_*]
set_property SLEW       FAST     [get_ports dac_*]
set_property DRIVE      8        [get_ports dac_*]
#set_property IOB        TRUE     [get_ports dac_*_o]

set_property PACKAGE_PIN M17 [get_ports dac_wrt]
set_property PACKAGE_PIN N16 [get_ports dac_sel]
set_property PACKAGE_PIN M18 [get_ports dac_clk]
set_property PACKAGE_PIN N15 [get_ports dac_rst]




软件还是用上面的就可以~实验现象自然是能根据不通输入输出不同波形~

工程下载地址:https://download.csdn.net/download/weixin_44852755/88580818?spm=1001.2014.3001.5503 

遇见的问题:综合分析过程出现错误

 [DRC REQP-1578] Input clock driver: Unsupported MMCME2_ADV connectivity. The signal system_i/clk_wiz_0/inst/clk_in1 on the system_i/clk_wiz_0/inst/mmcm_adv_inst/CLKIN1 pin of system_i/clk_wiz_0/inst/mmcm_adv_inst with COMPENSATION mode ZHOLD must be driven by a clock capable IO.(忘了截图了~跟下面类似,不过我用的锁相环是MMCM模式)

解决方法: 

将source由“single ended clock capable pin”调为“global buffer”即可。再次implementation时候就不报错。 


总结

总体来说,整体工程的完成自己断断续续大概用了20天,虽然实现的功能不复杂,但真正做起来,对于自己这个初学者还是非常费时费力的~回顾这个过程自己走了很多弯路,但也学会了很多,比如自己还没记录下来的IP核如何封装,如何移植,不用ILA IP核怎么用另一种方式去添加观测信号,如何把工程固化到板子上等等。~路阻且长,还在路上~加油

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空lg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值