pyadi-iio Python Interfaces

repository·main·Indexed 42 days ago

https://github.com/analogdevicesinc/pyadi-iio

High-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.

Tokens
22.6K
Snippets
93
Records
160
Agent score
53%

What's inside pyadi-iio

  1. Overview of pyadi-iio

    main
    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.
  2. Use the ad9081_mc multi-chip interface

    main

    The adi.ad9081_mc class 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.ad9081 class, 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()
  3. Synchronize DMA transfers using sync_start

    main

    In 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_start class. 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.py example in the repository.

  4. Use annotated buffers to label received data

    main

    By default, rx returns a list of arrays. For devices with heterogeneous channels (e.g., IMUs with both acceleration and angular velocity), you can use rx_annotated to receive a dictionary where keys are the channel names.

    Set dev.rx_annotated = True to 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 keys
  5. How device base classes work in pyadi-iio

    main

    pyadi-iio provides parent classes in adi.device_base to 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 against compatible_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. Use rx_chan_comp for devices with buffer support, and rx_chan_comp_no_buff for 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, and context_manager directly instead of using these base classes.

    from adi.device_base import rx_chan_comp
    
    class MyDevice(rx_chan_comp):
        # Implementation details
        pass
  6. Access libiio directly via the ctx property

    main

    While pyadi-iio provides high-level abstractions, you can access the underlying libiio Python API using the ctx property available in every device-specific class. This allows you to perform operations not directly exposed by pyadi-iio classes.

    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)
  7. Use high-level device attributes for hardware configuration

    main

    To simplify hardware configuration across different IIO drivers, pyadi-iio exposes basic class properties at the top-level of each device-specific class. These properties abstract away low-level libIIO details 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 = 1000000
  8. Quickstart with pyadi-iio

    main

    PyADI-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()
  9. Enable and use cyclic buffers for continuous transmission

    main

    Cyclic 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:

    1. Set sdr.tx_cyclic_buffer = True.
    2. Call sdr.tx(data) to start the transmission. The signal will repeat indefinitely until the buffer is destroyed or the device object is destroyed.
    3. Important: To update the buffer with new data, you must call sdr.tx_destroy_buffer() before calling sdr.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)
  10. Configure the DDS of an FPGA-based device

    main

    For transmit-capable FPGA-based devices (such as those using the DAQ2 class), you can configure the Direct Digital Synthesizer (DDS) using the dds_single_tone method. 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)