From eafb5add5f7963718917f062338848e90c31ce48 Mon Sep 17 00:00:00 2001
From: Guenter Quast <guenter.quast@online.de>
Date: Sat, 24 Feb 2024 12:20:42 +0100
Subject: [PATCH] rpOsci.py: added a data acquistion mode

---
 mcpha_daq.ui | 366 +++++++++++++++++++++++++++++++++++++++++++++++++++
 rpOsci.py    | 120 +++++++++++++++--
 2 files changed, 478 insertions(+), 8 deletions(-)
 create mode 100644 mcpha_daq.ui

diff --git a/mcpha_daq.ui b/mcpha_daq.ui
new file mode 100644
index 0000000..170b6e6
--- /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 2552538..a006131 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()
-- 
GitLab