Saqib Awan
Published © GPL3+

Signal Transmission and Reception Using ADRV9009 + ZCU102

This project is a step by step tutorial to transmit and receive signal using ADRV9009(WPCBZ) + ZCU102 (Eval Board)

AdvancedFull instructions provided5 hours2,101
Signal Transmission and Reception Using ADRV9009 + ZCU102

Things used in this project

Hardware components

AMD Zynq Ultrascale+ ZCU102 Eval Board
×1
Analog Devices ADRV9009-W/PCBZ
×1
E440B Spectrum Analyzer
×1
SMA RF Cables
×1

Software apps and online services

Vitis Unified Software Platform
AMD Vitis Unified Software Platform
Vivado Design Suite
AMD Vivado Design Suite
MATLAB
MATLAB

Story

Read more

Schematics

Signal Flow Receive Path

Signal Flow Transmit Path

Code

healess.c

C/C++
main file for signal transmission and reception
/**
 * \file adrv9009/src/app/headless.c
 *
 * \brief Contains example code for user integration with their application
 *
 * Copyright 2015-2017 Analog Devices Inc.
 * Released under the AD9378-AD9379 API license, for more information see the "LICENSE.txt" file in this zip file.
 *
 */

/****< Insert User Includes Here >***/

#include <stdio.h>
#include "adi_hal.h"
#include "no_os_spi.h"
#include "no_os_error.h"
#include "no_os_delay.h"
#include "parameters.h"
#include "no_os_util.h"
#include "axi_dac_core.h"
#include "axi_adc_core.h"
#include "axi_dmac.h"
#ifndef ALTERA_PLATFORM
#include "xil_cache.h"
#include "xilinx_gpio.h"
#include "xilinx_spi.h"
#else
#include "altera_spi.h"
#include "altera_gpio.h"
#endif
#include "talise.h"
#include "talise_config.h"
#include "app_config.h"
#include "app_clocking.h"
#include "app_jesd.h"
#include "app_transceiver.h"
#include "app_talise.h"
#include "ad9528.h"
#define	 DMA_EXAMPLE
#ifdef IIO_SUPPORT

#include "iio.h"
#include "iio_app.h"
#include "iio_axi_adc.h"
#include "iio_axi_dac.h"
#ifndef ALTERA_PLATFORM
#include "xilinx_uart.h"
#else
#include "altera_uart.h"
#endif

int32_t start_iiod(struct axi_dmac *rx_dmac, struct axi_dmac *tx_dmac,
		   struct axi_adc *rx_adc, struct axi_dac *tx_dac)
{
	struct iio_axi_adc_init_param	iio_axi_adc_init_par;
	struct iio_axi_dac_init_param	iio_axi_dac_init_par;
	struct iio_app_init_param app_init_param = { 0 };
	struct iio_axi_adc_desc		*iio_axi_adc_desc;
	struct iio_axi_dac_desc		*iio_axi_dac_desc;
	struct iio_device		*adc_dev_desc;
	struct iio_device		*dac_dev_desc;
	struct iio_app_desc *app;
	int32_t				status;
	struct xil_uart_init_param platform_uart_init_par = {
#ifdef XPAR_XUARTLITE_NUM_INSTANCES
		.type = UART_PL,
#else
		.type = UART_PS,
		.irq_id = UART_IRQ_ID
#endif
	};

	struct no_os_uart_init_param iio_uart_ip = {
		.device_id = UART_DEVICE_ID,
		.irq_id = UART_IRQ_ID,
		.baud_rate = UART_BAUDRATE,
		.size = NO_OS_UART_CS_8,
		.parity = NO_OS_UART_PAR_NO,
		.stop = NO_OS_UART_STOP_1_BIT,
		.extra = &platform_uart_init_par,
		.platform_ops = &xil_uart_ops
	};

	iio_axi_adc_init_par = (struct iio_axi_adc_init_param) {
		.rx_adc = rx_adc,
		.rx_dmac = rx_dmac,
#ifndef PLATFORM_MB
		.dcache_invalidate_range = (void (*)(uint32_t,
						     uint32_t))Xil_DCacheInvalidateRange
#endif
	};

#ifndef ADRV9008_1
	iio_axi_dac_init_par = (struct iio_axi_dac_init_param) {
		.tx_dac = tx_dac,
		.tx_dmac = tx_dmac,
#ifndef PLATFORM_MB
		.dcache_flush_range = (void (*)(uint32_t, uint32_t))Xil_DCacheFlushRange,
#endif
	};
#endif

	status = iio_axi_adc_init(&iio_axi_adc_desc, &iio_axi_adc_init_par);
	if (status < 0)
		return status;

#ifndef ADRV9008_1
	status = iio_axi_dac_init(&iio_axi_dac_desc, &iio_axi_dac_init_par);
	if (status < 0)
		return status;
#endif

	iio_axi_adc_get_dev_descriptor(iio_axi_adc_desc, &adc_dev_desc);
#ifndef ADRV9008_1
	iio_axi_dac_get_dev_descriptor(iio_axi_dac_desc, &dac_dev_desc);
#endif

	struct iio_data_buffer read_buff = {
		.buff = (void *)ADC_DDR_BASEADDR,
		.size = 0xFFFFFFFF,
	};
#ifndef ADRV9008_1
	struct iio_data_buffer write_buff = {
		.buff = (void *)DAC_DDR_BASEADDR,
		.size = 0xFFFFFFFF,
	};
#endif

	struct iio_app_device devices[] = {
		IIO_APP_DEVICE("axi_adc", iio_axi_adc_desc, adc_dev_desc,
			       &read_buff, NULL, NULL),
#ifndef ADRV9008_1
		IIO_APP_DEVICE("axi_dac", iio_axi_dac_desc, dac_dev_desc,
			       NULL, &write_buff, NULL)
#endif
	};

	app_init_param.devices = devices;
	app_init_param.nb_devices = NO_OS_ARRAY_SIZE(devices);
	app_init_param.uart_init_params = iio_uart_ip;

	status = iio_app_init(&app, app_init_param);
	if (status)
		return status;

	return iio_app_run(app);
}

#endif // IIO_SUPPORT

/**********************************************************/
/**********************************************************/
/********** Talise Data Structure Initializations ********/
/**********************************************************/
/**********************************************************/

int main(void)
{
	adiHalErr_t err;
	int status;

	// compute the lane rate from profile settings
	// lane_rate = input_rate * M * 20 / L
	// 		where L and M are explained in taliseJesd204bFramerConfig_t comments
	uint32_t rx_lane_rate_khz = talInit.rx.rxProfile.rxOutputRate_kHz *
				    talInit.jesd204Settings.framerA.M * (20 /
						    no_os_hweight8(talInit.jesd204Settings.framerA.serializerLanesEnabled));
	uint32_t rx_div40_rate_hz = rx_lane_rate_khz * (1000 / 40);
	uint32_t tx_lane_rate_khz = talInit.tx.txProfile.txInputRate_kHz *
				    talInit.jesd204Settings.deframerA.M * (20 /
						    no_os_hweight8(talInit.jesd204Settings.deframerA.deserializerLanesEnabled));
	uint32_t tx_div40_rate_hz = tx_lane_rate_khz * (1000 / 40);
	uint32_t rx_os_lane_rate_khz = talInit.obsRx.orxProfile.orxOutputRate_kHz *
				       talInit.jesd204Settings.framerB.M * (20 /
						       no_os_hweight8(talInit.jesd204Settings.framerB.serializerLanesEnabled));
	uint32_t rx_os_div40_rate_hz = rx_os_lane_rate_khz * (1000 / 40);

	// compute the local multiframe clock
	// serializer:   lmfc_rate = (lane_rate * 100) / (K * F)
	// deserializer: lmfc_rate = (lane_rate * 100) / (K * 2 * M / L)
	// 		where K, F, L, M are explained in taliseJesd204bFramerConfig_t comments
	uint32_t rx_lmfc_rate = (rx_lane_rate_khz * 100) /
				(talInit.jesd204Settings.framerA.K * talInit.jesd204Settings.framerA.F);
	uint32_t tx_lmfc_rate = (tx_lane_rate_khz * 100) /
				(talInit.jesd204Settings.deframerA.K * 2 * talInit.jesd204Settings.deframerA.M /
				 no_os_hweight8(talInit.jesd204Settings.deframerA.deserializerLanesEnabled));
	uint32_t rx_os_lmfc_rate = (rx_os_lane_rate_khz * 100) /
				   (talInit.jesd204Settings.framerB.K * talInit.jesd204Settings.framerB.F);

	uint32_t lmfc_rate = no_os_min(rx_lmfc_rate, rx_os_lmfc_rate);
	lmfc_rate = no_os_min(tx_lmfc_rate, lmfc_rate);

	struct axi_adc_init rx_adc_init = {
		"rx_adc",
		RX_CORE_BASEADDR,
		TALISE_NUM_CHANNELS / 2
	};
	struct axi_adc *rx_adc;

	struct axi_adc_init rx_os_adc_init = {
		"rx_os_adc",
		RX_OS_CORE_BASEADDR,
		TALISE_NUM_CHANNELS / 2
	};
	struct axi_adc *rx_os_adc;

	struct axi_dac_init tx_dac_init = {
		"tx_dac",
		TX_CORE_BASEADDR,
		TALISE_NUM_CHANNELS,
		NULL,
		3
	};
	struct axi_dac *tx_dac;

	struct axi_dmac_init rx_dmac_init = {
		"rx_dmac",
		RX_DMA_BASEADDR,
		IRQ_DISABLED
	};
	struct axi_dmac *rx_dmac;

	struct axi_dmac_init rx_os_dmac_init = {
		"rx_os_dmac",
		RX_OS_DMA_BASEADDR,
		IRQ_DISABLED
	};
	struct axi_dmac *rx_os_dmac;

	struct axi_dmac_init tx_dmac_init = {
		"tx_dmac",
		TX_DMA_BASEADDR,
		IRQ_DISABLED
	};
	struct axi_dmac *tx_dmac;

#ifdef DMA_EXAMPLE
	struct no_os_gpio_desc *gpio_plddrbypass;
	struct no_os_gpio_init_param gpio_init_plddrbypass;
	extern const uint32_t sine_lut_iq[1024];
#endif
#ifndef ALTERA_PLATFORM
	struct xil_spi_init_param hal_spi_param = {
#ifdef PLATFORM_MB
		.type = SPI_PL,
#else
		.type = SPI_PS,
#endif
		.flags = SPI_CS_DECODE
	};
	struct xil_gpio_init_param hal_gpio_param = {
#ifdef PLATFORM_MB
		.type = GPIO_PL,
#else
		.type = GPIO_PS,
#endif
		.device_id = GPIO_DEVICE_ID
	};
#else
	struct altera_spi_init_param hal_spi_param = {
		.type = NIOS_II_SPI,
		.base_address = SPI_BASEADDR
	};
	struct altera_gpio_init_param hal_gpio_param = {
		.type = NIOS_II_GPIO,
		.device_id = 0,
		.base_address = GPIO_BASEADDR
	};

	hal.extra_gpio = &hal_gpio_param;
#endif
	int t;
	struct adi_hal hal[TALISE_DEVICE_ID_MAX];
	taliseDevice_t tal[TALISE_DEVICE_ID_MAX];
	for (t = TALISE_A; t < TALISE_DEVICE_ID_MAX; t++) {
		hal[t].extra_gpio = &hal_gpio_param;
		hal[t].extra_spi = &hal_spi_param;
		tal[t].devHalInfo = (void *) &hal[t];
	}
	hal[TALISE_A].gpio_adrv_resetb_num = TRX_A_RESETB_GPIO;
	hal[TALISE_A].spi_adrv_csn = ADRV_CS;
#if defined(ZU11EG) || defined(FMCOMMS8_ZCU102)
	hal[TALISE_B].gpio_adrv_resetb_num = TRX_B_RESETB_GPIO;
	hal[TALISE_B].spi_adrv_csn = ADRV_B_CS;
#endif

#ifndef ALTERA_PLATFORM
	/* Enable the instruction cache. */
	Xil_ICacheEnable();
	/* Enable the data cache. */
	Xil_DCacheEnable();
#endif

	printf("Hello\n");

	/**********************************************************/
	/**********************************************************/
	/************ Talise Initialization Sequence *************/
	/**********************************************************/
	/**********************************************************/

	err = clocking_init(rx_div40_rate_hz,
			    tx_div40_rate_hz,
			    rx_os_div40_rate_hz,
			    talInit.clocks.deviceClock_kHz,
			    lmfc_rate);
	if (err != ADIHAL_OK)
		goto error_0;

	err = jesd_init(rx_div40_rate_hz,
			tx_div40_rate_hz,
			rx_os_div40_rate_hz);
	if (err != ADIHAL_OK)
		goto error_1;

	err = fpga_xcvr_init(rx_lane_rate_khz,
			     tx_lane_rate_khz,
			     rx_os_lane_rate_khz,
			     talInit.clocks.deviceClock_kHz);
	if (err != ADIHAL_OK)
		goto error_2;

	for (t = TALISE_A; t < TALISE_DEVICE_ID_MAX; t++) {
		err = talise_setup(&tal[t], &talInit);
		if (err != ADIHAL_OK)
			goto error_3;
	}
#if defined(ZU11EG) || defined(FMCOMMS8_ZCU102)
	printf("Performing multi-chip synchronization...\n");
	for (int i = 0; i < 12; i++) {
		for (t = TALISE_A; t < TALISE_DEVICE_ID_MAX; t++) {
			err = talise_multi_chip_sync(&tal[t], i);
			if (err != ADIHAL_OK)
				goto error_3;
		}
	}
#endif
	ADIHAL_sysrefReq(tal[TALISE_A].devHalInfo, SYSREF_CONT_ON);

	jesd_rx_watchdog();

	/* Print JESD status */
	jesd_status();

	/* Initialize the DAC core */
#ifndef ADRV9008_1
	status = axi_dac_init(&tx_dac, &tx_dac_init);
	if (status) {
		printf("axi_dac_init() failed with status %d\n", status);
		goto error_3;
	}
#endif

	/* Initialize the ADC core */
#ifndef ADRV9008_2
	status = axi_adc_init(&rx_adc, &rx_adc_init);
	if (status) {
		printf("axi_adc_init() failed with status %d\n", status);
		goto error_3;
	}

#endif

#ifndef ADRV9008_1
	status = axi_adc_init(&rx_os_adc, &rx_os_adc_init);
	if (status) {
		printf("OBS axi_adc_init() failed with status %d\n", status);
		goto error_3;
	}

	status = axi_dmac_init(&tx_dmac, &tx_dmac_init);
	if (status) {
		printf("axi_dmac_init() tx init error: %d\n", status);
		goto error_3;
	}

#endif
#ifndef ADRV9008_2
	status = axi_dmac_init(&rx_dmac, &rx_dmac_init);
	if (status) {
		printf("axi_dmac_init() rx init error: %d\n", status);
		goto error_3;
	}
#endif

	status = axi_dmac_init(&rx_os_dmac, &rx_os_dmac_init);
	if (status) {
		printf("OBS axi_dmac_init() rx init error: %d\n", status);
		goto error_3;
	}

#ifdef DMA_EXAMPLE
	gpio_init_plddrbypass.extra = &hal_gpio_param;
#ifndef ALTERA_PLATFORM
	gpio_init_plddrbypass.platform_ops = &xil_gpio_ops;
#else
	gpio_init_plddrbypass.platform_ops = &altera_gpio_ops;
#endif
	gpio_init_plddrbypass.number = DAC_FIFO_BYPASS_GPIO;
	status = no_os_gpio_get(&gpio_plddrbypass, &gpio_init_plddrbypass);
	if (status) {
		printf("no_os_gpio_get() failed with status %d", status);
		goto error_3;
	}
	no_os_gpio_direction_output(gpio_plddrbypass, 1);

#ifndef ADRV9008_1
	axi_dac_set_datasel(tx_dac, -1, AXI_DAC_DATA_SEL_DMA);
	axi_dac_load_custom_data(tx_dac, sine_lut_iq,
				 NO_OS_ARRAY_SIZE(sine_lut_iq),
				 DAC_DDR_BASEADDR);
#ifndef ALTERA_PLATFORM
	Xil_DCacheFlush();
#endif

	struct axi_dma_transfer transfer_tx = {
		// Number of bytes to write/read
		.size = sizeof(sine_lut_iq),
		// Transfer done flag
		.transfer_done = 0,
		// Signal transfer mode
#ifdef IIO_SUPPORT
		.cyclic = CYCLIC,
#else
		.cyclic = NO,
#endif
		// Address of data source
		.src_addr = (uintptr_t)DAC_DDR_BASEADDR,
		// Address of data destination
		.dest_addr = 0
	};
	axi_dmac_transfer_start(tx_dmac, &transfer_tx);
	Xil_DCacheInvalidateRange((uintptr_t)DAC_DDR_BASEADDR, sizeof(sine_lut_iq));

	no_os_mdelay(1000);
#endif

	/* Transfer 16384 samples from ADC to MEM */
	struct axi_dma_transfer transfer_rx = {
	        .size = 16384 * 1 * NO_OS_DIV_ROUND_UP(talInit.jesd204Settings.framerA.Np, 8),
	        .transfer_done = 0,
	        .cyclic = NO,
	        .src_addr = 0,
	        .dest_addr = (uintptr_t)(DDR_MEM_BASEADDR + 0x800000)
	    };
	#ifndef ADRV9008_2
	    status = axi_dmac_transfer_start(rx_dmac, &transfer_rx);
	    if (status)
	        return status;
	    printf("Rx \n");
	    status = axi_dmac_transfer_wait_completion(rx_dmac, 500);
	    uint8_t num_chans = rx_adc_init.num_channels;
	#else
	    status = axi_dmac_transfer_start(rx_os_dmac, &transfer_rx);
	    if (status)
	        return status;
	    printf("Rx obs ");
	    status = axi_dmac_transfer_wait_completion(rx_os_dmac, 500);
	    uint8_t num_chans = rx_os_adc_init.num_channels;
	#endif
	    if (status)
	        return status;
	#ifndef ALTERA_PLATFORM
	    Xil_DCacheInvalidateRange(DDR_MEM_BASEADDR + 0x800000,
	                              16384 * 1/*TALISE_NUM_CHANNELS*/ *
	                              NO_OS_DIV_ROUND_UP(talInit.jesd204Settings.framerA.Np, 8));
	#endif
	    printf("DMA_EXAMPLE: address=%#lx samples=%lu channels=%u bits=%u\n",
	           transfer_rx.dest_addr, transfer_rx.size / NO_OS_DIV_ROUND_UP(
	                   talInit.jesd204Settings.framerA.Np, 8),
	           num_chans,
	           8 * NO_OS_DIV_ROUND_UP(talInit.jesd204Settings.framerA.Np, 8));
	    uint16_t *adc_data = (uint16_t *)(DDR_MEM_BASEADDR + 0x800000);  // Cast to 16-bit

	    printf("Rx1 ADC Samples: ");
	    for (int i = 0; i < 1000 && i < (16384 * TALISE_NUM_CHANNELS); i += 2) {  // Rx1 samples only
	        printf("%d\n ", (int16_t)adc_data[i]);  // Signed 16-bit
	    }
	    printf("\n");
	#endif

#ifdef IIO_SUPPORT
	// Allow time to display messages correctly
	no_os_mdelay(100);
#ifdef ADRV9008_2
	status = start_iiod(rx_os_dmac, tx_dmac, rx_os_adc, tx_dac);
#else
	status = start_iiod(rx_dmac, tx_dmac, rx_adc, tx_dac);
#endif
	if (status)
		printf("iiod error: %d\n", status);
#endif // IIO_SUPPORT

/*	for (t = TALISE_A; t < TALISE_DEVICE_ID_MAX; t++) {
		talise_shutdown(&tal[t]);
	}*/
error_3:
	fpga_xcvr_deinit();
error_2:
	jesd_deinit();
error_1:
	clocking_deinit();
error_0:
	printf("Bye\n");

//#ifndef ALTERA_PLATFORM
	/* Disable the instruction cache. */
	//Xil_ICacheDisable();
	/* Disable the data cache. */
	//Xil_DCacheDisable();
//#endif

	return 0;
}

samples_plotting

MATLAB
This file is used for plotting received samples in MATLAB and taking their FFT
% Read Rx1 ADC samples from file
adc_samples = dlmread("samples.txt");

% Sampling frequency (245.76 MHz from rxOutputRate_kHz)
fs = 245760000;  % Hz
Ts = 1/fs;       % Sampling period in seconds

% Adjust sampling rate for I/Q decimation (effective rate for I samples)
% Since we're extracting only Rx1 I samples (i += 4 in the loop), the effective rate is halved
effective_fs = fs / 2;  % 122.88 MHz
effective_Ts = 1/effective_fs;  % ~8.14 ns
t = (0:length(adc_samples)-1) * effective_Ts;  % Time vector for I samples

% Plot the raw signal
figure;
subplot(2,1,1);
plot(t, adc_samples, 'b-', 'LineWidth', 1.5);
hold on;
plot(t, adc_samples, 'ro', 'MarkerSize', 5);
hold off;
xlabel('Time (s)');
ylabel('ADC Output (16-bit signed)');
title('Rx1 ADC Samples (Raw)');
grid on;

% Method 1: Time-Domain Frequency Calculation (Zero Crossings)
% Find zero crossings (where the signal changes sign)
zero_crossings = find(diff(sign(adc_samples)) ~= 0);
if length(zero_crossings) >= 2
    % Calculate period as the time between two consecutive zero crossings
    period_samples = zero_crossings(2) - zero_crossings(1);  % Number of samples for half a cycle
    period = period_samples * effective_Ts * 2;  % Full period (two half-cycles)
    freq_time_domain = 1 / period;
    disp(['Frequency (Time Domain): ', num2str(freq_time_domain/1e6), ' MHz']);
else
    disp('Not enough zero crossings to calculate frequency in time domain.');
    freq_time_domain = NaN;
end

% Method 2: FFT-Based Frequency Calculation
N = length(adc_samples);
Y = fft(adc_samples);
f = (0:(N-1)) * (effective_fs/N);  % Frequency vector (using effective_fs)
P = abs(Y/N);  % Magnitude of FFT
P = P(1:floor(N/2)+1);  % Take only the positive frequencies
f = f(1:floor(N/2)+1);

% Find the peak frequency (excluding DC component)
P(1) = 0;  % Ignore DC component
[~, max_idx] = max(P);
freq_fft = f(max_idx);
disp(['Frequency (FFT): ', num2str(freq_fft/1e6), ' MHz']);

% Plot the FFT
subplot(2,1,2);
plot(f/1e6, P, 'b-', 'LineWidth', 1.5);
xlabel('Frequency (MHz)');
ylabel('Magnitude');
title(['FFT of Rx1 ADC Samples (Peak at ', num2str(freq_fft/1e6), ' MHz)']);
grid on;
xlim([0 20]);  % Zoom into lower frequencies (e.g., 0-20 MHz)

Credits

Saqib Awan
5 projects • 14 followers
Hardware Engineer and Embedded Systems Enthusiast | Passionate about SoCs, RFSoCs, and Cutting-Edge Hardware Design

Comments