diff --git a/CFAR_RADAR_Waterfall.py b/CFAR_RADAR_Waterfall.py
index 435c4ce..292b9de 100644
--- a/CFAR_RADAR_Waterfall.py
+++ b/CFAR_RADAR_Waterfall.py
@@ -79,8 +79,8 @@ except:
sample_rate = 0.6e6
center_freq = 2.1e9
signal_freq = 100e3
-num_slices = 20 # this sets how much time will be displayed on the waterfall plot
-fft_size = 1024 * 4
+num_slices = 50 # this sets how much time will be displayed on the waterfall plot
+fft_size = 1024 * 8
plot_freq = 100e3 # x-axis freq range to plot
img_array = np.ones((num_slices, fft_size))*(-100)
@@ -170,7 +170,7 @@ default_chirp_bw = 500e6
N_frame = fft_size
freq = np.linspace(-fs / 2, fs / 2, int(N_frame))
slope = BW / ramp_time_s
-dist = (freq - signal_freq) * c / (4 * slope)
+dist = (freq - signal_freq) * c / (2 * slope)
plot_threshold = False
cfar_toggle = False
@@ -443,7 +443,7 @@ class Window(QMainWindow):
global dist, slope, signal_freq, plot_freq
bw = self.bw_slider.value() * 1e6
slope = bw / ramp_time_s
- dist = (freq - signal_freq) * c / (4 * slope)
+ dist = (freq - signal_freq) * c / (2 * slope)
my_phaser.freq_dev_range = int(bw / 4) # frequency deviation range in Hz
my_phaser.enable = 0
diff --git a/FMCW_RADAR_Waterfall.py b/FMCW_RADAR_Waterfall.py
index 360022c..80ea045 100644
--- a/FMCW_RADAR_Waterfall.py
+++ b/FMCW_RADAR_Waterfall.py
@@ -78,7 +78,7 @@ except:
sample_rate = 0.6e6
center_freq = 2.1e9
signal_freq = 100e3
-num_slices = 400 # this sets how much time will be displayed on the waterfall plot
+num_slices = 600 # this sets how much time will be displayed on the waterfall plot
fft_size = 1024 * 4
plot_freq = 100e3 # x-axis freq range to plot
img_array = np.ones((num_slices, fft_size))*(-100)
@@ -169,7 +169,7 @@ default_chirp_bw = 500e6
N_frame = fft_size
freq = np.linspace(-fs / 2, fs / 2, int(N_frame))
slope = BW / ramp_time_s
-dist = (freq - signal_freq) * c / (4 * slope)
+dist = (freq - signal_freq) * c / (2 * slope)
plot_dist = False
@@ -203,7 +203,7 @@ class Window(QMainWindow):
layout.addWidget(control_label, 0, 0, 1, 2)
# Check boxes
- self.x_axis_check = QCheckBox("Toggle Range/Frequency x-axis")
+ self.x_axis_check = QCheckBox("Convert to Distance")
font = self.x_axis_check.font()
font.setPointSize(10)
self.x_axis_check.setFont(font)
@@ -250,7 +250,7 @@ class Window(QMainWindow):
self.low_slider = QSlider(Qt.Horizontal)
self.low_slider.setMinimum(-100)
self.low_slider.setMaximum(0)
- self.low_slider.setValue(-40)
+ self.low_slider.setValue(-45)
self.low_slider.setTickInterval(20)
self.low_slider.setMaximumWidth(200)
self.low_slider.setTickPosition(QSlider.TicksBelow)
@@ -260,7 +260,7 @@ class Window(QMainWindow):
self.high_slider = QSlider(Qt.Horizontal)
self.high_slider.setMinimum(-100)
self.high_slider.setMaximum(0)
- self.high_slider.setValue(-5)
+ self.high_slider.setValue(-25)
self.high_slider.setTickInterval(20)
self.high_slider.setMaximumWidth(200)
self.high_slider.setTickPosition(QSlider.TicksBelow)
@@ -394,10 +394,10 @@ class Window(QMainWindow):
global dist, slope, signal_freq, plot_freq
bw = self.bw_slider.value() * 1e6
slope = bw / ramp_time_s
- dist = (freq - signal_freq) * c / (4 * slope)
+ dist = (freq - signal_freq) * c / (2 * slope)
if self.x_axis_check.isChecked() == True:
plot_dist = True
- range_x = (plot_freq) * c / (4 * slope)
+ range_x = (plot_freq) * c / (2 * slope)
self.fft_plot.setXRange(0, range_x)
else:
plot_dist = False
@@ -424,7 +424,7 @@ class Window(QMainWindow):
plot_state = win.fft_plot.getViewBox().state
if state == QtCore.Qt.Checked:
plot_dist = True
- range_x = (plot_freq) * c / (4 * slope)
+ range_x = (plot_freq) * c / (2 * slope)
self.fft_plot.setXRange(0, range_x)
else:
plot_dist = False
diff --git a/FMCW_Velocity_RADAR_Waterfall.py b/FMCW_Velocity_RADAR_Waterfall.py
new file mode 100644
index 0000000..2c76555
--- /dev/null
+++ b/FMCW_Velocity_RADAR_Waterfall.py
@@ -0,0 +1,493 @@
+#!/usr/bin/env python3
+# Must use Python 3
+# Copyright (C) 2022 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.
+
+'''FMCW Radar Demo with Phaser (CN0566)
+ Jon Kraft, Jan 20 2024'''
+
+# Imports
+import adi
+
+import sys
+import time
+import matplotlib.pyplot as plt
+import numpy as np
+import pyqtgraph as pg
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import *
+from pyqtgraph.Qt import QtCore, QtGui
+
+# Instantiate all the Devices
+rpi_ip = "ip:phaser.local" # IP address of the Raspberry Pi
+sdr_ip = "ip:192.168.2.1" # "192.168.2.1, or pluto.local" # IP address of the Transceiver Block
+my_sdr = adi.ad9361(uri=sdr_ip)
+my_phaser = adi.CN0566(uri=rpi_ip, sdr=my_sdr)
+
+# Initialize both ADAR1000s, set gains to max, and all phases to 0
+my_phaser.configure(device_mode="rx")
+my_phaser.load_gain_cal()
+my_phaser.load_phase_cal()
+for i in range(0, 8):
+ my_phaser.set_chan_phase(i, 0)
+
+gain_list = [8, 34, 84, 127, 127, 84, 34, 8] # Blackman taper
+for i in range(0, len(gain_list)):
+ my_phaser.set_chan_gain(i, gain_list[i], apply_cal=True)
+
+# Setup Raspberry Pi GPIO states
+try:
+ my_phaser._gpios.gpio_tx_sw = 0 # 0 = TX_OUT_2, 1 = TX_OUT_1
+ my_phaser._gpios.gpio_vctrl_1 = 1 # 1=Use onboard PLL/LO source (0=disable PLL and VCO, and set switch to use external LO input)
+ my_phaser._gpios.gpio_vctrl_2 = 1 # 1=Send LO to transmit circuitry (0=disable Tx path, and send LO to LO_OUT)
+except:
+ my_phaser.gpios.gpio_tx_sw = 0 # 0 = TX_OUT_2, 1 = TX_OUT_1
+ my_phaser.gpios.gpio_vctrl_1 = 1 # 1=Use onboard PLL/LO source (0=disable PLL and VCO, and set switch to use external LO input)
+ my_phaser.gpios.gpio_vctrl_2 = 1 # 1=Send LO to transmit circuitry (0=disable Tx path, and send LO to LO_OUT)
+
+sample_rate = 0.6e6
+center_freq = 2.1e9
+signal_freq = 100e3
+num_slices = 400 # this sets how much time will be displayed on the waterfall plot
+fft_size = 1024 * 4
+plot_freq = 100e3 # x-axis freq range to plot
+img_array = np.ones((num_slices, fft_size))*(-100)
+
+# Configure SDR Rx
+my_sdr.sample_rate = int(sample_rate)
+my_sdr.rx_lo = int(center_freq) # set this to output_freq - (the freq of the HB100)
+my_sdr.rx_enabled_channels = [0, 1] # enable Rx1 (voltage0) and Rx2 (voltage1)
+my_sdr.rx_buffer_size = int(fft_size)
+my_sdr.gain_control_mode_chan0 = "manual" # manual or slow_attack
+my_sdr.gain_control_mode_chan1 = "manual" # manual or slow_attack
+my_sdr.rx_hardwaregain_chan0 = int(30) # must be between -3 and 70
+my_sdr.rx_hardwaregain_chan1 = int(30) # must be between -3 and 70
+# Configure SDR Tx
+my_sdr.tx_lo = int(center_freq)
+my_sdr.tx_enabled_channels = [0, 1]
+my_sdr.tx_cyclic_buffer = True # must set cyclic buffer to true for the tdd burst mode. Otherwise Tx will turn on and off randomly
+my_sdr.tx_hardwaregain_chan0 = -88 # must be between 0 and -88
+my_sdr.tx_hardwaregain_chan1 = -0 # must be between 0 and -88
+
+# Configure the ADF4159 Rampling PLL
+output_freq = 12.145e9
+BW = 500e6
+num_steps = 500
+ramp_time = 0.5e3 # us
+my_phaser.frequency = int(output_freq / 4) # Output frequency divided by 4
+my_phaser.freq_dev_range = int(
+ BW / 4
+) # frequency deviation range in Hz. This is the total freq deviation of the complete freq ramp
+my_phaser.freq_dev_step = int(
+ (BW/4) / num_steps
+) # frequency deviation step in Hz. This is fDEV, in Hz. Can be positive or negative
+my_phaser.freq_dev_time = int(
+ ramp_time
+) # total time (in us) of the complete frequency ramp
+print("requested freq dev time = ", ramp_time)
+ramp_time = my_phaser.freq_dev_time
+ramp_time_s = ramp_time / 1e6
+print("actual freq dev time = ", ramp_time)
+my_phaser.delay_word = 4095 # 12 bit delay word. 4095*PFD = 40.95 us. For sawtooth ramps, this is also the length of the Ramp_complete signal
+my_phaser.delay_clk = "PFD" # can be 'PFD' or 'PFD*CLK1'
+my_phaser.delay_start_en = 0 # delay start
+my_phaser.ramp_delay_en = 0 # delay between ramps.
+my_phaser.trig_delay_en = 0 # triangle delay
+my_phaser.ramp_mode = "continuous_triangular" # ramp_mode can be: "disabled", "continuous_sawtooth", "continuous_triangular", "single_sawtooth_burst", "single_ramp_burst"
+my_phaser.sing_ful_tri = (
+ 0 # full triangle enable/disable -- this is used with the single_ramp_burst mode
+)
+my_phaser.tx_trig_en = 0 # start a ramp with TXdata
+my_phaser.enable = 0 # 0 = PLL enable. Write this last to update all the registers
+
+# Print config
+print(
+ """
+CONFIG:
+Sample rate: {sample_rate}MHz
+Num samples: 2^{Nlog2}
+Bandwidth: {BW}MHz
+Ramp time: {ramp_time}ms
+Output frequency: {output_freq}MHz
+IF: {signal_freq}kHz
+""".format(
+ sample_rate=sample_rate / 1e6,
+ Nlog2=int(np.log2(fft_size)),
+ BW=BW / 1e6,
+ ramp_time=ramp_time / 1e3,
+ output_freq=output_freq / 1e6,
+ signal_freq=signal_freq / 1e3,
+ )
+)
+
+# Create a sinewave waveform
+fs = int(my_sdr.sample_rate)
+N = int(my_sdr.rx_buffer_size)
+fc = int(signal_freq / (fs / N)) * (fs / N)
+ts = 1 / float(fs)
+t = np.arange(0, N * ts, ts)
+i = np.cos(2 * np.pi * t * fc) * 2 ** 14
+q = np.sin(2 * np.pi * t * fc) * 2 ** 14
+iq = 1 * (i + 1j * q)
+
+# Send data
+my_sdr._ctx.set_timeout(0)
+my_sdr.tx([iq * 0.5, iq]) # only send data to the 2nd channel (that's all we need)
+
+c = 3e8
+default_chirp_bw = 500e6
+N_frame = fft_size
+freq = np.linspace(-fs / 2, fs / 2, int(N_frame))
+slope = BW / ramp_time_s
+dist = (freq - signal_freq) * c / (2 * slope)
+
+plot_dist = False
+
+
+class Window(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Interactive FFT")
+ self.setGeometry(0, 0, 400, 400) # (x,y, width, height)
+ #self.setFixedWidth(600)
+ self.setWindowState(QtCore.Qt.WindowMaximized)
+ self.num_rows = 12
+ self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) #remove the window's close button
+ self.UiComponents()
+ self.show()
+
+ # method for components
+ def UiComponents(self):
+ widget = QWidget()
+
+ global layout, signal_freq
+ layout = QGridLayout()
+
+ # Control Panel
+ control_label = QLabel("PHASER Simple FMCW Radar")
+ font = control_label.font()
+ font.setPointSize(24)
+ control_label.setFont(font)
+ font.setPointSize(12)
+ control_label.setAlignment(Qt.AlignHCenter) # | Qt.AlignVCenter)
+ layout.addWidget(control_label, 0, 0, 1, 2)
+
+ # Check boxes
+ self.x_axis_check = QCheckBox("Toggle Range/Frequency x-axis")
+ font = self.x_axis_check.font()
+ font.setPointSize(10)
+ self.x_axis_check.setFont(font)
+
+ self.x_axis_check.stateChanged.connect(self.change_x_axis)
+ layout.addWidget(self.x_axis_check, 2, 0)
+
+ # Range resolution
+ # Changes with the Chirp BW slider
+ self.range_res_label = QLabel(
+ "B: %0.2f MHz - Rres: %0.2f m"
+ % (default_chirp_bw / 1e6, c / (2 * default_chirp_bw))
+ )
+ font = self.range_res_label.font()
+ font.setPointSize(10)
+ self.range_res_label.setFont(font)
+ self.range_res_label.setAlignment(Qt.AlignLeft)
+ self.range_res_label.setMaximumWidth(200)
+ self.range_res_label.setMinimumWidth(100)
+ layout.addWidget(self.range_res_label, 4, 1)
+
+ # Chirp bandwidth slider
+ self.bw_slider = QSlider(Qt.Horizontal)
+ self.bw_slider.setMinimum(100)
+ self.bw_slider.setMaximum(500)
+ self.bw_slider.setValue(int(default_chirp_bw / 1e6))
+ self.bw_slider.setTickInterval(50)
+ self.bw_slider.setMaximumWidth(200)
+ self.bw_slider.setTickPosition(QSlider.TicksBelow)
+ self.bw_slider.valueChanged.connect(self.get_range_res)
+ layout.addWidget(self.bw_slider, 4, 0)
+
+ self.set_bw = QPushButton("Set Chirp Bandwidth")
+ self.set_bw.setMaximumWidth(200)
+ self.set_bw.pressed.connect(self.set_range_res)
+ layout.addWidget(self.set_bw, 5, 0, 1, 1)
+
+ self.quit_button = QPushButton("Quit")
+ self.quit_button.pressed.connect(self.end_program)
+ layout.addWidget(self.quit_button, 30, 0, 4, 4)
+
+
+ # waterfall level slider
+ self.low_slider = QSlider(Qt.Horizontal)
+ self.low_slider.setMinimum(-1000)
+ self.low_slider.setMaximum(1000)
+ self.low_slider.setValue(-500)
+ self.low_slider.setTickInterval(50)
+ self.low_slider.setMaximumWidth(200)
+ self.low_slider.setTickPosition(QSlider.TicksBelow)
+ self.low_slider.valueChanged.connect(self.get_water_levels)
+ layout.addWidget(self.low_slider, 8, 0)
+
+ self.high_slider = QSlider(Qt.Horizontal)
+ self.high_slider.setMinimum(-1000)
+ self.high_slider.setMaximum(1000)
+ self.high_slider.setValue(500)
+ self.high_slider.setTickInterval(50)
+ self.high_slider.setMaximumWidth(200)
+ self.high_slider.setTickPosition(QSlider.TicksBelow)
+ self.high_slider.valueChanged.connect(self.get_water_levels)
+ layout.addWidget(self.high_slider, 10, 0)
+
+ self.water_label = QLabel("Waterfall Intensity Levels")
+ self.water_label.setFont(font)
+ self.water_label.setAlignment(Qt.AlignCenter)
+ self.water_label.setMinimumWidth(100)
+ self.water_label.setMaximumWidth(200)
+ layout.addWidget(self.water_label, 7, 0,1,1)
+ self.low_label = QLabel("LOW LEVEL: %0.0f" % (self.low_slider.value()))
+ self.low_label.setFont(font)
+ self.low_label.setAlignment(Qt.AlignLeft)
+ self.low_label.setMinimumWidth(100)
+ self.low_label.setMaximumWidth(200)
+ layout.addWidget(self.low_label, 8, 1)
+ self.high_label = QLabel("HIGH LEVEL: %0.0f" % (self.high_slider.value()))
+ self.high_label.setFont(font)
+ self.high_label.setAlignment(Qt.AlignLeft)
+ self.high_label.setMinimumWidth(100)
+ self.high_label.setMaximumWidth(200)
+ layout.addWidget(self.high_label, 10, 1)
+
+ self.steer_slider = QSlider(Qt.Horizontal)
+ self.steer_slider.setMinimum(-80)
+ self.steer_slider.setMaximum(80)
+ self.steer_slider.setValue(0)
+ self.steer_slider.setTickInterval(20)
+ self.steer_slider.setMaximumWidth(200)
+ self.steer_slider.setTickPosition(QSlider.TicksBelow)
+ self.steer_slider.valueChanged.connect(self.get_steer_angle)
+ layout.addWidget(self.steer_slider, 14, 0)
+ self.steer_title = QLabel("Receive Steering Angle")
+ self.steer_title.setFont(font)
+ self.steer_title.setAlignment(Qt.AlignCenter)
+ self.steer_title.setMinimumWidth(100)
+ self.steer_title.setMaximumWidth(200)
+ layout.addWidget(self.steer_title, 13, 0)
+ self.steer_label = QLabel("%0.0f DEG" % (self.steer_slider.value()))
+ self.steer_label.setFont(font)
+ self.steer_label.setAlignment(Qt.AlignLeft)
+ self.steer_label.setMinimumWidth(100)
+ self.steer_label.setMaximumWidth(200)
+ layout.addWidget(self.steer_label, 14, 1,1,2)
+
+ # FFT plot
+ self.fft_plot = pg.plot()
+ self.fft_plot.setMinimumWidth(600)
+ self.fft_curve = self.fft_plot.plot(freq, pen={'color':'y', 'width':2})
+ title_style = {"size": "20pt"}
+ label_style = {"color": "#FFF", "font-size": "14pt"}
+ self.fft_plot.setLabel("bottom", text="Frequency", units="Hz", **label_style)
+ self.fft_plot.setLabel("left", text="Magnitude", units="dB", **label_style)
+ self.fft_plot.setTitle("Received Signal - Frequency Spectrum", **title_style)
+ layout.addWidget(self.fft_plot, 0, 2, self.num_rows, 1)
+ self.fft_plot.setYRange(-60, 0)
+ self.fft_plot.setXRange(signal_freq, signal_freq+plot_freq)
+
+ # Waterfall plot
+ self.waterfall = pg.PlotWidget()
+ self.imageitem = pg.ImageItem()
+ self.waterfall.addItem(self.imageitem)
+ # Use a viridis colormap
+ pos = np.array([0.0, 0.25, 0.5, 0.75, 1.0])
+ color = np.array([[68, 1, 84,255], [59, 82, 139,255], [33, 145, 140,255], [94, 201, 98,255], [253, 231, 37,255]], dtype=np.ubyte)
+ lut = pg.ColorMap(pos, color).getLookupTable(0.0, 1.0, 256)
+ self.imageitem.setLookupTable(lut)
+ self.imageitem.setLevels([0,1])
+ # self.imageitem.scale(0.35, sample_rate / (N)) # this is deprecated -- we have to use setTransform instead
+ tr = QtGui.QTransform()
+ tr.translate(0,-sample_rate/2)
+ tr.scale(0.35, sample_rate / (N))
+ self.imageitem.setTransform(tr)
+ zoom_freq = 35e3
+ self.waterfall.setRange(yRange=(signal_freq, signal_freq + zoom_freq))
+ self.waterfall.setTitle("Waterfall Spectrum", **title_style)
+ self.waterfall.setLabel("left", "Frequency", units="Hz", **label_style)
+ self.waterfall.setLabel("bottom", "Time", units="sec", **label_style)
+ layout.addWidget(self.waterfall, 0 + self.num_rows + 1, 2, self.num_rows, 1)
+ self.img_array = np.ones((num_slices, fft_size))*(-100)
+
+ widget.setLayout(layout)
+ # setting this widget as central widget of the main window
+ self.setCentralWidget(widget)
+
+ def get_range_res(self):
+ """ Updates the slider bar label with RF bandwidth and range resolution
+ Returns:
+ None
+ """
+ bw = self.bw_slider.value() * 1e6
+ range_res = c / (2 * bw)
+ self.range_res_label.setText(
+ "B: %0.2f MHz - Rres: %0.2f m"
+ % (bw / 1e6, c / (2 * bw))
+ )
+
+ def get_water_levels(self):
+ """ Updates the waterfall intensity levels
+ Returns:
+ None
+ """
+ if self.low_slider.value() > self.high_slider.value():
+ self.low_slider.setValue(self.high_slider.value())
+ self.low_label.setText("LOW LEVEL: %0.0f" % (self.low_slider.value()))
+ self.high_label.setText("HIGH LEVEL: %0.0f" % (self.high_slider.value()))
+
+ def get_steer_angle(self):
+ """ Updates the steering angle readout
+ Returns:
+ None
+ """
+ self.steer_label.setText("%0.0f DEG" % (self.steer_slider.value()))
+ phase_delta = (
+ 2
+ * 3.14159
+ * 10.25e9
+ * 0.014
+ * np.sin(np.radians(self.steer_slider.value()))
+ / (3e8)
+ )
+ my_phaser.set_beam_phase_diff(np.degrees(phase_delta))
+
+ def set_range_res(self):
+ """ Sets the Chirp bandwidth
+ Returns:
+ None
+ """
+ global dist, slope, signal_freq, plot_freq
+ bw = self.bw_slider.value() * 1e6
+ slope = bw / ramp_time_s
+ dist = (freq - signal_freq) * c / (2 * slope)
+ if self.x_axis_check.isChecked() == True:
+ plot_dist = True
+ range_x = (plot_freq) * c / (2 * slope)
+ self.fft_plot.setXRange(0, range_x)
+ else:
+ plot_dist = False
+ self.fft_plot.setXRange(signal_freq, signal_freq+plot_freq)
+ my_phaser.freq_dev_range = int(bw / 4) # frequency deviation range in Hz
+ my_phaser.enable = 0
+
+ def end_program(self):
+ """ Gracefully shutsdown the program and Pluto
+ Returns:
+ None
+ """
+ my_sdr.tx_destroy_buffer()
+ self.close()
+
+ def change_x_axis(self, state):
+ """ Toggles between showing frequency and range for the x-axis
+ Args:
+ state (QtCore.Qt.Checked) : State of check box
+ Returns:
+ None
+ """
+ global plot_dist, slope, signal_freq, plot_freq
+ plot_state = win.fft_plot.getViewBox().state
+ if state == QtCore.Qt.Checked:
+ plot_dist = True
+ range_x = (plot_freq) * c / (2 * slope)
+ self.fft_plot.setXRange(0, range_x)
+ else:
+ plot_dist = False
+ self.fft_plot.setXRange(signal_freq, signal_freq+plot_freq)
+
+
+# create pyqt5 app
+App = QApplication(sys.argv)
+
+# create the instance of our Window
+win = Window()
+index = 0
+
+
+def update():
+ """ Updates the FFT in the window
+ Returns:
+ None
+ """
+ global index, plot_dist, freq, dist, s_vel
+ label_style = {"color": "#FFF", "font-size": "14pt"}
+
+ data = my_sdr.rx()
+ data = data[0] + data[1]
+ win_funct = np.blackman(len(data))
+ y = data * win_funct
+ sp = np.absolute(np.fft.fft(y))
+ sp = np.fft.fftshift(sp)
+ s_mag = np.abs(sp) / np.sum(win_funct)
+ s_mag = np.maximum(s_mag, 10 ** (-15))
+ s_dbfs = 20 * np.log10(s_mag / (2 ** 11))
+ index_100 = int(N_frame/2+N_frame*signal_freq/sample_rate)
+ vel_range = N_frame-index_100-1
+ s_vel = np.zeros(N_frame)
+ for i in range(vel_range):
+ index_high = index_100 + i
+ index_low = index_100 - i
+ s_vel[i] = 0.03/4 * (s_dbfs[index_high]-s_dbfs[index_low])*1000
+ s_vel = np.ones(N_frame)*abs(s_vel)
+
+
+
+ if plot_dist:
+ win.fft_curve.setData(dist, s_dbfs)
+ win.fft_plot.setLabel("bottom", text="Distance", units="m", **label_style)
+ else:
+ win.fft_curve.setData(freq, s_vel)
+ win.fft_plot.setLabel("bottom", text="Frequency", units="Hz", **label_style)
+
+ win.img_array = np.roll(win.img_array, 1, axis=0)
+ win.img_array[0] = s_vel
+ win.imageitem.setLevels([win.low_slider.value(), win.high_slider.value()])
+ win.imageitem.setImage(win.img_array, autoLevels=False)
+
+ if index == 1:
+ win.fft_plot.enableAutoRange("xy", False)
+ index = index + 1
+
+
+timer = QtCore.QTimer()
+timer.timeout.connect(update)
+timer.start(0)
+
+# start the app
+sys.exit(App.exec())
+