mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
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:
parent
5df2c21265
commit
4c74532d68
@ -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()
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -120,17 +187,17 @@ def main():
|
||||
print(metadata.strerror())
|
||||
if samps:
|
||||
real_samps = min(num_samps - recv_samps, samps)
|
||||
samples[:, recv_samps:recv_samps + real_samps] = recv_buffer[:, 0:real_samps]
|
||||
samples[:, recv_samps : recv_samps + real_samps] = recv_buffer[:, 0:real_samps]
|
||||
recv_samps += real_samps
|
||||
|
||||
# Get the power in each bin
|
||||
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()
|
||||
|
||||
@ -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.
|
||||
#
|
||||
|
||||
@ -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.
|
||||
"""
|
||||
|
||||
def get_stream_cmd(usrp, rate, duration, start_time=None):
|
||||
"""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())
|
||||
|
||||
@ -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()
|
||||
@ -240,23 +331,24 @@ def rx_data_to_host(replay, rx_streamer, output_data, mem_stride, num_samps,
|
||||
rx_streamer.issue_stream_cmd(stream_cmd)
|
||||
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:
|
||||
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:
|
||||
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!")
|
||||
elif rx_md.error_code != uhd.types.RXMetadataErrorCode.none:
|
||||
print("ERROR: recv() gave unexpected error code: " + rx_md.strerror())
|
||||
pbar.update(num_rx_i * BYTES_PER_SAMP * num_ports)
|
||||
output_data[:, num_rx:num_rx+num_rx_i] = output_buf[:, 0:num_rx_i]
|
||||
output_data[:, num_rx : num_rx + num_rx_i] = output_buf[:, 0:num_rx_i]
|
||||
num_rx += num_rx_i
|
||||
else:
|
||||
num_rx = rx_streamer.recv(output_data, rx_md, 5.0)
|
||||
@ -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())
|
||||
|
||||
@ -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))
|
||||
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))
|
||||
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()
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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]})"
|
||||
@ -112,10 +166,10 @@ def main():
|
||||
num_samps1 = 0
|
||||
num_ports = 1
|
||||
|
||||
cap_dtype=np.complex64
|
||||
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,27 +185,38 @@ 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.")
|
||||
ax_replay0.set_ylim(-160,20)
|
||||
(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.")
|
||||
ax_replay1.set_ylim(-160,20)
|
||||
(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...")
|
||||
# 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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user