EttusResearch / uhd

The USRP™ Hardware Driver Repository
http://uhd.ettus.com
Other
975 stars 655 forks source link

[TwinRX] LO sharing Phase Alignment Problems Multiple of 90 degrees, pi/2 #670

Closed chris-ste closed 1 year ago

chris-ste commented 1 year ago

Issue Description

Phase alignment with TwinRX using LO sharing seems to have some problems. We want to use a x310 with two TwinRX daughter boards for direction finding. Since TwinRX supports LO sharing, the phase offset between the four channels should be the same after each restart. This is the case at least in UHD 3.14. However, when we try to use UHD 4.1, 4.2 or 4.4 the phase offset between channel 0 and one of the other channels is not fixed but seems to change in multiples of 90° after restarting the device or the software.

Any help is greatly appreciated!

Similar topics/issues: Issue 524 Issue 237

Setup Details

We use a x310 with one or two TwinRX daughter boards. UHD Version 4.1 or 4.2 or 4.4 (other 4.x versions not tested). LO sharing active (A:0 sharing to all other channels). We connect a signal generator using a powersplitter to all Rx channels and send a sinusiodal signal at for example 100 MHz center frequency. We then analyse the phase offset between the received signals of the different channels.

Expected Behavior

The phase offset between channel 0 and channels 1/2/3 should be nearly constant even after power cycling the usrp.

Actual Behaviour

The phase offset between channel 0 and channels 1/2/3 varies in multiple of 90° (pi/2).

Steps to reproduce the problem

We modified the python rx_to_file.py example to reproduce the problem. The phase offset is printed and should always be around the same value with some noise. We usually use the C++ API, so it's not a python only problem. run with: python3 rx_to_file_phase.py -o test -f 100e6 -r 0.2e6 -d 1 -c 0 1 -g 70


rx_to_file_phase.py


#!/usr/bin/env python3
#
# Copyright 2017-2018 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
RX samples to file using Python API
"""

import argparse
import numpy as np
import uhd

def parse_args():
    """Parse the command line arguments"""
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--args", default="", type=str)
    parser.add_argument("-o", "--output-file", type=str, required=True)
    parser.add_argument("-f", "--freq", type=float, required=True)
    parser.add_argument("-r", "--rate", default=1e6, type=float)
    parser.add_argument("-d", "--duration", default=5.0, type=float)
    parser.add_argument("-c", "--channels", default=0, nargs="+", type=int)
    parser.add_argument("-g", "--gain", type=int, default=10)
    parser.add_argument("-n", "--numpy", default=False, action="store_true",
                        help="Save output file in NumPy format (default: No)")
    return parser.parse_args()

def main():
    """RX samples and write to file"""
    args = parse_args()
    usrp = uhd.usrp.MultiUSRP(args.args)
    #usrp.set_time_source('gpsdo',0)
    #usrp.set_clock_source('gpsdo', 0)
    usrp.set_rx_lo_source('internal', 'all', 0)
    usrp.set_rx_lo_export_enabled(True, 'all', 0)
    usrp.set_rx_lo_source('companion', 'all', 1)
    #usrp.set_rx_lo_source('internal', 'all', 1)
    usrp.set_rx_lo_export_enabled(False, 'all', 1)
    num_samps = int(np.ceil(args.duration*args.rate))
    if not isinstance(args.channels, list):
        args.channels = [args.channels]
    samps = usrp.recv_num_samps(num_samps, args.freq, args.rate, args.channels, args.gain)
    a = np.sum( np.sin( np.angle( np.multiply(samps[0,:], np.conj(samps[1,:])) ) ) )
    b = np.sum( np.cos( np.angle( np.multiply(samps[0,:], np.conj(samps[1,:])) ) ) )  
    print(np.arctan2(a,b)*180/np.pi )
    with open(args.output_file, 'wb') as out_file:
        if args.numpy:
            np.save(out_file, samps, allow_pickle=False, fix_imports=False)
        else:
            samps.tofile(out_file)

if __name__ == "__main__":
    main()

Additional Information

If you disable LO sharing by using usrp.set_rx_lo_source('internal', 'all', 1) instead of usrp.set_rx_lo_source('companion', 'all', 1) the phase offsets are random as expected.

chris-ste commented 1 year ago

Thanks to a comment in the mailing list, we were able to fix our C++ code using timed commands:

usrp->clear_command_time();
usrp->set_command_time(usrp->get_time_now() + uhd::time_spec_t(0.1)); //set cmd time for .1s in the future
//uhd::tune_request_t freq);
usrp->set_rx_freq(uhd::tune_request_t(cfg.frequency, LO_OFFSET),0);
usrp->set_rx_freq(uhd::tune_request_t(cfg.frequency, LO_OFFSET),1);
usrp->set_rx_freq(uhd::tune_request_t(cfg.frequency, LO_OFFSET),2);
usrp->set_rx_freq(uhd::tune_request_t(cfg.frequency, LO_OFFSET),3);
sleep(0.11); //sleep 110ms (~10ms after retune occurs) to allow LO to lock
usrp->clear_command_time();

However, the timed commands are not working with our Python code. That is not really important for us but may be relevant for other people. Thanks a lot for the suggestion in the mailing list.

cvbates commented 6 months ago

This looks like my problem, did you ever get the timed commands working in python?

chris-ste commented 6 months ago

Sadly, no. At some point I just stopped trying and went back to C++