Use this file to discover all available pages before exploring further.
Running two or more USRP devices in lock-step requires two things: a shared frequency reference so all devices tick at the same rate, and a shared time reference so all devices start capturing or transmitting samples at exactly the same moment. UHD exposes both requirements through simple API calls that select a clock source and a time source independently. Once these are aligned, issuing a timed stream command guarantees sample-aligned operation across the entire system.
The synchronization features described on this page do not apply to USRP1, which does not support the advanced clock and time features available in newer products.
UHD separates the frequency reference (clock_source) from the time reference (time_source). Set both before issuing any stream commands.
External SMA connectors
MIMO cable (USRP2 / N200-Series)
GPSDO (internal or external)
N200/N210 alternate PPS edge
Most USRPs have SMA connectors on the front or back panel for a 10 MHz reference and a PPS signal. These can come from an OctoClock, a third-party GPSDO, or a lab signal generator.
When generating your own PPS signal, clock it from the same 10 MHz reference being fed to the USRP. Consult your device’s application notes for the required voltage and edge specifications.
The MIMO expansion cable can distribute the master device’s clock and time to a second device without any external signal generators. This method is limited to two USRPs.
// On the slave device onlyusrp->set_clock_source("mimo");usrp->set_time_source("mimo");
A GPS-disciplined oscillator provides both a 10 MHz reference and a PPS aligned to GPS time. Select it the same way as an external reference:
After selecting the time source, you must initialize the device’s internal timestamp register so all devices share the same time base. The set_time_next_pps() call latches the provided time into the device on the next PPS rising edge.
This approach waits for a PPS edge by polling the device, then programs the next PPS to time 0.0:
// Wait for the current PPS to passconst uhd::time_spec_t last_pps_time = usrp->get_time_last_pps();while (last_pps_time == usrp->get_time_last_pps()) { // sleep ~100 ms between polls std::this_thread::sleep_for(std::chrono::milliseconds(100));}// The just-detected PPS edge has passed; the next one will latch this value:usrp->set_time_next_pps(uhd::time_spec_t(0.0));
Most GPSDOs output an NMEA sentence over serial once per PPS. Parse the sentence to obtain GPS time, then set the device to that value:
// Wait for the NMEA message that marks the upcoming PPS edge// (user implements wait_for_nmea_message() and parse_nmea_time())wait_for_nmea_message();usrp->set_time_next_pps(uhd::time_spec_t(0.0));// -- OR -- set the device to actual GPS time:wait_for_nmea_message();double gps_time = parse_nmea_time();// At the next PPS the device time will match GPS time:usrp->set_time_next_pps(uhd::time_spec_t(gps_time + 1));
The sync_to_gps example included with UHD provides a complete implementation of GPS-time locking. Look for it in <uhd-install>/share/uhd/examples/.
When using the MIMO cable, the slave device automatically synchronizes its time to the master device over the cable. No explicit set_time_next_pps() call is needed on the slave.
Sharing a clock and time reference synchronizes sample counts, but it does not automatically align the phase of the DSP CORDICs or the RF local oscillators. Two additional steps are needed for phase-coherent reception or transmission.
Step 1 — Align CORDICs with a timed stream command
The CORDIC is reset at every start-of-burst command. Issuing a timed stream command on all devices ensures all CORDICs reset at exactly the same sample, producing identical initial phase across devices.
1
RX — issue a timed stream command
uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);stream_cmd.num_samps = samps_to_recv;stream_cmd.stream_now = false;stream_cmd.time_spec = time_to_recv; // same value on all devicesrx_stream->issue_stream_cmd(stream_cmd);
2
TX — set time spec in metadata
uhd::tx_metadata_t md;md.start_of_burst = true;md.end_of_burst = false;md.has_time_spec = true;md.time_spec = time_to_send; // same value on all devicessize_t num_tx_samps = tx_stream->send(&buff.front(), samps_to_send, md);
Step 2 — Align LOs with timed tuning commands (SBX, UBX, OBX)
For frontends based on SBX, UBX, or OBX daughterboards, issue tuning commands at a precise future time using set_command_time(). This ensures the phase offset between VCO/PLL chains is the same after every re-tune.
// Schedule all tune commands 100 ms from nowuhd::time_spec_t cmd_time = usrp->get_time_now() + uhd::time_spec_t(0.1);usrp->set_command_time(cmd_time);// Tune both channels — these commands execute at cmd_timeusrp->set_rx_freq(1.03e9, 0); // Channel 0usrp->set_rx_freq(1.03e9, 1); // Channel 1// Clear the command time so subsequent calls execute immediatelyusrp->clear_command_time();
There is always a random phase offset between any two frontends. This offset is different for different LO frequencies, but it remains constant after each re-tune. For phase-coherent applications you must estimate and compensate for this offset using a training sequence, and you must re-calibrate after every tune command.
OctoClock — Distributing References to Multiple USRPs
For setups with more than two USRPs, the OctoClock (CDA-2990) distributes a 10 MHz reference and PPS signal to up to eight USRP devices simultaneously. Connect the OctoClock’s output ports to the SMA reference inputs on each USRP, then configure each USRP for external references:
// Apply to every USRP in the setupusrp->set_clock_source("external");usrp->set_time_source("external");
The OctoClock can itself be locked to a GPSDO, making it possible to distribute GPS-disciplined time and frequency to an entire array of USRPs from a single GPS antenna.
Connect 10 MHz and PPS signals from your reference (OctoClock, GPSDO, lab instrument) to the SMA connectors on every USRP.
2
Select sources in software
for (auto& usrp : usrp_list) { usrp->set_clock_source("external"); usrp->set_time_source("external");}
3
Verify the 10 MHz lock
for (auto& usrp : usrp_list) { bool locked = usrp->get_mboard_sensor("ref_locked").to_bool(); if (!locked) { /* handle error */ }}
4
Set time on PPS edge
// Wait for a PPS edge, then set all devices to the same timeconst uhd::time_spec_t last_pps_time = usrp_list[0]->get_time_last_pps();while (last_pps_time == usrp_list[0]->get_time_last_pps()) { std::this_thread::sleep_for(std::chrono::milliseconds(100));}for (auto& usrp : usrp_list) { usrp->set_time_next_pps(uhd::time_spec_t(0.0));}// Wait one more second to confirm the latch took effectstd::this_thread::sleep_for(std::chrono::seconds(1));