Hello,
I am trying to run the Phaser CN0566 code for FMCW radar by Jon.Kraft on a ADLAM-Pluto that I've hacked by using the steps given by Jon on his youtube channel, But as I don't need two TX/RX channels so I've just increased the pluto's bandwidth.
I've written a spectrum analyzer like code for the pluto bas per my requirements and removed the parts of code that are required by phaser.
When I run the code the GUI starts but as I press Start the pluto throws a connection time out error at the sdr.rx() line.
I have checked and I my software versions all satisfy the the requirements put forward by Jon Kraft in his youtube video.
I use a bin file to load the data so that there should not be any load on the PC the parameters I use in the bin file are
fs = 5e6 #sampling frequency
bandwidth = 750e3
on_time = 0.9e-3 # sec
off_time = 0.1e-3
onsamp = 4096
off_samp = 1024
Below is the code for the GUI
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QLabel from PyQt5.QtCore import Qt, QTimer import numpy as np import pyqtgraph as pg import adi import sys class RealTimeFFTWindow(QMainWindow): def __init__(self): super().__init__() # file_path = 'calc.iq' file_path = 'pylfm.iq' self.tx_data = np.fromfile(file_path, dtype=np.complex64) self.tx_data *= 2**14 # Initialize the PlutoSDR self.center_frequency = int(435e6) self.buffer_size = int(len(self.tx_data)) self.bandwidth = int(750e3) self.sample_rate = int(5e6) self.period = 200e-6 self.buffers = np.array([], dtype = np.complex64) try: # self.sdr = adi.Pluto(uri="usb:1.8.5") self.sdr = adi.Pluto(uri="ip:192.168.2.1") # Adjust IP address as necessary self.sdr.rx_lo = self.center_frequency # self.sdr.filter = 'AD936x_LP_666kHz_2MSPS.ftr' self.sdr.sample_rate = self.sample_rate self.sdr.rx_rf_bandwidth = self.bandwidth self.sdr.rx_hardwaregain_chan0 = 0.0 self.sdr.rx_buffer_size = self.buffer_size except Exception as e: print(f"Error initializing PlutoSDR: {e}") sys.exit(1) # Array to store received samples self.received_samples = np.array([], dtype=np.complex64) self.sdr.tx_rf_bandwidth = self.bandwidth self.sdr.tx_lo = self.center_frequency self.sdr._tx_buffer_size = self.buffer_size self.sdr.gain_control_mode_chan0 = "manual" self.sdr.tx_hardwaregain_chan0 = 0.0 # the range is -90 to 0 dB self.sdr.tx_cyclic_buffer = True # Enable cyclic buffers # Configure TDD controller sdr_pins = adi.one_bit_adc_dac(uri="ip:192.168.2.1") sdr_pins.gpio_tdd_ext_sync = False # If set to True, this enables external capture triggering using the L24N GPIO on the Pluto. When set to false, an internal trigger pulse will be generated every second tdd = adi.tddn(uri="ip:192.168.2.1") tdd.enable = False # disable TDD to configure the registers tdd.sync_external = True tdd.startup_delay_ms = 0 tdd.frame_length_ms = self.period/1e3 + 0.2 # each chirp is spaced this far apart num_chirps = 1 tdd.burst_count = num_chirps # number of chirps in one continuous receive buffer tdd.channel[0].enable = True tdd.channel[0].polarity = False tdd.channel[0].on_ms = 0.01 tdd.channel[0].off_ms = 0.1 tdd.channel[1].enable = True tdd.channel[1].polarity = False tdd.channel[1].on_ms = 0.01 tdd.channel[1].off_ms = 0.1 tdd.channel[2].enable = False tdd.enable = True # # Pluto receive buffer size needs to be greater than total time for all chirps # total_time = tdd.frame_length_ms * num_chirps # time in ms # print("Total Time for all Chirps: ", total_time, "ms") # buffer_time = 0 # power=12 # while total_time > buffer_time: # power=power+1 # buffer_size = int(2**power) # buffer_time = buffer_size/self.sdr.sample_rate*1000 # buffer time in ms # if power==23: # break # max pluto buffer size is 2**23, but for tdd burst mode, set to 2**22 # print("buffer_size:", buffer_size) # self.sdr.rx_buffer_size = buffer_size # print("buffer_time:", buffer_time, " ms") # Set up the GUI self.setWindowTitle("SAR GUI") self.setGeometry(400, 100, 1200, 800) central_widget = pg.GraphicsLayoutWidget() self.setCentralWidget(central_widget) layout = QHBoxLayout(central_widget) plot_panel = QWidget() plot_layout = QVBoxLayout(plot_panel) # Add side control panel side_panel = QWidget() side_layout = QVBoxLayout(side_panel) side_panel.setFixedWidth(220) layout.addWidget(plot_panel) layout.addWidget(side_panel) # Add label widget for displaying coordinates and data self.label = QLabel() self.label.setStyleSheet("color: yellow;") self.label.setAlignment(Qt.AlignRight) # Add PlotWidgets for FFT and Time Domain self.fft_plot = pg.PlotWidget(title="Beat Signal FFT") self.timedomain_plot = pg.PlotWidget(title="Time Domain") plot_layout.addWidget(self.timedomain_plot) plot_layout.addWidget(self.label) plot_layout.addWidget(self.fft_plot) # FFT Curve self.curve_fft_below = self.fft_plot.plot(pen=pg.mkPen(color="limegreen", width=1.5), fillLevel=-110, fillBrush=(50, 50, 200, 100)) self.curve_fft_above = self.fft_plot.plot(pen=pg.mkPen(color="limegreen", width=1.5), fillLevel=30, fillBrush=(1, 1, 1, 100)) self.curve_fft_max_hold = self.fft_plot.plot(pen=pg.mkPen(color="red", width=1.5), fillLevel=30) # Time Domain Plot Controls self.vLine_time = pg.InfiniteLine(angle=90, movable=False) self.hLine_time = pg.InfiniteLine(angle=0, movable=False) self.timedomain_plot.addItem(self.vLine_time, ignoreBounds=True) self.timedomain_plot.addItem(self.hLine_time, ignoreBounds=True) # Set background color to dark gray self.fft_plot.setBackground('#222222') self.timedomain_plot.setBackground('#222222') # Add controls to the side panel self.start_button = QPushButton("Start") self.start_button.clicked.connect(self.start_update) self.start_button.clicked.connect(self.transmit) self.start_button.setStyleSheet("background-color: green;") side_layout.addWidget(self.start_button) self.max_hold_button = QPushButton("Max Hold") self.max_hold_button.clicked.connect(self.toggle_max_hold) self.max_hold_button.setStyleSheet("background-color: white;") side_layout.addWidget(self.max_hold_button) side_layout.addStretch() # Crosshair lines self.vLine = pg.InfiniteLine(angle=90, movable=False) self.hLine = pg.InfiniteLine(angle=0, movable=False) self.fft_plot.addItem(self.vLine, ignoreBounds=True) self.fft_plot.addItem(self.hLine, ignoreBounds=True) # Mouse movement event handling self.proxy_fft = pg.SignalProxy(self.fft_plot.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMovedFFT) self.proxy_time = pg.SignalProxy(self.timedomain_plot.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMovedTime) # Variables for real-time update self.update_interval = 10 # milliseconds self.timer = QTimer() self.timer.timeout.connect(self.update_plot) self.is_running = False self.max_hold_enabled = False self.max_hold_data = None # Add x and y labels to FFT plot self.fft_plot.setLabel('left', text='Power (dB)') self.timedomain_plot.setLabel('bottom', text='Time') self.timedomain_plot.setLabel('left', text='Amplitude') def mouseMovedFFT(self, evt): pos = evt[0] # Using signal proxy turns original arguments into a tuple if self.fft_plot.sceneBoundingRect().contains(pos): mousePoint = self.fft_plot.plotItem.vb.mapSceneToView(pos) x = mousePoint.x() y = mousePoint.y() # Convert x from plot coordinate to frequency in MHz freq_mhz = x self.vLine.setPos(x) self.hLine.setPos(y) self.label.setText(f'Frequency: {freq_mhz:.2f} MHz, Power: {y:.2f} dB') def mouseMovedTime(self, evt): pos = evt[0] if self.timedomain_plot.sceneBoundingRect().contains(pos): mousePoint = self.timedomain_plot.plotItem.vb.mapSceneToView(pos) x = mousePoint.x() y = mousePoint.y() # Convert x from plot coordinate to time in seconds time_sec = x self.vLine_time.setPos(x) self.hLine_time.setPos(y) self.label.setText(f'Time: {time_sec:.2f} s, Amplitude: {y:.2f}') def start_update(self): if not self.is_running: self.timer.start(self.update_interval) self.is_running = True self.start_button.setText("Stop") self.start_button.setStyleSheet("background-color: red;") else: self.timer.stop() self.is_running = False self.start_button.setText("Start") self.start_button.setStyleSheet("background-color: green;") # Stop the SDR and save data self.stop_sdr() self.save_received_samples() def toggle_max_hold(self): self.max_hold_enabled = not self.max_hold_enabled if self.max_hold_enabled: self.max_hold_button.setText("Max Hold (Enabled)") self.max_hold_button.setStyleSheet("background-color: gray;") self.max_hold_data = None # Reset max hold data else: self.max_hold_button.setText("Max Hold") self.max_hold_button.setStyleSheet("background-color: white;") def transmit(self): self.sdr.tx(self.tx_data) def update_plot(self): rx_samples = self.sdr.rx() # Handle mismatch in lengths if len(rx_samples) != len(self.tx_data): print("Mismatch in transmitted and received data lengths.") if len(rx_samples) > len(self.tx_data): rx_samples = rx_samples[:len(self.tx_data)] print("Mismatch Fixed.") else: rx_samples = np.pad(rx_samples, (0, len(self.tx_data) - len(rx_samples)), 'constant') print("Mismatch Fixed.") # Store the received samples in the array self.received_samples = np.concatenate((self.received_samples, rx_samples)) # Multiply transmitted and received pulses beat_signal = self.tx_data * np.conj(rx_samples) window = np.hanning(len(beat_signal)) beat_signal_windowed = window * beat_signal # Time Domain iq_time = np.linspace(0, len(rx_samples) / self.sdr.sample_rate, len(rx_samples)) self.timedomain_plot.plot(iq_time, rx_samples.real, pen='limegreen', clear=True) # FFT update beat_signal_fft = np.fft.fftshift(np.fft.fft(beat_signal_windowed)) # Calculate the frequency axis freq_axis = np.fft.fftshift(np.fft.fftfreq(len(beat_signal_fft), d=1/self.sdr.sample_rate)) freq_axis_mhz = freq_axis / 1e6 # Convert to MHz # Update plot with axis limits and tick formatting self.fft_plot.setXRange(freq_axis_mhz[0], freq_axis_mhz[-1], padding=0) tick_step = 5 # Define the step for ticks in MHz tick_positions = np.arange(freq_axis_mhz[0], freq_axis_mhz[-1] + tick_step, tick_step) tick_labels = [f"{v:.2f} MHz" for v in tick_positions] self.fft_plot.getAxis('bottom').setTicks([list(zip(tick_positions, tick_labels))]) beat_signal_fft_db = 20 * np.log10(np.abs(beat_signal_fft) + 1e-6) # Added small offset to avoid log(0) baseline = np.mean(beat_signal_fft_db[:100]) baseline_adjustment = -90 - baseline beat_signal_fft_db_corrected = beat_signal_fft_db + baseline_adjustment threshold = -90 # Adjust threshold level as needed beat_signal_fft_db_corrected[beat_signal_fft_db_corrected < threshold] = threshold if self.max_hold_enabled: if self.max_hold_data is None: self.max_hold_data = beat_signal_fft_db_corrected else: self.max_hold_data = np.maximum(self.max_hold_data, beat_signal_fft_db_corrected) self.curve_fft_max_hold.setData(freq_axis_mhz, self.max_hold_data, connect='all', antialias=True) else: self.curve_fft_max_hold.setData([], []) self.curve_fft_above.setData(freq_axis_mhz, beat_signal_fft_db_corrected, connect='all', antialias=True) def stop_sdr(self): # Stop the transmission and reception self.sdr.tx_destroy_buffer() self.sdr.rx_destroy_buffer() print("SDR transmission and reception stopped.") def save_received_samples(self): # Save the received samples to a binary file output_file = "received_samples.bin" self.received_samples = self.received_samples.astype(np.complex64) self.received_samples.tofile(output_file) print(f"Received samples saved to {output_file}") if __name__ == "__main__": app = QApplication(sys.argv) window = RealTimeFFTWindow() window.show() sys.exit(app.exec_())
Below is the error thrown
Traceback (most recent call last): File "/home/oem/ADVR9361/SAR.py", line 443, in update_plot rx_samples = self.sdr.rx() File "/home/oem/.local/lib/python3.8/site-packages/adi/rx_tx.py", line 248, in rx data = self.__rx_complex() File "/home/oem/.local/lib/python3.8/site-packages/adi/rx_tx.py", line 213, in __rx_complex x = self._rx_buffered_data() File "/home/oem/.local/lib/python3.8/site-packages/adi/compat.py", line 149, in _rx_buffered_data self._rxbuf.refill() File "/home/oem/.local/lib/python3.8/site-packages/iio.py", line 1003, in refill _buffer_refill(self._buffer) File "/home/oem/.local/lib/python3.8/site-packages/iio.py", line 62, in _check_negative raise OSError(-result, _strerror(-result)) TimeoutError: [Errno 110] Connection timed out
small changes in description
[edited by: Deadshot at 10:00 AM (GMT -4) on 9 Aug 2024]