目录
前言
一个月没有继续更新是因为入手了一块新的板子——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 系统框图
根据实验任务我大致画出本次实验的系统框图 ~ 如图所示:
2.2 模块配置
2.2.1 PS与GPIO核配置
创建VIVADO工程之前我们要看一下火龙果板的原理图:要注意它内部各器件的型号~
其余配置如下:
注意DDR3型号
添加AXI-GPIO模块:AXI GPIO用于控制和读取GPIO引脚的状态。它可以配置GPIO引脚的输入/输出模式、电平状态和中断触发方式,并可以读取GPIO引脚的当前状态。
GPIO 接口的位宽“GPIO Width”, 最大可以支持 32 位,意味着一个通道最多可以控制32个GPIO引脚。这里我们需要控制8种不同频率,因此将其设置为 4。
“Enable Dual Channel”可以使能 GPIO 通道 2,GPIO 2 的配置与 GPIO 完全相同。该选项默认没有勾选,即该IP工作在单通道模式下。
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]
三、软件设计
#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核怎么用另一种方式去添加观测信号,如何把工程固化到板子上等等。~路阻且长,还在路上~加油