AD9361 官方例程详解(二)

6 篇文章 63 订阅
4 篇文章 6 订阅

AD936x 系列快速入口

AD9361 官方例程

MSK调制


一、主函数

int main(void)
{
	Xil_ICacheEnable();
	Xil_DCacheEnable();
	// NOTE: The user has to choose the GPIO numbers according to desired
	// carrier board.
	default_init_param.gpio_resetb = GPIO_RESET_PIN;
	
	gpio_init(GPIO_DEVICE_ID);
	gpio_direction(default_init_param.gpio_resetb, 1);
	spi_init(SPI_DEVICE_ID, 1, 0);
	
	ad9361_init(&ad9361_phy, &default_init_param);
	
	ad9361_set_tx_fir_config(ad9361_phy, tx_fir_config);
	ad9361_set_rx_fir_config(ad9361_phy, rx_fir_config);

    #ifdef DAC_DMA_EXAMPLE
	dac_init(ad9361_phy, DATA_SEL_DMA, 1);
    #else
	dac_init(ad9361_phy, DATA_SEL_DDS, 1);
    #endif

	mdelay(1000);
	adc_capture(16384, ADC_DDR_BASEADDR);

	Xil_DCacheInvalidateRange(ADC_DDR_BASEADDR,
			ad9361_phy->pdata->rx2tx2 ? 16384 * 8 : 16384 * 4);

    uint16_t index;
    uint32_t data;
    uint16_t Q1;
    uint16_t I1;

    for(index =0; index < 1024; index += 1)
    {
    	data =Xil_In32(ADC_DDR_BASEADDR + index*4);
    	Q1 = (data) & 0xFFFF;
    	I1 = (data >> 16) & 0xFFFF;
    	printf("%d,%d\n",(signed short)I1,(signed short)Q1);
    }

	printf("Done.\n");
	
	Xil_DCacheDisable();
	Xil_ICacheDisable();
	
	return 0;
}

1.1 指令和数据缓存

CPU的运行速度比存储器外部总线的频率高很多,对外部存储器的访问将使用几十甚至上百个CPU周期才能完成。一般通过高速缓存(CPU核对高速缓存的访问比对主存储器的访问快的多,其位于CPU核与主存储器之间,是对主存储器一部分内容的复制)加速程序运行。

zynq APU中,每个A9处理器都有独立的32KB L1指令高速缓存32KB L1数据高速缓存

主函数一开始:
// 启用指令缓存
Xil_ICacheEnable();
// 启用数据缓存
Xil_DCacheEnable();

主函数结束前需要:
// 禁用数据缓存
Xil_DCacheDisable();
// 禁用指令缓存
Xil_ICacheDisable();

1.2 初始化

// 用户根据板子来选择GPIO编号,复位引脚 GPIO_RESET_PIN,
// gpio_sync 、gpio_cal_sw1 和 gpio_cal_sw2在多片同步时使用,现在没用到

default_init_param.gpio_resetb = GPIO_RESET_PIN;

// GPIO 初始化
gpio_init(GPIO_DEVICE_ID);

// 配置复位引脚gpio_resetb方向,0 输入,1 输出
gpio_direction(default_init_param.gpio_resetb, 1);

// SPI 初始化,官方例程把SPI通过EMIO引出,后续通过SPI配置AD9361
spi_init(SPI_DEVICE_ID, 1, 0);

// AD9361 初始化,将AD9361 官方例程详解(一)中配置好的default_init_param 赋给 ad9361_phy
ad9361_init(&ad9361_phy, &default_init_param);

// 配置AD9361 TX/RX FIR滤波器,官方例程是带通滤波器,fs 30.72MHz,通带3/20 fs to 1/4 fs,即4.6MHz到7.68MHz
ad9361_set_tx_fir_config(ad9361_phy, tx_fir_config);
ad9361_set_rx_fir_config(ad9361_phy, rx_fir_config);

// DAC初始化,DATA_SEL_DMA通过DMA搬取存储器数据,DATA_SEL_DDS通过DDS产生数据
#ifdef DAC_DMA_EXAMPLE
dac_init(ad9361_phy, DATA_SEL_DMA, 1);
#else
dac_init(ad9361_phy, DATA_SEL_DDS, 1);
#endif

// 延时1秒,产生稳定的正弦波
mdelay(1000);

// ADC 数据捕获
adc_capture(16384, ADC_DDR_BASEADDR);

// 为了防止不必要的数据丢失,每次adc_capture()调用后使缓存失效,记住捕获的大小和起始地址必须与缓存行大小对齐。
Xil_DCacheInvalidateRange(ADC_DDR_BASEADDR,
ad9361_phy->pdata->rx2tx2 ? 16384 * 8 : 16384 * 4);

// 取出 IQ 数据,并通过串口输出

  • 使用Xil_In32函数,需要引入头文件 #include <xil_io.h>
    uint16_t index;
    uint32_t data;
    uint16_t Q1;
    uint16_t I1;

   for(index =0; index < 16384; index += 1)
    {
    	data =Xil_In32(ADC_DDR_BASEADDR + index*4);
    	Q1 = (data) & 0xFFFF;
    	I1 = (data >> 16) & 0xFFFF;//发送数据时I路放在高16位
    	printf("%d,%d\n",(signed short)I1,(signed short)Q1);
    }

二、ad9361_init

2.1 ad9361_init函数直接配置的参数

default_init_param包含AD9361的初始参数,初始化AD9361部分。但是default_init_param不全,在ad9361_init函数中还有如下配置

//  两发两收时,chip_info={"4_CH_DEV",4}
//  一发一收时,chip_info={"2_CH_DEV",2}
#ifndef AXI_ADC_NOT_PRESENT
	phy->adc_conv->chip_info = &axiadc_chip_info_tbl[phy->pdata->rx2tx2 ? ID_AD9361 : ID_AD9364];
#endif
//  如果default_init_param中的fdd_rx_rate_2tx_enable=1,Rx采样速率是Tx采样速率的两倍。rx_eq_2tx 会在ad9361_setup中配置为true。
	phy->rx_eq_2tx = false;
//  先把增益表current_table配置为NO_GAIN_TABLE,后续在ad9361_load_gt函数会把current_table配成符合对应频率的增益表
	phy->current_table = -1;
//  Tx 和 Rx FIR滤波器初始化时先旁路,后续可通过ad9361_set_trx_fir_en_dis函数启用
	phy->bypass_tx_fir = true;
	phy->bypass_rx_fir = true;
//  OSR 速率调控,1 标称OSR ,0 最高OSR
	phy->rate_governor = 1;
//  RX RFDC Tracking、RX BasebandDC Tracking和RX Quadrature Tracking使能,true 使能, false 不使能
	phy->rfdc_track_en = true;
	phy->bbdc_track_en = true;
	phy->quad_track_en = true;
//  不同频段增益表信息
	phy->gt_info = ad9361_adi_gt_info;
//  BIST 环回模式,0 不使能,1 AD9361 内部 TX->RX,2 FPGA 内部 RX->TX
	phy->bist_loopback_mode = 0;
//  BIST Config 寄存器0x3F4 
	phy->bist_config = 0;
//  BIST 模式,0 BIST_DISABLE,1 BIST_INJ_TX,2 BIST_INJ_RX
	phy->bist_prbs_mode = BIST_DISABLE;
//  Bist tone 模式,0 BIST_DISABLE,1 BIST_INJ_TX,2 BIST_INJ_RX
	phy->bist_tone_mode = BIST_DISABLE;
//  Bist tone 频率
	phy->bist_tone_freq_Hz = 0;
//  Bist tone 电平
	phy->bist_tone_level_dB = 0;
//  Bist 寄存器 mask
	phy->bist_tone_mask = 0;

2.2 ad9361_reset 、读取设备ID并初始化系统时钟

2.21 ad9361_reset

AD9361 设备复位
// 先拉低gpio_resetb引脚,再拉高gpio_resetb引脚
gpio_set_value(phy->pdata->gpio_resetb, 0);
mdelay(1);
gpio_set_value(phy->pdata->gpio_resetb, 1);

2.22 读取设备ID并初始化系统时钟

通过ret = ad9361_spi_read(phy->spi, REG_PRODUCT_ID)读取设备ID,不符合 PRODUCT_ID_9361(0x08)的话,程序转到out,退出初始化

out:
	free(phy->spi);
#ifndef AXI_ADC_NOT_PRESENT
	free(phy->adc_conv);
	free(phy->adc_state);
#endif
	free(phy->clk_refin);
	free(phy->pdata);
	free(phy);
	printf("%s : AD936x initialization error\n", __func__);

	return -ENODEV;

再通过register_clocks(phy)函数注册并初始化系统时钟

2.23 axiadc_init

// 通过AXI总线配置HDL axi_ad9361 IP核 中的ADC channel 0-3
adc_init(phy);
// 通过AXI总线配置HDL axi_ad9361 IP核 中DAC 寄存器
dac_init(phy, DATA_SEL_DDS, 0);

2.24 ad9361_setup和axiadc_post_setup

// 通过SPI 将AD9361的初始参数配置到设备中
ad9361_setup

// 通过AXI总线配置HDL axi_ad9361 IP核 ,并对9361 进行Digital tune、设置通道时钟和ENSM等操作

三、dac_init

dac_init(ad9361_phy, DATA_SEL_DDS, 1);

第一个参数:ad9361_phy是指向AD9361的结构体指针
第二个参数:发送数据源参数可以为以下几种:
DATA_SEL_DDS,
DATA_SEL_SED,
DATA_SEL_DMA,
DATA_SEL_ZERO,
DATA_SEL_PN7,
DATA_SEL_PN15,
DATA_SEL_PN23,
DATA_SEL_PN31,
DATA_SEL_LB,
DATA_SEL_PNXX,
但是dac_init函数中对DATA_SEL_DDS和DATA_SEL_DMA有具体函数操作,其他几种直接 break
第三个参数:在DATA_SEL_DMA数据源下使用,为1时配置DMA可以将需要发送的数据通过缓存打到DDR中。

3.1 配置HDL axi_ad9361 DAC部分

	dac_write(phy, DAC_REG_RSTN, 0x0);
	dac_write(phy, DAC_REG_RSTN, DAC_RSTN | DAC_MMCM_RSTN);

	dds_st[phy->id_no].dac_clk = &phy->clks[TX_SAMPL_CLK]->rate;
	dds_st[phy->id_no].rx2tx2 = phy->pdata->rx2tx2;
	dac_read(phy, DAC_REG_CNTRL_2, &reg_ctrl_2);
	if(dds_st[phy->id_no].rx2tx2)
	{
		dds_st[phy->id_no].num_buf_channels = 4;
		if(phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE)
			dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(3));
		else
			dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(1));
		reg_ctrl_2 &= ~DAC_R1_MODE;
	}
	else
	{
		dds_st[phy->id_no].num_buf_channels = 2;
		if(phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE)
			dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(1));
		else
			dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(0));
		reg_ctrl_2 |= DAC_R1_MODE;
	}
	dac_write(phy, DAC_REG_CNTRL_2, reg_ctrl_2);

	dac_read(phy, DAC_REG_VERSION, &dds_st[phy->id_no].pcore_version);

	dac_stop(phy);

3.2 准备发送数据

3.2.1 DATA_SEL_DDS

dds_default_setup(phy, DDS_CHAN_TX1_I_F1, 90000, 2500000, 250000);
第一个参数:指向AD9361的结构体指针
第二个参数:
DDS_CHAN_TX1_I_F1,TX1通道 I路,频率: FI1
DDS_CHAN_TX1_I_F2,TX1通道 I路,频率: FI2
DDS_CHAN_TX1_Q_F1,TX1通道 Q路,频率: FQ1
DDS_CHAN_TX1_Q_F2,TX1通道 Q路,频率: FQ2
所以通过DDS TX1通道最多可以有4个单音信号
第三个参数:单音信号相位
第四个参数:单音信号频率
第五个参数:单音信号scale

3.2.2 DATA_SEL_DMA

我们先看1R1T情况,2R2T同理

for(index = 0; index < tx_count; index += 1)
    {
	index_i1 = index;	
	index_q1 = index + (tx_count / 4);
	if(index_q1 >= tx_count)
		index_q1 -= tx_count;
	data_i1 = (sine_lut[index_i1] << 20);
	data_q1 = (sine_lut[index_q1] << 4);
    Xil_Out32(DAC_DDR_BASEADDR + index * 4, data_i1 | data_q1);
	}
	Xil_DCacheFlush();

sine_lut是sin信号采样点,以补码形式存储,data_i1 为sin信号,data_q1 由index_q1 = index + (tx_count / 4);可知360度分成4份,相位差90度,data_q1 是cos信号,

data_i1 存储在32位数据中的高16位,data_q1 存储在32位数据中的低16位。Xil_Out32将32位数据写到地址DAC_DDR_BASEADDR + index * 4,由于使能了cache,需要Xil_DCacheFlush把Cache里的数据Flush出去,清空Cache,将Cache内的数据推到DDR中去。

	dac_dma_write(AXI_DMAC_REG_CTRL, 0);//初始化DMA通道
	dac_dma_write(AXI_DMAC_REG_CTRL, AXI_DMAC_CTRL_ENABLE);
	dac_dma_write(AXI_DMAC_REG_FLAGS, DMAC_FLAGS_CYCLIC);
	dac_dma_write(AXI_DMAC_REG_SRC_ADDRESS, DAC_DDR_BASEADDR);
	dac_dma_write(AXI_DMAC_REG_SRC_STRIDE, 0x0);
	dac_dma_write(AXI_DMAC_REG_X_LENGTH, length - 1);
	dac_dma_write(AXI_DMAC_REG_Y_LENGTH, 0x0);
	dac_dma_write(AXI_DMAC_REG_START_TRANSFER, 0x1);

dac_dma_write 通过AXI总线配置HDL axi_dac_dma IP核:
第一个参数:偏移地址,最终写到基地址CF_AD9361_TX_DMA_BASEADDR + regAddr偏移地址
第二个参数:值,给特定地址传递的值

  • AXI_DMAC_REG_CTRL:DMAC控制寄存器,0表示关闭DMA通道,AXI_DMAC_CTRL_ENABLE(1<<0)表示使能DMA通道
  • AXI_DMAC_REG_FLAGS:循环标志位
  • AXI_DMAC_REG_SRC_ADDRESS:源地址寄存器,DMA传输数据的起始地址
  • AXI_DMAC_REG_SRC_STRIDE:传输数据时每行的字节数
  • AXI_DMAC_REG_X_LENGTH:需要传输的数据的总字节数
  • AXI_DMAC_REG_Y_LENGTH:传输数据的行数
  • AXI_DMAC_REG_START_TRANSFER:写入0x1会将新的传输数据加入传输队列

ADI 官方的DMA传输支持2维数据传输(按行列传输),例子中使用一维传输,AXI_DMAC_REG_SRC_ADDRESS和AXI_DMAC_REG_Y_LENGTH都写入0x0只使用一维传输。

四、adc_capture

adc_capture 接收信号,同时通过DMA将数据放到DDR
第一个参数:要接收的样本数
第二个参数:接收数据存储起始地址

先根据通道数将接收信号样本数转换为数据字节大小

adc_dma_write 通过AXI总线配置HDL axi_adc_dma IP核:
第一个参数:偏移地址,最终写到基地址CF_AD9361_RX_DMA_BASEADDR+ regAddr偏移地址
第二个参数:值,给特定地址传递的值

	adc_dma_write(AXI_DMAC_REG_CTRL, 0x0);//初始化DMA通道
	adc_dma_write(AXI_DMAC_REG_CTRL, AXI_DMAC_CTRL_ENABLE);//使能DMA通道
    adc_dma_write(AXI_DMAC_REG_IRQ_MASK, 0x0);

	adc_dma_read(AXI_DMAC_REG_TRANSFER_ID, &transfer_id);//读取下一次传输的ID号(5位)
	adc_dma_read(AXI_DMAC_REG_IRQ_PENDING, &reg_val);//读取中断状态
	adc_dma_write(AXI_DMAC_REG_IRQ_PENDING, reg_val);//写入中断状态寄存器
	adc_dma_write(AXI_DMAC_REG_DEST_ADDRESS, start_address);
	adc_dma_write(AXI_DMAC_REG_DEST_STRIDE, 0x0);
	adc_dma_write(AXI_DMAC_REG_X_LENGTH, length - 1);
	adc_dma_write(AXI_DMAC_REG_Y_LENGTH, 0x0);

	adc_dma_write(AXI_DMAC_REG_START_TRANSFER, 0x1);
  • AXI_DMAC_REG_IRQ_MASK:中断屏蔽寄存器,寄存器[1]位为EOT(End Of Transfer)IRQ,[0]位为SOT(Start Of Transfer) IRQ,哪一位置1,该位的中断请求被屏蔽
  • AXI_DMAC_REG_TRANSFER_ID:下一次传输的ID号
  • AXI_DMAC_REG_IRQ_PENDING:一次传输完成后 END_OF_TRANSFER 即 [1] 位会 置 1,一次传输加入队列后 START_OF_TRANSFER 即 [0] 位会 置 1
  • AXI_DMAC_REG_DEST_ADDRESS:目的地址
  • AXI_DMAC_REG_DEST_STRIDE:传输每行字节数
  • AXI_DMAC_REG_X_LENGTH:需要传输的数据的总字节数
  • AXI_DMAC_REG_Y_LENGTH:传输数据的行数
  • AXI_DMAC_REG_START_TRANSFER:写入0x1会将新的传输数据加入传输队列
/* Wait until the new transfer is queued. */
do {
	adc_dma_read(AXI_DMAC_REG_START_TRANSFER, &reg_val);
}
while(reg_val == 1);

等待,直到该新的传输加入传输队列开始传输,之前已经向AXI_DMAC_REG_START_TRANSFER写入了1,判断AXI_DMAC_REG_START_TRANSFER的值,若是1,表示新的传输仍然在排队,若是0,表示该传输已经开始。

/* Wait until the current transfer is completed. */
do {
	adc_dma_read(AXI_DMAC_REG_IRQ_PENDING, &reg_val);
}
while(reg_val != (IRQ_TRANSFER_QUEUED | IRQ_TRANSFER_COMPLETED));
adc_dma_write(AXI_DMAC_REG_IRQ_PENDING, reg_val);

等待,直到当前的传输完成。读取AXI_DMAC_REG_IRQ_PENDING的值,当传输进行时,AXI_DMAC_REG_IRQ_PENDING的[0]位SOT位始终为1,当传输完成时,[1]为EOT位由0置为1,因此,当AXI_DMAC_REG_IRQ_PENDING的值为0x3时,表示传输完成。

/* Wait until the transfer with the ID transfer_id is completed. */
do {
	adc_dma_read(AXI_DMAC_REG_TRANSFER_DONE, &reg_val);
}
while((reg_val & (1 << transfer_id)) != (1 << transfer_id));

等待,直到ID为transfer_id的传输完成,验证之前设置的传输已经完成。

五、Xil_DCacheInvalidateRange、Xil_DCacheFlushRange

PS和PL都在独立运行,PS通过DDR控制器来对DDR存储器进行访问,为了加速,常常将一些数据缓存(Cache),而且不是针对一个数据缓存,而是针对一批(Xilinx称为一行,即Line,一行长度为32)。

如果Cache里的数据如果发生了改变,不能迅速反映到DDR实际数据中,或者DDR中数据发生改变,不能迅速反映到Cache中。如当PL通过DMA修改了DDR数据,CPU可能还不知道数据已经改变,拿到的数据仍然是Cache中的没有改过的数据。

Xil_DCacheFlushRange就是把Cache里的数据Flush出去,清空Cache,将Cache的内容推到DDR中去。

Xil_DCacheInvalidateRange表示立即宣布Cache里的内容无效,需要从DDR中重新加载,即把数据从DDR中拉到Cache中来。保证Cache一致性

六、分析数据

uint16_t index;
uint32_t data;
uint16_t Q1;
uint16_t I1;

for(index =0; index < 1024; index += 1)
{
	data =Xil_In32(ADC_DDR_BASEADDR + index*4);
	Q1 = (data) & 0xFFFF;
	I1 = (data >> 16) & 0xFFFF;
	printf("%d,%d\n",(signed short)I1,(signed short)Q1);
}

利用 Xil_In32 从ADC_DDR_BASEADDR 地址读取数据,发送时I0在高16位,Q0在低16位,接收时IQ数据保持一致。

adc_capture采集16384个IQ数据,我们通过SDK自带的SDK Terminal串口助手,得到1024个将要分析的数据。
在这里插入图片描述
把打印出来的数据选中复制到data.txt文件中,在Matlab中分析
在这里插入图片描述

clc
clear all

%% 导入txt文件
H = importdata('data.txt');
% data=H.data;
data=H;

Fs=30.72e6;
FFT_N=1024;
complex_data=data(:,1)+1i*data(:,2);
H=fft(complex_data,FFT_N);
F_Hz=Fs/FFT_N*(-FFT_N/2+1:FFT_N/2);
F_amplitude=[abs(H(FFT_N/2+1:FFT_N))  ; abs(H(1:FFT_N/2) )]';

figure(1)
subplot(3,1,1)
plot(data(:,1))
title('I路时域信号')
subplot(3,1,2)
plot(data(:,2))
title('Q路时域信号')
subplot(3,1,3)
plot(F_Hz,F_amplitude);
title('I+jQ频谱')
  • 23
    点赞
  • 115
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值