Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions adi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
from adi.tdd import tdd
from adi.tddn import tddn
from adi.tmc5240 import tmc5240
from adi.trigger import hrtimer_trig, sysfs_trig

try:
from adi.jesd import jesd
Expand Down
106 changes: 83 additions & 23 deletions adi/ad5686.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
#
# SPDX short identifier: ADIBSD

import numpy as np
from decimal import Decimal
from enum import Enum

from adi.attribute import attribute
from adi.device_base import tx_chan_comp_no_buff
from adi.device_base import tx_chan_comp


class ad5686_channel(attribute):
Expand All @@ -14,15 +15,17 @@ class ad5686_channel(attribute):
def __init__(self, ctrl, channel_name):
self.name = channel_name
self._ctrl = ctrl
self._format = self._ctrl.find_channel(channel_name, True).data_format

@property
def raw(self):
"""AD5686 channel raw value"""
return self._get_iio_attr(self.name, "raw", True, self._ctrl)
return int(self._get_iio_attr_str(self.name, "raw", True, self._ctrl))

@raw.setter
def raw(self, value):
self._set_iio_attr(self.name, "raw", True, str(int(value)))
value = max(0, min(int(value), (2 ** self._format.bits) - 1))
self._set_iio_attr(self.name, "raw", True, value)

@property
def powerdown(self):
Expand All @@ -35,55 +38,85 @@ def powerdown(self, val):
self._set_iio_attr(self.name, "powerdown", True, val)

@property
def powerdown_mode(self):
def powerdown_mode(self) -> "ad5686.powerdown_mode":
"""AD5686 channel powerdown mode value"""
return self._get_iio_attr_str(self.name, "powerdown_mode", True)
return ad5686.powerdown_mode(
self._get_iio_attr_str(self.name, "powerdown_mode", True)
)

@powerdown_mode.setter
def powerdown_mode(self, val):
def powerdown_mode(self, mode):
"""AD5686 channel powerdown value"""
self._set_iio_attr_str(self.name, "powerdown_mode", True, val)
value = mode.value if isinstance(mode, ad5686.powerdown_mode) else mode
self._set_iio_attr(self.name, "powerdown_mode", True, value)

@property
def powerdown_mode_available(self):
"""Provides all available powerdown mode settings for the AD5686"""
return self._get_iio_attr_str(self.name, "powerdown_mode_available", True)
"""AD5686 channel available powerdown modes"""
modes_str = self._get_iio_attr_str(self.name, "powerdown_mode_available", True)
return [ad5686.powerdown_mode(m) for m in modes_str.split()]

@property
def scale(self):
def scale(self) -> Decimal:
"""AD5686 channel scale(gain)"""
return self._get_iio_attr(self.name, "scale", True)
return Decimal(self._get_iio_attr_str(self.name, "scale", True))

def to_raw(self, val):
"""Converts raw value to SI"""
return int(1000.0 * val / self.scale)
@scale.setter
def scale(self, value):
self._set_iio_attr(self.name, "scale", True, value)

@property
def volts(self):
"""AD5686 channel value in volts"""
return self.raw * self.scale
def scale_available(self):
"""AD5686 channel available scale values"""
scale_str = self._get_iio_attr_str(self.name, "scale_available", True)
return [Decimal(s) for s in scale_str.split()]

@volts.setter
def volts(self, val):
"""AD5686 channel value in volts"""
self.raw = self.to_raw(val)
@property
def voltage(self) -> float:
"""AD5686 channel value in Volts"""
return float(self.raw * self.scale / 1000)

@voltage.setter
def voltage(self, val: float):
"""AD5686 channel value in Volts"""
self.raw = int(1000 * Decimal(val) / self.scale)


class ad5686(tx_chan_comp_no_buff):
class ad5686(tx_chan_comp):
""" AD5686 DAC """

class powerdown_mode(Enum):
"""AD5686 Powerdown Mode Enumeration"""

PULLDOWN_1K = "1kohm_to_gnd"
PULLDOWN_100K = "100kohm_to_gnd"
TRISTATE = "three_state"

class gain(Enum):
"""AD5686 Gain Mode Enumeration"""

NORMAL = 1
DOUBLE = 2

compatible_parts = [
"ad5686",
"ad5310r",
"ad5311r",
"ad5313r",
"ad5316r",
"ad5317r",
"ad5338r",
"ad5671r",
"ad5672r",
"ad5673r",
"ad5674",
"ad5674r",
"ad5675",
"ad5675r",
"ad5676",
"ad5676r",
"ad5677r",
"ad5679",
"ad5679r",
"ad5681r",
"ad5682r",
Expand All @@ -93,6 +126,10 @@ class ad5686(tx_chan_comp_no_buff):
"ad5684r",
"ad5685r",
"ad5686r",
"ad5687",
"ad5687r",
"ad5689",
"ad5689r",
"ad5691r",
"ad5692r",
"ad5693",
Expand All @@ -102,7 +139,30 @@ class ad5686(tx_chan_comp_no_buff):
"ad5695r",
"ad5696",
"ad5696r",
"ad5697r",
]
_complex_data = False
_channel_def = ad5686_channel
_device_name = ""

def __init__(self, uri="", device_name="", device_index=0, trigger=None):
super().__init__(uri, device_name, device_index)
self._scales = self.channel[0].scale_available
if trigger:
self.set_tx_trigger(trigger)

def set_gain(self, value: "ad5686.gain"):
"""Set the DAC output gain.

Args:
value:
:class:`ad5686.gain` member selecting the output gain
(``NORMAL`` or ``DOUBLE``). Only supported on devices that
expose two entries in ``scale_available``.
"""
if len(self._scales) != 2:
Comment thread
rodrigo455 marked this conversation as resolved.
raise ValueError("Cannot set gain on this device")

self.channel[0].scale = (
self._scales[1] if value == ad5686.gain.DOUBLE else self._scales[0]
)
123 changes: 111 additions & 12 deletions adi/rx_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ class tx_core(dds, rx_tx_common, metaclass=ABCMeta):
# Set to True if complex data for TX only, overrides _complex_data
_tx_complex_data = None
_tx_data_type = None
_tx_input_type = "raw"
_txbuf = None
_output_byte_filename = "out.bin"
_push_to_file = False
Expand Down Expand Up @@ -332,6 +333,17 @@ def _complex_data(self):
return super()._complex_data
return self._tx_complex_data

@property
def tx_input_type(self) -> str:
"""tx_input_type: Set input data type for tx()"""
return self._tx_input_type

@tx_input_type.setter
def tx_input_type(self, value: str):
if value not in ["raw", "SI"]:
raise ValueError(f"Invalid tx_input_type: {value}. Must be raw or SI")
self._tx_input_type = value

@property
def tx_cyclic_buffer(self):
"""tx_cyclic_buffer: Enable cyclic buffer for TX"""
Expand Down Expand Up @@ -409,6 +421,60 @@ def tx_destroy_buffer(self):
"""tx_destroy_buffer: Clears TX buffer"""
self._txbuf = None

def __get_tx_channel_scales(self):
tx_scale = []
for i in self.tx_enabled_channels:
chan_name = self._tx_channel_names[i]
v = self._txdac.find_channel(chan_name, True)
if "scale" in v.attrs:
scale = self._get_iio_attr(chan_name, "scale", True, self._txdac)
else:
scale = 1.0
tx_scale.append(scale)
return tx_scale

def __get_tx_channel_offsets(self):
tx_offset = []
for i in self.tx_enabled_channels:
chan_name = self._tx_channel_names[i]
v = self._txdac.find_channel(chan_name, True)
if "offset" in v.attrs:
offset = self._get_iio_attr(chan_name, "offset", True, self._txdac)
else:
offset = 0.0
tx_offset.append(offset)
return tx_offset

def __tx_raw_data(self, data_np):
if self._num_tx_channels_enabled == 1:
data_np = [data_np]

if len(data_np) != self._num_tx_channels_enabled:
raise Exception("Not enough data provided for channel mapping")

if self._tx_input_type == "SI":
tx_scale = self.__get_tx_channel_scales()
tx_offset = self.__get_tx_channel_offsets()
raw_data_np = []

for i, ch in enumerate(data_np):
chan_name = self._tx_channel_names[self.tx_enabled_channels[i]]
df = self._txdac.find_channel(chan_name, True).data_format
if df.is_signed:
lo, hi = -(1 << (df.bits - 1)), (1 << (df.bits - 1)) - 1
else:
lo, hi = 0, (1 << df.bits) - 1
raw = np.clip(
np.asarray(ch) / tx_scale[i] - tx_offset[i], lo, hi
).astype(self._tx_data_type)
raw_data_np.append(raw << df.shift)
elif self._tx_input_type == "raw":
raw_data_np = data_np
else:
raise Exception("invalid _tx_input_type")

return raw_data_np

def tx(self, data_np=None):
"""Transmit data to hardware buffers for each channel index in
tx_enabled_channels.
Expand Down Expand Up @@ -447,13 +513,8 @@ def tx(self, data_np=None):
"To push more data the tx buffer must be destroyed first."
)

data_np = self.__tx_raw_data(data_np)
if self._complex_data:
if self._num_tx_channels_enabled == 1:
data_np = [data_np]

if len(data_np) != self._num_tx_channels_enabled:
raise Exception("Not enough data provided for channel mapping")

indx = 0
stride = self._num_tx_channels_enabled * 2
data = np.empty(stride * len(data_np[0]), dtype=self._tx_data_type)
Expand All @@ -464,12 +525,6 @@ def tx(self, data_np=None):
data[indx + 1 :: stride] = q.astype(self._tx_data_type)
indx = indx + 2
else:
if self._num_tx_channels_enabled == 1:
data_np = [data_np]

if len(data_np) != self._num_tx_channels_enabled:
raise Exception("Not enough data provided for channel mapping")

indx = 0
stride = self._num_tx_channels_enabled
data = np.empty(stride * len(data_np[0]), dtype=self._tx_data_type)
Expand Down Expand Up @@ -648,6 +703,22 @@ def __init__(
def __post_init__(self):
pass

def _resolve_trigger(self, trigger):
if isinstance(trigger, str):
trig = self._ctx.find_device(trigger) # iio.Trigger
if not trig:
raise ValueError(f"Trigger device '{trigger}' not found")
elif hasattr(trigger, "_ctrl"):
# wrapper class instance is passed in instead of a raw iio trigger
trig = trigger._ctrl
else:
trig = trigger

if trig is not None and not isinstance(trig, iio.Trigger):
raise ValueError(f"'{trig}' is not an IIO Trigger")

return trig


class rx_def_no_buff(shared_def, rx, context_manager, metaclass=ABCMeta):
@property
Expand Down Expand Up @@ -724,6 +795,20 @@ def __init__(
if self.__run_rx_post_init__:
self.__post_init__()

def set_rx_trigger(self, trigger):
"""Set the trigger for the RX ADC.

This interface is typically used by devices with triggered buffer
support that does not have an internal/default trigger

Args:
trigger:
Can be Trigger device name or id, an iio.Trigger instance, or
wrapper class instance with _ctrl attribute pointing to an iio.Trigger.
Set to None to clear the trigger.
"""
self._rxadc._set_trigger(self._resolve_trigger(trigger))


class tx_def_no_buff(shared_def, tx, context_manager, metaclass=ABCMeta):
@property
Expand Down Expand Up @@ -800,6 +885,20 @@ def __init__(
if self.__run_tx_post_init__:
self.__post_init__()

def set_tx_trigger(self, trigger):
"""Set the trigger for the TX DAC.

This interface is typically used by devices with triggered buffer
support that does not have an internal/default trigger

Args:
trigger:
Can be Trigger device name or id, an iio.Trigger instance, or
wrapper class instance with _ctrl attribute pointing to an iio.Trigger.
Set to None to clear the trigger.
"""
self._txdac._set_trigger(self._resolve_trigger(trigger))


class rx_tx_def(tx_def, rx_def):
"""Template metaclass for rx and tx device specific interfaces."""
Expand Down
Loading
Loading