将OpenWIFI移植到ADRV9002

Dec. 30, 2024

OpenWIFI是一套基于FPGA SDR的开源WIFI协议平台。

OpenWIFI的优势在于可以根据需要充分的定制,都够便利地实现一般WIFI网卡不具备的功能,所以在很多基于WIFI的研究中,会选择在OpenWIFI的平台上做, 比较常见的就是利用CSI进行环境探测, 另外就是把OpenWIFI作为WIFI探测设备使用,通过OpenWIFI可以直接提取IQ流,更可以直接在FPGA内计算

为了让OpenWIFI有更好的性能,我选择尝试将OpenWIFI从AD9361移植到ADRV9002上。这里实际用的硬件是一块搭载XZCU15EG芯片的开发板(天嵌通途TQ15EG)+ADRV9002的官方验证板ADRV9002BBZC。开发板是参考ZCU102设计的,它的FMC0和ZCU102完全兼容。

openwif架构图

这其中FPGA的部分相对简单,ADI提供的ADRV9002和AD9361的IP核接口比较相似,可以按照同样的方式连接。

主要工作:驱动上(sdr.c)将AD9361的操作替换为ADRV9002 2. ADRV9002和AD9361的增益表不同,ADRV9002通过PIN输出的AGC值的格式和AD9361也不一样,需要重新对齐。

  1. 采样率的问题,OpenWIFI原本是将20M调制后的IQ再插值到40M,AD9361使用40M采样率,用内置的FIR做差值滤波,实际出来的带宽还是20M。

    这里可以选择不做插值改为20M采样率,或保持40M采样率并用ADRV9002的FIR做差值滤波。

  2. 因为器件改变了,原本根据AD9361配置的一些如信号强度(RSSI)门限也需要调整,比如退避和OFDM检测都有相关的门限值, 理论上如果能够对齐ADRV9002和AD9361的增益,这些值应该不用改,但后来发现还是需要重新调整的。

  3. 除了信号强度门限值,一些计数器的超时值也需要调整。

一、工程迁移

首先需要搭建起OpenWIFI IP+ADRV9002的HDL工程,原本OpenWIFI是在ADI提供的HDL工程上增加OpenWIFI的内容,所以这里也按照相同的步骤。

openwifi使用的Vivado版本是2021,对应的ADI的HDL版本是2021_R1。

从ZCU102+FMCS2开始

选择和要迁移的硬件最相似的工程,也就是zcu102_fmcs2,Xilinx ZCU102 board + FMCOMMS2/3/4,先按照官方的步骤构建这个平台的工程,然后再往其他硬件上迁移。

从ZCU102到新的开发板

首先先把ZCU102迁移到新的开发板,然后用同样的FMCS2测试一下。

在工程里Project device修改芯片型号,这里是xczu15eg-ffvb1156-2-i,然后根据提示更新IP核。

两个板子的FMC0的FMC是兼容的,所以FMC部分的约束文件不用动,而其他的也只是一些DEBUG用的信号,这里仅仅将openwifi输出的几个LED改成新开发板上的LED。

设备树

openwifi里提供的设备树是把ADI的Kuipler镜像里的对应工程的设备树反编译出来后又修改的,不适合直接使用,这里还是自己重新生成设备树比较好。

从Vivado工程生成设备树的方法:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842279/Build+Device+Tree+Blob,不过我习惯用Petalinux里创建的设备树,因为Petalinux能顺便把我们后面要用到的uboot也打包出来。

生成基础的设备树之后,还要把AD9361和openwifi的信息添加进去,AD9361的设备树可以在ADI的内核里找到:arch/arm64/boot/dts/xilinx/adi-fmcomms2.dtsi,可以include或者干脆全粘贴进去。

#include <dt-bindings/interrupt-controller/irq.h>


#define fmc_spi spi0


/ {
	clocks {
		ad9361_clkin: clock@0 {
			compatible = "fixed-clock";

			clock-frequency = <40000000>;
			clock-output-names = "ad9361_ext_refclk";
			#clock-cells = <0>;
		};
	};
};


&fmc_spi {
	adc0_ad9361: ad9361-phy@0 {
		compatible = "adi,ad9361";
		reg = <0>;

		/* SPI Setup */
		spi-cpha;
		spi-max-frequency = <10000000>;

		/* Clocks */
		clocks = <&ad9361_clkin 0>;
		clock-names = "ad9361_ext_refclk";
		clock-output-names = "rx_sampl_clk", "tx_sampl_clk";
		#clock-cells = <1>;

		//adi,debug-mode-enable;
		/* Digital Interface Control */

		 /* adi,digital-interface-tune-skip-mode:
		  * 0 = TUNE RX&TX
		  * 1 = SKIP TX
		  * 2 = SKIP ALL
		  */
		adi,digital-interface-tune-skip-mode = <0>;

		adi,pp-tx-swap-enable;
		adi,pp-rx-swap-enable;
		adi,rx-frame-pulse-mode-enable;
		adi,lvds-mode-enable;
		adi,lvds-bias-mV = <150>;
		adi,lvds-rx-onchip-termination-enable;
		adi,rx-data-delay = <4>;
		adi,tx-fb-clock-delay = <7>;

		//adi,fdd-rx-rate-2tx-enable;

		adi,dcxo-coarse-and-fine-tune = <8 5920>;
		//adi,xo-disable-use-ext-refclk-enable;

		/* Mode Setup */

		adi,2rx-2tx-mode-enable;
		//adi,split-gain-table-mode-enable;

		/* ENSM Mode */
		adi,frequency-division-duplex-mode-enable;
		//adi,ensm-enable-pin-pulse-mode-enable;
		//adi,ensm-enable-txnrx-control-enable;


		/* adi,rx-rf-port-input-select:
		 * 0 = (RX1A_N &  RX1A_P) and (RX2A_N & RX2A_P) enabled; balanced
		 * 1 = (RX1B_N &  RX1B_P) and (RX2B_N & RX2B_P) enabled; balanced
		 * 2 = (RX1C_N &  RX1C_P) and (RX2C_N & RX2C_P) enabled; balanced
		 *
		 * 3 = RX1A_N and RX2A_N enabled; unbalanced
		 * 4 = RX1A_P and RX2A_P enabled; unbalanced
		 * 5 = RX1B_N and RX2B_N enabled; unbalanced
		 * 6 = RX1B_P and RX2B_P enabled; unbalanced
		 * 7 = RX1C_N and RX2C_N enabled; unbalanced
		 * 8 = RX1C_P and RX2C_P enabled; unbalanced
		 */

		adi,rx-rf-port-input-select = <0>; /* (RX1A_N &  RX1A_P) and (RX2A_N & RX2A_P) enabled; balanced */

		/* adi,tx-rf-port-input-select:
		 * 0	TX1A, TX2A
		 * 1	TX1B, TX2B
		 */

		adi,tx-rf-port-input-select = <0>; /* TX1A, TX2A */
		//adi,update-tx-gain-in-alert-enable;
		adi,tx-attenuation-mdB = <10000>;
		adi,tx-lo-powerdown-managed-enable;

		adi,rf-rx-bandwidth-hz = <18000000>;
		adi,rf-tx-bandwidth-hz = <18000000>;
		adi,rx-synthesizer-frequency-hz = /bits/ 64 <2400000000>;
		adi,tx-synthesizer-frequency-hz = /bits/ 64 <2450000000>;

		/*				BBPLL     ADC        R2CLK     R1CLK    CLKRF    RSAMPL  */
		adi,rx-path-clock-frequencies = <983040000 245760000 122880000 61440000 30720000 30720000>;
		/*				BBPLL     DAC        T2CLK     T1CLK    CLKTF    TSAMPL  */
		adi,tx-path-clock-frequencies = <983040000 122880000 122880000 61440000 30720000 30720000>;

		/* Gain Control */

		//adi,gaintable-name = "ad9361_std_gaintable";

		/* adi,gc-rx[1|2]-mode:
		 * 0 = RF_GAIN_MGC
		 * 1 = RF_GAIN_FASTATTACK_AGC
		 * 2 = RF_GAIN_SLOWATTACK_AGC
		 * 3 = RF_GAIN_HYBRID_AGC
		 */

		adi,gc-rx1-mode = <2>;
		adi,gc-rx2-mode = <2>;
		adi,gc-adc-ovr-sample-size = <4>; /* sum 4 samples */
		adi,gc-adc-small-overload-thresh = <47>; /* sum of squares */
		adi,gc-adc-large-overload-thresh = <58>; /* sum of squares */
		adi,gc-lmt-overload-high-thresh = <800>; /* mV */
		adi,gc-lmt-overload-low-thresh = <704>; /* mV */
		adi,gc-dec-pow-measurement-duration = <8192>; /* 0..524288 Samples */
		adi,gc-low-power-thresh = <24>; /* 0..-64 dBFS vals are set pos */
		//adi,gc-dig-gain-enable;
		//adi,gc-max-dig-gain = <15>;

		/* Manual Gain Control Setup */

		//adi,mgc-rx1-ctrl-inp-enable; /* uncomment to use ctrl inputs */
		//adi,mgc-rx2-ctrl-inp-enable; /* uncomment to use ctrl inputs */
		adi,mgc-inc-gain-step = <2>;
		adi,mgc-dec-gain-step = <2>;

		/* adi,mgc-split-table-ctrl-inp-gain-mode:
		 * (relevant if adi,split-gain-table-mode-enable is set)
		 * 0 = AGC determine this
		 * 1 = only in LPF
		 * 2 = only in LMT
		 */

		adi,mgc-split-table-ctrl-inp-gain-mode = <0>;

		/* Automatic Gain Control Setup */

		adi,agc-attack-delay-extra-margin-us= <1>; /* us */
		adi,agc-outer-thresh-high = <5>; /* -dBFS */
		adi,agc-outer-thresh-high-dec-steps = <2>; /* 0..15 */
		adi,agc-inner-thresh-high = <10>; /* -dBFS */
		adi,agc-inner-thresh-high-dec-steps = <1>; /* 0..7 */
		adi,agc-inner-thresh-low = <12>; /* -dBFS */
		adi,agc-inner-thresh-low-inc-steps = <1>; /* 0..7 */
		adi,agc-outer-thresh-low = <18>; /* -dBFS */
		adi,agc-outer-thresh-low-inc-steps = <2>; /* 0..15 */

		adi,agc-adc-small-overload-exceed-counter = <10>; /* 0..15 */
		adi,agc-adc-large-overload-exceed-counter = <10>; /* 0..15 */
		adi,agc-adc-large-overload-inc-steps = <2>; /* 0..15 */
		//adi,agc-adc-lmt-small-overload-prevent-gain-inc-enable;
		adi,agc-lmt-overload-large-exceed-counter = <10>; /* 0..15 */
		adi,agc-lmt-overload-small-exceed-counter = <10>; /* 0..15 */
		adi,agc-lmt-overload-large-inc-steps = <2>; /* 0..7 */
		//adi,agc-dig-saturation-exceed-counter = <3>; /* 0..15 */
		//adi,agc-dig-gain-step-size = <4>; /* 1..8 */

		//adi,agc-sync-for-gain-counter-enable;
		adi,agc-gain-update-interval-us = <1000>;  /* 1ms */
		//adi,agc-immed-gain-change-if-large-adc-overload-enable;
		//adi,agc-immed-gain-change-if-large-lmt-overload-enable;

		/* Fast AGC */

		adi,fagc-dec-pow-measurement-duration = <64>; /* 64 Samples */
                //adi,fagc-allow-agc-gain-increase-enable;
                adi,fagc-lp-thresh-increment-steps = <1>;
                adi,fagc-lp-thresh-increment-time = <5>;

                adi,fagc-energy-lost-stronger-sig-gain-lock-exit-cnt = <8>;
                adi,fagc-final-overrange-count = <3>;
                //adi,fagc-gain-increase-after-gain-lock-enable;
                adi,fagc-gain-index-type-after-exit-rx-mode = <0>;
                adi,fagc-lmt-final-settling-steps = <1>;
                adi,fagc-lock-level = <10>;
                adi,fagc-lock-level-gain-increase-upper-limit = <5>;
                adi,fagc-lock-level-lmt-gain-increase-enable;

                adi,fagc-lpf-final-settling-steps = <1>;
                adi,fagc-optimized-gain-offset = <5>;
                adi,fagc-power-measurement-duration-in-state5 = <64>;
                adi,fagc-rst-gla-engergy-lost-goto-optim-gain-enable;
                adi,fagc-rst-gla-engergy-lost-sig-thresh-below-ll = <10>;
                adi,fagc-rst-gla-engergy-lost-sig-thresh-exceeded-enable;
                adi,fagc-rst-gla-if-en-agc-pulled-high-mode = <0>;
                adi,fagc-rst-gla-large-adc-overload-enable;
                adi,fagc-rst-gla-large-lmt-overload-enable;
                adi,fagc-rst-gla-stronger-sig-thresh-above-ll = <10>;
                adi,fagc-rst-gla-stronger-sig-thresh-exceeded-enable;
                adi,fagc-state-wait-time-ns = <260>;
                adi,fagc-use-last-lock-level-for-set-gain-enable;

		/* RSSI */

		/* adi,rssi-restart-mode:
		 * 0 = AGC_IN_FAST_ATTACK_MODE_LOCKS_THE_GAIN,
		 * 1 = EN_AGC_PIN_IS_PULLED_HIGH,
		 * 2 = ENTERS_RX_MODE,
		 * 3 = GAIN_CHANGE_OCCURS,
		 * 4 = SPI_WRITE_TO_REGISTER,
		 * 5 = GAIN_CHANGE_OCCURS_OR_EN_AGC_PIN_PULLED_HIGH,
		 */
		adi,rssi-restart-mode = <3>;
		//adi,rssi-unit-is-rx-samples-enable;
		adi,rssi-delay = <1>; /* 1us */
		adi,rssi-wait = <1>; /* 1us */
		adi,rssi-duration = <1000>; /* 1ms */

		/* Control Outputs */
		adi,ctrl-outs-index = <0>;
		adi,ctrl-outs-enable-mask = <0xFF>;

		/* AuxADC Temp Sense Control */

		adi,temp-sense-measurement-interval-ms = <1000>;
		adi,temp-sense-offset-signed = <0xCE>;
		adi,temp-sense-periodic-measurement-enable;

		/* AuxDAC Control */

		adi,aux-dac-manual-mode-enable;

		adi,aux-dac1-default-value-mV = <0>;
		//adi,aux-dac1-active-in-rx-enable;
		//adi,aux-dac1-active-in-tx-enable;
		//adi,aux-dac1-active-in-alert-enable;
		adi,aux-dac1-rx-delay-us = <0>;
		adi,aux-dac1-tx-delay-us = <0>;

		adi,aux-dac2-default-value-mV = <0>;
		//adi,aux-dac2-active-in-rx-enable;
		//adi,aux-dac2-active-in-tx-enable;
		//adi,aux-dac2-active-in-alert-enable;
		adi,aux-dac2-rx-delay-us = <0>;
		adi,aux-dac2-tx-delay-us = <0>;
	};
};


&spi0 {
	status = "okay";
};


// #include "adi-fmcomms2.dtsi"

&adc0_ad9361 {
	en_agc-gpios = <&gpio 122 0>;
	sync-gpios = <&gpio 123 0>;
	reset-gpios = <&gpio 124 0>;
	enable-gpios = <&gpio 125 0>;
	txnrx-gpios = <&gpio 126 0>;
};


 
/ {

	fpga-axi@0 {
		interrupt-parent = <0x04>;
		compatible = "simple-bus";
		#address-cells = <0x01>;
		#size-cells = <0x01>;
		ranges = <0x00 0x00 0x00 0xffffffff>;
		phandle = <0xac>;

		// dma@9c400000 {
		// 	compatible = "adi,axi-dmac-1.00.a";
		// 	reg = <0x9c400000 0x10000>;
		// 	#dma-cells = <0x01>;
		// 	#clock-cells = <0x00>;
		// 	interrupts = <0x00 0x6d 0x04>;
		// 	clocks = <0x03 0x47>;
		// 	phandle = <0x3c>;

		// 	adi,channels {
		// 		#size-cells = <0x00>;
		// 		#address-cells = <0x01>;

		// 		dma-channel@0 {
		// 			reg = <0x00>;
		// 			adi,source-bus-width = <0x40>;
		// 			adi,source-bus-type = <0x02>;
		// 			adi,destination-bus-width = <0x40>;
		// 			adi,destination-bus-type = <0x00>;
		// 		};
		// 	};
		// };

		// dma@9c420000 {
		// 	compatible = "adi,axi-dmac-1.00.a";
		// 	reg = <0x9c420000 0x10000>;
		// 	#dma-cells = <0x01>;
		// 	#clock-cells = <0x00>;
		// 	interrupts = <0x00 0x6c 0x04>;
		// 	clocks = <0x03 0x47>;
		// 	phandle = <0x3e>;

		// 	adi,channels {
		// 		#size-cells = <0x00>;
		// 		#address-cells = <0x01>;

		// 		dma-channel@0 {
		// 			reg = <0x00>;
		// 			adi,source-bus-width = <0x40>;
		// 			adi,source-bus-type = <0x00>;
		// 			adi,destination-bus-width = <0x40>;
		// 			adi,destination-bus-type = <0x02>;
		// 		};
		// 	};
		// };

		sdr: sdr {
			compatible ="sdr,sdr";
			dmas = <&openwifi_ip_axi_dma_1 1
					&openwifi_ip_axi_dma_0 0>;
			dma-names = "rx_dma_s2mm", "tx_dma_mm2s";
			interrupt-names = "not_valid_anymore", "rx_pkt_intr", "tx_itrpt_useless", "tx_itrpt";
			interrupts = <0 89 1 0 90 1 0 93 1 0 94 1>;
		} ;

		axidmatest_1: axidmatest@1 {
			compatible ="xlnx,axi-dma-test-1.00.a";
			dmas = <&openwifi_ip_axi_dma_1 0
				    &openwifi_ip_axi_dma_0 1>;
			dma-names = "axidma0", "axidma1";
		} ;

		openwifi_ip_axi_bram_ctrl_0: axi_bram_ctrl@b0000000 {
			clock-names = "s_axi_aclk";
			clocks = <0x3 0x49>;
			compatible = "xlnx,axi-bram-ctrl-4.1";
			reg = <0x0 0xb0000000 0x0 0x80000>;
			xlnx,bram-addr-width = <0x10>;
			xlnx,bram-inst-mode = "EXTERNAL";
			xlnx,ecc = <0x0>;
			xlnx,ecc-onoff-reset-value = <0x0>;
			xlnx,ecc-type = <0x0>;
			xlnx,fault-inject = <0x0>;
			xlnx,memory-depth = <0x10000>;
			xlnx,rd-cmd-optimization = <0x1>;
			xlnx,read-latency = <0x1>;
			xlnx,s-axi-ctrl-addr-width = <0x20>;
			xlnx,s-axi-ctrl-data-width = <0x20>;
			xlnx,s-axi-id-width = <0x10>;
			xlnx,s-axi-supports-narrow-burst = <0x1>;
			xlnx,single-port-bram = <0x1>;
		};

		// tx_dma: dma@a0000000 {
		// 	#dma-cells = <1>;
		// 	clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
		// 	clocks = <0x3 0x49>, <0x3 0x49>, <0x3 0x49>, <0x3 0x49>;
		// 	compatible = "xlnx,axi-dma-1.00.a";
		// 	interrupt-names = "mm2s_introut", "s2mm_introut";
		// 	interrupts = <0 95 4 0 96 4>;
		// 	reg = <0xA0000000 0x10000>;
		// 	xlnx,addrwidth = <0x28>;
		// 	xlnx,include-sg ;
		// 	xlnx,sg-length-width = <0xe>;
		// 	dma-channel@a0000000 {
		// 		compatible = "xlnx,axi-dma-mm2s-channel";
		// 		dma-channels = <0x1>;
		// 		interrupts = <0 95 4>;
		// 		xlnx,datawidth = <0x40>;
		// 		xlnx,device-id = <0x0>;
		// 	};
		// 	dma-channel@A0000030 {
		// 		compatible = "xlnx,axi-dma-s2mm-channel";
		// 		dma-channels = <0x1>;
		// 		interrupts = <0 96 4>;
		// 		xlnx,datawidth = <0x40>;
		// 		xlnx,device-id = <0x0>;
		// 	};
		// };
		
		// rx_dma: dma@a0010000 {
		// 	#dma-cells = <1>;
		// 	clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
		// 	clocks = <0x3 0x49>, <0x3 0x49>, <0x3 0x49>, <0x3 0x49>;
		// 	compatible = "xlnx,axi-dma-1.00.a";
		// 	//dma-coherent ;
		// 	interrupt-names = "mm2s_introut", "s2mm_introut";
		// 	interrupts = <0 91 4 0 92 4>;
		// 	reg = <0xa0010000 0x10000>;
		// 	xlnx,addrwidth = <0x28>;
		// 	xlnx,include-sg ;
		// 	xlnx,sg-length-width = <0xe>;
		// 	dma-channel@a0010000 {
		// 		compatible = "xlnx,axi-dma-mm2s-channel";
		// 		dma-channels = <0x1>;
		// 		interrupts = <0 91 4>;
		// 		xlnx,datawidth = <0x40>;
		// 		xlnx,device-id = <0x1>;
		// 	};
		// 	dma-channel@A0001030 {
		// 		compatible = "xlnx,axi-dma-s2mm-channel";
		// 		dma-channels = <0x1>;
		// 		interrupts = <0 92 4>;
		// 		xlnx,datawidth = <0x40>;
		// 		xlnx,device-id = <0x1>;
		// 	};
		// };

		tx_intf_0: tx_intf@a0060000 {
			clock-names = "s00_axi_aclk", "s00_axis_aclk";//, "s01_axis_aclk", "m00_axis_aclk";
			clocks = <0x3 0x49>, <0x3 0x49>;//, <0x3 0x49>, <0x3 0x49>;
			compatible = "sdr,tx_intf";
			interrupt-names = "tx_itrpt";
			interrupts = <0 94 1>;
			reg = <0xa0060000 0x10000>;
			xlnx,s00-axi-addr-width = <0x7>;
			xlnx,s00-axi-data-width = <0x20>;
		};

		rx_intf_0: rx_intf@a0040000 {
			clock-names = "s00_axi_aclk", "m00_axis_aclk";//, "s00_axis_aclk";
			clocks = <0x3 0x49>, <0x3 0x49>;//, <0x3 0x49>;
			compatible = "sdr,rx_intf";
			interrupt-names = "not_valid_anymore", "rx_pkt_intr";
			interrupts = <0 89 1 0 90 1>;
			reg = <0xa0040000 0x10000>;
			xlnx,s00-axi-addr-width = <0x7>;
			xlnx,s00-axi-data-width = <0x20>;
		};

		openofdm_tx_0: openofdm_tx@a0030000 {
			clock-names = "clk";
			clocks = <0x3 0x49>;
			compatible = "sdr,openofdm_tx";
			reg = <0xa0030000 0x10000>;
		};

		openofdm_rx_0: openofdm_rx@a0020000 {
			clock-names = "clk";
			clocks = <0x3 0x49>;
			compatible = "sdr,openofdm_rx";
			reg = <0xa0020000 0x10000>;
		};

		xpu_0: xpu@a0070000 {
			clock-names = "s00_axi_aclk";
			clocks = <0x3 0x49>;
			compatible = "sdr,xpu";
			reg = <0xa0070000 0x10000>;
		};

		side_ch_0: side_ch@a0050000 {
			clock-names = "s00_axi_aclk";
			clocks = <0x3 0x49>;
			compatible = "sdr,side_ch";
			reg = <0xa0050000 0x10000>;
			dmas = <&openwifi_ip_axi_dma_1 0
					&openwifi_ip_axi_dma_0 1>;
			dma-names = "rx_dma_mm2s", "tx_dma_s2mm";
		};

		cf-ad9361-lpc@99020000 {
			compatible = "adi,axi-ad9361-6.00.a";
			reg = <0x99020000 0x6000>;
			// dmas = <0x3c 0x00>;
			// dma-names = "rx";
			spibus-connected = <0x3d>;
			phandle = <0xad>;
		};

		cf-ad9361-dds-core-lpc@99024000 {
			compatible = "adi,axi-ad9361-dds-6.00.a";
			reg = <0x99024000 0x1000>;
			clocks = <0x3d 0x0d>;arch/arm64/boot/dts/xilinx/adi-fmcomms2.dtsi
			clock-names = "sampl_clk";
			// dmas = <0x3e 0x00>;
			// dma-names = "tx";
			phandle = <0xae>;
		};

		// axi-sysid-0@85000000 {
		// 	compatible = "adi,axi-sysid-1.00.a";
		// 	reg = <0x85000000 0x10000>;
		// 	phandle = <0xaf>;
		// };
	};
};

在ADI的内核下编译设备树。

uboot

我使用petalinux打包出来的UBOOT,替换掉Kuipler镜像里的。

但是我一直遇到一个问题,Petalinux直接打包出来的UBOOT,包括WIC镜像,放到SD卡上都不能直接启动,因为总是少一个boot.scr,这个文件在Petalinux工程的image/linux下可以找到。

启动

替换openwif镜像里的uboot和设备树,启动之后按照openwifi的步骤启动。

从AD9361到ADRV9002

接下来是到ADRV9002的迁移,比较简单的方法是先用ADI HDL里的ZCU102+ADRV9002的工程,然后改变器件型号、修改约束、添加openwifi。

按照ADI的步骤构建ZCU102+ADRV9002的Vivado工程:https://wiki.analog.com/resources/eval/user-guides/adrv9002/quickstart/zynqmp

把之前ZCU102+FMCS2的openwifi工程下的ip_repo拷贝一份,添加到工程的IP Repository里。

之后,从先前的openwifi的Vivado工程里,把Block Design的openwifi部分导出来:

write_bd_tcl -hier_blks [get_bd_cells /openwifi_ip] <some path>/exported_openwifi_block.tcl

然后再导入到ZCU102+ADRV9002的工程里:

source  <some path>/exported_openwifi_block.tcl

到现在工程上的迁移完成了,之后的主要内容就是把原本AD9361接口连接到ADRV9001的IP上。

时钟

原工程的100M系统时钟实际上是从AD9361的CLK_I生成的,虽然rx_intf和tx_intf里把这个系统时钟和ADC的时钟视为两个时钟域,还增加了FIFO跨时钟域,实际上两个时钟还是一个时钟域。

因为收发的采样频率是一样的,因此

虽然在迁移的时候我希望让系统时钟来自PS,这样可以可以避免在配置ADRV9002的时候,影响其他模块的运行,但是测试了一下发现还是有问题,只能留待之后解决了。

在原工程里,AD9361的采样频率是40M,数据流是20M,接收和发送前分别做了2倍插值和抽样,在AD9361上配置FIR滤波器,将展宽的频率滤掉。这样应该是为了让频谱的两边更陡峭?

前端控制

原本openwif为了降低TX的本振影响接收,因此只在需要TX的时候让本振工作,在驱动初始化完成之后,把控制AD9361的SPI移交给了PL,也就是xpu上的SPI接口,PL在有数据需要发送的时候,就通过SPI打开载频,不需要的时候就关掉。

因为ADRV9002提供了直接控制前端开关的PIN,所以不需要再像上面这样周折,这里可以修改xpu把里面指示TX的信号拉出来,SPI的接口就不用了。

当然这样也会引发一些问题,后面会涉及。

AGC

OpenWIFI里是通过设定AD9361上和ControlOutput相关的Pin输出AGC增益值,ADRV9002也提供了类似的方式,ADRV9002上有10个DGPIO,可以通过配置DGPIO输出当前AGC增益值。

还有一点,AD9361的AGC增益步进是1dB,ADRV9002的AGC增益步进是0.5dB,openwifi内计算RSSI数值的单位是0.5dB,这样需要修改一下xpu叠加信号RSSI数值和AGC增益数值部分。

二、驱动移植

除了sdr.c之外,其他的模块基本都不用修改,因为它们只和PL有关。

原来驱动工程的编译有些让人迷惑,仔细看了看我又把包含头文件的宏重新加进去。

openwifi驱动部分(sdr.c)

软件部分只涉及初始的配置,Linux里面802.11传输层已经是一套很完整的系统了,这里的驱动主要是把上层来的MAC帧传进PL,让PL调制发送出去,另外就是频率切换也是由上层下发的,根据上层给的频段配置收发器,

这里就是把AD9361的API调用换成ADRV9002,

ADRV9002需要配置:

因为原本AD9361用了在发送时把20M的信号插值成40M,需要配置FIR,这里因为直接20M输出不再插值,就免去了FIR的配置。(ADRV9002因为支持多Profile,FIR配置也变得复杂了,不必自找麻烦)

使用AD9361时,可以直接从IIO的节点配置AGC模式为Fast Attack,而ADRV9002则是将超越门限的计数器配置为ByPass以工作为Fast Attack。

原本上层驱动配置完AD9361后,会让FPGA用SPI去控制AD9361 DAC的前端开关,因此配置ADRV9002的TX为PIN控制。

以及调试过程中发现一直收不到ACK,也是因为等待ACK的计数器超时,花了很长时间DEBUG才追溯到这儿,

三、调试

参数调试

最后还要试一下参数,因为器件变化,涉及增益和时序部分都会变化。

测试办法:用另一台正常运行openwifi的设备(LibreSDR)用openwifi提供的inject_80211程序互相发送帧,首先通过ILA看信号是不是正确解码,如果解码是对的,再看能不能收到对方ACK、收到ACK后能不能正确识别。

测试时需要调到一个2.4G/5.8G以外的频率。

./sdrctl dev sdr0 set reg rf 1 2200		# 接收频率2200MHz
./sdrctl dev sdr0 set reg rf 5 2200		# 发送频率2200MHz

./sdrctl dev sdr0 set reg drv_rx 7 3	# 驱动输出RX调试信息
./sdrctl dev sdr0 set reg drv_tx 7 3	# 驱动输出TX调试信息
  1. 验证ADC采集波形是否正确,因为ADRV9002的IP核配置不对的话,出来的数据不正常。可以用ILA把IQ导出来,用MATLAB的Wireless Waveform Analyzer解,能解出来说明信号是对的。
  2. 查看openofdm_rx的解调状态,如果解调成功,fcs_ok应该有1的信号,

能够成果接受和发送ACK是两个设备之间通信的关键,openwifi里的tx_intf在发送一个物理帧之后,还会向上层传递一个中断,也就是下面的openwifi_tx_interrupt,后面信息的pass就表示刚刚发出去的这一帧有没有收到ACK,

[ 1127.928551] sdr,sdr openwifi_tx: 92B RC8 65M FC00b0 DI0000 ADDR6655443322ac/665544332260/6655443322ac flag40010099 QoSd9 SC2145_0 retr0 ack1 prio0 q0 wr10 rd9
[ 1127.943091] sdr,sdr openwifi_tx_interrupt: tx_result [nof_retx 1 pass 0] SC2145 prio0 q0 wr11 rd10 num_slot0 cw0 hwq len00000000 no_room_flag0 0

[nof_retx 1 pass 0]这样是ACK没有收到。

如果从频谱仪上可以抓到发出来的ACK的波形,并且ADC的接口上也可以看到同样的波形的话,要么是没有解调出来,要么是解调出来了,但是已经超过了等待ACK的延时。

虽然是按照FDD模式配置的,两发两收是可以独立工作的,实际作为WiFI协议工作的时候仍然是时分的,rx_intf模块在发射信号的时候会把IQ置零,但这样不能阻止前端TX串进RX,所以RX刚打开的时候会有一小段直流,这段直流还没有研究是天线的问题还是ADC的问题,虽然不确定会不会影响到解调,但是这个过程会影响到AGC,因此后来我选择通过引脚去关掉ADC(和开关DAC一样的操作,当然二者在时间上是错开的)而不仅仅只是把数字信号置零。

因为ADRV9002能够直接用PIN控制前端开关,速度比AD9361要快一些,因此需要调一下控制发送部分的几个延时参数(XPU的寄存器10)

./sdrctl dev sdr0 set reg xpu 10 0x18040048
[ 7: 0] BB RF delay:
[14: 8] RF end ext time: 数据结束到TX关闭
[22:16] 

之所以PIN的信号比数据快,主要是因为数据从基带里出来之后,还要过一些fifo后再转换到LVDS的接口,在ADRV9002内还有一段数据接口的延迟。严格地来的话,应该让PIN上的控制信号和数字信号同步。

这是调整OFDM解调的RSSI门限,

./sdrctl dev sdr0 set reg rx 2 $((16#0040005e))

最初以AP模式工作时,发现其他设备几乎无法连接,上层一直报IEEE 802.11: did not acknowledge authentication response,最后用ILA看xpu里的tx_control模块的状态机,发现是ACK帧还没解出来的时候,等待ACK已经超时了,这个超时值也可以通过xpu的16、17号寄存器设置(bit30-16: timeout for PHY header detection):

reg_idx meaning comment
16 setting when wait for ACK in 2.4GHz unit 0.1us. bit14-0: OFDM decoding timeout (after detect PHY header), bit30-16: timeout for PHY header detection, bit31: 0: FCS valid is not needed for ACK packet, 1: FCS valid is needed for ACK packet
17 setting when wait for ACK in 5GHz unit 0.1us. bit14-0: OFDM decoding timeout (after detect PHY header), bit30-16: timeout for PHY header detection, bit31: 0: FCS valid is not needed for ACK packet, 1: FCS valid is needed for ACK packet

xpu.c里配置的值:

xpu_api->XPU_REG_RECV_ACK_COUNT_TOP0_write( (1<<31) | (((51+2+2)*10 + 15)<<16) | 10 );//2.4GHz. extra 300 clocks are needed when rx core fall into fake ht detection phase (rx mcs 6M)
xpu_api->XPU_REG_RECV_ACK_COUNT_TOP1_write( (1<<31) | (((51+2+2)*10 + 15)<<16) | 10 );//5GHz. extra 300 clocks are needed when rx core fall into fake ht detection phase (rx mcs 6M)

调高延时,直到ACK能收到(pass 1)。

[ 4968.183402] sdr,sdr openwifi_tx_interrupt: tx_result [nof_retx 1 pass 1] SC2145 prio0 q0 wr56 rd55 num_slot0 cw0 hwq len00000000 no_room_flag0 1

这部分其实是调SIFS(Short Interframe Space)。系统时钟和原工程是一样的100M,不知是哪个环节引起的偏差。

接收的SIFS有问题,发送的多半也逃不掉。

这部分调好之后,用手机可以连接热点了,但是连接距离十分有限,iperf测速也只有十几Mbps的样子,丢包很严重,和AD9361上还有差距。感觉是个比较棘手的问题。

因为rootfs直接用的zcu102的ADI Kuipler镜像,而内核因为修改过,rootfs里的modules需要全部重新编译,否则一些网络功能比如iptables是缺失的,这个因为比较繁琐就先略过了。

A photograph of Zion National Park

Zion National Park

reg_idx meaning comment
0 reset each bit is connected to rx_intf.v internal sub-module. 1 – reset; 0 – normal
1 trigger for ILA debug bit4 and bit0. Please check slv_reg1 in rx_intf.v
2 enable/disable rx interrupt 256(0x100):disable, 0:enable
3 get loopback I/Q from tx_intf 256(0x100):from tx_intf, 0:from ad9361 ADC
4 baseband clock and IQ fifo in/out control no use anymore – for old bb rf independent mode
5 control/config dma to cpu check rx_intf.v slv_reg5
6 abnormal packet length threshold bit31-16 to store the threshold. if the packet length is not in the range of 14 to threshold, terminate the dma to cpu
7 source selection of rx dma to cpu check rx_intf.v slv_reg7
8 reserved reserved
9 number of dma symbol to cpu only valid in manual mode (slv_reg5[5]==1). normally the dma is set automatically by the received packet length
10 rx adc fifo reading control check rx_intf.v slv_reg10
11 rx digital I/Q gain number of bit shift to left. default 4 in rx_intf.c: rx_intf_api->RX_INTF_REG_BB_GAIN_write(4)。
digital gain看来对后面解调影响很大,AD9361是12bit所以往高位移四位是正常的,但我想充分利用ADRV9002的16bit精度,把他设为0之后,同样的信号ofdm解调效果就差了很多,于是暂时又改回了4。(但是用MATLA的WLAN工具解调,无论移位基本不影响解调效果,所以我想openofdm可能还有改进的空间)

总结

上述部分就是整个迁移过程了,总共花了将近一个月的时间。我发现许多围绕openwifi的活动是把openwifi塞进更廉价硬件中,当然也无非是ZYNQ7000+AD936x的组合,像这样往更昂贵的SoC和射频芯片上迁移的反而比较少。openwifi主要目的也不是为了通信,在wifi感知、嗅探之类的地方用的比较多,换成更好的SoC和射频芯片的话,做感知、测量之类的话,计算性能和精度都会提升。

换成ADRV9002之后,至少Beacon的覆盖范围已经赶得上一般的家用Wi-Fi了,但是接收的效果却很不理想,跟手机设备通信也很不稳定,只能慢慢想办法调整了。

#Future 迁移至Petalinux

评估了一下迁移到Petalinux的工程量,openwifi这里如果802.11的API没变的话应该不会太麻烦。