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;
}
CPU的运行速度比存储器外部总线的频率高很多,对外部存储器的访问将使用几十甚至上百个CPU周期才能完成。一般通过高速缓存(CPU核对高速缓存的访问比对主存储器的访问快的多,其位于CPU核与主存储器之间,是对主存储器一部分内容的复制)加速程序运行。
zynq APU中,每个A9处理器都有独立的32KB L1指令高速缓存和32KB L1数据高速缓存。
主函数一开始:
// 启用指令缓存
Xil_ICacheEnable();
// 启用数据缓存
Xil_DCacheEnable();
主函数结束前需要:
// 禁用数据缓存
Xil_DCacheDisable();
// 禁用指令缓存
Xil_ICacheDisable();
// 用户根据板子来选择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 数据,并通过串口输出
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);
}
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;
AD9361 设备复位
// 先拉低gpio_resetb引脚,再拉高gpio_resetb引脚
gpio_set_value(phy->pdata->gpio_resetb, 0);
mdelay(1);
gpio_set_value(phy->pdata->gpio_resetb, 1);
通过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)函数注册并初始化系统时钟
// 通过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);
// 通过SPI 将AD9361的初始参数配置到设备中
ad9361_setup
// 通过AXI总线配置HDL axi_ad9361 IP核 ,并对9361 进行Digital tune、设置通道时钟和ENSM等操作
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中。
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, ®_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);
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
我们先看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偏移地址
第二个参数:值,给特定地址传递的值
ADI 官方的DMA传输支持2维数据传输(按行列传输),例子中使用一维传输,AXI_DMAC_REG_SRC_ADDRESS和AXI_DMAC_REG_Y_LENGTH都写入0x0只使用一维传输。
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, ®_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);
/* Wait until the new transfer is queued. */
do {
adc_dma_read(AXI_DMAC_REG_START_TRANSFER, ®_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, ®_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, ®_val);
}
while((reg_val & (1 << transfer_id)) != (1 << transfer_id));
等待,直到ID为transfer_id的传输完成,验证之前设置的传输已经完成。
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;
Fs=30.72e6;
FFT_N=1024;
complex_data=data(:,1)+1i*data(:,2);
H_I=fft(data(:,1),FFT_N);
H_Q=fft(data(:,2),FFT_N);
H=fft(complex_data,FFT_N);
F_Hz=Fs/FFT_N*(1:FFT_N/2);
F_amplitude_I=abs(H_I(1:FFT_N/2))';
F_amplitude_Q=abs(H_Q(1:FFT_N/2))';
F_amplitude=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频谱')