diff --git a/mcpha_daq.ui b/mcpha_daq.ui new file mode 100644 index 0000000000000000000000000000000000000000..170b6e67d2856698a4877106c5e7d52a79cc2aa0 --- /dev/null +++ b/mcpha_daq.ui @@ -0,0 +1,366 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>OscDisplay</class> + <widget class="QWidget" name="OscDisplay"> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="plotWidget" native="true"> + <layout class="QVBoxLayout" name="plotLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="controlWidget" native="true"> + <layout class="QVBoxLayout" name="controlLayout"> + <item> + <spacer name="spacer_0"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QWidget" name="cursorWidget" native="true"> + <layout class="QGridLayout" name="cursorLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <property name="horizontalSpacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="ch1Label"> + <property name="text"> + <string>channel 1</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="ch1Value"> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="ch2Label"> + <property name="text"> + <string>channel 2</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="ch2Value"> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="timeLabel"> + <property name="text"> + <string>time axis</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="timeValue"> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="spacer_1"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="modeLabel"> + <property name="text"> + <string>trigger mode</string> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="modeWidget" native="true"> + <layout class="QHBoxLayout" name="modeLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QRadioButton" name="normalButton"> + <property name="text"> + <string>normal</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="autoButton"> + <property name="text"> + <string>auto</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="sourceLabel"> + <property name="text"> + <string>trigger source</string> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="sourceWidget" native="true"> + <layout class="QHBoxLayout" name="sourceLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QRadioButton" name="ch1Button"> + <property name="text"> + <string>channel 1</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="ch2Button"> + <property name="text"> + <string>channel 2</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="slopeLabel"> + <property name="text"> + <string>trigger slope</string> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="slopeWidget" native="true"> + <layout class="QHBoxLayout" name="slopeLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QRadioButton" name="risingButton"> + <property name="text"> + <string>rising</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="fallingButton"> + <property name="text"> + <string>falling</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="levelLabel"> + <property name="text"> + <string>trigger level</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="levelValue"> + <property name="minimum"> + <number>-4090</number> + </property> + <property name="maximum"> + <number>4090</number> + </property> + <property name="singleStep"> + <number>10</number> + </property> + <property name="value"> + <number>500</number> + </property> + </widget> + </item> + <item> + <spacer name="spacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="startButton"> + <property name="text"> + <string>Start</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="startDAQButton"> + <property name="text"> + <string>Start DAQ</string> + </property> + </widget> + </item> + <item> + <spacer name="spacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="saveButton"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="loadButton"> + <property name="text"> + <string>Load</string> + </property> + </widget> + </item> + <item> + <spacer name="spacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/rpOsci.py b/rpOsci.py index 255253866936b2f72d153af6c18270bcde283493..a00613137a45e0143b91a1a45b0baa2f1cbe1161 100755 --- a/rpOsci.py +++ b/rpOsci.py @@ -6,7 +6,6 @@ This code is compatible with release 20240204 of the alpine linux image https://github.com/pavel-demin/red-pitaya-notes/releases/tag/20240204 - """ # Communication with server process is achieved via command codes: @@ -33,7 +32,6 @@ # code 29: start generator # code 30: stop generator - import argparse import os import sys @@ -65,7 +63,7 @@ else: Ui_MCPHA, QMainWindow = loadUiType("rpControl.ui") Ui_LogDisplay, QWidget = loadUiType("mcpha_log.ui") -Ui_OscDisplay, QWidget = loadUiType("mcpha_osc.ui") +Ui_OscDisplay, QWidget = loadUiType("mcpha_daq.ui") Ui_GenDisplay, QWidget = loadUiType("mcpha_gen.ui") if sys.platform != "win32": @@ -123,7 +121,7 @@ class rpControl(QMainWindow, Ui_MCPHA): self.startTimer = QTimer(self) # timer for network connectin self.startTimer.timeout.connect(self.start_timeout) self.readTimer = QTimer(self) # for readout - self.readTimer.timeout.connect(self.read_osci) + self.readTimer.timeout.connect(self.update_oscDisplay) # transfer command-line arguments to gui self.osc.levelValue.setValue(self.trigger_level) @@ -226,7 +224,7 @@ class rpControl(QMainWindow, Ui_MCPHA): self.set_negator(0, self.neg1Check.isChecked()) self.set_negator(1, self.neg2Check.isChecked()) # - # finally, start readout timer calling read_osci() + # finally, start readout timer calling update_oscDisplay() self.readTimer.start(self.readInterval) def command(self, code, number, value): @@ -243,7 +241,7 @@ class rpControl(QMainWindow, Ui_MCPHA): view[:] = np.frombuffer(self.socket.read(size), np.uint8) return True - def read_osci(self): + def update_oscDisplay(self): """data transfer from RP, triggered by QTimer readTimer """ if self.osc_reset: @@ -267,9 +265,39 @@ class rpControl(QMainWindow, Ui_MCPHA): self.log.print("failed to read oscilloscope data") return + def start_oscDaq(self): + """continuous data transfer from RedPitaya + """ + # depends on + # self.setup_redPdaq() + # osc.process_data() + + self.osc.setup_redPdaq() + + if self.osc_reset: + self.reset_osc() + if self.osc_start: + self.start_osc() + while self.daq_waiting: + # check status + self.read_status() + if not self.read_data(self.status): + self.log.print("failed to read status") + return + if not self.status[8] & 1: + self.command(20, 0, 0) # read oscilloscope data + if self.read_data(self.osc.buffer): + ## self.osc.update() + self.osc.process_data() + self.reset_osc() + self.start_osc() + else: + self.log.print("failed to read oscilloscope data") + return + def mark_start_osc(self): self.osc_start = True - self.osc_waiting = True + # self.osc_waiting = True def mark_reset_osc(self): self.osc_reset = True @@ -288,6 +316,7 @@ class rpControl(QMainWindow, Ui_MCPHA): def stop_osc(self): self.reset_osc() self.osc_waiting = False + self.daq_waiting = False def set_rate(self, index): # set RP decimation factor @@ -383,6 +412,9 @@ class OscDisplay(QWidget, Ui_OscDisplay): functions=(self.adc2mV, self.mV2adc)) self.ax_y2.set_ylabel("Voltage [mV]", color='grey', size='x-large') # gq end + self.osctxt= self.ax.text(0.1, 0.96, ' ', transform=self.ax.transAxes, + color="darkblue", alpha=0.7) + x = np.arange(self.tot) (self.curve2,) = self.ax.plot(x, self.buffer[1::2], color="#00CCCC", label="chan 2") @@ -421,6 +453,7 @@ class OscDisplay(QWidget, Ui_OscDisplay): self.fallingButton.toggled.connect(self.rpControl.set_trg_slope) self.levelValue.valueChanged.connect(self.set_trg_level) self.startButton.clicked.connect(self.start) + self.startDAQButton.clicked.connect(self.rpControl.start_oscDaq) self.saveButton.clicked.connect(self.save) self.loadButton.clicked.connect(self.load) self.canvas.mpl_connect("motion_notify_event", self.on_motion) @@ -471,16 +504,87 @@ class OscDisplay(QWidget, Ui_OscDisplay): self.log.print("oscilloscope started") # toggle startButton: turn to 'Stop' self.startButton.setText("Stop") + self.startButton.setStyleSheet("color: red") self.startButton.clicked.disconnect() self.startButton.clicked.connect(self.stop) + self.startDAQButton.setEnabled(False) + def setup_redPdaq(self): + """start oscilloscope in daq mode + """ + if self.rpControl.idle: + return + # statisics + self.NTrig = 0 + self.dN = 0 + self.Nprev = self.NTrig + self.T0 = time.time() + self.Tprev = self.T0 + self.dT = 0. + # extract parameters from gui + self.trg_mode = int(self.autoButton.isChecked()) + self.trg_source = int(self.ch2Button.isChecked()) + self.trg_slope = int(self.fallingButton.isChecked()) + self.trg_level = self.levelValue.value() + self.rpControl.set_trg_mode(self.trg_mode) + self.rpControl.set_trg_source(self.trg_source) + self.rpControl.set_trg_slope(self.trg_slope) + self.rpControl.set_trg_level(self.trg_level) + self.rpControl.set_osc_pre(self.pre) + self.rpControl.set_osc_tot(self.tot) + self.rpControl.mark_reset_osc() + self.rpControl.mark_start_osc() + self.rpControl.osc_waiting = False + self.rpControl.daq_waiting = True + self.log.print("daq started") + # toggle startButton: turn to 'Stop' + self.startButton.setText("Stop") + self.startButton.setStyleSheet("color: red") + self.startButton.clicked.disconnect() + self.startButton.clicked.connect(self.stop) + self.startDAQButton.setEnabled(False) + def stop(self): self.rpControl.stop_osc() self.startButton.setText("Start") + self.startButton.setStyleSheet("color: black") self.startButton.clicked.disconnect() self.startButton.clicked.connect(self.start) + self.startDAQButton.setEnabled(True) self.log.print("oscilloscope stopped") + def process_data(self): + # count number of Triggers and keep account of timing + self.NTrig += 1 + t = time.time() + dt = t - self.Tprev + self.Tprev = t + self.dT += dt + # copy data + chan1 = self.buffer[0::2] + chan2 = self.buffer[1::2] + # + # do something with data (e.g. pass to sub-process via mpQueue) + # + # .... + # + # output status once per second + if self.dT >= 1.: + dN = self.NTrig - self.Nprev + r = dN/self.dT + self.Nprev = self.NTrig + T_active = t - self.T0 + self.dT = 0. + status_txt = "active: {:.1f}s Trigger rate: {:.2f} Hz, Data Rate: {:.4g} MB/s".format(T_active, r, 4*r*len(chan1)*1e-6) + print(status_txt, end='\r') + self.osctxt.set_text(status_txt) + # update graph on display + self.curve1.set_ydata(chan1) + self.curve2.set_ydata(chan2) + self.xunit = "[{:d} ns / sample]".format(8*rpControl.rates[self.rpControl.rateValue.currentIndex()]) + self.ax.set_xlabel("sample number " + self.xunit) + self.canvas.draw() + def update(self): self.curve1.set_ydata(self.buffer[0::2]) self.curve2.set_ydata(self.buffer[1::2]) @@ -658,7 +762,7 @@ def runQt(): window.show() sys.exit(app.exec_()) -if __name__ == '__main__': +if __name__ == '__main__': # -------------------------------------------- # run the application with Qt5 interface runQt()