USRP N210 Guide
RemoteRF supports the USRP N210, a high-performance networked software-defined radio manufactured by Ettus Research. Equipped with dual 100 MSPS 14-bit ADCs, dual 400 MSPS 16-bit DACs, and a Gigabit Ethernet (1 GbE) host interface, the N210 delivers high dynamic range and real-time streaming bandwidths up to 25 MSps (with 16-bit complex samples). When paired with modular RF daughterboards (such as UBX, SBX, or WBX), it provides full-duplex single-channel (1 TX, 1 RX) coverage from HF up to 6 GHz. This guide covers how to initialize, configure, and stream baseband signals with a reserved USRP N210 through RemoteRF using Python and UHD.
Device Initialization
To interface with a USRP N210 through RemoteRF, import the UHD client driver from the remoterf library and initialize a MultiUSRP object with the token issued upon making your reservation.
This token is used by the RemoteRF platform to validate your reservation and route subsequent interfacing to the correct networked USRP corresponding to that reservation.
from remoterf.drivers.usrp import MultiUSRP, uhd
usrp = MultiUSRP(token='dUx6lG3I9lA') # token issued when making a reservationOnce initialized, the rest of your Python script follows standard UHD Python API usage, exactly as if connected directly via a dedicated Gigabit Ethernet network cable.
Example Script
The Python script below demonstrates how to configure a USRP N210 and perform synchronous transmit and receive operations on its single channel (1 × 1 SISO) via the RemoteRF platform. Because the USRP N210 features full-duplex operation and onboard hardware timestamping, timed bursts can be scheduled and executed synchronously in a single thread without requiring background threads or asynchronous loops. In this example, a complex tone (+100 kHz) is generated, scaled, and scheduled for transmission on TX0. Simultaneously, a timed receive command captures 50,000 samples on RX0, and the FFT spectrum is plotted.
Note that the device initialization lines described above have already been included in the code below. You should not need to change any of the code below except for the token you received upon making a reservation.
import numpy as np
import matplotlib.pyplot as plt
# the below code can also be used to ensure you have the most
# up-to-date drivers pulled from the RemoteRF server
# from remoterf.drivers import ensure_driver
# ensure_driver(token='dUx6lG3I9lA')
# for interfacing with USRP through RemoteRF
from remoterf.drivers.usrp import MultiUSRP, uhd
# ---------------------------------------------------------------
# Digital communication system parameters.
# ---------------------------------------------------------------
fs = 1e6 # baseband sampling rate (1 MSps)
ts = 1 / fs # sampling period (seconds per sample)
sps = 10 # samples per data symbol
T = ts * sps # symbol period (seconds per symbol)
# ---------------------------------------------------------------
# USRP system parameters.
# ---------------------------------------------------------------
sample_rate = fs # baseband sampling rate (Hz), up to 25 MSps (16-bit)
tx_carrier_freq_Hz = 915e6 # center RF frequency for TX (Hz)
rx_carrier_freq_Hz = 915e6 # center RF frequency for RX (Hz)
tx_gain_dB = 25 # TX gain in dB
rx_gain_dB = 25 # RX gain in dB
num_tx_samples = 50_000 # number of samples to transmit
num_rx_samples = 50_000 # number of samples to capture
# ---------------------------------------------------------------
# Create transmit signal (1 Channel).
# ---------------------------------------------------------------
t = np.arange(num_tx_samples) / sample_rate # time vector
# Channel 0: Complex exponential tone at +100 kHz
tone_freq_0 = 100e3 # tone offset frequency for channel 0
tx_signal_chan0 = np.exp(2.0j * np.pi * tone_freq_0 * t).astype(np.complex64)
# Scale signal to target digital amplitude
target_amplitude = 1.0 # target peak digital amplitude
max_peak = np.max(np.abs(tx_signal_chan0))
scale_factor = target_amplitude / max_peak # uniform scale factor
tx_signal_chan0_scaled = tx_signal_chan0 * scale_factor
# Pack scaled signal into 2D buffer (shape: [num_channels, num_samples])
tx_buffer = np.zeros((1, num_tx_samples), dtype=np.complex64)
tx_buffer[0] = tx_signal_chan0_scaled
# ---------------------------------------------------------------
# Connect to USRP and perform synchronous transmit & receive!
# ---------------------------------------------------------------
usrp = MultiUSRP(token='dUx6lG3I9lA') # token issued when making a reservation
# Configure RF parameters for channel 0
usrp.set_tx_rate(sample_rate, 0) # set transmitter sampling rate
usrp.set_tx_freq(tx_carrier_freq_Hz, 0) # set transmitter carrier frequency
usrp.set_tx_gain(tx_gain_dB, 0) # set transmit gain
usrp.set_rx_rate(sample_rate, 0) # set receiver sampling rate
usrp.set_rx_freq(rx_carrier_freq_Hz, 0) # set receiver carrier frequency
usrp.set_rx_gain(rx_gain_dB, 0) # set receive gain
# Setup stream arguments: host fc32 (complex64), wire sc16 (16-bit integer)
rx_args = uhd.usrp.StreamArgs("fc32", "sc16")
rx_args.channels = [0]
rx_streamer = usrp.get_rx_stream(rx_args)
rx_buffer = np.zeros((1, num_rx_samples), dtype=np.complex64)
rx_metadata = uhd.types.RXMetadata()
tx_args = uhd.usrp.StreamArgs("fc32", "sc16")
tx_args.channels = [0]
tx_streamer = usrp.get_tx_stream(tx_args)
# Schedule synchronized burst transmission and reception using hardware timestamps
burst_start = usrp.get_time_now() + 1.0 # schedule 1.0s in the future
# Step 1: Issue timed RX command
rx_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
rx_cmd.num_samps = num_rx_samples # number of samples to receive
rx_cmd.stream_now = False # do not stream immediately
rx_cmd.time_spec = burst_start + 0.005 # begin capture 5 ms after TX burst start
rx_streamer.issue_stream_cmd(rx_cmd)
# Step 2: Send TX burst (queues to device FIFO and returns immediately)
tx_metadata = uhd.types.TXMetadata()
tx_metadata.start_of_burst = True # marks start of burst
tx_metadata.end_of_burst = True # marks end of burst
tx_metadata.has_time_spec = True # enable hardware timestamping
tx_metadata.time_spec = burst_start # execute burst at timestamp
tx_streamer.send(tx_buffer, tx_metadata, 5.0) # submit transmit buffer
# Step 3: Fetch captured RX samples (blocks until hardware receives them)
num_received = int(rx_streamer.recv(rx_buffer, rx_metadata, 5.0))
if num_received == 0:
raise RuntimeError("No RX samples received.")
# Close streamers
tx_streamer.close() # close transmit streamer
rx_streamer.close() # close receive streamer
# ---------------------------------------------------------------
# Take FFT of received signal.
# ---------------------------------------------------------------
rx_chan0 = rx_buffer[0, :num_received]
rx_fft_0 = np.abs(np.fft.fftshift(np.fft.fft(rx_chan0)))
f = np.linspace(-sample_rate / 2, sample_rate / 2, num_received)
# ---------------------------------------------------------------
# Plot spectral response.
# ---------------------------------------------------------------
fig, ax = plt.subplots(figsize=(8, 4.5), constrained_layout=True)
ax.plot(f / 1e3, rx_fft_0, color="tab:blue")
ax.set_xlabel("Frequency (kHz)")
ax.set_ylabel("Magnitude")
ax.set_title("FFT of Received Signal - RX Channel 0")
ax.grid(True)
plt.show()Properties and Methods
When interacting with a USRP N210 through RemoteRF, client scripts communicate with a MultiUSRP object generated dynamically from RemoteRF’s built-in usrp schema. This interface exposes the complete UHD Python API over the remote RPC connection.
Configuration Methods
| Method | Arguments | Range / Options | Description |
|---|---|---|---|
| Configuration Methods | |||
usrp.set_tx_rate( |
rate (float), chan (int) |
Up to 25 MSPS (16-bit) | Sets the baseband transmit sampling rate in samples per second for the specified channel. |
usrp.set_rx_rate( |
rate (float), chan (int) |
Up to 25 MSPS (16-bit) | Sets the baseband receive sampling rate in samples per second for the specified channel. |
usrp.get_tx_rate( |
chan (int) |
Rate in Hz | Returns the actual hardware baseband transmit sampling rate in samples per second. |
usrp.get_rx_rate( |
chan (int) |
Rate in Hz | Returns the actual hardware baseband receive sampling rate in samples per second. |
usrp.set_tx_freq( |
freq (float), chan (int) |
Daughterboard-dependent (e.g., 10 MHz to 6 GHz) | Sets the transmit carrier center frequency in Hz on the active daughterboard. |
usrp.set_rx_freq( |
freq (float), chan (int) |
Daughterboard-dependent (e.g., 10 MHz to 6 GHz) | Sets the receive carrier center frequency in Hz on the active daughterboard. |
usrp.get_tx_freq( |
chan (int) |
Frequency in Hz | Queries and returns the current transmit carrier frequency in Hz. |
usrp.get_rx_freq( |
chan (int) |
Frequency in Hz | Queries and returns the current receive carrier frequency in Hz. |
usrp.set_tx_gain( |
gain (float), chan (int) |
Daughterboard-dependent (e.g., 0 to ~31.5 dB) | Sets the transmit analog gain in dB on the specified channel. |
usrp.set_rx_gain( |
gain (float), chan (int) |
Daughterboard-dependent (e.g., 0 to ~38 dB) | Sets the receive analog gain in dB on the specified channel. |
usrp.set_tx_bandwidth( |
bw (float), chan (int) |
Daughterboard-dependent (up to 40 MHz) | Sets the analog baseband transmit low-pass filter bandwidth in Hz. |
usrp.set_rx_bandwidth( |
bw (float), chan (int) |
Daughterboard-dependent (up to 40 MHz) | Sets the analog baseband receive low-pass filter bandwidth in Hz. |
usrp.get_mboard_name() |
None | str |
Queries and returns the USRP motherboard model name (e.g., 'N210'). |
usrp.get_pp_string() |
None | str |
Returns a formatted diagnostic string detailing device subdevices, clocks, sensors, and channels. |
usrp.set_clock_source( |
source (str) |
'internal', 'external', 'mimo' |
Selects the 10 MHz reference clock source for frequency synthesis and timing. |
usrp.set_time_source( |
source (str) |
'internal', 'external', 'mimo' |
Selects the 1 PPS time synchronization source for hardware timestamps. |
Streaming & Synchronization Methods
| Method | Arguments | Returns | Description |
|---|---|---|---|
| Stream Management Methods | |||
usrp.get_tx_stream( |
stream_args (StreamArgs) |
TXStreamer |
Allocates and initializes a UHD hardware transmit streamer for the specified channels and data format. |
usrp.get_rx_stream( |
stream_args (StreamArgs) |
RXStreamer |
Allocates and initializes a UHD hardware receive streamer for the specified channels and data format. |
tx_streamer.send( |
buffer (ndarray), metadata (TXMetadata), timeout (float) |
int |
Sends complex IQ samples into the USRP hardware transmit FIFO queue over Gigabit Ethernet. |
rx_streamer.recv( |
buffer (ndarray), metadata (RXMetadata), timeout (float) |
int |
Receives complex IQ samples from the USRP hardware receive FIFO queue into host memory over Gigabit Ethernet. |
rx_streamer.issue_stream_cmd( |
stream_cmd (StreamCMD) |
None |
Issues a hardware streaming command (e.g., timed burst capture or continuous streaming). |
tx_streamer.close() / rx_streamer.close() |
None | None |
Closes the active streamer channel and frees onboard DMA FIFO resources. |
usrp.get_time_now() |
None | TimeSpec |
Reads and returns the current hardware timestamp from the onboard FPGA master clock. |
usrp.set_time_now( |
time_spec (TimeSpec) |
None |
Sets the FPGA master clock to the specified timestamp. |
Tips and Tricks
Common Configuration Template
For quick reference, the following template brings together the most commonly configured RF, rate, and gain properties for a USRP N210 session:
# Common USRP N210 Configuration Template (1x1 SISO)
usrp.set_tx_rate(int(1e6), 0) # baseband sampling rate (1 MSps), up to 25 MSps (16-bit)
usrp.set_tx_freq(int(915e6), 0) # center RF frequency for TX (Hz)
usrp.set_tx_gain(25, 0) # TX gain in dB
usrp.set_rx_rate(int(1e6), 0) # baseband sampling rate (1 MSps), up to 25 MSps (16-bit)
usrp.set_rx_freq(int(915e6), 0) # center RF frequency for RX (Hz)
usrp.set_rx_gain(25, 0) # RX gain in dBGigabit Ethernet Architecture & Daughterboards
The USRP N210 communicates over Gigabit Ethernet using standard UDP/IP network packets. Because RF front-end features (frequency range, analog bandwidth, and gain limits) are determined by the installed daughterboard (such as UBX, SBX, or WBX), check the daughterboard specifications in your lab setup to ensure your desired RF frequencies and gains fall within hardware limits.
Synchronous Timed Bursts
Because UHD supports FPGA hardware timestamps, synchronous transmit and receive bursts can be executed cleanly in a single thread without background worker threads or asynchronous callbacks:
- Query current hardware time with
burst_start = usrp.get_time_now() + 1.0(scheduling 1 second ahead). - Schedule the receive capture using
uhd.types.StreamCMDwithtime_spec = burst_start + 0.005. - Submit the transmit burst with
tx_metadata.time_spec = burst_start. The call totx_streamer.send()queues samples directly into the USRP hardware FIFO and returns immediately. - Call
rx_streamer.recv(), which blocks until the scheduled samples arrive from the hardware.
Stream Formats (fc32 vs. sc16)
When creating streamers with uhd.usrp.StreamArgs("fc32", "sc16"):
- Host / CPU Format (
fc32): Complex 32-bit floating point numbers (numpy.complex64), normalized between −1.0 and +1.0. - Over-the-Wire Format (
sc16): 16-bit signed integer complex on the Gigabit Ethernet network link and FPGA hardware (−32,768 to +32,767).
UHD handles automatic scaling, conversion, and quantization between fc32 and sc16.
Managing Streamer Cleanups
Explicitly closing transmit and receive streamers when streaming finishes is optional, but considered good practice:
# Explicitly close active streamers to release hardware resources
tx_streamer.close()
rx_streamer.close()Calling .close() on your active TXStreamer and RXStreamer objects frees onboard FPGA DMA FIFO channels and hardware buffer allocations on the USRP, ensuring the device returns to a clean state ready for subsequent streaming tasks. While RemoteRF automatically reclaims device resources when your session ends, manually closing streamers prevents leftover buffer states or FIFO underflow/overflow issues across consecutive operations within the same session.