USRP Streaming with UHD in C++: Transmit and Receive Examples
This guide shows how to configure a USRP with UHD from C++, and provides compact end‑to‑end examples for transmitting and receiving complex IQ samples. It focuses on the core API for clocking, rates, frequency, gains, and stream control, then demonstrates complete programs for a file‑based transmitter and a continuous receiver.
1. UHD essentials
1.1 Transmit‑side configuration
-
Create the device
std::string device_args = ""; // e.g., "type=b200, addr=192.168.10.2" auto usrp = uhd::usrp::multi_usrp::make(device_args); -
Clocking
usrp->set_clock_source("internal"); // "internal", "external", "mimo" usrp->set_master_clock_rate(30.72e6); // optional; device dependent auto clk_src = usrp->get_clock_source(); auto clk_sources = usrp->get_clock_sources(); auto mcr = usrp->get_master_clock_rate(); (void)clk_src; (void)clk_sources; (void)mcr; // suppress unused warnings -
Sample rate and frequency
double tx_rate = 20e6; // samples per second usrp->set_tx_rate(tx_rate); auto rate_set = usrp->get_tx_rate(); auto rate_rng = usrp->get_tx_rates(); double tx_center_hz = 2.412e9; // center frequency in Hz usrp->set_tx_freq(tx_center_hz); auto tx_freq = usrp->get_tx_freq(); auto tx_ranges = usrp->get_fe_tx_freq_range(); (void)rate_set; (void)rate_rng; (void)tx_freq; (void)tx_ranges; -
Gain and antenna
usrp->set_tx_gain(60.0); // Optional: usrp->set_tx_antenna("TX/RX"); // auto which_ant = usrp->get_tx_antenna(); // auto ants = usrp->get_tx_antennas(); -
Transmit streamer
// CPU sample type: fc32 = std::complex<float> // Wire format: sc16 = 16-bit I/Q across the USRP link uhd::stream_args_t tx_args("fc32", "sc16"); auto tx_stream = usrp->get_tx_stream(tx_args); uhd::tx_metadata_t tx_md; tx_md.start_of_burst = false; // assert true for the first packet of a burst tx_md.end_of_burst = false; // assert true for the final packet of a burst tx_md.has_time_spec = false; // set true and fill time_spec for timed TX
1.2 Receive‑side configuration
-
Device and clock
auto rx_usrp = uhd::usrp::multi_usrp::make(""); rx_usrp->set_clock_source("internal"); rx_usrp->set_master_clock_rate(30.72e6); // optional -
Rate, frequency, and gain
rx_usrp->set_rx_rate(20e6); rx_usrp->set_rx_freq(2.412e9); rx_usrp->set_rx_gain(50.0); -
Receive streamer and start comand
uhd::stream_args_t rx_args("fc32", "sc16"); auto rx_stream = rx_usrp->get_rx_stream(rx_args); uhd::stream_cmd_t cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); // Alternatives: // - STREAM_MODE_STOP_CONTINUOUS // - STREAM_MODE_NUM_SAMPS_AND_DONE // - STREAM_MODE_NUM_SAMPS_AND_MORE cmd.stream_now = true; // start immediately cmd.num_samps = 1000; // used for NUM_SAMPS* modes rx_stream->issue_stream_cmd(cmd);
2. Example: Transmitting IQ samples from a binary file
The following program streams interleaved complex float (fc32) samples from a file, converting to sc16 on the wire. The file is expected to contain [real, imag] pairs stored as 32‑bit floats per sample.
// tx_stream.cpp
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/types/stream_cmd.hpp>
#include <csignal>
#include <complex>
#include <cstdio>
#include <vector>
#include <iostream>
namespace {
volatile std::sig_atomic_t g_stop = 0;
void handle_sigint(int) { g_stop = 1; }
}
int main() {
try {
// Create device
std::string dev_args;
uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(dev_args);
std::cout << "USRP created\n";
// Basic TX configuration
usrp->set_clock_source("internal");
// Optional: set device master clock if supported
// usrp->set_master_clock_rate(30.72e6);
const double tx_rate = 20e6;
const double tx_hz = 2.412e9;
const double tx_gain = 60.0;
usrp->set_tx_rate(tx_rate);
usrp->set_tx_freq(tx_hz);
usrp->set_tx_gain(tx_gain);
std::cout << "TX rate: " << usrp->get_tx_rate() << " sps\n";
std::cout << "TX freq: " << usrp->get_tx_freq() << " Hz\n";
std::cout << "TX gain: " << usrp->get_tx_gain() << " dB\n";
// Streamer
uhd::stream_args_t sargs("fc32", "sc16");
uhd::tx_streamer::sptr tx = usrp->get_tx_stream(sargs);
// Metadata for burst control
uhd::tx_metadata_t md;
md.start_of_burst = false;
md.end_of_burst = false;
md.has_time_spec = false; // set to true for timed start
// Signal handler to stop cleanly
std::signal(SIGINT, handle_sigint);
// Open fc32 file (interleaved float32 IQ)
const char* path = "tx_waveform_fc32.bin"; // replace with your file
FILE* f = std::fopen(path, "rb");
if (!f) {
std::perror("fopen");
return 1;
}
// Chunked streaming
const std::size_t chunk = 2000; // samples per send call
std::vector<std::complex<float>> iq(chunk);
bool first_packet = true;
while (!g_stop) {
// Read as complex<float> blocks directly
std::size_t nread = std::fread(iq.data(), sizeof(std::complex<float>), iq.size(), f);
if (nread > 0) {
md.start_of_burst = first_packet;
first_packet = false;
md.end_of_burst = false;
tx->send(iq.data(), nread, md);
continue;
}
// End of file: signal EOB and loop
if (std::feof(f)) {
md.start_of_burst = false;
md.end_of_burst = true;
tx->send(nullptr, 0, md); // end of burst marker
std::clearerr(f);
std::rewind(f);
first_packet = true;
md.end_of_burst = false;
continue;
}
// I/O error
std::perror("fread");
break;
}
// Ensure a graceful stop if interrupted mid‑burst
md.start_of_burst = false;
md.end_of_burst = true;
tx->send(nullptr, 0, md);
std::fclose(f);
return 0;
} catch (const std::exception& e) {
std::cerr << "TX error: " << e.what() << "\n";
return 1;
}
}
Build:
g++ -std=c++11 tx_stream.cpp -o tx -luhd
3. Example: Continuous receive stream
The next program starts a continuous receive stream, checks metadata for timeouts and overflows, and maintains a running count of received samples.
// rx_stream.cpp
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/types/stream_cmd.hpp>
#include <csignal>
#include <complex>
#include <cstring>
#include <vector>
#include <iostream>
namespace {
volatile std::sig_atomic_t g_stop = 0;
void handle_sigint(int) { g_stop = 1; }
}
int main() {
try {
std::string dev_args;
uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(dev_args);
// Basic RX configuration
usrp->set_clock_source("internal");
// Optional: usrp->set_master_clock_rate(30.72e6);
const double rx_rate = 20e6;
const double rx_hz = 2.412e9;
const double rx_gain = 50.0;
usrp->set_rx_rate(rx_rate);
usrp->set_rx_freq(rx_hz);
usrp->set_rx_gain(rx_gain);
std::cout << "RX rate: " << usrp->get_rx_rate() << " sps\n";
std::cout << "RX freq: " << usrp->get_rx_freq() << " Hz\n";
std::cout << "RX gain: " << usrp->get_rx_gain() << " dB\n";
// Streamer and buffers
uhd::stream_args_t sargs("fc32", "sc16");
uhd::rx_streamer::sptr rx = usrp->get_rx_stream(sargs);
const std::size_t chunk = 2000;
std::vector<std::complex<float>> iq(chunk);
// Start continuous streaming
uhd::stream_cmd_t cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
cmd.stream_now = true;
cmd.num_samps = 0; // ignored for continuous mode
rx->issue_stream_cmd(cmd);
uhd::rx_metadata_t md;
std::signal(SIGINT, handle_sigint);
std::uint64_t total = 0;
while (!g_stop) {
int got = rx->recv(iq.data(), iq.size(), md, 3.0, false);
if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) {
std::cout << "RX timeout\n";
break;
}
if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) {
// Overflow indicates samples were dropped; continue receiving
std::cout << "RX overflow\n";
continue;
}
if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) {
std::cout << "RX error: " << md.strerror() << "\n";
continue;
}
if (got > 0) total += static_cast<std::uint64_t>(got);
}
std::cout << "Total samples: " << total << "\n";
// Stop streaming
cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
rx->issue_stream_cmd(cmd);
return 0;
} catch (const std::exception& e) {
std::cerr << "RX error: " << e.what() << "\n";
return 1;
}
}
Build:
g++ -std=c++11 rx_stream.cpp -o rx -luhd
Notes:
- CPU sample format "fc32" means std::complex; the file‑based transmitter expects real/imag float32 pairs.
- Wire format "sc16" performs conversion between host and radio link.
- Adjust clocking, rates, frequency, and gain to match your hardware, RF environment, and bandwidth.
- For deterministic start times, set tx_md.has_time_spec = true and use usrp->get_time_now() + offset.