当前位置:   article > 正文

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

ip核引出uart

目录

前言

实验环境:

一、设计需求

二、硬件设计

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核

  1. module red_ADDA_shell (
  2. input [0:0] fclk , //[0]-125MHz
  3. input [0:0] frstn ,
  4. // ADC
  5. input [14-1:0] adc_dat_0_i , // ADC data
  6. input [14-1:0] adc_dat_1_i , // ADC data
  7. input [ 2-1:0] adc_clk_i , // ADC clock {p,n}
  8. output [ 2-1:0] adc_clk_o , // optional ADC clock source (unused)
  9. output adc_cdcs_o , // ADC clock duty cycle stabilizer
  10. output [14-1:0] rtl_adc_0_o , // rtl side adc 0 output
  11. output [14-1:0] rtl_adc_1_o , // rtl side adc 1 output
  12. output rtl_adc_clk_o , // rtl side adc clk output
  13. // DAC
  14. output [14-1:0] dac_dat_o , // DAC combined data
  15. output dac_wrt_o , // DAC write
  16. output dac_sel_o , // DAC channel select
  17. output dac_clk_o , // DAC clock
  18. output dac_rst_o , // DAC reset
  19. input [14-1:0] rtl_dac_0_i,
  20. input [14-1:0] rtl_dac_1_i,
  21. output rtl_dac_clk );
  22. red_pitaya_ADDA U_adda(
  23. .fclk (fclk ), //[0]-125MHz
  24. .frstn (frstn ),
  25. .adc_dat_0_i (adc_dat_0_i ), // ADC data
  26. .adc_dat_1_i (adc_dat_1_i ), // ADC data
  27. .adc_clk_i (adc_clk_i ), // ADC clock {p,n}
  28. .adc_clk_o (adc_clk_o ), // optional ADC clock source (unused)
  29. .adc_cdcs_o (adc_cdcs_o ), // ADC clock duty cycle stabilizer
  30. .rtl_adc_0_o (rtl_adc_0_o ), // rtl side adc 0 output
  31. .rtl_adc_1_o (rtl_adc_1_o ), // rtl side adc 1 output
  32. .rtl_adc_clk_o (rtl_adc_clk_o ), // rtl side adc clk output
  33. .dac_dat_o (dac_dat_o ), // DAC combined data
  34. .dac_wrt_o (dac_wrt_o ), // DAC write
  35. .dac_sel_o (dac_sel_o ), // DAC channel select
  36. .dac_clk_o (dac_clk_o ), // DAC clock
  37. .dac_rst_o (dac_rst_o ), // DAC reset
  38. .rtl_dac_0_i (rtl_dac_0_i ),
  39. .rtl_dac_1_i (rtl_dac_1_i ),
  40. .rtl_dac_clk (rtl_dac_clk ));
  41. endmodule
  1. module red_pitaya_ADDA (
  2. input logic [0:0] fclk , //[0]-125MHz
  3. input logic [0:0] frstn ,
  4. // ADC
  5. input logic [14-1:0] adc_dat_0_i , // ADC data
  6. input logic [14-1:0] adc_dat_1_i , // ADC data
  7. input logic [ 2-1:0] adc_clk_i , // ADC clock {p,n}
  8. output logic [ 2-1:0] adc_clk_o , // optional ADC clock source (unused)
  9. output logic adc_cdcs_o , // ADC clock duty cycle stabilizer
  10. output logic [14-1:0] rtl_adc_0_o , // rtl side adc 0 output
  11. output logic [14-1:0] rtl_adc_1_o , // rtl side adc 1 output
  12. output logic rtl_adc_clk_o , // rtl side adc clk output
  13. // DAC
  14. output logic [14-1:0] dac_dat_o , // DAC combined data
  15. output logic dac_wrt_o , // DAC write
  16. output logic dac_sel_o , // DAC channel select
  17. output logic dac_clk_o , // DAC clock
  18. output logic dac_rst_o , // DAC reset
  19. input logic [14-1:0] rtl_dac_0_i,
  20. input logic [14-1:0] rtl_dac_1_i,
  21. output logic rtl_dac_clk
  22. );
  23. // PLL signals
  24. logic adc_clk_in; //ADC 时钟输入信号。
  25. logic pll_adc_clk; //ADC 时钟的锁相环信号。
  26. logic pll_dac_clk_1x; //DAC 时钟的锁相环信号
  27. logic pll_dac_clk_2x; //DAC 时钟的锁相环信号,频率是 pll_dac_clk_1x 的两倍。
  28. logic pll_dac_clk_2p; //DAC 时钟的锁相环信号,相位是 pll_dac_clk_1x 的两倍。
  29. logic pll_ser_clk; //串行时钟的锁相环信号。
  30. logic pll_locked;
  31. // ADC clock/reset
  32. logic adc_clk,adc_rstn;
  33. // stream bus type
  34. localparam type SBA_T = logic signed [14-1:0]; // 表示采集的数据流总线类型,具有 14 位带符号的数据。
  35. SBA_T [ 2-1:0] adc_dat; //一个大小为 2 的数据流数组,用于表示 ADC 数据。adc_dat 包含两个元素,每个元素都是带有 14 位符号的数据。
  36. // DAC 时钟信号
  37. logic dac_clk_1x,dac_clk_2x,dac_clk_2p,dac_rst;
  38. //DAC数据信号
  39. logic [14-1:0] dac_dat_a, dac_dat_b; //14 位的 DAC 数据信号 A 和 B。
  40. // PLL (clock and reset)
  41. // diferential clock input
  42. IBUFDS i_clk (.I (adc_clk_i[1]), .IB (adc_clk_i[0]), .O (adc_clk_in));
  43. // IBUFDS: 差分输入缓冲器,用于处理差分信号。adc_clk_i[1] 和 adc_clk_i[0] 是差分时钟信号的两个分量。IBUFDS 模块接收这两个差分信号作为输入,并将它们转换为单端信号 adc_clk_in,以便后续的逻辑电路可以使用这个单端信号进行处理。
  44. red_pitaya_pll pll (
  45. // inputs
  46. .clk (adc_clk_in), // clock
  47. .rstn (frstn[0] ), // reset - active low,将输入时钟 adc_clk_in 和复位信号 frstn[0] 作为输入,为不同的模块提供多个时钟输出和锁定状态输出。
  48. // output clocks
  49. .clk_adc (pll_adc_clk ), // ADC clock
  50. .clk_dac_1x (pll_dac_clk_1x), // DAC clock 125MHz
  51. .clk_dac_2x (pll_dac_clk_2x), // DAC clock 250MHz
  52. .clk_dac_2p (pll_dac_clk_2p), // DAC clock 250MHz -45DGR
  53. .clk_ser (pll_ser_clk ), // fast serial clock
  54. // status outputs
  55. .pll_locked (pll_locked)
  56. );
  57. logic ser_clk ;//用于串行通信的时钟信号。
  58. BUFG bufg_adc_clk (.O (adc_clk ), .I (pll_adc_clk ));
  59. BUFG bufg_dac_clk_1x (.O (dac_clk_1x), .I (pll_dac_clk_1x));
  60. BUFG bufg_dac_clk_2x (.O (dac_clk_2x), .I (pll_dac_clk_2x));
  61. BUFG bufg_dac_clk_2p (.O (dac_clk_2p), .I (pll_dac_clk_2p));
  62. BUFG bufg_ser_clk (.O (ser_clk ), .I (pll_ser_clk ));
  63. //复位逻辑,adc_rstn: ADC 复位信号,与 frstn[0] 和 pll_locked 有关。dac_rst:
  64. // ADC reset (active low)
  65. always @(posedge adc_clk)
  66. adc_rstn <= frstn[0] & pll_locked;
  67. // DAC reset (active high),DAC 复位信号,与 frstn[0] 和 pll_locked 有关。
  68. always @(posedge dac_clk_1x)
  69. dac_rst <= ~frstn[0] | ~pll_locked;
  70. // ADC IO
  71. // generating ADC clock is disabled,adc_clk_o: 这个信号被设定为固定的值 2'b10,意味着 ADC 时钟生成被禁用。系统中已经存在一个稳定的外部时钟源,因此不需要通过逻辑电路生成 ADC 时钟。在这种情况下,禁用内部时钟生成可以节省资源并避免冲突。
  72. assign adc_clk_o = 2'b10;
  73. // ADC clock duty cycle stabilizer is enabled,表明 ADC 时钟占空比稳定器被启用,并且将信号 adc_cdcs_o 赋值为 1。ADC 时钟占空比稳定器(Clock Duty Cycle Stabilizer,CDCS)用于确保 ADC 时钟的占空比保持稳定,以提高系统的稳定性和性能。
  74. assign adc_cdcs_o = 1'b1 ;
  75. logic [2-1:0] [14-1:0] adc_dat_raw;//一个 2x14 的二维数组,用于存储 ADC 采集到的原始数据。
  76. // IO block registers should be used here
  77. always @(posedge adc_clk)
  78. begin
  79. adc_dat_raw[0] <= adc_dat_0_i[13:0];
  80. adc_dat_raw[1] <= adc_dat_1_i[13:0];
  81. end
  82. //通过输出 ADC 数据到 rtl_adc_0_o 和 rtl_adc_1_o,可以方便地对数据进行监控和调试,以确保 ADC 模块正常工作。这些输出端口可以连接到外部设备或接口,以便将 ADC 数据导出到外部系统或外部显示设备进行显示或记录。
  83. always @(posedge adc_clk)
  84. begin
  85. rtl_adc_0_o <= adc_dat_0_i;
  86. rtl_adc_1_o <= adc_dat_1_i;
  87. end
  88. assign rtl_adc_clk_o = adc_clk;
  89. // transform into 2's complement (negative slope),adc_dat[0] 和 adc_dat[1] 用于将 ADC 数据转换为 2 的补码形式,以便后续处理或存储。
  90. assign adc_dat[0] = {adc_dat_raw[0][14-1], ~adc_dat_raw[0][14-2:0]};
  91. assign adc_dat[1] = {adc_dat_raw[1][14-1], ~adc_dat_raw[1][14-2:0]};
  92. // DAC IO
  93. assign rtl_dac_clk = dac_clk_1x;
  94. always @(posedge dac_clk_1x)
  95. begin
  96. dac_dat_a <= rtl_dac_0_i;
  97. dac_dat_b <= rtl_dac_1_i;
  98. end
  99. // DDR outputs
  100. 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));
  101. 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));
  102. 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));
  103. 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));
  104. 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));
  105. endmodule:red_pitaya_ADDA
  106. module red_pitaya_pll (
  107. // inputs
  108. input logic clk , // clock
  109. input logic rstn , // reset - active low
  110. // output clocks
  111. output logic clk_adc , // ADC clock
  112. output logic clk_dac_1x, // DAC clock
  113. output logic clk_dac_2x, // DAC clock
  114. output logic clk_dac_2p, // DAC clock
  115. output logic clk_ser , // fast serial clock
  116. // status outputs
  117. output logic pll_locked
  118. );
  119. logic clk_fb;
  120. PLLE2_ADV #(
  121. .BANDWIDTH ("OPTIMIZED"),
  122. .COMPENSATION ("ZHOLD" ),
  123. .DIVCLK_DIVIDE ( 1 ),
  124. .CLKFBOUT_MULT ( 8 ),
  125. .CLKFBOUT_PHASE ( 0.000 ),
  126. .CLKOUT0_DIVIDE ( 8 ),
  127. .CLKOUT0_PHASE ( 0.000 ),
  128. .CLKOUT0_DUTY_CYCLE ( 0.5 ),
  129. .CLKOUT1_DIVIDE ( 8 ),
  130. .CLKOUT1_PHASE ( 0.000 ),
  131. .CLKOUT1_DUTY_CYCLE ( 0.5 ),
  132. .CLKOUT2_DIVIDE ( 4 ),
  133. .CLKOUT2_PHASE ( 0.000 ),
  134. .CLKOUT2_DUTY_CYCLE ( 0.5 ),
  135. .CLKOUT3_DIVIDE ( 4 ),
  136. .CLKOUT3_PHASE (-45.000 ),
  137. .CLKOUT3_DUTY_CYCLE ( 0.5 ),
  138. .CLKOUT4_DIVIDE ( 4 ), // 4->250MHz, 2->500MHz
  139. .CLKOUT4_PHASE ( 0.000 ),
  140. .CLKOUT4_DUTY_CYCLE ( 0.5 ),
  141. .CLKOUT5_DIVIDE ( 4 ),
  142. .CLKOUT5_PHASE ( 0.000 ),
  143. .CLKOUT5_DUTY_CYCLE ( 0.5 ),
  144. .CLKIN1_PERIOD ( 8.000 ),
  145. .REF_JITTER1 ( 0.010 )
  146. ) pll (
  147. // Output clocks
  148. .CLKFBOUT (clk_fb ),
  149. .CLKOUT0 (clk_adc ),
  150. .CLKOUT1 (clk_dac_1x),
  151. .CLKOUT2 (clk_dac_2x),
  152. .CLKOUT3 (clk_dac_2p),
  153. .CLKOUT4 (clk_ser ),
  154. // Input clock control
  155. .CLKFBIN (clk_fb ),
  156. .CLKIN1 (clk ),
  157. .CLKIN2 (1'b0 ),
  158. // Tied to always select the primary input clock
  159. .CLKINSEL (1'b1 ),
  160. // Ports for dynamic reconfiguration
  161. .DADDR (7'h0 ),
  162. .DCLK (1'b0 ),
  163. .DEN (1'b0 ),
  164. .DI (16'h0),
  165. .DO ( ),
  166. .DRDY ( ),
  167. .DWE (1'b0 ),
  168. // Other control and status signals
  169. .LOCKED (pll_locked),
  170. .PWRDWN (1'b0 ),
  171. .RST (!rstn )
  172. );
  173. 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 正确运行并输出正确的模拟信号。

其中代码:

  1. 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));
  2. 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));
  3. 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));
  4. 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));
  5. 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核

  1. module dds_contral(
  2. input clk,
  3. input rst_n,
  4. input [3 : 0] key_PINC,
  5. output reg [23 : 0] Fword
  6. );
  7. always@(*)
  8. begin
  9. case(key_PINC)
  10. 4'b0001: Fword <= 'h20c4;
  11. 4'b0010: Fword <= 'h624c;
  12. 4'b0011: Fword <= 'ha3d4;
  13. 4'b0100: Fword <= 'h147a8;
  14. 4'b0101: Fword <= 'h1EB85;
  15. 4'b0110: Fword <= 'h28f5c;
  16. 4'b0111: Fword <= 'h33333;
  17. 4'b1000: Fword <= 'h3d70a;
  18. default: Fword <= 'h1EB85;
  19. endcase
  20. end
  21. endmodule

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

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

  1. module signed_to_unsigned(
  2. DIN ,
  3. DOUT
  4. );
  5. parameter DWL = 14;
  6. input [DWL-1:0] DIN;
  7. output[DWL-1:0] DOUT;
  8. assign DOUT[DWL-1] = ~DIN[DWL-1];
  9. assign DOUT[DWL-2:0] = DIN[DWL-2:0];
  10. 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观测的信号进行对比~

约束文件:

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

  1. # ADC data
  2. set_property IOSTANDARD LVCMOS18 [get_ports {adc_dat_*_i[*]}]
  3. set_property IOB TRUE [get_ports {adc_dat_*_i[*]}]
  4. # ADC 0 data
  5. set_property PACKAGE_PIN Y17 [get_ports {adc_dat_0_i[0]}]
  6. set_property PACKAGE_PIN U17 [get_ports {adc_dat_0_i[1]}]
  7. set_property PACKAGE_PIN Y16 [get_ports {adc_dat_0_i[2]}]
  8. set_property PACKAGE_PIN W15 [get_ports {adc_dat_0_i[3]}]
  9. set_property PACKAGE_PIN W14 [get_ports {adc_dat_0_i[4]}]
  10. set_property PACKAGE_PIN Y14 [get_ports {adc_dat_0_i[5]}]
  11. set_property PACKAGE_PIN W13 [get_ports {adc_dat_0_i[6]}]
  12. set_property PACKAGE_PIN V12 [get_ports {adc_dat_0_i[7]}]
  13. set_property PACKAGE_PIN V13 [get_ports {adc_dat_0_i[8]}]
  14. set_property PACKAGE_PIN T14 [get_ports {adc_dat_0_i[9]}]
  15. set_property PACKAGE_PIN T15 [get_ports {adc_dat_0_i[10]}]
  16. set_property PACKAGE_PIN V15 [get_ports {adc_dat_0_i[11]}]
  17. set_property PACKAGE_PIN T16 [get_ports {adc_dat_0_i[12]}]
  18. set_property PACKAGE_PIN V16 [get_ports {adc_dat_0_i[13]}]
  19. # ADC 1 data
  20. set_property PACKAGE_PIN R18 [get_ports {adc_dat_1_i[0]}]
  21. set_property PACKAGE_PIN R16 [get_ports {adc_dat_1_i[1]}]
  22. set_property PACKAGE_PIN P18 [get_ports {adc_dat_1_i[2]}]
  23. set_property PACKAGE_PIN N17 [get_ports {adc_dat_1_i[3]}]
  24. set_property PACKAGE_PIN R19 [get_ports {adc_dat_1_i[4]}]
  25. set_property PACKAGE_PIN T20 [get_ports {adc_dat_1_i[5]}]
  26. set_property PACKAGE_PIN T19 [get_ports {adc_dat_1_i[6]}]
  27. set_property PACKAGE_PIN U20 [get_ports {adc_dat_1_i[7]}]
  28. set_property PACKAGE_PIN V20 [get_ports {adc_dat_1_i[8]}]
  29. set_property PACKAGE_PIN W20 [get_ports {adc_dat_1_i[9]}]
  30. set_property PACKAGE_PIN W19 [get_ports {adc_dat_1_i[10]}]
  31. set_property PACKAGE_PIN Y19 [get_ports {adc_dat_1_i[11]}]
  32. set_property PACKAGE_PIN W18 [get_ports {adc_dat_1_i[12]}]
  33. set_property PACKAGE_PIN Y18 [get_ports {adc_dat_1_i[13]}]
  34. set_property IOSTANDARD DIFF_HSTL_I_18 [get_ports adc_clk_i[*]]
  35. set_property PACKAGE_PIN U18 [get_ports adc_clk_i[1]]
  36. set_property PACKAGE_PIN U19 [get_ports adc_clk_i[0]]
  37. # Output ADC clock
  38. set_property IOSTANDARD LVCMOS18 [get_ports {adc_clk_o[*]}]
  39. set_property SLEW FAST [get_ports {adc_clk_o[*]}]
  40. set_property DRIVE 8 [get_ports {adc_clk_o[*]}]
  41. #set_property IOB TRUE [get_ports {adc_clk_o[*]}]
  42. set_property PACKAGE_PIN N20 [get_ports {adc_clk_o[0]}]
  43. set_property PACKAGE_PIN P20 [get_ports {adc_clk_o[1]}]
  44. # ADC clock stabilizer
  45. set_property IOSTANDARD LVCMOS18 [get_ports adc_cdcs_o]
  46. set_property PACKAGE_PIN V18 [get_ports adc_cdcs_o]
  47. set_property SLEW FAST [get_ports adc_cdcs_o]
  48. set_property DRIVE 8 [get_ports adc_cdcs_o]
  49. ### DAC
  50. # data
  51. set_property IOSTANDARD LVCMOS33 [get_ports {dac_dat_o[*]}]
  52. set_property SLEW SLOW [get_ports {dac_dat_o[*]}]
  53. set_property DRIVE 8 [get_ports {dac_dat_o[*]}]
  54. #set_property IOB TRUE [get_ports {dac_dat_o[*]}]
  55. set_property PACKAGE_PIN M19 [get_ports {dac_dat_o[0]}]
  56. set_property PACKAGE_PIN M20 [get_ports {dac_dat_o[1]}]
  57. set_property PACKAGE_PIN L19 [get_ports {dac_dat_o[2]}]
  58. set_property PACKAGE_PIN L20 [get_ports {dac_dat_o[3]}]
  59. set_property PACKAGE_PIN K19 [get_ports {dac_dat_o[4]}]
  60. set_property PACKAGE_PIN J19 [get_ports {dac_dat_o[5]}]
  61. set_property PACKAGE_PIN J20 [get_ports {dac_dat_o[6]}]
  62. set_property PACKAGE_PIN H20 [get_ports {dac_dat_o[7]}]
  63. set_property PACKAGE_PIN G19 [get_ports {dac_dat_o[8]}]
  64. set_property PACKAGE_PIN G20 [get_ports {dac_dat_o[9]}]
  65. set_property PACKAGE_PIN F19 [get_ports {dac_dat_o[10]}]
  66. set_property PACKAGE_PIN F20 [get_ports {dac_dat_o[11]}]
  67. set_property PACKAGE_PIN D20 [get_ports {dac_dat_o[12]}]
  68. set_property PACKAGE_PIN D19 [get_ports {dac_dat_o[13]}]
  69. # control
  70. set_property IOSTANDARD LVCMOS33 [get_ports dac_*_o]
  71. set_property SLEW FAST [get_ports dac_*_o]
  72. set_property DRIVE 8 [get_ports dac_*_o]
  73. #set_property IOB TRUE [get_ports dac_*_o]
  74. set_property PACKAGE_PIN M17 [get_ports dac_wrt_o]
  75. set_property PACKAGE_PIN N16 [get_ports dac_sel_o]
  76. set_property PACKAGE_PIN M18 [get_ports dac_clk_o]
  77. set_property PACKAGE_PIN N15 [get_ports dac_rst_o]

三、软件设计

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

 

代码的主体部分如下所示:
这段代码的功能是读取用户输入的数字,并根据数字控制 GPIO 输出信号的状态。然后从 GPIO 读取输出状态并通过 UART 发送回去,以检测GPIO输出是否符合自己预期。
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include "xparameters.h"
  4. #include "xgpio.h"
  5. #include "xuartps.h"
  6. #define GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
  7. #define UART_DEVICE_ID XPAR_XUARTPS_0_DEVICE_ID
  8. #define CHANNEL 1
  9. XGpio Gpio;
  10. XUartPs Uart_Ps;
  11. int main() {
  12. XUartPs_Config *Config;
  13. Config = XUartPs_LookupConfig(UART_DEVICE_ID);
  14. if (Config == NULL) {
  15. printf("无法找到UART配置。\n");
  16. return 1;
  17. }
  18. XUartPs *Uart_PsPtr = &Uart_Ps;
  19. XUartPs_CfgInitialize(Uart_PsPtr, Config, Config->BaseAddress);
  20. XUartPs_SetBaudRate(Uart_PsPtr, 115200);
  21. if (XGpio_Initialize(&Gpio, GPIO_DEVICE_ID) != XST_SUCCESS) {
  22. printf("GPIO初始化失败。\n");
  23. return 1;
  24. }
  25. XGpio_SetDataDirection(&Gpio, CHANNEL, 0x0);
  26. int input;
  27. int output;
  28. int gpio_output;
  29. int in_num; // 用整数存储输入
  30. while (1) {
  31. printf("输入一个数字频率模式:\n ");
  32. scanf("%d", &in_num); // 直接读取整数
  33. if (in_num >= 1 && in_num <= 8) {
  34. input = in_num;
  35. // 将输入转换为4位二进制数
  36. output = input & 0xF;
  37. //4位二进制数输出到GPIO
  38. XGpio_DiscreteWrite(&Gpio, CHANNEL, output);
  39. // 从GPIO读取值
  40. gpio_output = XGpio_DiscreteRead(&Gpio, CHANNEL);
  41. // 将从GPIO读取的值发送到串口
  42. char buffer[15];
  43. sprintf(buffer, "频率%d", gpio_output);
  44. XUartPs_Send(Uart_PsPtr, (u8 *)buffer, strlen(buffer)); // 发送整个字符串
  45. XUartPs_Send(Uart_PsPtr, (u8 *)"\n", 1); // 发送换行符
  46. } else {
  47. printf("输入错误。请确保输入一个1到8之间的数字。\n");
  48. }
  49. }
  50. return 0;
  51. }

代码通过 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模块: 

  1. `timescale 1 ns / 1 ps
  2. module axis_red_pitaya_dac #
  3. (
  4. parameter integer DAC_DATA_WIDTH = 14,
  5. parameter integer AXIS_TDATA_WIDTH = 32
  6. )
  7. (
  8. // PLL signals
  9. input wire aclk,
  10. input wire ddr_clk,
  11. input wire locked,
  12. // DAC signals
  13. output wire dac_clk,
  14. output wire dac_rst,
  15. output wire dac_sel,
  16. output wire dac_wrt,
  17. output wire [DAC_DATA_WIDTH-1:0] dac_dat,
  18. // Slave side
  19. output wire s_axis_tready,
  20. input wire [AXIS_TDATA_WIDTH-1:0] s_axis_tdata,
  21. input wire s_axis_tvalid
  22. );
  23. reg [DAC_DATA_WIDTH-1:0] int_dat_a_reg;
  24. reg [DAC_DATA_WIDTH-1:0] int_dat_b_reg;
  25. reg int_rst_reg;
  26. wire [DAC_DATA_WIDTH-1:0] int_dat_a_wire;
  27. wire [DAC_DATA_WIDTH-1:0] int_dat_b_wire;
  28. assign int_dat_a_wire = s_axis_tdata[DAC_DATA_WIDTH-1:0];
  29. assign int_dat_b_wire = s_axis_tdata[AXIS_TDATA_WIDTH/2+DAC_DATA_WIDTH-1:AXIS_TDATA_WIDTH/2];
  30. genvar j;
  31. always @(posedge aclk)
  32. begin
  33. if(~locked | ~s_axis_tvalid)
  34. begin
  35. int_dat_a_reg <= {(DAC_DATA_WIDTH){1'b0}};
  36. int_dat_b_reg <= {(DAC_DATA_WIDTH){1'b0}};
  37. end
  38. else
  39. begin
  40. int_dat_a_reg <= {int_dat_a_wire[DAC_DATA_WIDTH-1], ~int_dat_a_wire[DAC_DATA_WIDTH-2:0]};
  41. int_dat_b_reg <= {int_dat_b_wire[DAC_DATA_WIDTH-1], ~int_dat_b_wire[DAC_DATA_WIDTH-2:0]};
  42. end
  43. int_rst_reg <= ~locked | ~s_axis_tvalid;
  44. end
  45. 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));
  46. ODDR ODDR_sel(.Q(dac_sel), .D1(1'b0), .D2(1'b1), .C(aclk), .CE(1'b1), .R(1'b0), .S(1'b0));
  47. ODDR ODDR_wrt(.Q(dac_wrt), .D1(1'b0), .D2(1'b1), .C(ddr_clk), .CE(1'b1), .R(1'b0), .S(1'b0));
  48. ODDR ODDR_clk(.Q(dac_clk), .D1(1'b0), .D2(1'b1), .C(ddr_clk), .CE(1'b1), .R(1'b0), .S(1'b0));
  49. generate
  50. for(j = 0; j < DAC_DATA_WIDTH; j = j + 1)
  51. begin : DAC_DAT
  52. ODDR ODDR_inst(
  53. .Q(dac_dat[j]),
  54. .D1(int_dat_a_reg[j]),
  55. .D2(int_dat_b_reg[j]),
  56. .C(aclk),
  57. .CE(1'b1),
  58. .R(1'b0),
  59. .S(1'b0)
  60. );
  61. end
  62. endgenerate
  63. assign s_axis_tready = 1'b1;
  64. endmodule

如何使用?

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

xdc文件只保留DAC就可:

  1. ### DAC
  2. # data
  3. set_property IOSTANDARD LVCMOS33 [get_ports {dac_dat[*]}]
  4. set_property SLEW SLOW [get_ports {dac_dat[*]}]
  5. set_property DRIVE 8 [get_ports {dac_dat[*]}]
  6. #set_property IOB TRUE [get_ports {dac_dat_o[*]}]
  7. set_property PACKAGE_PIN M19 [get_ports {dac_dat[0]}]
  8. set_property PACKAGE_PIN M20 [get_ports {dac_dat[1]}]
  9. set_property PACKAGE_PIN L19 [get_ports {dac_dat[2]}]
  10. set_property PACKAGE_PIN L20 [get_ports {dac_dat[3]}]
  11. set_property PACKAGE_PIN K19 [get_ports {dac_dat[4]}]
  12. set_property PACKAGE_PIN J19 [get_ports {dac_dat[5]}]
  13. set_property PACKAGE_PIN J20 [get_ports {dac_dat[6]}]
  14. set_property PACKAGE_PIN H20 [get_ports {dac_dat[7]}]
  15. set_property PACKAGE_PIN G19 [get_ports {dac_dat[8]}]
  16. set_property PACKAGE_PIN G20 [get_ports {dac_dat[9]}]
  17. set_property PACKAGE_PIN F19 [get_ports {dac_dat[10]}]
  18. set_property PACKAGE_PIN F20 [get_ports {dac_dat[11]}]
  19. set_property PACKAGE_PIN D20 [get_ports {dac_dat[12]}]
  20. set_property PACKAGE_PIN D19 [get_ports {dac_dat[13]}]
  21. # control
  22. set_property IOSTANDARD LVCMOS33 [get_ports dac_*]
  23. set_property SLEW FAST [get_ports dac_*]
  24. set_property DRIVE 8 [get_ports dac_*]
  25. #set_property IOB TRUE [get_ports dac_*_o]
  26. set_property PACKAGE_PIN M17 [get_ports dac_wrt]
  27. set_property PACKAGE_PIN N16 [get_ports dac_sel]
  28. set_property PACKAGE_PIN M18 [get_ports dac_clk]
  29. 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核怎么用另一种方式去添加观测信号,如何把工程固化到板子上等等。~路阻且长,还在路上~加油

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/830458
推荐阅读
相关标签
  

闽ICP备14008679号