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()