Fading Coder

An Old Coder’s Final Dance

Home > Tech > Content

USRP Streaming with UHD in C++: Transmit and Receive Examples

Tech 1

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.
Tags: uhdusrp

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.