AD9361 TDD状态机分析与控制

最近,由于产品开发需要,我们一直在公司自研的软件无线电平台SDR-A1上调试AD9361,这款硬件平台采用的是Xilinx的XC7A100T FPGA,配合两个MCU,并且具备以太网PHY接口,其硬件架构如下图所示。

首先要完成的自然是AD9361的配置,我们将ADI官方的no-OS软件移植到了STM32单片机上,并使用FPGA进行逻辑电平转换(为了省掉一颗逻辑电平转换芯片),很顺利就完成了。然后是数据接口,由于本产品目标应用是TDD 2T2R模式,同时考虑到尽量支持较高的数据速率,所以选择了双端口TDD模式,也很容易就调通了。但是,在接下来的AD9361 TDD状态机调试中,就遇到了较多的问题。在ADI官方文档《AD9361 Enable State Machine Guide》中,我们可以看到如下的两张示意图,Figure 2是脉冲模式,Figure 3是电平模式,我们选择了电平模式。在电平模式下,文档中这样描述:Enable引脚控制着ENSM的状态,Enable的下降沿将AD9361移动至Alert状态,TXNRX引脚必须在Alert状态下进行拉高或者拉低,然后Enable的上升沿将AD9361移动至TX状态(TXNRN为高电平),或者RX状态(TXNRX为低电平)。如果寄存器0x014中TO_ALERT为0,那么AD9361会进入Wait状态。我们在单片机串口命令行中通过register?0x014命令确认,TO_ALERT为1,也就是说AD9361不会进入Wait状态,正常情况下只会按照如下情况跳转:

……TX->Alert->RX-Alert->TX……

按照上述逻辑,我们编写了相应的VHDL代码,并且通过AD9361的GPO观察AD9361的状态:GPO0拉高表示AD9361进入Alert或者Wait状态,GPO1拉高表示AD9361进入RX状态,GPO2拉高表示AD9361进入TX状态。同时使用一个按键作为TX、RX的切换控制信号,结果发现每次TX、RX状态改变时,AD9361总是进入Alert状态,无法进入预期状态。反复仿真与分析,没有发现VHDL代码的问题。然而仔细观察Figure 3,发现图中并没有给出AD9361需要在Alert状态下停留的时间,问题很可能就出在这里。

继续查看《AD9361 Enable State Machine Guide》,发现有一段关于状态机与VCO校准的描述,文档中写道:TDD模式下,频率综合器不总是处于锁定状态,当AD9361处于RX状态下时,TX的频率综合器是关闭的(为了省电);当AD9631处于TX状态下时,RX的频率综合器时关闭的。在TDD模式下,当TXNRX信号发生变化时,ENSM都会重新校准VCO。例如,在Alert状态下,TXNRX信号由0(RX)变为1(TX),TX频率综合器会加电,在达到TX Load Synth(0x025寄存器,其值一定为0x02,文档中没有给出更详细的说明)中设定的加电延迟时间后,ENSM会给VCO一个校准TX VCO的信号,可想而知,必然是TX VCO校准完成后,AD9361才能进入TX状态,同理,TXNRX信号由1(TX)变为0(RX)必然是RX VCO校准完成后,AD9361才能进入RX状态。

顺着这个思路,查看UG-570中关于VCO校准的说明,文档中写道:在TDD模式下,RX频率综合器仅在TXNRX为低电平时使能,TX频率综合器仅在TXNRX为高电平时使能,每次TX VCO或者RX VCO加电都会重新校准,这与《AD9361 Enable State Machine Guide》中的描述是相符的。文档中给出了VCO校准时间的计算式:

UG-570中还写道:如果需要频率综合器快速锁定,VCO校准是可以禁用的。当VCO校准完成时,TX PLL与RX PLL锁定信号可以通过Control Output引脚读取。我们首先尝试禁用VCO校准,将no-OS代码中AD9361初始化参数tdd_skip_vco_cal_enable由默认值由0改为1,编译并将二进制文件下载至STM32单片机中,再次使用按键作为TX、RX的切换控制信号,发现AD9361可以正确地在Tx、Rx、Alert状态之间来回切换,这也说明了VHDL代码的逻辑是正确的。这样一来,在原有VHDL代码基础上,将TX PLL或者RX PLL锁定状态考虑进来(通过Control Output引脚读取),就可以正确地配置AD9361的状态机。按照这个思路,我们修改了VHDL代码,并且将tdd_skip_vco_cal_enable改为默认值0,重新编译、下载、测试,终于可以使用按键正确地控制AD9361的TX、RX状态了。

相应的VHDL代码如下,仿真与实际测试都能通过,其中的TXON信号来自于外部输入,MAC或者按键都可以。


TDD_CTRL_001: process (RX_CLK)
begin
	if rising_edge(RX_CLK) then
	   if (MCU_CFG_DONE = '0') then
	       TXON_D <= '0';
	   elsif (MCU_CFG_DONE = '1') then
	       TXON_D <= TXON;
       end if;
    end if;   
end process;

TR_STATE_CHANGED <= '1' when (TXON_D /= TXON) else '0';

TDD_CTRL_002: if TDD_SKIP_VCO_CAL = '0' generate 
process (RX_CLK)
begin
	if rising_edge(RX_CLK) then
	   if (MCU_CFG_DONE = '0') then
	       TXEN <= '0';
	       ENABLE <= '0';
	       TDD_CNT <= (others => '1');
	   elsif (TR_STATE_CHANGED = '1') then
	       TDD_CNT <= (others => '0');
	       ENABLE <= '0';                  -- to alert, 1 clock delay
	   elsif (TDD_CNT < "1000" ) then
	       TDD_CNT <= TDD_CNT + 1;
	   elsif (TDD_CNT >= "1000" and  TDD_CNT < "1111") then
	       TDD_CNT <= TDD_CNT + 1;
	       TXEN <= TXON;                   -- make sure TXEN is changed in alert state
	   else
	       if (TXON = '1') then
	       ENABLE <= TX_PLL_LOCKED;        -- wait for tx pll lock
	       else
	       ENABLE <= RX_PLL_LOCKED;        -- wait for rx pll lock
	       end if;
	       TXEN <= TXON;
	   end if;   
	end if;    
end process;
end generate;

AD_PLL_LOCK_DET: process (RX_CLK)
begin
	if rising_edge(RX_CLK) then
	   if (MCU_CFG_DONE = '0') then
	       TX_PLL_LOCKED <= '0';
	       RX_PLL_LOCKED <= '0';
	       BB_PLL_LOCKED <= '0';
	   else
	       TX_PLL_LOCKED <= CTRL_OUT(7);
	       RX_PLL_LOCKED <= CTRL_OUT(6);
	       BB_PLL_LOCKED <= CTRL_OUT(5);
	   end if;
    end if;    
end process;