Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pyadi support for ADAQ4001 and ADAQ4003 #571

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion adi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from adi.ad936x import Pluto, ad9361, ad9363, ad9364
from adi.ad937x import ad9371, ad9375
from adi.ad3552r import ad3552r
from adi.ad4020 import ad4000, ad4001, ad4002, ad4003, ad4020
from adi.ad4020 import ad4000, ad4001, ad4002, ad4003, ad4020, adaq4003
from adi.ad4110 import ad4110
from adi.ad4130 import ad4130
from adi.ad4170 import ad4170
Expand Down
54 changes: 53 additions & 1 deletion adi/ad4020.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
#
# SPDX short identifier: ADIBSD

from decimal import Decimal

import numpy as np
from adi.attribute import attribute
from adi.context_manager import context_manager
from adi.rx_tx import rx


class ad4020(rx, context_manager):
"""AD4020 device"""
"""AD4000 series of differential SAR ADC device"""

_compatible_parts = [
"ad4020",
Expand All @@ -33,6 +36,13 @@ def __init__(self, uri="", device_name="ad4020"):

self._rxadc = self._ctx.find_device(_device_name)
self._ctrl = self._ctx.find_device(_device_name)

# Dynamically get channel after the index
for ch in self._ctrl.channels:
name = ch._id
output = ch._output
setattr(self, name, self._channel_adc(self._ctrl, name, output))

rx.__init__(self)

@property
Expand All @@ -45,6 +55,36 @@ def sampling_frequency(self, value):
"""Set the sampling frequency."""
self._set_iio_dev_attr("sampling_frequency", str(value))

class _channel_adc(attribute):
"""AD4000 series differential input voltage channel"""

# AD4000 series ADC channel
def __init__(self, ctrl, channel_name, output):
self.name = channel_name
self._ctrl = ctrl
self._output = output

@property
def raw(self):
return self._get_iio_attr(self.name, "raw", self._output)

@property
def scale(self):
return float(self._get_iio_attr_str(self.name, "scale", self._output))

@scale.setter
def scale(self, value):
self._set_iio_attr(self.name, "scale", False, str(Decimal(value).real))

@property
def scale_available(self):
"""Provides all available scale(gain) settings for the ADC channel"""
Comment on lines +79 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@machschmitt I believe these are all fixed-scale devices. Accessing the scale_available property throws a KeyError.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well yes, at least what is in ADI Linux right now.
Though, the driver will provide a scale_available attribute in the future for configuring span compression.
Some explanation taken from commit log:
AD4000 series of devices have a span compression feature that allows
reducing the ADC input range while keeping the same range of raw output
codes, thus providing a slight increase in measurement precision.
The span compression selection (enable/disable) is done by writing to
the scale attribute. The list of valid scale values is listed by the
scale_available attribute.

@tfcollins, can we have scale_available attribute (even if it is not working right now) or would you prefer to only introduce it when the changes get to ADI Linux?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@machschmitt noted - although has the decision been made whether the compression will be changeable at run time, or set in the device tree? Typically the decision would be made as the analog front end circuit is being designed, then "set and forget" in software / device tree.
But if it will be run-time adjustable, this makes perfect sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mthoren-adi, I was developing this as a runtime feature but it could definitely be set in device tree as you said so that's indeed a good question. Who can I ask about this configuration decision? Part application engineer?

return self._get_iio_attr(self.name, "scale_available", False)

def __call__(self):
"""Convenience function, get voltages in IIO units (millivolts)"""
return self.raw * self.scale


class ad4000(ad4020):
_compatible_parts = [
Expand Down Expand Up @@ -95,3 +135,15 @@ class ad4003(ad4020):

def __init__(self, uri="ip:analog.local", device_name="ad4003"):
super().__init__(uri, device_name)


class adaq4003(ad4020):
_compatible_parts = [
"adaq4001",
"adaq4003",
]

_rx_data_type = np.int32

def __init__(self, uri="ip:analog.local", device_name="adaq4003"):
super().__init__(uri, device_name)
2 changes: 2 additions & 0 deletions doc/source/devices/adi.ad4020.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Each device class in this module supports multiple parts, as follows:

**ad4003:** ad4003, ad4007, ad4011

**adaq4003:** adaq4001, adaq4003

By default, the device_name parameter in the class constructor is the
same as the class name (e.g. "ad4001" for the ad4001). To use the class
with another supported model, the name must be given when instantiating
Expand Down
57 changes: 44 additions & 13 deletions examples/ad4020_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,71 @@
#
# SPDX short identifier: ADIBSD


import argparse
import sys

import adi
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal

device_name = "ad4020"
vref = 5.0 # Manually entered, consult eval board manual

# Optionally pass URI as command line argument,
# else use default context manager search
my_uri = sys.argv[1] if len(sys.argv) >= 2 else "ip:analog.local"
print("uri: " + str(my_uri))
parser = argparse.ArgumentParser(description="AD4000 Series Example Script")
parser.add_argument(
"-u",
"--uri",
metavar="uri",
default="ip:analog.local",
help="An URI to the libiio context. e.g.: 'ip:analog.local'\
'ip:192.168.1.3'\
'serial:/dev/ttyUSB1,115200,8n1n'",
)
parser.add_argument(
"-d",
"--device",
metavar="device",
default="ad4020",
help="Device name. e.g.: 'ad4000', 'ad4020', 'adaq4003'",
)

args = parser.parse_args()
print("uri: " + str(args.uri))
print("device: " + str(args.device))

if args.device == "ad4000" or args.device == "ad4004" or args.device == "ad4008":
my_adc = adi.ad4000(uri=args.uri, device_name=args.device)
elif args.device == "ad4001" or args.device == "ad4005":
my_adc = adi.ad4001(uri=args.uri, device_name=args.device)
elif args.device == "ad4002" or args.device == "ad4006" or args.device == "ad4010":
my_adc = adi.ad4002(uri=args.uri, device_name=args.device)
elif args.device == "ad4003" or args.device == "ad4007" or args.device == "ad4011":
my_adc = adi.ad4003(uri=args.uri, device_name=args.device)
elif args.device == "ad4020" or args.device == "ad4021" or args.device == "ad4022":
my_adc = adi.ad4020(uri=args.uri, device_name=args.device)
elif args.device == "adaq4001" or args.device == "adaq4003":
my_adc = adi.adaq4003(uri=args.uri, device_name=args.device)
else:
print("Error: device: " + str(args.device) + " not supported.")
quit()

my_adc = adi.ad4020(uri=my_uri)
my_adc.rx_buffer_size = 4096

print("Sample Rate: ", my_adc.sampling_frequency)

data = my_adc.rx()

x = np.arange(0, len(data))
voltage = data * 2.0 * vref / (2 ** 20)
voltage = data * my_adc.voltage0.scale
dc = np.average(voltage) # Extract DC component
ac = voltage - dc # Extract AC component

plt.figure(1)
plt.clf()
plt.title("AD4020 Time Domain Data")
plt.title(args.device.upper() + " Time Domain Data")
plt.plot(x, voltage)
plt.xlabel("Data Point")
plt.ylabel("Voltage (V)")
plt.ylabel("Voltage (mV)")
plt.show()

f, Pxx_spec = signal.periodogram(
Expand All @@ -45,12 +76,12 @@

plt.figure(2)
plt.clf()
plt.title("AD4020 Spectrum (Volts absolute)")
plt.title(args.device.upper() + " Spectrum (Millivolts absolute)")
plt.semilogy(f, Pxx_abs)
plt.ylim([1e-6, 4])
plt.xlabel("frequency [Hz]")
plt.ylabel("Voltage (V)")
plt.ylabel("Voltage (mV)")
plt.draw()
plt.pause(0.05)
plt.show()

del my_adc
2 changes: 2 additions & 0 deletions supported_parts.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@
- AD4858
- AD9739A
- ADA4961
- ADAQ4001
- ADAQ4003
- ADAQ4216
- ADAQ4220
- ADAQ4224
Expand Down
1 change: 1 addition & 0 deletions test/emu/devices/adaq4003.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE context [<!ELEMENT context (device | context-attribute)*><!ELEMENT context-attribute EMPTY><!ELEMENT device (channel | attribute | debug-attribute | buffer-attribute)*><!ELEMENT channel (scan-element?, attribute*)><!ELEMENT attribute EMPTY><!ELEMENT scan-element EMPTY><!ELEMENT debug-attribute EMPTY><!ELEMENT buffer-attribute EMPTY><!ATTLIST context name CDATA #REQUIRED description CDATA #IMPLIED><!ATTLIST context-attribute name CDATA #REQUIRED value CDATA #REQUIRED><!ATTLIST device id CDATA #REQUIRED name CDATA #IMPLIED><!ATTLIST channel id CDATA #REQUIRED type (input|output) #REQUIRED name CDATA #IMPLIED><!ATTLIST scan-element index CDATA #REQUIRED format CDATA #REQUIRED scale CDATA #IMPLIED><!ATTLIST attribute name CDATA #REQUIRED filename CDATA #IMPLIED value CDATA #IMPLIED><!ATTLIST debug-attribute name CDATA #REQUIRED value CDATA #IMPLIED><!ATTLIST buffer-attribute name CDATA #REQUIRED value CDATA #IMPLIED>]><context name="network" description="192.168.1.7 Linux raspberrypi 6.9.0-rc6-v8+ #10 SMP PREEMPT Fri May 10 11:46:32 -03 2024 aarch64" ><context-attribute name="local,kernel" value="6.9.0-rc6-v8+" /><context-attribute name="uri" value="ip:192.168.1.7" /><context-attribute name="ip,ip-addr" value="192.168.1.7" /><device id="iio:device0" name="adaq4003" ><channel id="voltage0-voltage1" type="input" ><scan-element index="0" format="be:s18/32&gt;&gt;14" scale="0.084024" /><attribute name="raw" filename="in_voltage0-voltage1_raw" value="98432" /><attribute name="scale" filename="in_voltage0-voltage1_scale" value="0.084024162" /><attribute name="scale_available" filename="in_voltage0-voltage1_scale_available" value="0.084024162 0.067219330" /></channel><attribute name="waiting_for_supplier" value="0" /><buffer-attribute name="data_available" value="0" /><buffer-attribute name="direction" value="in" /><debug-attribute name="direct_reg_access" value="0x5CE1" /></device></context>
9 changes: 9 additions & 0 deletions test/emu/hardware_map.yml
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,15 @@ ad4020:
- filename: ad4020.xml
- data_devices:
- iio:device0
adaq4003:
- adaq4001
- adaq4003
- pyadi_iio_class_support:
- adaq4003
- emulate:
- filename: adaq4003.xml
- data_devices:
- iio:device0
adaq4224:
- adaq4224
- pyadi_iio_class_support:
Expand Down
71 changes: 71 additions & 0 deletions test/test_adaq4003.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import adi
import pytest

hardware = "adaq4003"
classname = "adi.adaq4003"

#########################################
@pytest.mark.iio_hardware(hardware)
@pytest.mark.parametrize("classname", [(classname)])
@pytest.mark.parametrize(
"attr, val",
[
(
"sampling_frequency",
[10000, 50000, 100000, 200000, 500000, 1000000, 2000000],
),
],
)
def test_adaq4003_attr(test_attribute_multiple_values, iio_uri, classname, attr, val):

# Setting the sampling frequency is only possible through FPGA implemented
# hardware (SPI-Engine offload) so the attribute might not exist if the
# the platform is not set with proper FPGA bitstream or doesn't have
# a integrated FPGA.

try:
test_attribute_multiple_values(iio_uri, classname, attr, val, 1)
except KeyError:
pytest.skip("sampling_frequency attribute not available on test platform.")


#########################################
@pytest.mark.iio_hardware(hardware)
@pytest.mark.parametrize("classname", [(classname)])
@pytest.mark.parametrize(
"attr, avail_attr, tol, repeats, sub_channel",
[("scale", "scale_available", 0, 1, "voltage0-voltage1",),],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above, there is no scale_available. Also this is a single-channel device with a single differential input, voltage0. The voltage0-voltage-1 nomenclature is typical of multichannel devices that can be arbitrarily configured.
Note that I am looking at the ADAQ4003 on Cora, but I see zynq-zed-adv7511-adxxxx noted below. The ADAQ4003 attributes, channel, etc.

)
def test_adaq4003_scale_attr(
test_attribute_multiple_values,
iio_uri,
classname,
attr,
avail_attr,
tol,
repeats,
sub_channel,
):
# Get the device
sdr = eval(classname + "(uri='" + iio_uri + "')")

# Check hardware
if not hasattr(sdr, sub_channel):
raise AttributeError(sub_channel + " not defined in " + classname)
if not hasattr(getattr(sdr, sub_channel), avail_attr):
raise AttributeError(avail_attr + " not defined in " + classname)

# Get the list of available scale values
val = getattr(getattr(sdr, sub_channel), avail_attr)

test_attribute_multiple_values(
iio_uri, classname, attr, val, tol, repeats, sub_channel=sub_channel
)


#########################################
@pytest.mark.iio_hardware(hardware, True)
@pytest.mark.parametrize("classname", [(classname)])
@pytest.mark.parametrize("channel", [0])
def test_adaq4003_rx_data(test_dma_rx, iio_uri, classname, channel):
test_dma_rx(iio_uri, classname, channel, buffer_size=2 ** 15)
1 change: 1 addition & 0 deletions test/test_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def get_test_map():
"vc707_fmcjesdadc1",
]
test_map["ad4020"] = ["zynq-zed-adv7511-ad4020"]
test_map["adaq4003"] = ["zynq-zed-adv7511-adaq4003"]
test_map["ad4630"] = ["zynq-zed-adv7511-ad4630-24"]
test_map["ad9467"] = [
"zynq-zed-adv7511-ad9467-fmc-250ebz",
Expand Down
Loading