本文主要是介绍ZYNQ学习笔记(三):PL与PS数据交互—— UART串口+AXI GPIO控制DDS IP核输出实验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
前言
实验环境:
一、设计需求
二、硬件设计
2.1 系统框图
2.2 模块配置
三、软件设计
四、实验现象
总结
前言
一个月没有继续更新是因为入手了一块新的板子——redpitaya。
自上一个实验完成后,在罗德频谱仪上观测信号,发现信号质量不够好,毕竟黑金开发板配套的AN108模块,它的ADDA芯片都是8位的,使我们输出DDS信号的SDRF最多是48,所以信号观测起来体验感很差,而火龙果板拥有支持最高125Mhz,14位的ADDA芯片,能很大程度上提供更精确和稳定的模拟信号输出,所以以后的实验就在这块板子上进行~
实验环境:
火龙果(redpitaya)开发板(SOC型号zynq7010),2018.3 Vivado,2018.3 SDK, 示波器。
一、设计需求
1.学会利用redpitaya板输出波形
2.学会利用PS端串口来控制AXI GPIO核的输出
3.最终实现能够通过串口输入来控制AXI GPIO核的输出,进而控制DDS IP核输出不同频率信号。
二、硬件设计
2.1 系统框图
2.2 模块配置
创建VIVADO工程之前我们要看一下火龙果板的原理图:要注意它内部各器件的型号~
其余配置如下:
添加AXI-GPIO模块:
GPIO 接口的位宽“GPIO Width”, 最大可以支持 32 位。这里我们需要控制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是三态控制寄存器,控制GPIO引脚作为输入还是输出,GPIO_T是三态的使能信号。GPIO_T=1配置为输入(in),GPIO_T=0配置为输出(out)。GPIO_WIDTH是用户指定的位宽,可以配置在1位到32位之间。
此外输入数据一方面进入READ_REG,一方面进入中断检测模块Interrupt Detection。只要输入端口数据发生改变就可以生成中断,中断检测实现该功能并通过ip2intc_irpt输出一个中断。
添加配置完这两个IP核后,点击Diagram 窗口的1、2:
接下来我们配置PL端DDS与ADDA部分~
DDS ip核:
自定义ADDA ip核
module red_ADDA_shell (input [0:0] fclk , //[0]-125MHzinput [0:0] frstn ,// ADCinput [16-1:0] adc_dat_0_i , // ADC datainput [16-1:0] adc_dat_1_i , // ADC datainput [ 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 stabilizeroutput [16-1:0] rtl_adc_0_o , // rtl side adc 0 outputoutput [16-1:0] rtl_adc_1_o , // rtl side adc 1 outputoutput rtl_adc_clk_o , // rtl side adc clk output// DACoutput [14-1:0] dac_dat_o , // DAC combined dataoutput dac_wrt_o , // DAC writeoutput dac_sel_o , // DAC channel selectoutput dac_clk_o , // DAC clockoutput dac_rst_o , // DAC resetinput [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]-125MHzinput logic [0:0] frstn ,// ADCinput logic [16-1:0] adc_dat_0_i , // ADC datainput logic [16-1:0] adc_dat_1_i , // ADC datainput 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 stabilizeroutput logic [16-1:0] rtl_adc_0_o , // rtl side adc 0 outputoutput logic [16-1:0] rtl_adc_1_o , // rtl side adc 1 outputoutput logic rtl_adc_clk_o , // rtl side adc clk output// DACoutput logic [14-1:0] dac_dat_o , // DAC combined dataoutput logic dac_wrt_o , // DAC writeoutput logic dac_sel_o , // DAC channel selectoutput logic dac_clk_o , // DAC clockoutput logic dac_rst_o , // DAC resetinput 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;
logic pll_adc_clk;
logic pll_dac_clk_1x;
logic pll_dac_clk_2x;
logic pll_dac_clk_2p;
logic pll_ser_clk;
logic pll_pwm_clk;
logic pll_locked;// ADC clock/reset
logic adc_clk;
logic adc_rstn;// stream bus type
localparam type SBA_T = logic signed [14-1:0]; // acquire
localparam type SBG_T = logic signed [14-1:0]; // generateSBA_T [ 2-1:0] adc_dat;// DAC signals
logic dac_clk_1x;
logic dac_clk_2x;
logic dac_clk_2p;
logic dac_rst;logic [14-1:0] dac_dat_a, dac_dat_b;
logic [14-1:0] dac_a , dac_b ;
logic signed [15-1:0] dac_a_sum, dac_b_sum;// ASG
SBG_T [2-1:0] asg_dat;// PID
SBA_T [2-1:0] pid_dat;// configuration
// logic digital_loop;// PLL (clock and reset)// diferential clock input
IBUFDS i_clk (.I (adc_clk_i[1]), .IB (adc_clk_i[0]), .O (adc_clk_in)); // differential clock inputred_pitaya_pll pll (// inputs.clk (adc_clk_in), // clock.rstn (frstn[0] ), // reset - active low// 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.clk_pdm (pll_pwm_clk ), // PWM clock// status outputs.pll_locked (pll_locked)
);logic pwm_clk ;
logic pwm_rstn;
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 ));
BUFG bufg_pwm_clk (.O (pwm_clk ), .I (pll_pwm_clk ));// ADC reset (active low)
always @(posedge adc_clk)
adc_rstn <= frstn[0] & pll_locked;// DAC reset (active high)
always @(posedge dac_clk_1x)
dac_rst <= ~frstn[0] | ~pll_locked;// Analog mixed signals (PDM analog outputs)logic [ 4-1:0] [8-1:0] pdm_cfg;
logic [ 4-1:0] dac_pwm_o ; // 1-bit PWM DACred_pitaya_pdm pdm (// system signals.clk (adc_clk ),.rstn (adc_rstn),// configuration.cfg (pdm_cfg),.ena (1'b1),.rng (8'd255),// PWM outputs.pdm (dac_pwm_o));// ADC IO// generating ADC clock is disabled
assign adc_clk_o = 2'b10;
.R(1'b0), .S(1'b0));
.R(1'b0), .S(1'b0));// ADC clock duty cycle stabilizer is enabled
assign adc_cdcs_o = 1'b1 ;logic [2-1:0] [14-1:0] adc_dat_raw;// IO block registers should be used here
// lowest 2 bits reserved for 16bit ADC
always @(posedge adc_clk)
beginadc_dat_raw[0] <= adc_dat_0_i[16-1:2];adc_dat_raw[1] <= adc_dat_0_i[16-1:2];
endalways @(posedge adc_clk)
beginrtl_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)
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// Sumation of ASG and PID signal perform saturation before sending to DAC
assign dac_a_sum = asg_dat[0] + pid_dat[0];
assign dac_b_sum = asg_dat[1] + pid_dat[1];// saturation
assign dac_a = (^dac_a_sum[15-1:15-2]) ? {dac_a_sum[15-1], {13{~dac_a_sum[15-1]}}} : dac_a_sum[14-1:0];
assign dac_b = (^dac_b_sum[15-1:15-2]) ? {dac_b_sum[15-1], {13{~dac_b_sum[15-1]}}} : dac_b_sum[14-1:0];assign rtl_dac_clk = dac_clk_1x;
always @(posedge dac_clk_1x)
begindac_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_ADDAmodule red_pitaya_pll (// inputsinput logic clk , // clockinput logic rstn , // reset - active low// output clocksoutput logic clk_adc , // ADC clockoutput logic clk_dac_1x, // DAC clockoutput logic clk_dac_2x, // DAC clockoutput logic clk_dac_2p, // DAC clockoutput logic clk_ser , // fast serial clockoutput logic clk_pdm , // PDM clock// status outputsoutput 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 ),.CLKOUT5 (clk_pdm ),// 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_pllmodule red_pitaya_pdm #(int unsigned DWC = 8, // counter width (resolution)int unsigned CHN = 4 // output width
)(// system signalsinput logic clk , // clockinput logic rstn, // reset// configurationinput logic [CHN-1:0] [DWC-1:0] cfg , // input logic ena ,input logic [DWC-1:0] rng ,// PWM outputsoutput logic [CHN-1:0] pdm // PWM output - driving RC
);
generate
for (genvar i=0; i<CHN; i++) begin: for_chnlogic [DWC-1:0] dat;// input data copy
always_ff @(posedge clk)
if (~rstn) dat <= '0;
else beginif (ena) dat <= cfg[i];
endlogic [DWC-1:0] acu; // accumulator
logic [DWC :0] sum; // summation
logic [DWC :0] sub; // subtraction// accumulator
always_ff @(posedge clk)
if (~rstn) acu <= '0;
else beginif (ena) acu <= ~sub[DWC] ? sub[DWC-1:0] : sum[DWC-1:0];else acu <= '0;
end// summation
assign sum = acu + dat;// subtraction
assign sub = sum - rng;// PDM output
always_ff @(posedge clk)
if (~rstn) pdm[i] <= 1'b0;
else pdm[i] <= ena & (~sub[DWC] | ~|sub[DWC-1:0]);end: for_chn
endgenerate
endmodule: red_pitaya_pdm
解释一下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 的逻辑包括了一些处理步骤。ASG 和 PID 信号的加和后进行饱和处理,最终的输出数据存储在 dac_dat_a 和 dac_dat_b 中。这些数据最终通过 rtl_dac_0_i 和 rtl_dac_1_i 输出到外部的 DAC 接口。
red_pitaya_pdm 模块:这个模块似乎用于控制 PWM 输出信号。它包含一个循环,用于处理 PWM 输出的逻辑。其中包括了数据累加、数据比较和输出控制等逻辑,最终控制 pdm 的输出。
时钟控制和同步逻辑:在整个模块中,存在一些时序逻辑,用于控制时钟的频率和相位,以及确保数据在正确的时钟边沿进行传输和处理。这些逻辑包括了时钟分频、时钟同步和时钟延迟等方面的处理。
自定义dds_contral ip核
module dds_contral(input clk,input rst_n,input [3 : 0] key_PINC,output reg [23 : 0] Fword);
always@(*)
begincase(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;endcaseend
endmodule
频率字的计算参考第一篇文章~不再细说。
自定义signed_to_unsigned模块:目的是为了在示波器上显示
module signed_to_unsigned(
DIN ,
DOUT );parameter DWL = 16;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
其余的模块参考文章开头的Block Design~
三、软件设计
#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 1XGpio 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位二进制数输出到GPIOXGpio_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
由此可以看出,比较符合自己一开始的预期~
总结
总体来说,整体工程的完成自己断断续续大概用了20天,虽然实现的功能不复杂,但真正做起来,对于自己这个初学者还是非常费时费力的~回顾这个过程自己走了很多弯路,但也学会了很多,比如自己还没记录下来的IP核如何封装,如何移植,不用ILA IP核怎么用另一种方式去添加观测信号等等。这个工程实现过程中也发现了一些问题,比如说DDS IP核,当整体设计中存在锁相环倍频时,选择硬件模式就是无法正常输出波形,选择系统模式功能才正常,更详细的原因我还是没有查到,可能等以后自己对zynq知识学习的更系统更完善时,这个问题可能就有了答案~路阻且长,还在路上~加油
这篇关于ZYNQ学习笔记(三):PL与PS数据交互—— UART串口+AXI GPIO控制DDS IP核输出实验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!