diff --git a/cn0577/README.md b/cn0577/README.md new file mode 100644 index 0000000..9c3886a --- /dev/null +++ b/cn0577/README.md @@ -0,0 +1,21 @@ +# Test script for [EVAL-CN0577-FMCZ](https://www.analog.com/en/design-center/reference-designs/circuits-from-the-lab/cn0577.html) + +- Program FMC ID EEPROM with serial number. + +- Prompt the test operator to short the input to ground + - Verify RMS noise less than 0.002 counts. + +- Prompt the user to connect an ADALM2000 test jig to analog inputs. +- Play back a 90% full-scale sinewave at 20kHz, capture a block of 256k samples per channel. + - Verify DC component less than 0.1 + - Subtract DC offset from data record, apply window + - Take FFT of data (via sin_params.py functions), verify: + - location of fundamental between bin 510 and 514 (correct bin = 512) + - fundamental amplitude between 2 and 2.8 (correct fundamental amplitude = 2.048) + - Total Harmonic Distortion less than -65 + - SNR better than 50 + - Switch in a 100:1 attenuator, same FFT tests: + - location of fundamental between bin 510 and 514 (correct bin = 512) + - fundamental amplitude between 0.012 and 0.014 + - Total Harmonic Distortion less than -65 + - SNR better than 35 diff --git a/cn0577/cn0577_production_test.py b/cn0577/cn0577_production_test.py new file mode 100644 index 0000000..c88e867 --- /dev/null +++ b/cn0577/cn0577_production_test.py @@ -0,0 +1,248 @@ +# Copyright (C) 2023 Analog Devices, Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# - Neither the name of Analog Devices, Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# - The use of this software may or may not infringe the patent rights +# of one or more patent holders. This license does not release you +# from the requirement that you obtain separate licenses from these +# patent holders to use this software. +# - Use of the software either in source or binary form, must be run +# on or directly connected to an Analog Devices Inc. component. +# +# THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. +# +# IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, INTELLECTUAL PROPERTY +# RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import sys +import adi +import numpy as np +import sine_gen +import sin_params +import matplotlib.pyplot as plt +import time +import os + +def eeprom_frudump(): + path_EEPROM ="/sys/devices/soc0/fpga-axi@0/41620000.i2c/i2c-1/1-0050/eeprom" + path_masterfile = "cn0577/cn0577master.bin" + + res1 = '' + res1 = os.system('fru-dump -i '+ path_masterfile + " -o " + path_EEPROM + " -s " + sn) + #if writing to eeprom fail, res1=1 + if (res1): + sys.exit('Dumping of bin file to eeprom FAILED\n') + +def rms_noise(my_uri): + my_adc = adi.ltc2387(my_uri) + my_adc.rx_buffer_size = 8000 + my_adc.sampling_frequency = 10000000 + + shorted_input = my_adc.rx() + time.sleep(2) + noise_v = shorted_input * vref * 2 / (2 ** 18) #Convert output digital code to voltage + measured_noise = np.std(noise_v) + print("Measured Noise: ", measured_noise) + + if measured_noise < 0.002: + print("RMS noise test PASS") + else: + print("RMS noise test FAIL") + failed_tests.append("Failed rms noise test") + record = open("error.csv","a") + record.write(sn + "," + "RMS noise=" + "," + str(measured_noise) + "," + "\n") + record.close() + + del my_adc + +def fft_test(my_uri,att): + + my_adc = adi.ltc2387(uri=my_uri) + my_adc.rx_buffer_size = 256000 + my_adc.sampling_frequency = 10000000 + + data = my_adc.rx() + time.sleep(2) + + # to sign extend bit 17 + for i in range(len(data)): + if data[i] > (2 ** 17)-1: + data[i] -= 2 ** 18 + + # Verify DC component less than 0.1 + x = np.arange(0, len(data)) + voltage = data * 2.0 * vref / (2 ** 18) + dc = np.average(voltage) # Extract DC component + print("DC component= ", dc) + + if dc < 0.1: + print("DC component test PASS") + else: + print("DC component test FAIL") + failed_tests.append("Fails DC component test, attenuation setting = " + str(att)) + record = open("error.csv","a") + record.write(sn + "," + str(att) + "," + "DC component=" + "," + str(dc) + "," + "\n") + record.close() + + if do_plots == True: + plt.plot(voltage) + plt.title("Voltage reading") + plt.show() + + # Subtract DC offset from data record, apply window + ac = voltage - dc # Extract AC component + + # Take FFT of data (via sin_params.py functions), verify: + # window_type= BLACKMAN_HARRIS_92 + fft_data = sin_params.windowed_fft_mag(voltage) + + if do_plots == True: + plt.plot(fft_data) + plt.title("FFT") + plt.xlabel("FFT Bin") + plt.ylabel("Amplitude") + plt.show() + + # Verify location of fundamental between bin 510 and 514 (correct bin = 512) + fund, fund_bin = sin_params.get_max(fft_data) + print("Fundamental bin location =", fund_bin) + if (510 < fund_bin < 514): + print("Fundamental bin location test PASS") + else: + print("Fundamental bin location test FAIL") + failed_tests.append("Fails Fundamental bin location test, attenuation setting = " + str(att)) + record = open("error.csv","a") + record.write(sn + "," + str(att) + "," + "Fundamental bin location=" + "," + str(fund_bin) + "," + "\n") + record.close() + + # Verify fundamental amplitude between lim1 and lim2 (expected fundamental amplitude = 2.048 @ 1:1, 0.0138) + if att==1: + fund_lim1= 2 + fund_lim2= 2.8 + else: #att==100 + fund_lim1= 0.012 + fund_lim2= 0.014 + + print("Fundamental bin amplitude =", fund) + if (fund_lim1 < fund < fund_lim2): + print("Fundamental amplitude test PASS") + else: + print("Fundamental amplitude test FAIL") + failed_tests.append("Fails Fundamental amplitude test, attenuation setting = " + str(att)) + record = open("error.csv","a") + record.write(sn + "," + str(att) + "," + "Fundamental amplitude=" + "," + str(fund) + "," + "\n") + record.close() + + # For 1:1 attenuator: + # Total Harmonic Distortion less than 65 + # SNR better than 50 + + # For 100:1 attenuator, same FFT tests: + # Total Harmonic Distortion less than 65 + # SNR better than 35 + parameters = sin_params.sin_params(voltage) + snr = parameters[1] + thd = parameters[2] + sinad = parameters[3] + enob = parameters[4] + sfdr = parameters[5] + floor = parameters[6] + + if att==1: + snr_lim= 50 + else: #att==100 + snr_lim= 35 + + print("SNR =", snr) + if snr > snr_lim: + print("SNR test PASS") + else: + print("SNR test FAIL") + failed_tests.append("Fails SNR test, attenuation setting = " + str(att)) + record = open("error.csv","a") + record.write(sn + "," + str(att) + "," + "SNR=" + "," + str(snr) + "," + "\n") + record.close() + + print("THD =", thd) + if thd < -65: + print("THD test PASS") + else: + print("THD test FAIL") + failed_tests.append("Fails THD test, attenuation setting = " + str(att)) + record = open("error.csv","a") + record.write(sn + "," + str(att) + "," + "THD=" + "," + str(thd) + "," + "\n") + record.close() + + record = open("cn0577_report.csv","a") + record.write("SN, Attenuation, Sampling Frequency, Fundamental Amplitude, Fundamental bin location, DC component, SNR, THD, Floor\n") + record.write(sn + "," + str(att) + "," + str(my_adc.sampling_frequency) + "," + str(fund)+ "," + str(fund_bin) + "," + str(dc) + "," + str(snr)+ "," + str(thd)+ "," + str(floor)+ "\n") + record.close() + del my_adc + + +my_uri = sys.argv[1] if len(sys.argv) >= 2 else "ip:analog.local" +print("Connecting with CN0577 context at " + str(my_uri)) + +vref = 4.096 +# Program FMC ID EEPROM with serial number. +sn = input("Enter serial number: ") +eeprom_frudump() +failed_tests = [] + +# my_adc = adi.ltc2387(uri=my_uri) +# Prompt the test operator to short the input to ground +input("\nStarting Production Test! \n\nConnect ADALM2000 with test jig \nWARNING: CN0577 should not be removed to the ZedBoard while the power is ON\nSet ADALM2000 switch in test jig: OFF. \nPress enter to continue...") +input("\nShort both input to ground, press enter to continue...") +# Verify RMS noise less than TBD counts +rms_noise(my_uri) + +# Prompt the user to connect an ADALM2000 test jig to analog inputs. +input( "\nCarefully remove short connection of input to ground, press enter to continue...") +input( "\nSwitch attenuation 1:1.\nSwitch ON the ADALM2000 input on test jig, press enter to continue...") + +#Play back a 90% full-scale sinewave at 20kHz using ADALM2000 +ampl= 2.048 +offset=2.048 +sine_gen.main(ampl, offset) + +do_plots = False + +att=1 +fft_test(my_uri,att) + +att=100 +input( "\nSwitch attenuation 100:1.\nSwitch ON the ADALM2000 input on test jig, press enter to continue...") +fft_test(my_uri,att) + +if len(failed_tests) == 0: + print("\n\nBoard PASSES!!") +else: + print("\n\nBoard FAILED the following tests:") + for failure in failed_tests: + print(failure) + print("\nNote failures and set aside for debug.") + +#Automatic shutdown after completing the test to avoid improper shutdown and hotswap of board. +x = input("Press enter to finish test.") +print("Shutting down... \nTurn off ZedBoard.") +if os.name == "posix": + os.system("sudo shutdown -h now") +else: + print("Sorry, can only shut down system when running locally on Zedboard\n") diff --git a/cn0577/cn0577master.bin b/cn0577/cn0577master.bin new file mode 100644 index 0000000..24bf9f0 Binary files /dev/null and b/cn0577/cn0577master.bin differ diff --git a/cn0577/eeprom_frudump.py b/cn0577/eeprom_frudump.py new file mode 100644 index 0000000..a58bfba --- /dev/null +++ b/cn0577/eeprom_frudump.py @@ -0,0 +1,9 @@ +# importing os module +import os + +def input_data(sn): + path_EEPROM ="/sys/devices/soc0/fpga-axi@0/41620000.i2c/i2c-1/1-0050/eeprom" + path_masterfile = "cn0577/cn0577master.bin" + + os.system('fru-dump -i '+ path_masterfile + " -o " + path_EEPROM + " -s " + sn) + # print("Succesfully loaded the FMC ID EEPROM with serial number:" + sn) diff --git a/cn0577/rms_noise.py b/cn0577/rms_noise.py new file mode 100644 index 0000000..c64c432 --- /dev/null +++ b/cn0577/rms_noise.py @@ -0,0 +1,48 @@ +import sys + +import adi +import numpy as np +from scipy import signal + + +def count(sn, uri): + # 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("Connecting with CN0577 context at " + str(my_uri)) + + device_name = "ltc2387" + vref = 4.096 + + my_adc = adi.ltc2387(uri) + # my_adc.rx_buffer_size = 131072 + my_adc.rx_buffer_size = 8000 + # my_adc.sampling_frequency = 5* (3-j) *1000000 + my_adc.sampling_frequency = 15000000 + + + data = my_adc.rx() + x = np.arange(0, len(data)) + voltage = data * 2.0 * vref / (2 ** 18) + dc = np.average(voltage) # Extract DC component + ac = voltage - dc # Extract AC component + + rms= np.std(voltage) + if rms < 0.002: + print("RMS noise count PASS, RMS noise count =", rms) + result=1 + else: + result=0 + print("RMS noise count FAIL, RMS noise count =", rms) + record = open("error.csv","a") + record.write(sn + "," + "RMS noise count=" + "," + str(rms) + "," + "\n") + record.close() + + + # record = open("rms_data.csv","a") + # record.write(sn + "," + str(my_adc.sampling_frequency) + "," + str(rms) + "," + "\n") + # record.close() + + del my_adc + + return result diff --git a/cn0577/sin_params.py b/cn0577/sin_params.py new file mode 100644 index 0000000..b3b478d --- /dev/null +++ b/cn0577/sin_params.py @@ -0,0 +1,335 @@ +# --------------------LICENSE AGREEMENT---------------------------------------- +# Copyright (c) 2020 Analog Devices, Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Modified versions of the software must be conspicuously marked as such. +# - This software is licensed solely and exclusively for use with +# processors/products manufactured by or for Analog Devices, Inc. +# - This software may not be combined or merged with other code in any manner +# that would cause the software to become subject to terms and conditions +# which differ from those listed here. +# - Neither the name of Analog Devices, Inc. nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# - The use of this software may or may not infringe the patent rights of +# one or more patent holders. This license does not release you from the +# requirement that you obtain separate licenses from these patent holders +# to use this software. +# +# THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES, INC. AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# NON-INFRINGEMENT, TITLE, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANALOG DEVICES, INC. OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, PUNITIVE OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# DAMAGES ARISING OUT OF CLAIMS OF INTELLECTUAL PROPERTY RIGHTS INFRINGEMENT; +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# 2020-02-24-7CBSD SLA +# ----------------------------------------------------------------------------- + +import math as m + +import numpy as np + +NONE = 0x00 +HAMMING = 0x10 +HANN = 0x11 +BLACKMAN = 0x20 +BLACKMAN_EXACT = 0x21 +BLACKMAN_HARRIS_70 = 0x22 +FLAT_TOP = 0x23 +BLACKMAN_HARRIS_92 = 0x30 + +DEF_WINDOW_TYPE = BLACKMAN_HARRIS_92 + +BW = 3 + + +def sin_params( + data, window_type=DEF_WINDOW_TYPE, mask=None, num_harms=9, spur_in_harms=True +): + fft_data = windowed_fft_mag(data, window_type) + harm_bins, harms, harm_bws = find_harmonics(fft_data, num_harms) + spur, spur_bw = find_spur( + spur_in_harms, harm_bins[0], harms, harm_bws, fft_data, window_type + ) + + if mask is None: + mask = calculate_auto_mask(fft_data, harm_bins, window_type) + noise, noise_bins = masked_sum_of_sq(fft_data, mask) + + average_noise = noise / max(1, noise_bins) + noise = average_noise * (len(fft_data) - 1) + + # create a dictionary where key is harmonic number and value is tuple of harm and bin + harmonics = {} + for i, (h, (b, bw)) in enumerate(zip(harms, zip(harm_bins, harm_bws))): + h -= average_noise * bw + if h > 0: + harmonics[i + 1] = (h, b) + elif i < 9: + harmonics[i + 1] = (h, b) + + spur -= average_noise * spur_bw + + signal = harmonics[1][0] + floor = 10 * m.log10(average_noise / signal) # dBc + snr = 10 * m.log10(signal / noise) + harm_dist = ( + harmonics.get(2, (0, 0))[0] + + harmonics.get(3, (0, 0))[0] + + harmonics.get(4, (0, 0))[0] + + harmonics.get(5, (0, 0))[0] + ) + thd = 10 * m.log10(harm_dist / signal) if harm_dist > 0 else 0 + sinad = 10 * m.log10(signal / (harm_dist + noise)) + enob = (sinad - 1.76) / 6.02 + sfdr = 10 * m.log10(signal / spur) if spur > 0 else 0 + + return (harmonics, snr, thd, sinad, enob, sfdr, floor) + + +def window(size, window_type=DEF_WINDOW_TYPE): + if window_type == NONE: + return None + if window_type == HAMMING: + return _one_cos(size, 0.54, 0.46, 1.586303) + elif window_type == HANN: + return _one_cos(size, 0.50, 0.50, 1.632993) + elif window_type == BLACKMAN: + return _two_cos(size, 0.42, 0.50, 0.08, 1.811903) + elif window_type == BLACKMAN_EXACT: + return _two_cos(size, 42659071, 0.49656062, 0.07684867, 1.801235) + elif window_type == BLACKMAN_HARRIS_70: + return _two_cos(size, 0.42323, 0.49755, 0.07922, 1.807637) + elif window_type == FLAT_TOP: + return _two_cos(size, 0.2810639, 0.5208972, 0.1980399, 2.066037) + elif window_type == BLACKMAN_HARRIS_92: + return _three_cos(size, 0.35875, 0.48829, 0.14128, 0.01168, 1.968888) + else: + raise ValueError("Unknown window type") + + +def _one_cos(n, a0, a1, norm): + t = np.linspace(0, 1, n, False) + win = a0 - a1 * np.cos(2 * np.pi * t) + return win * norm + + +def _two_cos(n, a0, a1, a2, norm): + t = np.linspace(0, 1, n, False) + win = a0 - a1 * np.cos(2 * np.pi * t) + a2 * np.cos(4 * np.pi * t) + return win * norm + + +def _three_cos(n, a0, a1, a2, a3, norm): + t = np.linspace(0, 1, n, False) + win = ( + a0 + - a1 * np.cos(2 * np.pi * t) + + a2 * np.cos(4 * np.pi * t) + - a3 * np.cos(6 * np.pi * t) + ) + return win * norm + + +def windowed_fft_mag(data, window_type=BLACKMAN_HARRIS_92): + n = len(data) + data = np.array(data, dtype=np.float64) + data -= np.mean(data) + w = window(n, window_type) + if w is not None: + data = data * w + n_by_2 = (int)(n / 2) + fft_data = np.fft.fft(data)[0 : n_by_2 + 1] + fft_data = abs(fft_data) / n + fft_data[1:n_by_2] *= 2 + return fft_data + + +def find_harmonics(fft_data, max_harms): + BW = 3 + harm_bins = np.zeros(max_harms, dtype=int) + harms = np.zeros(max_harms) + harm_bws = np.zeros(max_harms, dtype=int) + + _, fund_bin = get_max(fft_data) + harm_bins[0] = fund_bin + + for h in range(1, max_harms + 1): + # first find the location by taking max in area of uncertainty + mask = init_mask(len(fft_data), False) + nominal_bin = h * fund_bin + h_2 = h / 2 + if h > 1: + mask = set_mask(mask, nominal_bin - h_2, nominal_bin + h_2) + for i in range(h - 1): + mask = clear_mask(mask, harm_bins[i], harm_bins[i]) + _, harm_bins[h - 1] = masked_max(fft_data, mask) + + mask = clear_mask(mask, nominal_bin - h_2, nominal_bin + h_2) + mask = set_mask(mask, harm_bins[h - 1] - BW, harm_bins[h - 1] + BW) + for i in range(h - 1): + mask = clear_mask(mask, harm_bins[i] - BW, harm_bins[i] + BW) + harms[h - 1], harm_bws[h - 1] = masked_sum_of_sq(fft_data, mask) + return (harm_bins, harms, harm_bws) + + +def calculate_auto_mask(fft_data, harm_bins, window_type): + BANDWIDTH_DIVIDER = 80 + NUM_INITAL_NOISE_HARMS = 5 + n = len(fft_data) + bw = n / BANDWIDTH_DIVIDER + + mask = init_mask(n) + for i in range(NUM_INITAL_NOISE_HARMS): + clear_mask(mask, harm_bins[i] - bw, harm_bins[i] + bw) + mask[0] = False + + noise_est, noise_bins = masked_sum(fft_data, mask) + noise_est /= noise_bins + + mask = init_mask(n) + clear_mask_at_dc(mask, window_type) + for h in harm_bins: + if mask[h] == 0: + continue + + j = 1 + while ( + h - j > 0 + and mask[h - j] == 1 + and sum(fft_data[(h - j) : (h - j + 3)]) / 3 > noise_est + ): + j += 1 + low = h - j + 1 + + j = 1 + while ( + h + j < n + and mask[h + j] == 1 + and sum(fft_data[(h + j - 2) : (h + j + 1)]) / 3 > noise_est + ): + j += 1 + high = h + j - 1 + + clear_mask(mask, low, high) + + return mask + + +def find_spur(find_in_harms, fund_bin, harms, harm_bws, fft_data, window_type): + if find_in_harms: + spur, index = get_max(harms[1:]) + return (spur, harm_bws[index + 1]) + else: + return find_spur_in_data(fft_data, window_type, fund_bin) + + +def find_spur_in_data(fft_data, window_type, fund_bin): + BW = 3 + n = len(fft_data) + mask = init_mask(n) + mask = clear_mask_at_dc(mask, window_type) + mask = clear_mask(mask, fund_bin - BW, fund_bin + BW) + + index = 0 + for i, v in enumerate(mask): + if v: + index = i + break + + max_value = masked_sum_of_sq(fft_data, mask, index - BW, index + BW) + max_index = index + + while index < len(fft_data): + if mask[index]: + value = masked_sum_of_sq(fft_data, mask, index - BW, index + BW) + if value > max_value: + max_value = value + max_index = index + index += 1 + _, spur_bin = masked_max(fft_data, mask, max_index - BW, max_index + BW) + spur, spur_bw = masked_sum_of_sq(fft_data, mask, spur_bin - BW, spur_bin + BW) + return (spur, spur_bw) + + +def clear_mask_at_dc(mask, window_type): + return clear_mask(mask, 0, window_type >> 4) + + +def init_mask(n, initial_value=True): + if initial_value: + return np.ones(n, dtype=bool) + else: + return np.zeros(n, dtype=bool) + + +def set_mask(mask, start, end, set_value=True): + nyq = len(mask) + mask[map_nyquist(np.array(range(int(start), int(end) + 1)), nyq)] = set_value + return mask + + +def clear_mask(mask, start, end): + return set_mask(mask, start, end, False) + + +def map_nyquist(indices, nyq): + n = 2 * (nyq - 1) + indices = np.mod(indices + n, n) + if isinstance(indices, np.ndarray): + indices[indices > nyq] = n - indices[indices > nyq] + else: + indices = n - indices if indices > nyq else indices + return indices + + +def masked_max(data, mask, start=0, finish=None): + if finish is None: + finish = len(data) - 1 + _, indices = masked_subset(mask, start, finish) + [value, i] = get_max(data[indices]) + return (value, indices[i]) + + +def masked_sum(data, mask, start=0, finish=None): + if finish is None: + finish = len(data) - 1 + mask, indices = masked_subset(mask, start, finish) + value = sum(data[indices]) + return value, len(indices) + + +def masked_sum_of_sq(data, mask, start=0, finish=None): + if finish is None: + finish = len(data) - 1 + mask, indices = masked_subset(mask, start, finish) + value = sum(data[indices] * data[indices]) + return value, len(indices) + + +def masked_subset(mask, start, finish): + nyq = len(mask) - 1 + mapped_subset = map_nyquist(np.array(range(start, finish)), nyq) + indices = np.array(range(0, finish)) + indices = indices[mapped_subset] + mask = mask[mapped_subset] + indices = indices[mask] + return (mask, indices) + + +def get_max(data): + index = np.argmax(data) + return (data[index], index) \ No newline at end of file diff --git a/cn0577/sine_gen.py b/cn0577/sine_gen.py new file mode 100644 index 0000000..f73d825 --- /dev/null +++ b/cn0577/sine_gen.py @@ -0,0 +1,124 @@ +# +# Copyright (c) 2019 Analog Devices Inc. +# +# This file is part of libm2k +# (see http://www.github.com/analogdevicesinc/libm2k). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# +import libm2k +import math +import matplotlib.pyplot as plt +import time + +available_sample_rates= [750, 7500, 75000, 750000, 7500000, 75000000] +max_rate = available_sample_rates[-1] # last sample rate = max rate +min_nr_of_points=10 +max_buffer_size = 500000 +uri = "ip:192.168.3.2" +# uri = "usb:1.21.5" #if m2k connected to pc +#if m2k connected to fpga, remove uri + +def get_best_ratio(ratio): + max_it=max_buffer_size/ratio + best_ratio=ratio + best_fract=1 + + for i in range(1,int(max_it)): + new_ratio = i*ratio + (new_fract, integral) = math.modf(new_ratio) + if new_fract < best_fract: + best_fract = new_fract + best_ratio = new_ratio + if new_fract == 0: + break + + return best_ratio,best_fract + + +def get_samples_count(rate, freq): + ratio = rate/freq + if ratio. +# + +# This example will generate a binary counter on the first N_BITS of the +# digital interface and read them back - no additional connection required +import time +import libm2k + +n_bits=15 + +m2k1= "ip:192.168.2.1" #ADALM2000-A +m2k2= "ip:192.168.3.1" #ADALM2000-B reconfigured + +def pin_check(name, indexw, indexr): + + level=1 + dig.setValueRaw(indexw, level) + read_pin= dig2.getValueRaw(indexr) + # print(name, "=", read_pin) + if (read_pin==1): + # print("PIN HIGH OKAY") + high_check=1 + else: + print(name,"PIN HIGH NOT OKAY") + high_check=0 + failed_tests.append("pin "+ name + " failed") + + # time.sleep(1) + + level=0 + dig.setValueRaw(indexw, level) + read_pin= dig2.getValueRaw(indexr) + # print(name, "=", read_pin) + if (read_pin==0): + # print("PIN LOW OKAY") + low_check=1 + else: + print(name, "PIN LOW NOT OKAY") + low_check=0 + failed_tests.append("pin "+ name + " failed") + + # time.sleep(1) + + if (high_check==1) and (low_check==1): + print(name, "PASS") + return failed_tests + else: + print(name, "FAIL\n") + return failed_tests + + + +ctx=libm2k.m2kOpen(m2k1) +if ctx is None: + print("Connection Error: No ADALM2000-A device available/connected to your PC.") + exit(1) + +ctx2=libm2k.m2kOpen(m2k2) +if ctx2 is None: + print("Connection Error: No ADALM2000-B device available/connected to your PC.") + exit(1) + +dig=ctx.getDigital() +dig.reset() + +dig2=ctx2.getDigital() +dig2.reset() + +#Setting voltage neede by the FTHR-PMOD-INTZ +#M2K1 for 3V3: connect m2k1 V+ to 3V3 pin of FTHR-PMOD-INTZ, GND to GND +ps1=ctx.getPowerSupply() +ps1.reset() +ps1.enableChannel(0,True) +ps1.pushChannel(0,3.3) +print("ADALM2000-A voltage supply running with 3V3") + +#M2K2 for 5V: connect m2k2 V+ to 5V pin of FTHR-PMOD-INTZ, GND to GND +ps2=ctx2.getPowerSupply() +ps2.reset() +ps2.enableChannel(0,True) +ps2.pushChannel(0,5) +print("ADALM2000-B voltage supply running with 5V") + +dig.setSampleRateIn(10000) #Set the sample rate for all digital input channels. +dig.setSampleRateOut(10000) #Set the sample rate for all digital output channels. + +for i in range(n_bits): + dig.setDirection(i,libm2k.DIO_OUTPUT) #Set the direction of the given digital channel. + dig.enableChannel(i,True) + +failed_tests = [] +sn = input("Enter board serial number:") + +while(1): + # pin_check('PIN', 0, 7) + print("\nTesting SPI pins. . .") + + #pin_check(pin_name, m2k_write_pin, m2k_read_pin) + failed_tests = pin_check('CS', 0, 0) + failed_tests = pin_check('MOSI', 1, 1) + failed_tests = pin_check('MISO', 2, 2) + failed_tests = pin_check('SCLK', 3, 3) + failed_tests = pin_check('D13', 4, 4) + failed_tests = pin_check('D12', 5, 5) + failed_tests = pin_check('D11', 6, 6) + failed_tests = pin_check('D10', 7, 7) + + print("\nTesting I2C pins. . .") + + failed_tests = pin_check('D6_a', 8, 8) + failed_tests = pin_check('D6_b', 8, 9) + failed_tests = pin_check('D5_a', 9, 10) + failed_tests = pin_check('D5_b', 9, 11) + failed_tests = pin_check('SCL1', 10, 12) + failed_tests = pin_check('SCL2', 10, 13) + failed_tests = pin_check('SDA1', 11, 14) + failed_tests = pin_check('SDA2', 11, 15) + + if len(failed_tests) == 0: + print("\n\nBoard PASSES!!") + else: + print("\n\nBoard FAILED the following tests:") + for failure in failed_tests: + print(failure) + print("\nNote failures and set aside for debug.\nMake sure to secure pin connections with the ADALM2000 before repeating the test.") + + record = open("fthr-pmod-intz_report.csv","a") + record.write(sn + "," + str(failed_tests) + "\n") + record.close() + + next=input("Enter e to end test, r to repeat test:") + failed_tests = [] + if next=="e": + del m2k1 + del m2k2 + ps1.pushChannel(0,0) + ps2.pushChannel(0,0) + del ps1 + del ps2 + exit()