On 02/24/2020 05:10 PM, Michael Wentz via USRP-users wrote:
Hi,

I'm trying to synchronize multiple B210s to receive data at the same time. I'm only concerned about sample time alignment across devices, and am aware there is a phase offset that will need to be calibrated out separately. I've had success with 1 RX channel across 2 B210s, but something seems wrong when using 2 RX channels across 2 B210s.

I've pasted a program below to help reproduce this issue. It uses 1 B210 to TX noise and 2 separate B210s to RX at the same time. There is a 40 dB attenuator on the output of the transmitting B210, then a 4 way splitter, and equal length cables to both receivers on the other 2 B210s. Additionally, the 2 receiving B210s get 10 MHz and PPS from a common OctoClock-G.

Here are 10 observations of sample offsets between devices (1 MSPS rate):
Only channel 0 on both RX: [0,0,0,0,0,0,0,0,0,0]
Only channel 1 on both RX: [0,0,0,0,0,0,0,0,0,0]
Channels 0 and 1 simultaneously on both RX: [14,1,-126,48,2,-88,17,-204,-96,2]

In the 2 channel case both channels on one device are always aligned, but are offset from the channels on the other device. Both of the receiving B210s show the same time last PPS when asked before the streams start. The times in the RX metadata also match. But after the streams stop, the devices respond with different times - neither of which are correct - and the difference between them matches what I'm measuring.

I'm using UHD 3.14.1.1 and GNU Radio 3.8 on RHEL 7.7. Any ideas?

Thanks,
Michael

----------

#!/usr/bin/env python3

from gnuradio import gr, uhd, analog, blocks
import time
import numpy as np

tx_serial = '30D1***'
rx_serials = ['3153***', '3153***']
rx_channels = [0, 1]

n_rx = len(rx_serials)
n_chan = len(rx_channels)

class top_block(gr.top_block):
    def __init__(self):
        gr.top_block.__init__(self)

        # transmit path
        noise_src = analog.noise_source_c(analog.GR_GAUSSIAN, 0.1)
        tx_strm_args = uhd.stream_args(cpu_format='fc32', channels=[0])
        tx = uhd.usrp_sink('serial=' + tx_serial, tx_strm_args)
        tx.set_clock_rate(16e6)
        tx.set_samp_rate(1e6)
        tx.set_center_freq(1e9)
        tx.set_gain(40)
        tx.set_antenna('TX/RX')
        self.connect(noise_src, tx)

        # receive path
rx_strm_args = uhd.stream_args(cpu_format='fc32', channels=rx_channels)
        self.rx = [None]*n_rx
        head = [None]*n_rx*n_chan
        file_sink = [None]*n_rx*n_chan
        for i in range(n_rx):
self.rx[i] = uhd.usrp_source('serial=' + rx_serials[i], rx_strm_args)
            self.rx[i].set_clock_rate(16e6)
            self.rx[i].set_samp_rate(1e6)
            self.rx[i].set_center_freq(1e9)
            self.rx[i].set_gain(40)
            self.rx[i].set_antenna('RX2')
            self.rx[i].set_time_source('external')
            self.rx[i].set_clock_source('external')
            for j in range(n_chan):
                ch = i*n_chan + j
                head[ch] = blocks.head(2*gr.sizeof_float, 10000)
                f = 'rx_%d%d.dat' % (i, j)
                file_sink[ch] = blocks.file_sink(2*gr.sizeof_float, f)
                file_sink[ch].set_unbuffered(True)
                self.connect((self.rx[i], j), head[ch], file_sink[ch])

        # make sure 10 MHz ref is locked
        for i in range(n_rx):
while not self.rx[i].get_mboard_sensor('ref_locked').to_bool():
                print('RX %d waiting for 10 MHz ref lock...' % i)
                time.sleep(1)
            print('RX %d 10 MHz ref locked' % i)

        # time sync the two receivers
        for i in range(n_rx):
            t = self.rx[i].get_time_last_pps().get_real_secs()
            print('RX %d time before align: %r' % (i, t))
        time_last_pps = self.rx[0].get_time_last_pps().get_real_secs()
while time_last_pps == self.rx[0].get_time_last_pps().get_real_secs():
            time.sleep(0.1)
        for i in range(n_rx):
            self.rx[i].set_time_next_pps(uhd.time_spec_t(0))
        time_last_pps = self.rx[0].get_time_last_pps().get_real_secs()
while time_last_pps == self.rx[0].get_time_last_pps().get_real_secs():
            time.sleep(0.1)
        for i in range(n_rx):
            t = self.rx[i].get_time_last_pps().get_real_secs()
            print('RX %d time after align: %r' % (i, t))

        # collect in the future
        rx_time = uhd.time_spec_t(2)
        for i in range(n_rx):
            self.rx[i].set_start_time(rx_time)
            self.rx[i].set_recv_timeout(3)

# run flowgraph
tb = top_block()
tb.start()
time.sleep(5)
tb.stop()

# print time
t = tb.rx[0].get_time_last_pps().get_real_secs()
while t == tb.rx[0].get_time_last_pps().get_real_secs():
    time.sleep(0.1)
for i in range(n_rx):
    t = tb.rx[i].get_time_last_pps().get_real_secs()
    print('RX %d time after run: %r' % (i, t))

# cross-correlate receivers and print delay
y = [None]*n_rx*n_chan
for i in range(n_rx):
    for j in range(n_chan):
        ch = i*n_chan + j
        y[ch] = np.fromfile('rx_%d%d.dat' % (i, j), dtype=np.complex64)
        r = np.correlate(y[0], y[ch], mode='full')
        max_lag = np.max([len(y[0]), len(y[ch])]) - 1
        lags = np.arange(-max_lag, max_lag+1)
        d = lags[np.argmax(np.abs(r))]
        print('RX %d, channel %d: %d samples' % (i, j, d))


_______________________________________________
USRP-users mailing list
USRP-users@lists.ettus.com
http://lists.ettus.com/mailman/listinfo/usrp-users_lists.ettus.com
IN the two channel case, you should probably set the subdev spec to:

A:A A:B

I didn't set a set_rx_subdev_spec()  in the code.


_______________________________________________
USRP-users mailing list
USRP-users@lists.ettus.com
http://lists.ettus.com/mailman/listinfo/usrp-users_lists.ettus.com

Reply via email to