From ff613beec25480ede2406ba0c95e2d95c9445e9c Mon Sep 17 00:00:00 2001 From: Nathaniel Acut <127278797+nsacut@users.noreply.github.com> Date: Fri, 21 Jul 2023 00:11:38 +0800 Subject: [PATCH] Adding Support for CN0579 (#450) * Add support to cn0579 Signed-off-by: Nathaniel Acut * Add docstring to current source controller Also, reused sin_params from ad4639 folder. Signed-off-by: Nathaniel Acut * Was rearranged by pre-commit Signed-off-by: Nathaniel Acut * Removed var 'dac_scale' is assigned to but never used Signed-off-by: Nathaniel Acut * Fix remaining linting issues Signed-off-by: Nathaniel Acut * Fix Codacy Static Code Analysis Issues Signed-off-by: Nathaniel Acut * Remove unused import Signed-off-by: Nathaniel Acut --------- Signed-off-by: Nathaniel Acut --- adi/__init__.py | 1 + adi/cn0579.py | 107 +++++++++++++++++++++++++++++ doc/source/devices/adi.cn0579.rst | 7 ++ doc/source/devices/index.rst | 1 + examples/cn0579/cn0579_example.py | 108 ++++++++++++++++++++++++++++++ examples/cn0579/save_for_scope.py | 61 +++++++++++++++++ supported_parts.md | 1 + test/emu/devices/cn0579.xml | 1 + test/emu/hardware_map.yml | 13 +++- test/test_cn0579.py | 61 +++++++++++++++++ 10 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 adi/cn0579.py create mode 100644 doc/source/devices/adi.cn0579.rst create mode 100644 examples/cn0579/cn0579_example.py create mode 100644 examples/cn0579/save_for_scope.py create mode 100644 test/emu/devices/cn0579.xml create mode 100644 test/test_cn0579.py diff --git a/adi/__init__.py b/adi/__init__.py index c3a5be043..0cb356152 100644 --- a/adi/__init__.py +++ b/adi/__init__.py @@ -74,6 +74,7 @@ from adi.cn0554 import cn0554 from adi.cn0566 import CN0566 from adi.cn0575 import cn0575 +from adi.cn0579 import cn0579 from adi.daq2 import DAQ2 from adi.daq3 import DAQ3 from adi.fmc_vna import fmcvna diff --git a/adi/cn0579.py b/adi/cn0579.py new file mode 100644 index 000000000..c20971535 --- /dev/null +++ b/adi/cn0579.py @@ -0,0 +1,107 @@ +# Copyright (C) 2023 Analog Devices, Inc. +# +# SPDX short identifier: ADIBSD + +from adi.ad7768 import ad7768_4 + + +class cn0579(ad7768_4): + + """ CN0579 - Multichannel IEPE DAQ for CbM """ + + def __init__( + self, uri="ip:analog.local", + ): + + ad7768_4.__init__(self, uri) + + self._gpio = self._ctx.find_device("cn0579_control") + self._ad5696 = self._ctx.find_device("ad5696") + + @property + def shift_voltage0(self): + """shift_voltage: Shift voltage in mV from AD5696 to bias sensor data""" + dac_chan = self._ad5696 + raw = self._get_iio_attr("voltage0", "raw", True, dac_chan) + return raw # * dac_scale * 1.22 + + @shift_voltage0.setter + def shift_voltage0(self, value): + dac_chan = self._ad5696 + self._set_iio_attr_int("voltage0", "raw", True, int(value), dac_chan) + + @property + def shift_voltage1(self): + """shift_voltage: Shift voltage in mV from AD5696 to bias sensor data""" + dac_chan = self._ad5696 + raw = self._get_iio_attr("voltage1", "raw", True, dac_chan) + return raw # * dac_scale * 1.22 + + @shift_voltage1.setter + def shift_voltage1(self, value): + dac_chan = self._ad5696 + self._set_iio_attr_int("voltage1", "raw", True, int(value), dac_chan) + + @property + def shift_voltage2(self): + """shift_voltage: Shift voltage in mV from AD5696 to bias sensor data""" + dac_chan = self._ad5696 + raw = self._get_iio_attr("voltage2", "raw", True, dac_chan) + return raw # * dac_scale * 1.22 + + @shift_voltage2.setter + def shift_voltage2(self, value): + dac_chan = self._ad5696 + self._set_iio_attr_int("voltage2", "raw", True, int(value), dac_chan) + + @property + def shift_voltage3(self): + """shift_voltage: Shift voltage in mV from AD5696 to bias sensor data""" + dac_chan = self._ad5696 + raw = self._get_iio_attr("voltage3", "raw", True, dac_chan) + return raw # * dac_scale * 1.22 + + @shift_voltage3.setter + def shift_voltage3(self, value): + dac_chan = self._ad5696 + self._set_iio_attr_int("voltage3", "raw", True, int(value), dac_chan) + + @property + def CC_CH0(self): + """Get Channel 0 Current Source Control""" + return self._get_iio_attr("voltage0", "raw", True, self._gpio) + + @CC_CH0.setter + def CC_CH0(self, value): + """Set Channel 0 Current Source Control""" + self._set_iio_attr_int("voltage0", "raw", True, value, self._gpio) + + @property + def CC_CH1(self): + """Get Channel 1 Current Source Control""" + return self._get_iio_attr("voltage1", "raw", True, self._gpio) + + @CC_CH1.setter + def CC_CH1(self, value): + """Set Channel 1 Current Source Control""" + self._set_iio_attr_int("voltage1", "raw", True, value, self._gpio) + + @property + def CC_CH2(self): + """Get Channel 2 Current Source Control""" + return self._get_iio_attr("voltage2", "raw", True, self._gpio) + + @CC_CH2.setter + def CC_CH2(self, value): + """Set Channel 2 Current Source Control""" + self._set_iio_attr_int("voltage2", "raw", True, value, self._gpio) + + @property + def CC_CH3(self): + """Get Channel 3 Current Source Control""" + return self._get_iio_attr("voltage3", "raw", True, self._gpio) + + @CC_CH3.setter + def CC_CH3(self, value): + """Set Channel 3 Current Source Control""" + self._set_iio_attr_int("voltage3", "raw", True, value, self._gpio) diff --git a/doc/source/devices/adi.cn0579.rst b/doc/source/devices/adi.cn0579.rst new file mode 100644 index 000000000..bb5a88196 --- /dev/null +++ b/doc/source/devices/adi.cn0579.rst @@ -0,0 +1,7 @@ +cn0579 +================= + +.. automodule:: adi.cn0579 + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/devices/index.rst b/doc/source/devices/index.rst index b22625bc1..51ca275ea 100644 --- a/doc/source/devices/index.rst +++ b/doc/source/devices/index.rst @@ -83,6 +83,7 @@ Supported Devices adi.cn0554 adi.cn0566 adi.cn0575 + adi.cn0579 adi.daq2 adi.daq3 adi.fmc_vna diff --git a/examples/cn0579/cn0579_example.py b/examples/cn0579/cn0579_example.py new file mode 100644 index 000000000..eafb8673a --- /dev/null +++ b/examples/cn0579/cn0579_example.py @@ -0,0 +1,108 @@ +# Copyright (C) 2023 Analog Devices, Inc. +# +# SPDX short identifier: ADIBSD + +import inspect +import os +import sys + +import matplotlib.pyplot as plt +import numpy as np +from adi import cn0579 + +# Lets try to reuse the ./examples/ad4630/sin_params.py file instead of having +# our own copy. Add path prior to importing sin_params +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +parentdir = os.path.dirname(currentdir) +ad4630_dir = os.path.join(parentdir, "ad4630") +sys.path.insert(0, ad4630_dir) + +import sin_params # isort:skip + +# from save_for_pscope import save_for_pscope + +# Optionally pass URI as command line argument, +# else use default ip:analog.local +my_uri = sys.argv[1] if len(sys.argv) >= 2 else "ip:analog.local" +print("uri: " + str(my_uri)) + +my_adc = cn0579(uri=my_uri) +my_adc.rx_buffer_size = 1024 + +# Set Sample Rate. Options are 1ksps to 256ksps, 1k* power of 2. +# Note that sample rate and power mode are not orthogonal - refer +# to datasheet. +my_adc.sampling_frequency = 256000 + +# Choose a power mode: +# my_adc.power_mode_avail = 'LOW_POWER_MODE MEDIAN_MODE FAST_MODE' +my_adc.power_mode = "FAST_MODE" + +# Choose a filter type: +# my_adc.filter_type_avail = 'WIDEBAND SINC5' +my_adc.filter_type = "WIDEBAND" + +# Choose output format: +# my_adc.rx_output_type = "raw" +my_adc.rx_output_type = "SI" + +# Set Shift Voltage: +vshift = 43355 +my_adc.shift_voltage0 = vshift +my_adc.shift_voltage1 = vshift +my_adc.shift_voltage2 = vshift +my_adc.shift_voltage3 = vshift + +# Current Source for each channel: +my_adc.CC_CH0 = 1 +my_adc.CC_CH1 = 1 +my_adc.CC_CH2 = 0 +my_adc.CC_CH3 = 0 + +# Verify settings: +print("Power Mode: ", my_adc.power_mode) +print("Sampling Frequency: ", my_adc.sampling_frequency) +print("Filter Type: ", my_adc.filter_type) +print("Enabled Channels: ", my_adc.rx_enabled_channels) +print("Ch0 Shift Voltage: ", my_adc.shift_voltage0) +print("Ch1 Shift Voltage: ", my_adc.shift_voltage1) +print("Ch2 Shift Voltage: ", my_adc.shift_voltage2) +print("Ch3 Shift Voltage: ", my_adc.shift_voltage3) + + +plt.clf() +data = my_adc.rx() +for ch in my_adc.rx_enabled_channels: + plt.plot(range(0, len(data[0])), data[ch], label="voltage" + str(ch)) +plt.xlabel("Data Point") +if my_adc.rx_output_type == "SI": + plt.ylabel("Millivolts") +else: + plt.ylabel("ADC counts") +plt.legend( + bbox_to_anchor=(0.0, 1.02, 1.0, 0.102), + loc="lower left", + ncol=4, + mode="expand", + borderaxespad=0.0, +) + +for ch in my_adc.rx_enabled_channels: + parameters = sin_params.sin_params(data[ch]) + snr = parameters[1] + thd = parameters[2] + sinad = parameters[3] + enob = parameters[4] + sfdr = parameters[5] + floor = parameters[6] + print("\nChannel " + str(ch)) + print("SNR = " + str(snr)) + print("THD = " + str(thd)) + print("SINAD = " + str(sinad)) + print("ENOB = " + str(enob)) + print("SFDR = " + str(sfdr)) + print("FLOOR = " + str(floor)) + +plt.show() + +# save_for_pscope("Vshift=" + str(my_adc.shift_voltage2) + "_" + str(my_adc.sampling_frequency) + "_cn0579_data.adc" , 24, True, len(data), "DC0000", "LTC1111", data, data, ) diff --git a/examples/cn0579/save_for_scope.py b/examples/cn0579/save_for_scope.py new file mode 100644 index 000000000..081966a8c --- /dev/null +++ b/examples/cn0579/save_for_scope.py @@ -0,0 +1,61 @@ +# Copyright (C) 2023 Analog Devices, Inc. +# +# SPDX short identifier: ADIBSD + +import math as m + + +def save_for_pscope( + out_path, num_bits, is_bipolar, num_samples, dc_num, ltc_num, *data +): + num_channels = len(data) + if num_channels < 0 or num_channels > 16: + raise ValueError("pass in a list for each channel (between 1 and 16)") + + full_scale = 1 << num_bits + if is_bipolar: + min_val = -full_scale // 2 + max_val = full_scale // 2 + else: + min_val = 0 + max_val = full_scale + + with open(out_path, "w") as out_file: + out_file.write("Version,115\n") + out_file.write( + "Retainers,0,{0:d},{1:d},1024,0,{2:0.15f},1,1\n".format( + num_channels, num_samples, 0.0 + ) + ) + out_file.write("Placement,44,0,1,-1,-1,-1,-1,10,10,1031,734\n") + out_file.write("DemoID," + dc_num + "," + ltc_num + ",0\n") + for i in range(num_channels): + out_file.write( + "RawData,{0:d},{1:d},{2:d},{3:d},{4:d},{5:0.15f},{3:e},{4:e}\n".format( + i + 1, int(num_samples), int(num_bits), min_val, max_val, 1.0 + ) + ) + for samp in range(num_samples): + out_file.write(str(data[0][samp])) + for ch in range(1, num_channels): + out_file.write(", ," + str(data[ch][samp])) + out_file.write("\n") + out_file.write("End\n") + + +if __name__ == "__main__": + num_bits = 16 + num_samples = 65536 + channel_1 = [int(8192 * m.cos(0.12 * d)) for d in range(num_samples)] + channel_2 = [int(8192 * m.cos(0.034 * d)) for d in range(num_samples)] + + save_for_pscope( + "test.adc", + num_bits, + True, + num_samples, + "DC9876A-A", + "LTC9999", + channel_1, + channel_2, + ) diff --git a/supported_parts.md b/supported_parts.md index 90f668343..c4d430d98 100644 --- a/supported_parts.md +++ b/supported_parts.md @@ -131,6 +131,7 @@ - CN0549 - CN0566 - CN0575 +- CN0579 - DAQ2 - DAQ3 - FMCADC3 diff --git a/test/emu/devices/cn0579.xml b/test/emu/devices/cn0579.xml new file mode 100644 index 000000000..3219438a9 --- /dev/null +++ b/test/emu/devices/cn0579.xml @@ -0,0 +1 @@ +]> \ No newline at end of file diff --git a/test/emu/hardware_map.yml b/test/emu/hardware_map.yml index b4e153ebf..43841da98 100644 --- a/test/emu/hardware_map.yml +++ b/test/emu/hardware_map.yml @@ -185,7 +185,18 @@ cn0575: - filename: cn0575.xml - data_devices: - hwmon2 - - iio:device0 + - iio:device0 + +cn0579: + - cf_axi_adc + - cn0579_control + - ad5696 + - pyadi_iio_class_support: + - cn0579 + - emulate: + - filename: cn0579.xml + - data_devices: + - iio:device1 # SOMS adrv9361: diff --git a/test/test_cn0579.py b/test/test_cn0579.py new file mode 100644 index 000000000..15465a29f --- /dev/null +++ b/test/test_cn0579.py @@ -0,0 +1,61 @@ +import pytest + +hardware = "cn0579" +classname = "adi.cn0579" + +######################################### +@pytest.mark.iio_hardware(hardware, True) +@pytest.mark.parametrize("classname", [(classname)]) +@pytest.mark.parametrize("channel", [0, 1, 2, 3]) +def test_cn0579_rx_data(test_dma_rx, iio_uri, classname, channel): + test_dma_rx(iio_uri, classname, channel) + + +######################################### +@pytest.mark.iio_hardware(hardware) +@pytest.mark.parametrize("classname", [(classname)]) +@pytest.mark.parametrize( + "attr, val", + [ + ( + "sampling_frequency", + [ + 1000, + 2000, + 4000, + 8000, + 16000, + 32000, + 64000, + 128000, + 256000, + 32000, + ], # End on a rate compatible with all power modes + ), + ("filter_type", ["WIDEBAND", "SINC5"],), + ("power_mode", ["MEDIAN_MODE", "FAST_MODE"],), + ("sync_start_enable", ["arm"],), + ], +) +def test_cn0579_attr_multiple( + test_attribute_multipe_values, iio_uri, classname, attr, val +): + test_attribute_multipe_values(iio_uri, classname, attr, val, 0) + + +######################################### +@pytest.mark.iio_hardware(hardware) +@pytest.mark.parametrize("classname", [(classname)]) +@pytest.mark.parametrize( + "attr, start, stop, step, tol", + [ + ("CC_CH0", 0, 1, 1, 0), + ("CC_CH1", 0, 1, 1, 0), + ("CC_CH2", 0, 1, 1, 0), + ("CC_CH3", 0, 1, 1, 0), + ], +) +def test_cn0579_attr_single( + test_attribute_single_value, iio_uri, classname, attr, start, stop, step, tol +): + test_attribute_single_value(iio_uri, classname, attr, start, stop, step, tol)