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