Hi all,
when using 3 X310 USRPs (TwinRX, UHD 3.15 LTS) synchronized via the CDA-2990 Octoclock (10MHz Ref and PPS distributed with length-matched cables), we are observing deviations of up to two samples (e.g. one board late, one early) between the individual devices. We are seeing these offsets in the measured RF signals, but to have a minimal example to show the effect, I've included an example where the timing offsets can be observed using the GPIO pins. The screenshots show the GPIO signals attached to the ATR-Receive signals. With every run we get different offsets between the boards and sometimes all boards are in sync. Is this a known issue? Thanks, David
// // Copyright 2011 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: GPL-3.0-or-later // #include <uhd/usrp/multi_usrp.hpp> #include <uhd/utils/safe_main.hpp> #include <uhd/utils/thread.hpp> #include <boost/algorithm/string.hpp> #include <boost/format.hpp> #include <chrono> #include <complex> #include <iostream> #include <thread> // set up our masks, defining the pin numbers #define RX_GPIO_MASK (1 << 0) int UHD_SAFE_MAIN(int argc, char* argv[]) { // variables to be set by po std::string args = "type=x300,addr0=192.168.11.2,addr1=192.168.12.2,addr2=192.168.13.2"; std::string subdev = "A:0 A:1 B:0 B:1"; std::string channel_list = "0,1,2,3,4,5,6,7,8,9,10,11"; double seconds_in_future = 5; size_t total_num_samps = 100; double rate = 100e6; // create a usrp device std::cout << std::endl; std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); size_t devices = usrp->get_num_mboards(); usrp->set_rx_subdev_spec(subdev); // sets across all mboards // set the rx sample rate (sets across all channels) std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate / 1e6) << std::endl; usrp->set_rx_rate(rate); std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp->get_rx_rate() / 1e6) << std::endl << std::endl; usrp->set_clock_source("external"); usrp->set_time_source("external"); usrp->set_time_unknown_pps(uhd::time_spec_t(0.0)); // wait for pps sync pulse std::this_thread::sleep_for(std::chrono::seconds(1)); // Checks that all time registers are approximately close but not // exact, given that the RTT may varying for a control packet // transaction. std::cout << (usrp->get_time_synchronized() ? "PPS synchronized." : "PPS: NO SYNC!") << std::endl; // setup ATR RX GPIO for offset measurement on all boards for (size_t i = 0; i < devices; i++) { usrp->set_gpio_attr("FP0", "CTRL", RX_GPIO_MASK, RX_GPIO_MASK, i); usrp->set_gpio_attr("FP0", "DDR", RX_GPIO_MASK, RX_GPIO_MASK, i); usrp->set_gpio_attr("FP0", "ATR_0X", 0, RX_GPIO_MASK, i); usrp->set_gpio_attr("FP0", "ATR_RX", 1, RX_GPIO_MASK, i); usrp->set_gpio_attr("FP0", "ATR_TX", 0, RX_GPIO_MASK, i); usrp->set_gpio_attr("FP0", "ATR_XX", 0, RX_GPIO_MASK, i); } // detect which channels to use std::vector<std::string> channel_strings; std::vector<size_t> channel_nums; boost::split(channel_strings, channel_list, boost::is_any_of("\"',")); for (size_t ch = 0; ch < channel_strings.size(); ch++) { size_t chan = std::stoi(channel_strings[ch]); if (chan >= usrp->get_rx_num_channels()) { throw std::runtime_error("Invalid channel(s) specified."); } else channel_nums.push_back(std::stoi(channel_strings[ch])); } // create a receive streamer // linearly map channels (index0 = channel0, index1 = channel1, ...) uhd::stream_args_t stream_args("fc32"); // complex floats stream_args.channels = channel_nums; uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); // setup streaming std::cout << std::endl; std::cout << boost::format("Begin streaming %u samples, %f seconds in the future...") % total_num_samps % seconds_in_future << std::endl; uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); stream_cmd.num_samps = total_num_samps; stream_cmd.stream_now = false; stream_cmd.time_spec = uhd::time_spec_t(seconds_in_future); rx_stream->issue_stream_cmd(stream_cmd); // tells all channels to stream // meta-data will be filled in by recv() uhd::rx_metadata_t md; // allocate buffers to receive with samples (one buffer per channel) const size_t samps_per_buff = rx_stream->get_max_num_samps(); std::vector<std::vector<std::complex<float>>> buffs( usrp->get_rx_num_channels(), std::vector<std::complex<float>>(samps_per_buff)); // create a vector of pointers to point to each of the channel buffers std::vector<std::complex<float>*> buff_ptrs; for (size_t i = 0; i < buffs.size(); i++) buff_ptrs.push_back(&buffs[i].front()); // the first call to recv() will block this many seconds before receiving double timeout = seconds_in_future + 0.1; // timeout (delay before receive + padding) size_t num_acc_samps = 0; // number of accumulated samples while (num_acc_samps < total_num_samps) { // receive a single packet size_t num_rx_samps = rx_stream->recv(buff_ptrs, samps_per_buff, md, timeout); // use a small timeout for subsequent packets timeout = 0.1; // handle the error code if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) break; if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) { throw std::runtime_error( str(boost::format("Receiver error %s") % md.strerror())); } std::cout << boost::format( "Received packet: %u samples, %u full secs, %f frac secs") % num_rx_samps % md.time_spec.get_full_secs() % md.time_spec.get_frac_secs() << std::endl; num_acc_samps += num_rx_samps; } if (num_acc_samps < total_num_samps) std::cerr << "Receive timeout before all samples received..." << std::endl; // finished std::cout << std::endl << "Done!" << std::endl << std::endl; return EXIT_SUCCESS; }
_______________________________________________ USRP-users mailing list USRP-users@lists.ettus.com http://lists.ettus.com/mailman/listinfo/usrp-users_lists.ettus.com