USRP Hardware Driver and USRP Manual  Version: 3.13.0.0-266-gd8c8cf3e5
UHD and USRP Manual
Python API

UHD supports a Python API, in case the C++ or C APIs are not the right solution for your application.

Installing the Python API

In order to install the Python API when building UHD from source, make sure all the dependencies are available (see also Building and Installing UHD from source, you need Boost.Python from your Boost library). Make sure you have the CMake variable ENABLE_PYTHON_API set to ON (e.g., by running cmake -DENABLE_PYTHON_API=ON).

Python 2 vs. 3

The Python API supports both Python 2 and 3, but if you have both versions installed, CMake might require some hints which version is the desired one. To force Python 3, UHD has a CMake variable ENABLE_PYTHON3. If you set it, e.g., by running cmake -DENABLE_PYTHON3=ON, it will force the usage of Python 3.

Installing on Windows

On Windows, only certain combinations of MSVC and Boost have proven functional. The following combinations are known to work (others might also work):

  • Visual Studio 2017 (version 15.7.3), Release X64 on Windows 10 with Boost 1.65.1 and Boost 1.66, Python27 x64 bit.

Static linking on is currently unsupported on Windows.

Using the Python API

The Python API mirrors the C++ API, so the C++ reference manual can be used to understand the behaviour of the Python API as well.

Names in the Python API have been modified to follow a PEP8-compatible naming convention, for example, uhd::usrp::multi_usrp in C++ corresponds to uhd.usrp.MultiUSRP in Python (this makes UHD/Python code implicitly compatible with most linters, but it also has the side-effect of hiding symbols that get imported from the C++ domain). The following two snippets are equivalent. First the C++ version:

// ...
auto usrp = uhd::usrp::multi_usrp::make("type=b200");
usrp->set_rx_freq(100e6);

Now the Python version:

import uhd
# ...
usrp = uhd.usrp.MultiUSRP("type=b200")
usrp.set_rx_freq(100e6)

Not all API calls from the C++ API are also supported in the Python API, and the Python API has some additional functions that are not available in C++, but for the most part, the uhd::usrp::multi_usrp API is identical.

One-off transmit/receive applications

A common type of Python-based SDR applications are those which produce or consume a limited number of samples. For example, an application could receive a second's worth of samples, then do offline processing, print the result, and exit. For this case, convenience API calls were added to the Python API. The following snippet is an example of how to store 1 second of samples acquired at 1 Msps:

import uhd
def recv_to_file():
"""RX samples and write to file"""
usrp = uhd.usrp.MultiUSRP("type=b200")
num_samps = 1e6
if not isinstance(args.channels, list):
args.channels = [args.channels]
samps = usrp.recv_num_samps(
1e6, # Number of samples
2.4e9, # Frequency in Hz
1e6, # Sampling rate
[0], # Receive on channel 0
80, # 80 dB of RX gain
)
samps.tofile('samples.dat')

This kind of API is particularly useful in combination with Jupyter Notebooks or similar interactive environments.

Python Global Interpreter Lock

From the Python wiki page on the GIL:

In CPython, the global interpreter lock, or GIL, is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once.

During some performance-critical function calls, the UHD Python API releases the GIL, during which Python objects have their contents modified. The functions calls which do so are uhd::rx_streamer::recv, uhd::tx_streamer::send, and uhd::tx_streamer::recv_async_msg. To be clear, the functions listed here violate the expected contract set out by the GIL by accessing Python objects (from C++) without holding the GIL. This is necessary to achieve rates similar to what the C++ API can provide.

To this end, users must ensure that the Python objects accessed by the listed functions are handled with care. In simple, single threaded applications, this won't require any extra work. However, in more complicated and/or multi- threaded applications, steps must be taken to avoid thread-unsafe behavior. For example, if an application needs to call recv() in one thread, and access the sample buffer from another thread, a synchronization method (ie. a mutex) must be used to safeguard access to that buffer.