Buffers

Using buffers or transmitting and receiving data is done through interacting with two methods.

For receivers this is the rx method. How data is captured and therefore produced by this method is dependent on two main properties:

  • rx_enabled_channels: This is an array of integers (or channel names) and the number of elements in the array will determine the number of list items returned by rx. For devices with complex data types these are the indexes of the complex channels, not the individual I or Q channels. When len(rx_enabled_channels) == 1, rx will return just a single array and not a single array within a list.

  • rx_buffer_size: This is the number of samples returned in each array within the list. If the device produces complex data, like a transceiver, it will return complex data. This is defined by the author of each device specific class.

For transmitters this is the tx method. How data is sent and therefore must be passed by this method is dependent on one main property:

  • tx_enabled_channels: This is an array of integers and the number of elements in the array will determine the number of items in the list to be submitted to tx. Like for rx_enabled_channels, devices with complex data types these are the indexes of the complex channels, not the individual I or Q channels. When only a single channel is enabled the data can be passed to tx as just an array and not an array within a list.

rx_enabled_channels must have a length greater than zero but tx_enabled_channels can be set to None or an empty list. In this case when tx is called it must be called without inputs. This is a special case and will connect a zero source into the TX input stream within the FPGA for FPGA based devices. For background on how this internally works with FPGA based devices reference the generic DAC driver.

Cyclic Mode

In many cases, it can be useful to continuously transmit a signal over and over, even for just debugging and testing. This can be especially handy when the hardware you are using has very high transmit or receive rates, and therefore impossible to keep providing data to in real-time. To complement these use cases it is possible to create transmit buffer which repeats, which we call cyclic buffers. Cyclic buffers are identical or normal or non-cyclic buffers, except when they reach hardware they will continuously repeat or be transmitted. Here is a small example on how to create a cyclic buffer:

import adi

sdr = adi.ad9361()
# Create a complex sinusoid
fc = 3000000
N = 1024
ts = 1 / 30000000.0
t = np.arange(0, N * ts, ts)
i = np.cos(2 * np.pi * t * fc) * 2 ** 14
q = np.sin(2 * np.pi * t * fc) * 2 ** 14
iq = i + 1j * q
# Enable cyclic buffers
sdr.tx_cyclic_buffer = True
# Send data cyclically
sdr.tx(iq)

At this point, the transmitter will keep transmitting the create sinusoid indefinitely until the buffer is destroyed or the sdr object destructor is called. Once data is pushed to hardware with a cyclic buffer the buffer must be manually destroyed or an error will occur if more data push. To update the buffer use the tx_destroy_buffer method before passing a new vector to the tx method.

Annotated Buffers

By default buffers appear as an array or a list of arrays. This can be confusing if all your channels do not produce similar data. For example, for IMUs like ADI16495 certain channels are for acceleration data and others are for angular velocity. To label this data the rx_annotated property can be used. When setting it to True the output of the rx method will be a dictionary with keys as channel names. Here an example:

import adi

dev = adi.adis16495()
dev.rx_enabled_channels = [0, 3]
print(dev.rx())
dev.rx_annotated = True
print(dev.rx())

With output

[array([    35681,     84055,   -175914,   -203645,    698249,    -51670,
      -1770250,   1529968,   2586191,  -5353355,   -827741,  11736339,
      -9847894, -17242014,  97421833, 277496774], dtype=int32),
array([     49151,     753663,    3571711,    9928703,   18956287,
         25165823,   18612223,  -10125313,  -60850176, -114491392,
      -131350528,  -61521920,  135069695,  466845695,  899235839,
      1362378751], dtype=int32)]
{'accel_x': array([1775091711, 2072264703, 2147483647, 2147483647, 2147483647,
      2147483647, 2143404031, 2125430783, 2123120639, 2130821119,
      2139488255, 2144911359, 2147041279, 2147467263, 2147483647,
      2147483647], dtype=int32),
'anglvel_x': array([357750219, 335109279, 323033231, 337667193, 337100396, 330408402,
      333459194, 335322576, 333247166, 333223475, 333996322, 333805525,
      333659152, 333664680, 333718473, 333895650], dtype=int32)}

Buffer Units

For certain devices it is possible to convert types to scientific units, such as volts, degrees, or meters per second among others. This is controlled by setting the property rx_output_type to either raw or SI. If set to SI, returned data from the rx method will be in scientific units (assuming its supported by the driver). Below is an example using an IMU:

import adi

dev = adi.adis16495()
dev.rx_annotated = True  # Make channel names appear in data
dev.rx_enabled_channels = [3]  # channel 0 is angular velocity in the x direction
print(dev.rx())
dev.rx_output_type = "SI"
print(dev.rx())

With output

{'anglvel_x': array([    35644,     84039,   -175647,   -203867,    697612,    -50201,
       -1770177,   1526291,   2589741,  -5349126,   -839188,  11738313,
       -9824911, -17267701,  97333042, 277410285], dtype=int32)}
{'anglvel_x': array([9.29996712, 9.71257202, 9.40097973, 9.78345151, 9.77009362,
      9.59662456, 9.67300333, 9.71593538, 9.65847317, 9.6580597 ,
      9.68022501, 9.67715545, 9.67511814, 9.67609361, 9.67323293,
      9.67104074])}

To understand the exact scaling the driver documentation should be reviewed.

Members

class adi.rx_tx.rx(rx_buffer_size=1024)

Buffer handling for receive devices

rx()

Receive data from hardware buffers for each channel index in rx_enabled_channels.

returns: type=numpy.array or list of numpy.array

An array or list of arrays when more than one receive channel is enabled containing samples from a channel or set of channels. Data will be complex when using a complex data device.

property rx_annotated: bool

rx_annotated: Set output data from rx() to be annotated

property rx_buffer_size

rx_buffer_size: Size of receive buffer in samples

property rx_channel_names: List[str]

rx_channel_names: List of RX channel names

rx_destroy_buffer()

rx_destroy_buffer: Clears RX buffer

property rx_enabled_channels: List[int]

rx_enabled_channels: List of enabled channels (channel 1 is 0)

Either a list of channel numbers or channel names can be used to set rx_enabled_channels. When channel names are used, they will be translated to channel numbers.

property rx_output_type: str

rx_output_type: Set output data type from rx()

class adi.rx_tx.rx_def(*args: Union[str, Context], **kwargs: Union[str, Context])

Template metaclass for rx only device specific interfaces.

class adi.rx_tx.rx_tx
class adi.rx_tx.rx_tx_common

Common functions for RX and TX

class adi.rx_tx.rx_tx_def(*args: Union[str, Context], **kwargs: Union[str, Context])

Template metaclass for rx and tx device specific interfaces.

class adi.rx_tx.shared_def(*args: Union[str, Context], **kwargs: Union[str, Context])

Shared components for rx and tx metaclasses.

class adi.rx_tx.tx(tx_cyclic_buffer=False)

Buffer handling for transmit devices

tx(data_np=None)

Transmit data to hardware buffers for each channel index in tx_enabled_channels.

args: type=numpy.array or list of numpy.array

An array or list of arrays when more than one transmit channel is enabled containing samples from a channel or set of channels. Data must be complex when using a complex data device.

property tx_channel_names

tx_channel_names: Names of the transmit channels

property tx_cyclic_buffer

tx_cyclic_buffer: Enable cyclic buffer for TX

tx_destroy_buffer()

tx_destroy_buffer: Clears TX buffer

property tx_enabled_channels

tx_enabled_channels: List of enabled channels (channel 1 is 0)

Either a list of channel numbers or channel names can be used to set tx_enabled_channels. When channel names are used, they will be translated to channel numbers.

class adi.rx_tx.tx_def(*args: Union[str, Context], **kwargs: Union[str, Context])

Template metaclass for rx only device specific interfaces.

Buffer Examples

Collect data from one channel

import adi

sdr = adi.ad9361()
# Get complex data back
sdr.rx_enabled_channels = [0]
chan1 = sdr.rx()

Collect data from two channels

import adi

sdr = adi.ad9361()
# Get both complex channel back
sdr.rx_enabled_channels = [0, 1]
data = sdr.rx()
chan1 = data[0]
chan2 = data[1]

Send data on two channels

import adi
import numpy as np

# Create radio
sdr = adi.ad9371()
sdr.tx_enabled_channels = [0, 1]
# Create a sinewave waveform
N = 1024
fs = int(sdr.tx_sample_rate)
fc = 40000000
ts = 1 / float(fs)
t = np.arange(0, N * ts, ts)
i = np.cos(2 * np.pi * t * fc) * 2 ** 14
q = np.sin(2 * np.pi * t * fc) * 2 ** 14
iq = i + 1j * q
fc = -30000000
i = np.cos(2 * np.pi * t * fc) * 2 ** 14
q = np.sin(2 * np.pi * t * fc) * 2 ** 14
iq2 = i + 1j * q
# Send data to both channels
sdr.tx([iq, iq2])