examples: updated help description and doc string

Improving user experience with examples.
- added/updated example's functionality and intent in --help
- added/updated each arg help description in --help
- updated doc string as per ni-python-styleguide
This commit is contained in:
Abhishek Verma 2025-04-15 09:37:35 +02:00 committed by Jörg Hofrichter
parent 5df2c21265
commit 4c74532d68
9 changed files with 977 additions and 421 deletions

View File

@ -1,11 +1,52 @@
#!/usr/bin/env python3
#
# Copyright 2018 Ettus Research, a National Instruments Company
# Copyright 2019 Ettus Research, a National Instruments Brand
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Benchmark rate using Python API."""
"""Benchmark rate using Python API.
This script benchmarks the receive and transmit rates of a USRP device using
the UHD Python API. It allows users to specify the device address, sample rate,
subdevice specifications, reference clock source, and other parameters for
the benchmarking process. The script can perform receive-only, transmit-only,
or full-duplex tests, and it provides detailed statistics on the number of
samples received, transmitted, dropped, and any errors encountered during
the process. The results are printed in a formatted summary at the end of
the test.
This script is useful for evaluating I/O performance between host PC and USRP
device. It is helpful for troubleshooting and optimizing network performance.
It can be used in research, development, and testing scenarios where accurate
measurement of data rates and error rates is critical.
Example Usage:
# Receive-only test at 10 Msps for 5 seconds
python3 benchmark_rate.py --rx_rate 10e6 --duration 5
# Transmit-only test at 5 Msps for 10 seconds
python3 benchmark_rate.py --tx_rate 5e6 --duration 10
# Full-duplex test with RX at 10 Msps and TX at 5 Msps
python3 benchmark_rate.py --rx_rate 10e6 --tx_rate 5e6 --duration 15
# Specify device arguments and subdevice
python3 benchmark_rate.py -a addr=192.168.10.2 --rx_subdev "A:0" --tx_subdev "A:0"
# Use external clock reference and PPS
python3 benchmark_rate.py --ref external --pps external --rx_rate 10e6 --tx_rate 5e6
Note 1: Refer https://files.ettus.com/manual/structuhd_1_1stream__args__t.html
to see the list of available over-the-wire (--rx_otw, --tx_otw) and CPU
(--rx_cpu, --tx_cpu) sample modes.
Note 2: --tx_channels and --rx_channels are used to specify the TX and RX channels
respectively. Or one can also use argument --channels to specify both TX and RX
channels together. Note that if both --rx_channels/--tx_channels and --channels
are specified than preference is always given to --rx_channels/--tx_channels.
The argument--channels is only used as a fallback if --rx_channels/--tx_channels
are not mentioned.
"""
import argparse
import logging
@ -22,47 +63,99 @@ INIT_DELAY = 0.05 # 50mS initial delay before transmit
def parse_args():
"""Parse the command line arguments."""
description = """UHD Benchmark Rate (Python API)
"""Parse the command line arguments.
Utility to stress test a USRP device.
UHD Benchmark Rate (Python API).
Utility to benchmark the rates at which the USRP and the host (external
CPU, embedded ARM) can receive and transmit without loosing any data.
Specify --rx_rate for a receive-only test.
Specify --tx_rate for a transmit-only test.
Specify both options for a full-duplex test.
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter, description=description
)
parser.add_argument("-a", "--args", default="", type=str, help="single uhd device address args")
parser.add_argument(
"-d", "--duration", default=10.0, type=float, help="duration for the test in seconds"
)
parser.add_argument("--rx_subdev", type=str, help="specify the device subdev for RX")
parser.add_argument("--tx_subdev", type=str, help="specify the device subdev for TX")
parser.add_argument("--rx_rate", type=float, help="specify to perform a RX rate test (sps)")
parser.add_argument("--tx_rate", type=float, help="specify to perform a TX rate test (sps)")
parser.add_argument(
"--rx_otw", type=str, default="sc16", help="specify the over-the-wire sample mode for RX"
formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__
)
parser.add_argument(
"--tx_otw", type=str, default="sc16", help="specify the over-the-wire sample mode for TX"
"-a",
"--args",
default="",
type=str,
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"--rx_cpu", type=str, default="fc32", help="specify the host/cpu sample mode for RX"
"-d",
"--duration",
default=10.0,
type=float,
help="specify the duration for the test in seconds [default = 10.0].",
)
parser.add_argument(
"--tx_cpu", type=str, default="fc32", help="specify the host/cpu sample mode for TX"
"--rx_subdev", type=str, help="specify the subdevice for RX [default = None]."
)
parser.add_argument(
"--tx_subdev", type=str, help="specify the subdevice for TX [default = None]."
)
parser.add_argument(
"--rx_rate", type=float, help="specify to perform a RX rate test (samples/sec)."
)
parser.add_argument(
"--tx_rate", type=float, help="specify to perform a TX rate test (samples/sec)."
)
parser.add_argument(
"--rx_otw",
type=str,
default="sc16",
help="specify the over-the-wire sample mode for RX [default= sc16].",
)
parser.add_argument(
"--tx_otw",
type=str,
default="sc16",
help="specify the over-the-wire sample mode for TX [default = sc16].",
)
parser.add_argument(
"--rx_cpu",
type=str,
default="fc32",
help="specify the host/cpu sample mode for RX [default = fc32].",
)
parser.add_argument(
"--tx_cpu",
type=str,
default="fc32",
help="specify the host/cpu sample mode for TX [default =fc32].",
)
parser.add_argument(
"--rx_stream_args",
default="",
help="specify the additional stream arguments for the RX streamer "
"(e.g., samples per packet).",
)
parser.add_argument(
"--tx_stream_args",
default="",
help="specify the additional stream arguments for the TX streamer "
"(e.g., samples per packet).",
)
parser.add_argument(
"--ref",
type=str,
help="specify the reference clock source (internal, external, mimo, gpsdo).",
)
parser.add_argument(
"--pps", type=str, help="specify the PPS source (internal, external, mimo, gpsdo)."
)
parser.add_argument("--rx_stream_args", help="stream args for RX streamer", default="")
parser.add_argument("--tx_stream_args", help="stream args for TX streamer", default="")
parser.add_argument("--ref", type=str, help="clock reference (internal, external, mimo, gpsdo)")
parser.add_argument("--pps", type=str, help="PPS source (internal, external, mimo, gpsdo)")
parser.add_argument(
"--random",
action="store_true",
default=False,
help="Run with random values of samples in send() and recv() to stress-test" " the I/O.",
help="specify whether to run the test with random packet size. If set to true, "
"a random number of samples ranging from 1 to maximum samples per packet is "
"used per send() and recv(). If set to false, the number of samples will be "
"set to maximum samples per packet.",
)
parser.add_argument(
"-c",
@ -70,19 +163,20 @@ def parse_args():
default=[0],
nargs="+",
type=int,
help='which channel(s) to use (specify "0", "1", "0 1", etc)',
help='specify the channel(s) to use as both RX and TX (e.g., "0", "1", "0 1", etc) '
"Refer Note 2 above on how --channels related to --rx_channel/--tx_channels [default = 0].",
)
parser.add_argument(
"--rx_channels",
nargs="+",
type=int,
help='which RX channel(s) to use (specify "0", "1", "0 1", etc)',
help='specify the RX channel(s) to use (e.g., "0", "1", "0 1", etc) [default = None].',
)
parser.add_argument(
"--tx_channels",
nargs="+",
type=int,
help='which TX channel(s) to use (specify "0", "1", "0 1", etc)',
help='specify the TX channel(s) to use (e.g., "0", "1", "0 1", etc) [default = None].',
)
return parser.parse_args()

View File

@ -1,35 +1,102 @@
#!/usr/bin/env python3
#
# Copyright 2017-2018 Ettus Research, a National Instruments Company
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Curses FFT example using Python API
"""Curses FFT example using Python API.
This example demonstrates how to use the UHD Python API to create a simple
FFT (Fast Fourier Transform) display using the curses library. The script
receives samples from a USRP device and displays the instantaneous estimate of
the power spectral density of the received signal on the terminal window. The
display is updated in real-time as new samples are received. The user can
adjust parameters such as frequency, gain, and sample rate through command-line
arguments. This example is useful for visualizing the frequency spectrum of
signals received by the USRP device, allowing users to monitor and analyze the
signal characteristics in a user-friendly manner.
Example Usage:
curses_fft.py --args addr=192.168.10.2 --freq 2.4e9 --rate 1e6 --gain 10
--nsamps 100000 --dyn 60
"""
import argparse
import curses as cs
import numpy as np
import uhd
def parse_args():
"""Parse the command line arguments"""
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--args", default="", type=str)
parser.add_argument("-f", "--freq", type=float, required=True)
parser.add_argument("-r", "--rate", default=1e6, type=float)
parser.add_argument("-g", "--gain", type=int, default=10)
parser.add_argument("-c", "--channel", type=int, default=0)
parser.add_argument("-n", "--nsamps", type=int, default=100000)
parser.add_argument("--dyn", type=int, default=60)
parser.add_argument("--ref", type=int, default=0)
"""Parse the command line arguments."""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__,
)
parser.add_argument(
"-a",
"--args",
default="",
type=str,
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"-f",
"--freq",
type=float,
required=True,
help="specifies the center frequency in Hz [default = 1e4].",
)
parser.add_argument(
"-r",
"--rate",
default=1e6,
type=float,
help="specifies the sample rate in samples/sec [default = 1e6].",
)
parser.add_argument(
"-g", "--gain", type=int, default=10, help="specifies the gain in dB [default = 10]."
)
parser.add_argument(
"-c",
"--channel",
type=int,
default=0,
help='specifies the channels to use (e.g., "0", "1", "0 1", etc) [Default = 0]',
)
parser.add_argument(
"-n",
"--nsamps",
type=int,
default=100000,
help="specifies the total number of samples to be received for FFT calculation "
"and display on the screen before refereshing [default = 100000].",
)
parser.add_argument(
"--dyn",
type=int,
default=60,
help="specifies the dynamic range in dB. This "
"defines the range of power levels relative to the reference level. Adjusting "
"dynamic range influences the resolution displayed on y-axis of the plot [default = 60].",
)
parser.add_argument(
"--ref",
type=int,
default=0,
help="specifies the reference level in dB. "
"This represent the maximum power level displayed on the plot. All power levels "
" are relative to reference level Adjusting the reference level allows to "
"focus on specific power ranges in the signal [default = 0].",
)
return parser.parse_args()
def psd(nfft, samples):
"""Return the power spectral density of `samples`"""
"""Return the power spectral density of `samples`."""
window = np.hamming(nfft)
result = np.multiply(window, samples)
result = np.fft.fftshift(np.fft.fft(result, nfft))
@ -40,12 +107,12 @@ def psd(nfft, samples):
def clip(minval, maxval, value):
"""Clip the value between a and b"""
"""Clip the value between a and b."""
return min(minval, max(maxval, value))
def main():
"""Create Curses display of FFT"""
"""Create Curses display of FFT."""
args = parse_args()
usrp = uhd.usrp.MultiUSRP(args.args)
@ -127,10 +194,10 @@ def main():
bins = psd(width, samples[args.channel][0:width])
for i in range(y_axis_width, width):
vertical_slot = clip(height, 0, np.int(bins[i] / db_step))
vertical_slot = clip(height, 0, np.int32(bins[i] / db_step))
try:
for j in range(vertical_slot, height):
screen.addch(j, i, '*')
screen.addch(j, i, "*")
except cs.error:
pass
screen.refresh()

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright 2024 Ettus Research, a National Instruments Brand
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
@ -20,9 +20,10 @@ The data flow is as follows:
│
└─> Radio Rx ─> DDC ─> OFDM Rx ─> Host Rx Streamer
It assumes the RFNoC topology like the one shown below. The configuration of the
SFPs may be 10 GbE (1 to 4 ports) or 100 GbE. The number of channels per radio
and DDC/DUC pair can be 1 or 2, with the DDC/DUC being optional. The following
It assumes the RFNoC topology shown below. Make sure that the bitfile used has
an FFT block instantiated as shown below. The configuration of the SFPs may
be 10 GbE (1 to 4 ports) or 100 GbE. The number of channels per radio and
DDC/DUC pair can be 1 or 2, with the DDC/DUC being optional. The following
diagram shows one channel per radio for simplicity.
┌────────────┐ Ch0 ┌─────────┐ ┌─────────┐ ┌─────────┐
@ -51,14 +52,18 @@ diagram shows one channel per radio for simplicity.
│ │<─────────────┤ │<────┤ │<────┤ │
└────────────┘ └─────────┘ └─────────┘ └─────────┘
Example usage:
fft_loopback.py --args addr=192.168.10.2 --fft-length 1024 --num-symbols 10
--freq 915e6 --amplitude 0.6 --cp-list 352 288 --channels 0,1
--tx-gain 25 --rx-gain 45 --rate 10e6 --delay 5
"""
import argparse
import math
import matplotlib.pyplot as plt
import numpy as np
import uhd
import math
from matplotlib.widgets import Slider
graph_connections = {
@ -105,28 +110,30 @@ def parse_args():
"--args",
type=str,
default="",
help="Device args (e.g., addr=192.168.10.2)",
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"-s",
"--fft-length",
type=int,
default=4096,
help="FFT length (default = 4096)",
help="specifies the FFT length [default = 4096].",
)
parser.add_argument(
"-n",
"--num-symbols",
type=int,
default=14,
help="Number of symbols to transmit/receive (default=14)",
help="specifies the number of symbols to transmit/receive [default = 14].",
)
parser.add_argument(
"-y",
"--amplitude",
type=float,
default=0.5,
help="Amplitude of the tone",
help="specifies the amplitude of the tone that is being transmitted [default = 0.5].",
)
parser.add_argument(
"-p",
@ -134,7 +141,7 @@ def parse_args():
type=int,
nargs="+",
default=[],
help="List of cyclic prefix lengths to use (default=[])",
help="specifies the list of cyclic prefix lengths to use [default = []].",
)
parser.add_argument(
"-c",
@ -142,45 +149,45 @@ def parse_args():
type=str,
choices=["0", "1", "2", "3", "0,1", "2,3", "0,1,2,3"],
default="0,1",
help="The channels to use (default=0,1)",
help="specifies the channels to use [default = 0,1].",
)
parser.add_argument(
"-l",
"--loopback",
action="store_true",
help="Use internal loopback",
help="specifies to loop the transmit signal back to FPGA internally within the radio block.",
)
parser.add_argument(
"-d",
"--delay",
type=int,
default=None,
help="The number of cycles to delay RX vs. TX when streaming",
help="specifies the number of FPGA clock cycles to delay RX vs. TX when streaming [default = None].",
)
parser.add_argument(
"--tx-gain",
type=int,
default=15,
help="The TX gain (default=15)",
help="specifies the TX gain in dB [default = 15].",
)
parser.add_argument(
"--rx-gain",
type=int,
default=50,
help="The RX gain (default=50)",
help="specifies the RX gain in dB [default = 50].",
)
parser.add_argument(
"-r",
"--rate",
type=float,
help="The TX/RX rate of host data (default is master clock rate)",
help="specifies the TX/RX sample rate in samples/sec of host data [default = master clock rate].",
)
parser.add_argument(
"-f",
"--freq",
type=float,
default=1.5e9,
help="The TX/RX radio frequency (default is 1.5 GHz)",
help="specifies the TX/RX radio center frequency in Hz [default = 1.5 GHz]",
)
return parser.parse_args()
@ -297,7 +304,7 @@ def print_fft_block_info(ofdm):
def default_scaling(fft_length):
"""Return the scaling value for the default 1/N scaling"""
"""Return the scaling value for the default 1/N scaling."""
# 1/N scaling should be 1010...10 if fft_length is a power of 4 and 0110...10
# if fft_length is not a power of 4.
#

View File

@ -1,53 +1,128 @@
#!/usr/bin/env python3
#
# Copyright 2022 Ettus Research, a National Instruments Brand
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Stream IQ data from a USRP to a remote destination.
"""Stream IQ data from a USRP to a remote destination.
The script is designed to stream IQ (In-phase and Quadrature) data from a
USRP device (connected to a host computer) to a remote destination over a
network. The remote destination can be different from the host computer.
The example allows users to configure various parameters such as frequency,
sampling rate, gain, and destination details for the streaming process.
Data is streamed until the configured duration expires or the user interrupts
the process (Ctrl-C).
This script is useful for applications where IQ data needs to be streamed
from a USRP device to a remote server or application for further processing,
such as signal analysis, demodulation, or storage. It is particularly suited
for networked SDR setups.
The hardware setup includes
- a USRP device
- a host computer connected to the USRP device
- a remote destination (IP address and port) where the IQ data will be sent
- a network connection between the host computer, USRP device and the remote
destination
Example Usage:
remote_rx.py --args addr=192.168.10.2 --rate 1e6 --freq 2.4e9 --gain 10
--duration 10 --channels 0 1 --dest-addr 192.168.10.100
--dest-port 12345 12346
"""
import sys
import argparse
import sys
import time
import uhd
INIT_DELAY = 0.05 # 50mS initial delay before receive
def parse_args():
"""Parse the command line arguments"""
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--args", default="",
help="Device args (e.g., addr=192.168.40.2)")
parser.add_argument("-r", "--rate", type=float,
help="Output rate (samples/sec)")
parser.add_argument("-f", "--freq", type=float, required=True,
help="Center frequency")
parser.add_argument("-g", "--gain", type=float, default=0.0,
help="Gain (dB)")
parser.add_argument("-d", "--duration", default=None, type=float,
help="Stream duration (seconds), leave out to stream until stopped")
parser.add_argument("-c", "--channels", default=[0], nargs="+", type=int,
help="Which channel(s) to use (specify \"0\", \"1\", \"0 1\", etc)")
parser.add_argument("-i", "--dest-addr", type=str, required=True,
help="Remote destination IP address")
parser.add_argument("-p", "--dest-port", nargs="+", type=int, required=True,
help="Remote destination UDP port")
parser.add_argument("--adapter", type=str,
help="Adapter to use for remote streaming (e.g. 'sfp0')")
parser.add_argument("--dest-mac-addr",
help="Manually provide destination MAC address in the "
"format 01:a2:4f:6d:7e:5f. By default, the device will "
"use ARP to identify the MAC address.")
parser.add_argument("--keep-hdr", action="store_true",
help="Specify this argument to keep CHDR headers on outgoing packets")
"""Parse the command line arguments."""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__,
)
parser.add_argument(
"-a",
"--args",
default="",
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"-r",
"--rate",
type=float,
default=1e6,
help="Specifies the sample rate in samples/sec [default = 1e6].",
)
parser.add_argument(
"-f",
"--freq",
type=float,
required=True,
help="Specifies the center frequency in Hz [input is required].",
)
parser.add_argument(
"-g", "--gain", type=float, default=0.0, help="Specifies the RX gain in dB [default = 0.0]."
)
parser.add_argument(
"-d",
"--duration",
default=None,
type=float,
help="Specifies the stream duration in seconds, leave out to stream until stopped [default = None].",
)
parser.add_argument(
"-c",
"--channels",
default=[0],
nargs="+",
type=int,
help='Specifies the RX channel(s) to use (specify "0", "1", "0 1", etc) [default = 0].',
)
parser.add_argument(
"-i",
"--dest-addr",
type=str,
required=True,
help="Specifies the remote destination IP address [input is required].",
)
parser.add_argument(
"-p",
"--dest-port",
nargs="+",
type=int,
required=True,
help="Specifies the remote destination UDP port [input is required].",
)
parser.add_argument(
"--adapter",
type=str,
help="Specifies the adapter to use for remote streaming (e.g. 'sfp0').",
)
parser.add_argument(
"--dest-mac-addr",
help="Specifies the destination MAC address in the format 01:a2:4f:6d:7e:5f. "
"If this argument is not used, the USRP device will use ARP to identify the MAC address.",
)
parser.add_argument(
"--keep-hdr",
action="store_true",
help="Specify this argument to keep CHDR headers on outgoing packets. If not specified, "
"the headers will be stripped from the packets.",
)
return parser.parse_args()
def get_stream_cmd(usrp, rate, duration, start_time=None):
"""
Generate a stream command based on rate and duration.
"""
"""Generate a stream command based on rate and duration."""
if duration:
stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
stream_cmd.num_samps = int(rate * duration)
@ -60,8 +135,9 @@ def get_stream_cmd(usrp, rate, duration, start_time = None):
stream_cmd.time_spec = start_time
return stream_cmd
def check_channels(usrp, args):
"""Check that the device has sufficient RX channels available"""
"""Check that the device has sufficient RX channels available."""
# Check that each channel specified is less than the number of total number of rx channels
# the device can support
channels = args.channels
@ -71,10 +147,9 @@ def check_channels(usrp, args):
return []
return channels
def main():
"""
Go, go, go!
"""
"""Run remote Rx."""
args = parse_args()
usrp = uhd.usrp.MultiUSRP(args.args)
channels = check_channels(usrp, args)
@ -83,7 +158,7 @@ def main():
if len(channels) != len(args.dest_port):
print("Number of channels must match number of ports.")
return False
print("Selected RX channels: {}.".format(', '.join(str(ch) for ch in channels)))
print("Selected RX channels: {}.".format(", ".join(str(ch) for ch in channels)))
if args.rate:
print(f"Requesting sampling rate {args.rate/1e6} Msps...")
for chan in channels:
@ -103,14 +178,19 @@ def main():
for chx, chan in enumerate(channels):
stream_args = uhd.usrp.StreamArgs("sc16", "sc16")
stream_args.channels = [chan]
stream_args.args = \
f"dest_addr={args.dest_addr},dest_port={args.dest_port[chx]}," \
f"stream_mode={'full_packet' if args.keep_hdr else 'raw_payload'}" + \
(f",adapter={args.adapter}" if args.adapter else "") + \
(f",dest_mac_addr={args.dest_mac_addr}" if args.dest_mac_addr else "")
stream_args.args = (
f"dest_addr={args.dest_addr},dest_port={args.dest_port[chx]},"
f"stream_mode={'full_packet' if args.keep_hdr else 'raw_payload'}"
+ (f",adapter={args.adapter}" if args.adapter else "")
+ (f",dest_mac_addr={args.dest_mac_addr}" if args.dest_mac_addr else "")
)
rx_streamers.append(usrp.get_rx_stream(stream_args))
print("Starting stream(s)...")
start_time = uhd.types.TimeSpec(usrp.get_time_now().get_real_secs() + INIT_DELAY) if len(channels) > 1 else None
start_time = (
uhd.types.TimeSpec(usrp.get_time_now().get_real_secs() + INIT_DELAY)
if len(channels) > 1
else None
)
for rx_streamer in rx_streamers:
stream_cmd = get_stream_cmd(usrp, actual_rate, args.duration, start_time)
rx_streamer.issue_stream_cmd(stream_cmd)
@ -129,5 +209,6 @@ def main():
print("Streaming complete. Exiting.")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,6 +1,13 @@
#!/usr/bin/env python3
"""
Captures samples into DRAM, then streams those to the host.
#
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Captures samples into DRAM, then streams those to the host.
The example uses a single replay block to capture samples from multiple radios
and stores data in a file on the host.
Note: The --freq, --gain, and --antenna options can take a single value, which
is applied to all channels, or a list of values which is applied to individual
@ -20,18 +27,31 @@ If this happens, the following options may help:
by adapting network driver settings (see also
https://kb.ettus.com/USRP_Host_Performance_Tuning_Tips_and_Tricks#Increasing_Ring_Buffers)
- Fall back to a slower data rate, e.g., use 1 GbE instead of 10 or 100 GbE
Example usage:
replay_capture.py --args addr=192.168.10.2,type=x300
--output-file captured_samples.dat --rate 10e6 --freq 1e9 1.1e9
--gain 20 30 --antenna TX/RX RX2 --duration 5
--radio-channels "0/Radio#0:0" "0/Radio#0:1" --block "0/Replay#0"
--pkt-size 1472 --numpy
Note: The argument --output-file specifies the file path to store received data
and needs to point to a location where the user has write access.
"""
import os
import sys
import time
import shutil
import tempfile
import argparse
import os
import shutil
import sys
import tempfile
import time
import numpy as np
import uhd
try:
import tqdm
HAVE_TQDM = True
except ImportError:
HAVE_TQDM = False
@ -42,53 +62,126 @@ BYTES_PER_SAMP = 4
# pylint: disable=too-many-arguments
def parse_args():
"""
Return parsed command line args
"""
"""Return parsed command line args."""
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--args", "-a", type=str, default="",
help="Device args to use when connecting to the USRP.")
parser.add_argument("-o", "--output-file", required=True,
help="Where to store the samples.")
parser.add_argument("-r", "--rate", type=float,
help="Sampling Rate (defaults to maximum rate)")
parser.add_argument("-f", "--freq", type=float, required=True, nargs="+",
help="Center frequency")
parser.add_argument("-g", "--gain", type=int, default=[10], nargs="+", help="Gain (dB)")
parser.add_argument("--antenna", help="Antenna", nargs="+")
parser.add_argument("-d", "--duration", type=float,
help="Duration in seconds to capture. "
"Default behavior is to fill the DRAM.")
parser.add_argument("--delay", "-l", type=float, default=0.5,
help="Capture delay in seconds")
parser.add_argument("-c", "--radio-channels", default=["0/Radio#0:0"], nargs="+",
help="List radios plus their channels "
"(defaults to \"0/Radio#0:0\")")
parser.add_argument("--block", "-b", type=str, default="0/Replay#0",
help="Replay block to test. Defaults to \"0/Replay#0\".")
parser.add_argument("--pkt-size", "-k", type=int, default=None,
help="Playback packet size in bytes. "
"Defaults to maximum packet size for this transport")
parser.add_argument("-n", "--numpy", default=False, action="store_true",
help="Save output file in NumPy format (default: No)")
parser.add_argument("--cpu-format", default="sc16", choices=["sc16", "fc32"],
help="Data format for storing data")
parser.add_argument("--throttle", type=float, default=0.25,
help="Throttle for streaming to host, range (0, 1] (default: 0.25)")
parser.add_argument("--no-progress", "-N", action="store_true",
help="Do not display progress bar during data transfer")
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"--args",
"-a",
type=str,
default="",
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"-o",
"--output-file",
required=True,
help="specifies the file path to store the captured samples [input is required].",
)
parser.add_argument(
"-r",
"--rate",
type=float,
help="specifies the sample rate in samples/sec [default = MCR].",
)
parser.add_argument(
"-f",
"--freq",
type=float,
required=True,
nargs="+",
help="specifies the center frequency in Hz for each channel [input is required].",
)
parser.add_argument(
"-g",
"--gain",
type=int,
default=[10],
nargs="+",
help="specifies the receive gain in dB for each channel [default = 10].",
)
parser.add_argument(
"--antenna",
type=str,
nargs="+",
help="specifies the antenna to use for each channel " "(e.g., TX/RX TX2) [default = None].",
)
parser.add_argument(
"-d",
"--duration",
type=float,
help="specifies the duration of the capture in seconds "
"[default = sets the duration such that DRAM completely fills].",
)
parser.add_argument(
"--delay",
"-l",
type=float,
default=0.5,
help="specifies the delay in seconds between issuing a capture command "
"on the host and starting the capture [default = 0.5].",
)
parser.add_argument(
"-c",
"--radio-channels",
default=["0/Radio#0:0"],
nargs="+",
help="specifies the list of radios plus their channels " '[default = "0/Radio#0:0"].',
)
parser.add_argument(
"--block",
"-b",
type=str,
default="0/Replay#0",
help='specifies the Replay block to test [default = "0/Replay#0"].',
)
parser.add_argument(
"--pkt-size",
"-k",
type=int,
default=None,
help="specifies the CHDR packet size in bytes to stream data to the host "
"[default = maximum CHDR packet size for this transport layer].",
)
parser.add_argument(
"-n",
"--numpy",
default=False,
action="store_true",
help="specifies whether to save the output file in NumPy format. If --numpy "
"is not specified, the output file will be saved in binary format.",
)
parser.add_argument(
"--cpu-format",
default="sc16",
choices=["sc16", "fc32"],
help="specifies the cpu format for storing data [default = sc16].",
)
parser.add_argument(
"--throttle",
type=float,
default=0.25,
help="specifies the throttle for streaming to host in the range of (0, 1]. "
"E.g., use 1 for maximum rate, use 0.5 for half the maximum rate, etc. "
"Note that other factors may affect the actual rate, such as the rate of "
"the source or the speed supported by the transport [default = 0.25].",
)
parser.add_argument(
"--no-progress",
"-N",
action="store_true",
help="specifies whether to display progress bar or not during data transfer.",
)
return parser.parse_args()
def enumerate_radios(graph, radio_chans):
"""
Return a list of radio/chan pairs to use for this test.
"""
"""Return a list of radio/chain pairs to use for this test."""
radio_id_chan_pairs = [
(r.split(':', 2)[0], int(r.split(':', 2)[1]))
if ':' in r else (r, 0)
for r in radio_chans
(r.split(":", 2)[0], int(r.split(":", 2)[1])) if ":" in r else (r, 0) for r in radio_chans
]
# Sanity checks
available_radios = graph.find_blocks("Radio")
@ -96,14 +189,12 @@ def enumerate_radios(graph, radio_chans):
for rcp in radio_id_chan_pairs:
if rcp[0] not in available_radios:
raise RuntimeError(f"'{rcp[0]}' is not a valid radio block ID!")
radio_chan_pairs.append(
(uhd.rfnoc.RadioControl(graph.get_block(rcp[0])), rcp[1]))
radio_chan_pairs.append((uhd.rfnoc.RadioControl(graph.get_block(rcp[0])), rcp[1]))
return radio_chan_pairs
def connect_radios(graph, replay, radio_chan_pairs, freqs, gains, antennas, rate):
"""
Set up the replay/radio part of the graph, and configure radios
"""
"""Set up the replay/radio part of the graph, and configure radios."""
if rate is None:
rate = radio_chan_pairs[0][0].get_rate()
print(f"Requested rate: {rate/1e6:.2f} Msps")
@ -112,27 +203,33 @@ def connect_radios(graph, replay, radio_chan_pairs, freqs, gains, antennas, rate
radio, chan = rcp
print(
f"Connecting {rcp[0].get_unique_id()}:{rcp[1]} to "
f"{replay.get_unique_id()}:{replay_port_idx}")
f"{replay.get_unique_id()}:{replay_port_idx}"
)
radio.set_rx_frequency(freqs[replay_port_idx % len(freqs)], rcp[1])
radio.set_rx_gain(gains[replay_port_idx % len(gains)], rcp[1])
if antennas is not None:
radio.set_rx_antenna(antennas[replay_port_idx % len(antennas)], rcp[1])
print(f"--> Radio settings: fc={radio.get_rx_frequency(chan)/1e6:.2f} MHz, "
print(
f"--> Radio settings: fc={radio.get_rx_frequency(chan)/1e6:.2f} MHz, "
f" gain={radio.get_rx_gain(chan)} dB, "
f"antenna={radio.get_rx_antenna(chan)}")
f"antenna={radio.get_rx_antenna(chan)}"
)
radio_to_replay_graph = uhd.rfnoc.connect_through_blocks(
graph,
rcp[0].get_unique_id(), rcp[1],
replay.get_unique_id(), replay_port_idx)
ddc_block = next((
graph, rcp[0].get_unique_id(), rcp[1], replay.get_unique_id(), replay_port_idx
)
ddc_block = next(
(
(x.dst_blockid, x.dst_port)
for x in radio_to_replay_graph
if uhd.rfnoc.BlockID(x.dst_blockid).get_block_name() == 'DDC'
), None)
if uhd.rfnoc.BlockID(x.dst_blockid).get_block_name() == "DDC"
),
None,
)
if ddc_block is not None:
print(f"Found DDC block on channel {chan}.")
this_rate = uhd.rfnoc.DdcBlockControl(
graph.get_block(ddc_block[0])).set_output_rate(rate, rcp[1])
this_rate = uhd.rfnoc.DdcBlockControl(graph.get_block(ddc_block[0])).set_output_rate(
rate, rcp[1]
)
else:
this_rate = rcp[0].set_rate(rate)
if actual_rate is None:
@ -144,9 +241,7 @@ def connect_radios(graph, replay, radio_chan_pairs, freqs, gains, antennas, rate
def _sanitize_args(replay, num_ports, num_bytes, pkt_size_bytes=None):
"""
Sanitize requested args based on the capabilities of the replay block
"""
"""Sanitize requested args based on the capabilities of the replay block."""
assert num_ports <= replay.get_num_input_ports()
## Figure out how many bytes to send
mem_size = replay.get_mem_size()
@ -157,11 +252,15 @@ def _sanitize_args(replay, num_ports, num_bytes, pkt_size_bytes=None):
print(f"Requested Record size per port: {num_bytes // 1024 // 1024} MiB")
if num_bytes > mem_size // num_ports:
num_bytes = mem_size // num_ports
print(f"WARNING: Exceeds allocated space per port! "
f"Reducing to {num_bytes // 1024 // 1024} MiB")
print(
f"WARNING: Exceeds allocated space per port! "
f"Reducing to {num_bytes // 1024 // 1024} MiB"
)
for port in range(num_ports):
print(f"Port {port} address space: "
f"0x{mem_stride*port:08X} - 0x{mem_stride*port+num_bytes:08X}")
print(
f"Port {port} address space: "
f"0x{mem_stride*port:08X} - 0x{mem_stride*port+num_bytes:08X}"
)
replay.set_play_type("sc16", 0)
replay.set_record_type("sc16", 0)
if pkt_size_bytes is not None:
@ -169,11 +268,8 @@ def _sanitize_args(replay, num_ports, num_bytes, pkt_size_bytes=None):
return mem_stride, num_bytes
def run_capture(graph, replay, radio_chan_pairs, mem_stride, num_samps, rate,
cap_delay):
"""
Record from radio into DRAM
"""
def run_capture(graph, replay, radio_chan_pairs, mem_stride, num_samps, rate, cap_delay):
"""Record from radio into DRAM."""
num_bytes = num_samps * BYTES_PER_SAMP
num_ports = len(radio_chan_pairs)
## Arm replay block for recording
@ -186,9 +282,9 @@ def run_capture(graph, replay, radio_chan_pairs, mem_stride, num_samps, rate,
stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
stream_cmd.num_samps = num_samps * rate_ratio
stream_cmd.stream_now = False
stream_cmd.time_spec = \
graph.get_mb_controller().get_timekeeper(0).get_time_now() + \
uhd.types.TimeSpec(cap_delay)
stream_cmd.time_spec = graph.get_mb_controller().get_timekeeper(
0
).get_time_now() + uhd.types.TimeSpec(cap_delay)
print(f"Requesting {num_samps} samples from {num_ports} radio(s)...")
print(f"Capture will take approx. {num_samps/rate:.1f} seconds...")
for rcp in radio_chan_pairs:
@ -196,32 +292,27 @@ def run_capture(graph, replay, radio_chan_pairs, mem_stride, num_samps, rate,
## Wait for record buffers to fill up
timeout = time.monotonic() + num_samps / rate + cap_delay + 2.0
if HAVE_TQDM:
with tqdm.tqdm(total=num_bytes*num_ports,
unit_scale=True, unit="byte") as pbar:
with tqdm.tqdm(total=num_bytes * num_ports, unit_scale=True, unit="byte") as pbar:
total_bytes_recorded = 0
while total_bytes_recorded < num_ports * num_bytes:
bytes_recorded = sum([
replay.get_record_fullness(port)
for port in range(num_ports)])
bytes_recorded = sum(
[replay.get_record_fullness(port) for port in range(num_ports)]
)
pbar.update(bytes_recorded - total_bytes_recorded)
total_bytes_recorded = bytes_recorded
time.sleep(0.100)
if time.monotonic() > timeout:
raise RuntimeError("Timeout while loading replay buffer!")
else:
while any((replay.get_record_fullness(port) < num_bytes
for port in range(num_ports))):
while any((replay.get_record_fullness(port) < num_bytes for port in range(num_ports))):
time.sleep(0.200)
if time.monotonic() > timeout:
raise RuntimeError("Timeout while loading replay buffer!")
return num_bytes // BYTES_PER_SAMP
def rx_data_to_host(replay, rx_streamer, output_data, mem_stride, num_samps,
pkt_size_bytes):
"""
Download data from a previously configured replay block via a streamer
object.
"""
def rx_data_to_host(replay, rx_streamer, output_data, mem_stride, num_samps, pkt_size_bytes):
"""Download data from previously configured replay block via streamer object."""
print("Downloading data to host...")
rx_md = uhd.types.RXMetadata()
num_ports = rx_streamer.get_num_channels()
@ -241,15 +332,16 @@ def rx_data_to_host(replay, rx_streamer, output_data, mem_stride, num_samps,
if HAVE_TQDM:
num_rx = 0
output_buf = np.zeros((num_ports, 1000 * max_samps_per_pkt), dtype=output_data.dtype)
with tqdm.tqdm(total=num_bytes*num_ports,
unit_scale=True, unit="byte") as pbar:
with tqdm.tqdm(total=num_bytes * num_ports, unit_scale=True, unit="byte") as pbar:
while num_rx < num_samps:
num_rx_i = rx_streamer.recv(output_buf, rx_md, 1.0)
if rx_md.error_code == uhd.types.RXMetadataErrorCode.timeout:
print("recv() timed out. Exiting...")
break
if rx_md.error_code == uhd.types.RXMetadataErrorCode.overflow and \
rx_md.out_of_sequence:
if (
rx_md.error_code == uhd.types.RXMetadataErrorCode.overflow
and rx_md.out_of_sequence
):
print("Detected sequence error!")
elif rx_md.error_code == uhd.types.RXMetadataErrorCode.overflow:
print("ERROR: Overflow detected!")
@ -269,9 +361,7 @@ def rx_data_to_host(replay, rx_streamer, output_data, mem_stride, num_samps,
def main():
"""
Run capture
"""
"""Run capture."""
global HAVE_TQDM
args = parse_args()
if args.no_progress:
@ -279,12 +369,13 @@ def main():
graph = uhd.rfnoc.RfnocGraph(args.args)
replay = uhd.rfnoc.ReplayBlockControl(graph.get_block(args.block))
radio_chan_pairs = enumerate_radios(graph, args.radio_channels)
rate = connect_radios(graph, replay, radio_chan_pairs,
args.freq, args.gain, args.antenna, args.rate)
rate = connect_radios(
graph, replay, radio_chan_pairs, args.freq, args.gain, args.antenna, args.rate
)
print(f"Using rate: {rate/1e6:.3f} Msps")
# Set up streamer
stream_args = uhd.usrp.StreamArgs(args.cpu_format, "sc16")
stream_args.args['throttle'] = str(args.throttle)
stream_args.args["throttle"] = str(args.throttle)
rx_streamer = graph.create_rx_streamer(len(radio_chan_pairs), stream_args)
num_ports = rx_streamer.get_num_channels()
for chan in range(len(radio_chan_pairs)):
@ -293,9 +384,7 @@ def main():
graph.connect(replay.get_unique_id(), chan, rx_streamer, chan)
graph.commit()
num_bytes = (
args.duration * rate * BYTES_PER_SAMP if args.duration is not None else None
)
num_bytes = args.duration * rate * BYTES_PER_SAMP if args.duration is not None else None
mem_stride, num_bytes = _sanitize_args(
replay,
len(radio_chan_pairs),
@ -303,30 +392,30 @@ def main():
pkt_size_bytes=args.pkt_size,
)
num_samps = run_capture(
graph, replay, radio_chan_pairs,
mem_stride, num_bytes//BYTES_PER_SAMP, rate,
args.delay)
graph, replay, radio_chan_pairs, mem_stride, num_bytes // BYTES_PER_SAMP, rate, args.delay
)
if args.numpy:
tmp_dir = tempfile.mkdtemp()
mmap_filename = os.path.join(tmp_dir, 'replay_capture.dat')
mmap_filename = os.path.join(tmp_dir, "replay_capture.dat")
else:
mmap_filename = args.output_file
cap_dtype = np.complex64 if args.cpu_format == 'fc32' else np.uint32
cap_dtype = np.complex64 if args.cpu_format == "fc32" else np.uint32
output_data = np.memmap(
mmap_filename, shape=(num_ports, num_samps), mode='w+', dtype=cap_dtype)
output_data = np.memmap(mmap_filename, shape=(num_ports, num_samps), mode="w+", dtype=cap_dtype)
output_data.flush()
rx_data_to_host(replay, rx_streamer, output_data,
mem_stride, num_samps, replay.get_max_packet_size(0))
rx_data_to_host(
replay, rx_streamer, output_data, mem_stride, num_samps, replay.get_max_packet_size(0)
)
if args.numpy:
print(f"Saving data as Numpy array to {args.output_file}...")
with open(args.output_file, 'wb') as out_file:
with open(args.output_file, "wb") as out_file:
np.save(out_file, output_data)
output_data = None
shutil.rmtree(tmp_dir)
return True
if __name__ == "__main__":
sys.exit(not main())

View File

@ -1,62 +1,141 @@
#!/usr/bin/env python3
#
# Copyright 2017-2018 Ettus Research, a National Instruments Company
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
RX samples to file using Python API
"""Stream received samples to the host and store in a file using Python API.
This example demonstrates how to use the UHD Python API to receive samples
from a USRP device and store them in a file on the host computer. It supports
two modes of operation:
1. Host based: This mode uses the MultiUSRP API
<https://files.ettus.com/manual/page_coding.html#coding_api_hilevel> to stream
received samples directly from the USRP to a host. This mode subjects the
host's ability (which depends on CPU speed, network interface speed, CPU thread
interruptions) to stream samples as fast as the USRP is receiving them.
2. RFNoC Replay: This mode uses advanced API functions to stream samples via
DRAM using capabilities of RFNoC (RF Network-on-Chip)
<https://www.ettus.com/sdr-software/rfnoc/>. The samples are first received and
buffered in DRAM and them streamed to host. This mode requires FPGA bitfiles
that contain the RFNoC Replay Block and is not supported by all USRP devices.
This example is useful for applications where users want to capture
samples from a USRP device for offline processing, analysis, or storage.
Example Usage:
rx_to_file.py --args addr=192.168.10.2 --output-file samples.dat --freq 2.45e9
--rate 20e6 --duration 15 --channels 0 1 --gain 30
Note: argument --output-file needs to point to a location where thr user has
write access.
"""
import argparse
import numpy as np
import uhd
from uhd.usrp import dram_utils
from uhd.types import StreamCMD, StreamMode
from uhd.usrp import dram_utils
def parse_args():
"""Parse the command line arguments"""
"""Parse the command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--args", default="", type=str)
parser.add_argument("-o", "--output-file", type=str, required=True)
parser.add_argument("-f", "--freq", type=float, required=True)
parser.add_argument("-r", "--rate", default=1e6, type=float)
parser.add_argument("-d", "--duration", default=5.0, type=float)
parser.add_argument("-c", "--channels", default=0, nargs="+", type=int)
parser.add_argument("-g", "--gain", type=int, default=10)
parser.add_argument("-n", "--numpy", default=False, action="store_true",
help="Save output file in NumPy format (default: No)")
parser.add_argument("--dram", action='store_true',
help="If given, will attempt to stream via DRAM")
parser.add_argument(
"-a",
"--args",
default="",
type=str,
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"-o",
"--output-file",
type=str,
required=True,
help="specifies the name of the file(s) to write binary samples to, "
"inserts channel number into the file name if more than one is given [default = None].",
)
parser.add_argument(
"-f",
"--freq",
type=float,
required=True,
help="specifies the center frequency in Hz [input is required].",
)
parser.add_argument(
"-r",
"--rate",
default=1e6,
type=float,
help="specifies the sample rate in samples/sec [default = 1e6].",
)
parser.add_argument(
"-d",
"--duration",
default=5.0,
type=float,
help="specifies the RX stream duration in seconds [default = 5.0].",
)
parser.add_argument(
"-c",
"--channels",
default=0,
nargs="+",
type=int,
help='specifies the channel(s) to use (specify "0", "1", "0 1", etc) [default = 0].',
)
parser.add_argument(
"-g",
"--gain",
type=int,
default=10,
help="specifies the receive gain in dB [default = 10].",
)
parser.add_argument(
"-n",
"--numpy",
default=False,
action="store_true",
help="specifies whether to save the output file in NumPy format. If --numpy "
"is not specified, the output file will be in binary format.",
)
parser.add_argument(
"--dram",
action="store_true",
help="specifies the mode of operation (Host based or RFNoC Replay). "
"If argument --dram is specified, the program will attempt to use the RFNoC "
"Replay mode, else the Host Based is used.",
)
return parser.parse_args()
def multi_usrp_rx(args):
"""
multi_usrp based RX example
"""
"""multi_usrp based RX example."""
usrp = uhd.usrp.MultiUSRP(args.args)
num_samps = int(np.ceil(args.duration * args.rate))
if not isinstance(args.channels, list):
args.channels = [args.channels]
samps = usrp.recv_num_samps(num_samps, args.freq, args.rate, args.channels, args.gain)
with open(args.output_file, 'wb') as out_file:
with open(args.output_file, "wb") as out_file:
if args.numpy:
np.save(out_file, samps, allow_pickle=False, fix_imports=False)
else:
samps.tofile(out_file)
def rfnoc_dram_rx(args):
"""
rfnoc_graph + replay-block based RX example
"""
"""rfnoc_graph + replay-block based RX example."""
# Init graph
graph = uhd.rfnoc.RfnocGraph(args.args)
num_samps = int(np.ceil(args.duration * args.rate))
if graph.get_num_mboards() > 1:
print(
"ERROR: This example only supports DRAM streaming on a single "
"motherboard.")
print("ERROR: This example only supports DRAM streaming on a single " "motherboard.")
return
# Init radios and replay block
available_radio_chans = [
@ -67,7 +146,7 @@ def rfnoc_dram_rx(args):
radio_chans = [available_radio_chans[x] for x in args.channels]
print("Receiving from radio channels:", end="")
print("\n* ".join((f"{r}:{c}" for r, c in radio_chans)))
dram = dram_utils.DramReceiver(graph, radio_chans, cpu_format='fc32')
dram = dram_utils.DramReceiver(graph, radio_chans, cpu_format="fc32")
replay = dram.replay_blocks[0]
print(f"Using replay block {replay.get_block_id()}")
for (radio, radio_chan), ddc_info in zip(dram.radio_chan_pairs, dram.ddc_chan_pairs):
@ -90,14 +169,15 @@ def rfnoc_dram_rx(args):
dram.issue_stream_cmd(stream_cmd)
rx_md = uhd.types.RXMetadata()
dram.recv(data, rx_md)
with open(args.output_file, 'wb') as out_file:
with open(args.output_file, "wb") as out_file:
if args.numpy:
np.save(out_file, data, allow_pickle=False, fix_imports=False)
else:
data.tofile(out_file)
def main():
"""RX samples and write to file"""
"""RX samples and write to file."""
args = parse_args()
if args.dram:
@ -105,5 +185,6 @@ def main():
else:
multi_usrp_rx(args)
if __name__ == "__main__":
main()

View File

@ -1,6 +1,6 @@
# !/usr/bin/env python3
#
# Copyright 2025 Ettus Research, a National Instruments Company
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""Example to transmit waveforms using UHD python API.
@ -27,8 +27,8 @@ pattern from DRAM. This mode requires FPGA bitfiles that contain the RFNoC
Replay Block and is not supported on all USRPs.
Example usage:
tx_waveforms.py [--args addr=192.168.10.2] --freq 2.4e9 [--rate 1e6 --duration 10
--channels 0 --wave-freq 1e4 --wave-ampl 0.3] [--dram]
tx_waveforms.py --args addr=192.168.10.2 --freq 2.4e9 --rate 1e6 --duration 10
--channels 0 --wave-freq 1e4 --wave-ampl 0.3 --dram
"""
import argparse
@ -54,7 +54,9 @@ def parse_args():
"--args",
type=str,
default="",
help='specifies the uhd device arguments, which holds multiple key value pairs separated by commas (e.g., addr=192.168.40.2,type=x300) [Default = ""]',
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"-w",

View File

@ -1,65 +1,130 @@
#!/usr/bin/env python3
#
# Copyright 2020 Ettus Research, a National Instruments Brand
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Use a calibrated USRP as a power meter
"""Use a calibrated USRP as a power meter.
This example demonstrates how to use a calibrated USRP device as a power
meter. It measures the received signal power in dBm at a specified center
frequency (plus a local oscillator offset) and within the configured analog
filter bandwidth. Results are provided either as a one-time measurement or
continuously until interrupted. This script is useful for applications
requiring precise power measurements, such as signal strength analysis or
calibration.
Refer to the UHD Manual on how to calibrate a USRP device:
https://files.ettus.com/manual/page_calibration.html
Example Usage:
usrp_power_meter.py --args addr=192.168.10.2 --freq 2.1e9 --rate 10e6
--bandwidth 5e6 --antenna RX1 --ref-level -10
--samps-per-est 1e7 --mode continuous
"""
import sys
import signal
import argparse
import signal
import sys
import uhd
def parse_args():
"""Parse the command line arguments"""
"""Parse the command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--args", default="",
help="USRP Device Args")
parser.add_argument("-f", "--freq", type=float, required=True,
help="Center Frequency")
parser.add_argument("-o", "--lo-offset", type=float, default=0.0,
help="Optional LO offset")
parser.add_argument("-c", "--channel", type=int, default=0,
help="USRP RX Channel Index")
parser.add_argument("-t", "--antenna",
help="USRP RX Antenna")
parser.add_argument("-r", "--rate", default=1e6, type=float,
help="Sampling Rate")
parser.add_argument("-b", "--bandwidth", type=float,
help="Analog filter bandwidth (if supported)")
parser.add_argument("-l", "--ref-level", type=float, default=-15,
help="RX reference power level. "
"This should be higher than the expected power.")
parser.add_argument("-n", "--samps-per-est", type=float, default=1e6,
help="Samples per estimate.")
parser.add_argument("--mode", choices=['one-shot', 'continuous'], default='one-shot',
help="Measure once, or keep measuring until Ctrl-C is pressed.")
parser.add_argument(
"-a",
"--args",
default="",
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"-f",
"--freq",
type=float,
required=True,
help="specifies the center frequency in Hz [input is required].",
)
parser.add_argument(
"-o",
"--lo-offset",
type=float,
default=0.0,
help="specifies the local oscillator (LO) offset with respect to the "
"configured center frequency [default = 0.0].",
)
parser.add_argument(
"-c",
"--channel",
type=int,
default=0,
help="specifies the RX Channel to use [default = 0].",
)
parser.add_argument("-t", "--antenna", help="specifies the RX Antenna to use [default = None].")
parser.add_argument(
"-r",
"--rate",
default=1e6,
type=float,
help="specifies the sample rate in samples/sec [default = 1e6].",
)
parser.add_argument(
"-b",
"--bandwidth",
type=float,
help="specifies the analog filter bandwidth in Hz (if supported) [default = None].",
)
parser.add_argument(
"-l",
"--ref-level",
type=float,
default=-15,
help="specifies the RX reference power level in dBm. The receiver will "
"automatically pick a gain such that an input CW tone at reference power "
"level will fully excite the ADC. The Reference power level should be higher "
"than the expected power level [default = -15].",
)
parser.add_argument(
"-n",
"--samps-per-est",
type=float,
default=1e6,
help="specifies the number of samples per estimate [default = 1e6].",
)
parser.add_argument(
"--mode",
choices=["one-shot", "continuous"],
default="one-shot",
help="specifies the operation mode, that is either measure once, "
"or keep measuring until Ctrl-C is pressed. [default = one-shot]",
)
return parser.parse_args()
def get_streamer(usrp, chan):
"""
Return an RX streamer with fc32 output
"""
stream_args = uhd.usrp.StreamArgs('fc32', 'sc16')
"""Return an RX streamer with fc32 output."""
stream_args = uhd.usrp.StreamArgs("fc32", "sc16")
stream_args.channels = [chan]
return usrp.get_rx_stream(stream_args)
def setup_device(usrp, args):
"""
Apply the settings from args to the device
"""
"""Apply the settings from args to the device."""
chan = args.channel
if chan > usrp.get_rx_num_channels():
print("ERROR: Invalid channel selected: {} (only {} channels available!)"
.format(chan, usrp.get_rx_num_channels()))
print(
"ERROR: Invalid channel selected: {} (only {} channels available!)".format(
chan, usrp.get_rx_num_channels()
)
)
raise RuntimeError("Invalid channel selected")
print("Using channel: {}".format(chan))
if args.antenna:
print("Setting RX antenna to `{}'...".format(args.antenna), end='')
print("Setting RX antenna to `{}'...".format(args.antenna), end="")
usrp.set_rx_antenna(args.antenna, chan)
print("OK")
@ -68,15 +133,14 @@ def setup_device(usrp, args):
print("ERROR: This device is not calibrated for RX at RF%d-%s!" % (chan, antenna))
raise RuntimeError("Device not calibrated for selected antenna")
print("Requesting RX rate of {} Msps...".format(args.rate / 1e6), end='')
print("Requesting RX rate of {} Msps...".format(args.rate / 1e6), end="")
usrp.set_rx_rate(args.rate, chan)
if abs(usrp.get_rx_rate(chan) - args.rate) > 1.0:
print("ALMOST. Actual rate: {} Msps"
.format(usrp.get_rx_rate(chan) / 1e6))
print("ALMOST. Actual rate: {} Msps".format(usrp.get_rx_rate(chan) / 1e6))
else:
print("OK")
print("Requesting RX frequency of {} MHz...".format(args.freq / 1e6), end='')
print("Requesting RX frequency of {} MHz...".format(args.freq / 1e6), end="")
tr = uhd.types.TuneRequest(args.freq, args.lo_offset)
usrp.set_rx_freq(tr, chan)
if abs(usrp.get_rx_freq(chan) - args.freq) > 1.0:
@ -84,8 +148,7 @@ def setup_device(usrp, args):
else:
print("OK")
print("Requesting RX power reference level of {:.2f} dBm..."
.format(args.ref_level), end='')
print("Requesting RX power reference level of {:.2f} dBm...".format(args.ref_level), end="")
usrp.set_rx_power_reference(args.ref_level, chan)
ref_level = usrp.get_rx_power_reference(chan)
if abs(ref_level - args.ref_level) > 1.0:
@ -94,45 +157,45 @@ def setup_device(usrp, args):
print("OK")
if args.bandwidth:
print("Requesting analog RX bandwidth of {} Msps..."
.format(args.bandwidth), end='')
print("Requesting analog RX bandwidth of {} Msps...".format(args.bandwidth), end="")
usrp.set_rx_bandwidth(args.bandwidth, chan)
if abs(usrp.get_rx_bandwidth(chan) - args.bandwidth) > 1.0:
print("ALMOST. Actual bandwidth: {} MHz"
.format(usrp.get_rx_bandwidth(chan) / 1e6))
print("ALMOST. Actual bandwidth: {} MHz".format(usrp.get_rx_bandwidth(chan) / 1e6))
else:
print("OK")
return (chan, ref_level)
RUN = True
def main():
"""
gogogo
"""
"""Run USRP power meter."""
args = parse_args()
usrp = uhd.usrp.MultiUSRP(args.args)
(chan, ref_level) = setup_device(usrp, args)
streamer = get_streamer(usrp, chan)
if args.mode == 'continuous':
if args.mode == "continuous":
def handle_sigint(_sig, _frame):
print("Caught Ctrl-C, exiting...")
global RUN
RUN = False
signal.signal(signal.SIGINT, handle_sigint)
while RUN:
try:
power_dbfs = uhd.dsp.signals.get_usrp_power(
streamer, num_samps=int(args.samps_per_est))
power_dbfs = uhd.dsp.signals.get_usrp_power(streamer, num_samps=int(args.samps_per_est))
except RuntimeError:
# This is a hack b/c the signal handler is not gracefully handling
# SIGINT
break
power_dbm = power_dbfs + ref_level
print("Received power: {:+6.2f} dBm".format(power_dbm))
if args.mode == 'one-shot':
if args.mode == "one-shot":
break
return True
if __name__ == "__main__":
sys.exit(not main())

View File

@ -1,87 +1,141 @@
#!/usr/bin/env python3
"""
#
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""x440 L-band capture example using Python API.
This example requires
-a USRP X440
-UHD 4.6 or higher
-matplotlib in Python installation
-the X4_1600 bitfile installed
-X4_1600 bitfile installed
Demonstrates dual rates on X440 at the example of the L-band by recording data into the replay
blocks at two given master clock rates and two different center frequencies and displays the
captured data in a pyplot. Since the X440 is a direct sampling device, Nyquist zones and their
boundaries are important to keep in mind. The converter rate determines the Nyquist zone
boundaries:
This example demonstrates simultaneous reception at two different rates in the
L band. Samples received at two different channels at two different master
clock rates and two different center frequencies are captured in two different
replay blocks. An FFT is computed for each of the two sets of captured data.
The FFT outputs are concatenated and displayed in a single plot. Since the X440
is a direct sampling device, Nyquist zones and their boundaries are important
to keep in mind. The converter rate determines the Nyquist zone boundaries:
Nyquist boundaries = n * (converter rate / 2)
The master clock rate can be derived from the converter rate by dividing it by the decimation
factor 2, 4 or 8. It is equivalent to the bandwidth that can be captured on one channel.
The maximum bandwidth that can be achieved by the X440 is 2 GHz when using the maximum converter
rate of 4 GSps. Although from a bandwidth perspective this would be sufficient to cover the L-band,
this does not work because these 2 GHz are roughly between 0 and 2 GHz while the L-band is located
between 1 GHz and 2.4 GHz. To work around this, two different rates can be used in the X440. The
captured spectra of both can be stitched together. This example does this in a visual way only to
demonstrate the principle. In that way we are crossing Nyquist gaps in the two spectra, but
you should apply external filtering to avoid the aliasing images that will be visible otherwise.
The master clock rate can be derived from the converter rate by dividing it by
decimation factor of 2, 4 or 8. It is equivalent to the bandwidth that can be
captured on one channel. The maximum master clock rate (and hence bandwidth)
that is supported by USRP X440 is 2 GHz, when using the maximum converter rate
of 4 GSps. Although from a bandwidth perspective this would be sufficient to
cover the L-band, this does not work because these 2 GHz are roughly between 0
and 2 GHz while the L-band is located between 1 GHz and 2.4 GHz. To work around
this, two different rates can be used in the X440. The captured spectra of both
can be stitched together. This example does this in a visual way only to
demonstrate this principle. In that way we are crossing Nyquist gaps in the two
spectra. Users should apply external filtering to avoid the aliasing images
that will be visible otherwise.
Because the X4_1600 bitfile is used, only channel 0 of each radio is available and will be used.
Because the X4_1600 bitfile is used, only channel 0 of each radio is available
and will be used.
By default this example will use master clock rates, converter rates and center frequencies to
cover the L-band. The parameters are:
By default, this example will use master clock rates, converter rates and
center frequencies to cover the L-band. The parameters are:
-Master clock rate 0: 1024 MHz (converter rate: 4.096 GHz)
-Master clock rate 1: 1280 MHz (converter rate: 2.560 GHz)
-Center frequency 0: 0.9 GHz
-Center frequency 1: 2 GHz
With these settings, radio 0 will be able to capture the L-band between 0.39 GHz and 1.4 GHz in its
first Nyquist zone, and radio 1 will cover the range between 1.36 GHz and 2.64 GHz with its second
Nyquist zone.
With these settings, radio 0 will be able to capture the L-band between 0.39 GHz
and 1.4 GHz in its first Nyquist zone, and radio 1 will cover the range between
1.36 GHz and 2.64 GHz with its second Nyquist zone.
Example usage:
x440_L_band_capture.py --args addr=192.168.10.2 --mcrs 1024e6 1280e6
--freq 1.1e9 2.3e9 --antenna RX1 RX2 --duration 0.005
--delay 0.015 --throttle 0.3
"""
import time
import argparse
import sys
import uhd
from uhd import rfnoc
import numpy as np
import time
import matplotlib.pyplot as plt
import numpy as np
import uhd
# sc16 (32-bit) samples on the USRP
BYTES_PER_SAMP = 4
# pylint: disable=too-many-arguments
def parse_args():
"""
Return parsed command line args
"""
"""Return parsed command line args."""
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--args", "-a", type=str, default="",
help="Device args to use when connecting to the USRP.")
parser.add_argument("--mcrs", "-m", type=float, required=False, nargs=2,
help="Master clock rates for both channels, default "
"1024e6 1280e6",
default=[1024e6,1280e6])
parser.add_argument("-f", "--freq", type=float, required=False, nargs=2,
help="Center frequencies for 2 channels, default 900e6 2e9",
default=[900e6, 2e9])
parser.add_argument("--antenna", help="Antennas for both channels, default "
"RX1 RX1", nargs=2,
default=['RX1', 'RX1'])
parser.add_argument("-d", "--duration", type=float,
help="Duration in seconds to capture. "
"Default is 0.0001 seconds.", default=0.0001)
parser.add_argument("--delay", "-l", type=float, default=0.01,
help="Capture delay in seconds, default 0.01")
parser.add_argument("--throttle", type=float, default=0.25,
help="Throttle for streaming to host, range (0, 1] (default: 0.25)")
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"--args",
"-a",
type=str,
default="",
help="""specifies the USRP device arguments, which holds
multiple key value pairs separated by commas
(e.g., addr=192.168.40.2,type=x300) [default = ""].""",
)
parser.add_argument(
"--mcrs",
"-m",
type=float,
required=False,
nargs=2,
help="specifies the master clock rates in samples/sec for both receive channels "
"[default = 1024e6 1280e6].",
default=[1024e6, 1280e6],
)
parser.add_argument(
"-f",
"--freq",
type=float,
required=False,
nargs=2,
help="specifies the center frequencies in Hz for both receive channels "
"[default = 900e6 2e9].",
default=[900e6, 2e9],
)
parser.add_argument(
"--antenna",
nargs=2,
help="specifies the antennas for both receive channels [default = RX1 RX1].",
default=["RX1", "RX1"],
)
parser.add_argument(
"-d",
"--duration",
type=float,
help="specifies the duration in seconds to capture [default = 0.0001].",
default=0.0001,
)
parser.add_argument(
"--delay",
"-l",
type=float,
default=0.01,
help="specifies the delay in seconds between issuing a capture command "
"on the host and starting the capture [default = 0.01].",
)
parser.add_argument(
"--throttle",
type=float,
default=0.25,
help="specifies the throttle for streaming to host in the range of (0, 1]. "
"E.g., use 1 for maximum rate, use 0.5 for half the maximum rate, etc. "
"Note that other factors may affect the actual rate, such as the rate of "
"the source or the speed supported by the transport [default = 0.25].",
)
return parser.parse_args()
def main():
"""
Main function of the dual rate example.
"""
"""Main function of the dual rate example."""
args = parse_args()
dev_args = f"{args.args},product=x440,master_clock_rate=({args.mcrs[0]};{args.mcrs[1]})"
@ -115,7 +169,7 @@ def main():
cap_dtype = np.complex64
stream_args = uhd.usrp.StreamArgs("fc32", "sc16")
stream_args.args['throttle'] = str(throttle)
stream_args.args["throttle"] = str(throttle)
# Set up streamer0
rx_streamer0 = graph.create_rx_streamer(num_ports, stream_args)
@ -131,26 +185,37 @@ def main():
cap_delay = args.delay
update_interval = 0.2 # Update every update_interval seconds.
# Plotting initialization.
plt.ion() # Stop matplotlib windows from blocking
plt.rcParams['toolbar'] = 'None'
plt.rcParams["toolbar"] = "None"
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 6), gridspec_kw = {'wspace':0, 'hspace':0})
fig, axes = plt.subplots(
nrows=1, ncols=2, figsize=(15, 6), gridspec_kw={"wspace": 0, "hspace": 0}
)
ax_replay0 = axes[0]
ax_replay1 = axes[1]
# Setup figure, axis and initiate plot
xdata_replay0, ydata_replay0 = [], []
ln_replay0, = ax_replay0.plot([], [],)
ax_replay0.title.set_text(f"Radio0 operating at {round(radio0.get_rate()/1e6, 2)} MSps tuned to {round(radio0.get_rx_frequency(0)/1e9, 2)} GHz.")
(ln_replay0,) = ax_replay0.plot(
[],
[],
)
ax_replay0.title.set_text(
f"Radio0 operating at {round(radio0.get_rate()/1e6, 2)} MSps tuned to {round(radio0.get_rx_frequency(0)/1e9, 2)} GHz."
)
ax_replay0.set_ylim(-160, 20)
ax_replay0.grid()
ax_replay0.margins(x=0)
xdata_replay1, ydata_replay1 = [], []
ln_replay1, = ax_replay1.plot([], [],)
ax_replay1.title.set_text(f"Radio1 operating at {round(radio1.get_rate()/1e6, 2)} MSps tuned to {round(radio1.get_rx_frequency(0)/1e9, 2)} GHz.")
(ln_replay1,) = ax_replay1.plot(
[],
[],
)
ax_replay1.title.set_text(
f"Radio1 operating at {round(radio1.get_rate()/1e6, 2)} MSps tuned to {round(radio1.get_rx_frequency(0)/1e9, 2)} GHz."
)
ax_replay1.set_ylim(-160, 20)
ax_replay1.grid()
ax_replay1.margins(x=0)
@ -186,12 +251,9 @@ def main():
stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
stream_cmd.num_samps = num_samps0
stream_cmd.stream_now = False
stream_cmd.time_spec = \
time_now + \
uhd.types.TimeSpec(cap_delay)
stream_cmd.time_spec = time_now + uhd.types.TimeSpec(cap_delay)
radio0.issue_stream_cmd(stream_cmd, 0)
# Record from radio1 into DRAM using replay1
num_samps1 = int(args.mcrs[1] * capture_duration)
num_bytes1 = num_samps1 * BYTES_PER_SAMP
@ -205,23 +267,23 @@ def main():
stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
stream_cmd.num_samps = num_samps1
stream_cmd.stream_now = False
stream_cmd.time_spec = \
time_now + \
uhd.types.TimeSpec(cap_delay)
stream_cmd.time_spec = time_now + uhd.types.TimeSpec(cap_delay)
radio1.issue_stream_cmd(stream_cmd, 0)
# Wait for record buffers to fill up
timeout = time.monotonic() + num_samps0 / args.mcrs[0] + cap_delay + 10
while any((replay0.get_record_fullness(port) < num_bytes0
for port in range(num_ports))):
while any(
(replay0.get_record_fullness(port) < num_bytes0 for port in range(num_ports))
):
time.sleep(0.100)
if time.monotonic() > timeout:
raise RuntimeError("Timeout while loading replay0 buffer!")
## Wait for record buffers to fill up
timeout = time.monotonic() + num_samps1 / args.mcrs[1] + cap_delay + 10
while any((replay1.get_record_fullness(port) < num_bytes1
for port in range(num_ports))):
while any(
(replay1.get_record_fullness(port) < num_bytes1 for port in range(num_ports))
):
time.sleep(0.100)
if time.monotonic() > timeout:
raise RuntimeError("Timeout while loading replay1 buffer!")
@ -230,8 +292,8 @@ def main():
rx_md = uhd.types.RXMetadata()
num_bytes = num_samps0 * BYTES_PER_SAMP
pkt_size_bytes = replay0.get_max_packet_size(0)
max_samps_per_pkt = pkt_size_bytes // BYTES_PER_SAMP
# pkt_size_bytes = replay0.get_max_packet_size(0)
# max_samps_per_pkt = pkt_size_bytes // BYTES_PER_SAMP
mem_stride = replay0.get_mem_size() // num_ports
# Configure playback regions
for idx in range(num_ports):
@ -244,7 +306,6 @@ def main():
stream_cmd.time_spec = uhd.types.TimeSpec(0.0)
rx_streamer0.issue_stream_cmd(stream_cmd)
num_rx = rx_streamer0.recv(output_buf_replay0, rx_md, 5.0)
if rx_md.error_code != uhd.types.RXMetadataErrorCode.none:
print("Error during download: " + rx_md.strerror())
@ -252,22 +313,28 @@ def main():
print("ERROR: Fewer samples received than expected!")
# Get the new data
N = len(output_buf_replay0)
window = np.hamming(N)
len_buf_replay0 = len(output_buf_replay0)
window = np.hamming(len_buf_replay0)
fft = np.fft.fft(output_buf_replay0 * window)
window_power = sum(window * window) / N
logfft = 20*np.log10(np.abs(np.fft.fftshift(fft))) - 10*np.log10(window_power) - 20*np.log10(N) + 3
xdata_replay0 = np.arange(-N/2,N/2,1) / N * args.mcrs[0] + radio0.get_rx_frequency(0)
window_power = sum(window * window) / len_buf_replay0
logfft = (
20 * np.log10(np.abs(np.fft.fftshift(fft)))
- 10 * np.log10(window_power)
- 20 * np.log10(len_buf_replay0)
+ 3
)
xdata_replay0 = np.arange(
-len_buf_replay0 / 2, len_buf_replay0 / 2, 1
) / len_buf_replay0 * args.mcrs[0] + radio0.get_rx_frequency(0)
ydata_replay0 = logfft
output_buf_replay1 = np.zeros(num_samps1, dtype=cap_dtype)
# print("Downloading data to host from Replay1...")
rx_md = uhd.types.RXMetadata()
num_bytes = num_samps1 * BYTES_PER_SAMP
pkt_size_bytes = replay1.get_max_packet_size(0)
max_samps_per_pkt = pkt_size_bytes // BYTES_PER_SAMP
# pkt_size_bytes = replay1.get_max_packet_size(0)
# max_samps_per_pkt = pkt_size_bytes // BYTES_PER_SAMP
mem_stride = replay1.get_mem_size() // num_ports
# Configure playback regions
for idx in range(num_ports):
@ -280,7 +347,6 @@ def main():
stream_cmd.time_spec = uhd.types.TimeSpec(0.0)
rx_streamer1.issue_stream_cmd(stream_cmd)
num_rx = rx_streamer1.recv(output_buf_replay1, rx_md, 5.0)
if rx_md.error_code != uhd.types.RXMetadataErrorCode.none:
print("Error during download: " + rx_md.strerror())
@ -288,12 +354,19 @@ def main():
print("ERROR: Fewer samples received than expected!")
# Get the new data
N = len(output_buf_replay1)
window = np.hamming(N)
len_buf_replay1 = len(output_buf_replay1)
window = np.hamming(len_buf_replay1)
fft = np.fft.fft(output_buf_replay1 * window)
window_power = sum(window * window) / N
logfft = 20*np.log10(np.abs(np.fft.fftshift(fft))) - 10*np.log10(window_power) - 20*np.log10(N) + 3
xdata_replay1 = np.arange(-N/2,N/2,1) / N * args.mcrs[1] + radio1.get_rx_frequency(0)
window_power = sum(window * window) / len_buf_replay1
logfft = (
20 * np.log10(np.abs(np.fft.fftshift(fft)))
- 10 * np.log10(window_power)
- 20 * np.log10(len_buf_replay1)
+ 3
)
xdata_replay1 = np.arange(
-len_buf_replay1 / 2, len_buf_replay1 / 2, 1
) / len_buf_replay1 * args.mcrs[1] + radio1.get_rx_frequency(0)
ydata_replay1 = logfft
# Reset the data in the plot
@ -312,7 +385,6 @@ def main():
fig.canvas.draw()
fig.canvas.flush_events()
time.sleep(update_interval)
except KeyboardInterrupt: