pyadi-iio Python Interfaces
repository·main·Indexed 42 days ago
https://github.com/analogdevicesinc/pyadi-iioHigh-level Python abstractions for Analog Devices hardware using Industrial I/O (IIO) drivers. It simplifies libIIO interactions into device classes for easier hardware configuration and data capture. Features include support for RX/TX data transfer, cyclic buffers, and a Model Context Protocol (MCP) server for runtime device introspection and control. Includes examples for CN0556 and CN0565 boards, including Electrical Impedance Tomography (EIT) capabilities.
What's inside pyadi-iio
- pyadi-iio is a Python abstraction module designed for Analog Devices (ADI) hardware that utilizes Industrial I/O (IIO) drivers. It provides custom interface classes for specific hardware components and development systems, reducing the boilerplate code typically required when using the raw libIIO interface, particularly when working with buffers.
Use the ad9081_mc multi-chip interface
mainThe
adi.ad9081_mcclass provides a scalable Python interface for managing multiple AD9081 chips within a single libIIO context. It automatically handles driver selection, chip arrangements (CDDC/FDDC/CDUC/FDUC), and DMA/DDS IP management.While the interface is similar to the single-chip
adi.ad9081class, it handles property access differently depending on whether you are interacting with a single chip or a multi-chip configuration.import adi dev = adi.ad9081_mc()Synchronize DMA transfers using sync_start
mainIn specific HDL reference designs, you can synchronize transfers between the transmit (TX) and receive (RX) data paths. This is critical for radar processing, communications, and instrumentation where the timing of the receive capture must be triggered by the transmitted data.
This functionality is managed via the
sync_startclass. It uses special control signals within the FPGA fabric to trigger receive captures from transmitted buffers, ensuring that data enters the receive DMA at a precise moment relative to the transmit trigger.For a complete implementation pattern, refer to the
ad9081_sync_start_example.pyexample in the repository.Use annotated buffers to label received data
mainBy default,
rxreturns a list of arrays. For devices with heterogeneous channels (e.g., IMUs with both acceleration and angular velocity), you can userx_annotatedto receive a dictionary where keys are the channel names.Set
dev.rx_annotated = Trueto enable this mapping.import adi dev = adi.adis16495() dev.rx_enabled_channels = [0, 3] print(dev.rx()) # Returns list of arrays dev.rx_annotated = True print(dev.rx()) # Returns dictionary with channel name keysImplement new device class interfaces
mainWhen developing new device-specific classes, do not reimplement device discovery or channel initialization. Instead, inherit from the common base classes provided inadi.device_base.How device base classes work in pyadi-iio
mainpyadi-iio provides parent classes in
adi.device_baseto encapsulate boilerplate for device discovery, channel initialization, and buffer setup. Instead of re-implementing initialization patterns, new drivers should subclass these base classes to ensure consistent behavior across the library.Available Base Classes
All base classes are located in
adi/device_base.py:device_base: A core mixin that handles device name validation againstcompatible_parts, device index selection, and channel instance creation via_add_channel_instances. It is intended to be combined with an RX or TX class rather than used directly.rx_chan_comp/rx_chan_comp_no_buff: RX device classes. Userx_chan_compfor devices with buffer support, andrx_chan_comp_no_bufffor devices that do not expose a capture buffer. These also support complex channel objects.tx_chan_comp/tx_chan_comp_no_buff: TX equivalents of the RX classes above.
When to avoid these classes
These classes are designed for the common pattern of a single control device with one optional RX or TX buffer. If your device has a complex topology (e.g., multi-chip systems, mixed DMA paths, or non-standard channel discovery), you should compose
rx,tx, andcontext_managerdirectly instead of using these base classes.from adi.device_base import rx_chan_comp class MyDevice(rx_chan_comp): # Implementation details passHow CN0532 relates to CN0540
mainThe
adi.cn0532.cn0532interface is an extension of theadi.cn0540.cn0540interface.Inherited Capabilities:
- Buffered acquisition
- Sample-rate control
- Bias-voltage control
- GPIO controls
Inherited Attributes:
shift_voltagesensor_voltagefda_modemonitor_powerup
Unique CN0532 Feature:
- Automatic offset calibration via
calibrate().
Access libiio directly via the ctx property
mainWhile
pyadi-iioprovides high-level abstractions, you can access the underlyinglibiioPython API using thectxproperty available in every device-specific class. This allows you to perform operations not directly exposed bypyadi-iioclasses.By convention, device-specific classes often populate these internal drivers:
_ctrl: Main control driver_rxadc: RX driver associated with data (DMA)_txdac: TX driver associated with data (DMA) and DDSs
Note: These internal driver names are not guaranteed across all class implementations.
import adi import iio sdr = adi.Pluto("ip:pluto.local") ctx = iio.Context("ip:pluto.local") # Comparing devices found in pyadi-iio vs libiio context for d1, d2 in zip(sdr.ctx.devices, ctx.devices): print(d1.name, "|", d2.name)Use high-level device attributes for hardware configuration
mainTo simplify hardware configuration across different IIO drivers,
pyadi-iioexposes basic class properties at the top-level of each device-specific class. These properties abstract away low-levellibIIOdetails such as specific channel names, attribute types, and source device names.You can read and write hardware settings directly using standard Python property syntax. This provides easy-to-understand names and built-in error handling for interfacing with different hardware components.
import adi lidar = adi.fmclidar1() # Read current pulse width print(lidar.laser_pulse_width) # Change laser frequency to 1 MHz lidar.laser_frequency = 1000000Quickstart with pyadi-iio
mainPyADI-IIO is a Python abstraction module for Analog Devices (ADI) hardware using IIO drivers. It simplifies the use of the libIIO interface by providing custom interface classes for specific hardware components, reducing the boilerplate code required for tasks like buffer management.
To get started, you can instantiate a device using its URI and perform basic operations like receiving data.
import adi # Create device from specific uri address sdr = adi.ad9361(uri="ip:192.168.2.1") # Get data from transceiver data = sdr.rx()Enable and use cyclic buffers for continuous transmission
mainCyclic buffers allow a signal to repeat continuously in hardware, which is useful for high-rate devices where real-time data feeding is difficult.
To use cyclic mode:
- Set
sdr.tx_cyclic_buffer = True. - Call
sdr.tx(data)to start the transmission. The signal will repeat indefinitely until the buffer is destroyed or the device object is destroyed. - Important: To update the buffer with new data, you must call
sdr.tx_destroy_buffer()before callingsdr.tx()again. Failing to destroy the buffer before pushing new data will cause an error.
import adi import numpy as np 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)- Set
Configure the DDS of an FPGA-based device
mainFor transmit-capable FPGA-based devices (such as those using the
DAQ2class), you can configure the Direct Digital Synthesizer (DDS) using thedds_single_tonemethod. This allows you to generate a single tone at a specific frequency.import adi sdr = adi.DAQ2() # Configure DDS tone_freq_hz = 1000 # In Hz tone_scale = 0.9 # Range: 0-1.0 tx_channel = 1 # Starts at 0 sdr.dds_single_tone(tone_freq_hz, tone_scale, tx_channel)