aboutsummaryrefslogtreecommitdiff
path: root/qolab/hardware
diff options
context:
space:
mode:
Diffstat (limited to 'qolab/hardware')
-rw-r--r--qolab/hardware/scope/sds800xhd.py125
1 files changed, 123 insertions, 2 deletions
diff --git a/qolab/hardware/scope/sds800xhd.py b/qolab/hardware/scope/sds800xhd.py
index 561f308..436254f 100644
--- a/qolab/hardware/scope/sds800xhd.py
+++ b/qolab/hardware/scope/sds800xhd.py
@@ -24,12 +24,11 @@ class SDS800XHD(SDS1104X):
super().__init__(resource, *args, **kwds)
self.config["Device model"] = "SDS800XHD"
self.resource.read_termination = "\n"
- self.resource.timeout=1000
+ self.resource.timeout = 1000
self.numberOfChannels = 4
self.maxRequiredPoints = 1000
# desired number of points per channel, can return twice more
-
@BasicInstrument.tsdb_append
def getTimePerDiv(self):
qstr = "TDIV?"
@@ -46,6 +45,128 @@ class SDS800XHD(SDS1104X):
)
return float(numberString)
+ def getRawWaveform(
+ self, chNum, availableNpnts=None, maxRequiredPoints=None, decimate=True
+ ):
+ """
+ Get raw channel waveform in binary format.
+
+ Parameters
+ ----------
+ chNum : int
+ Scope channel to use: 1, 2, 3, or 4
+ availableNpnts : int or None (default)
+ Available number of points. Do not set it if you want it auto detected.
+ maxRequiredPoints : int
+ Maximum number of required points, if we ask less than available
+ we well get sparse set which proportionally fills all available time range.
+ decimate : False or True (default)
+ Decimate should be read as apply the low pass filter or not, technically
+ for both setting we get decimation (i.e. smaller than available
+ at the scope number of points). The name came from
+ ``scipy.signal.decimate`` filtering function.
+ If ``decimate=True`` is used, we get all available points
+ and then low-pass filter them to get ``maxRequiredPoints``
+ The result is less noisy then, but transfer time from the instrument
+ is longer.
+ If ``decimate=False``, then it we are skipping points to get needed number
+ but we might see aliasing, if there is a high frequency noise
+ and sparing > 1. Unless you know what you doing, it is recommended
+ to use ``decimate=True``.
+ """
+
+ rawChanCfg = {}
+ # switching to binary data transfer
+ self.write(":WAVeform:WIDTh WORD") # two bytes per data point
+ rawChanCfg["WaveformWidth"] = "WORD"
+ self.write(":WAVeform:BYTeorder LSB")
+ rawChanCfg["WaveformByteorder"] = "LSB"
+
+ if availableNpnts is None:
+ # using channel 1 to get availableNpnts
+ availableNpnts = self.getAvailableNumberOfPoints(1)
+ rawChanCfg["availableNpnts"] = availableNpnts
+
+ if maxRequiredPoints is None:
+ maxRequiredPoints = self.maxRequiredPoints
+ (
+ sparsing,
+ Npnts,
+ availableNpnts,
+ maxRequiredPoints,
+ ) = calcSparsingAndNumPoints(availableNpnts, maxRequiredPoints)
+ rawChanCfg["Npnts"] = Npnts
+ rawChanCfg["sparsing"] = sparsing
+ if decimate:
+ Npnts = availableNpnts # get all of them and decimate later
+ if (sparsing == 1 and Npnts == availableNpnts) or decimate:
+ # We are getting all points of the trace
+ self.write(":WAVeform:STARt 0") # start point to read from the scope memory
+ self.write(f":WAVeform:MAXPoint {availableNpnts}") # last point to read
+ self.write(":WAVeform:INTerval 1") # sparsing of 1, i.e. read every point
+ self.write(f":WAVeform:POINt {availableNpnts}") # transfer all points
+ else:
+ # we just ask every point with 'sparsing' interval
+ # fast to grab but we could do better with more advance decimate
+ # method, which allow better precision for the price
+ # of longer acquisition time
+ self.write(":WAVeform:STARt 0") # start point to read from the scope memory
+ self.write(f":WAVeform:MAXPoint {availableNpnts}") # last point to read
+ self.write(f":WAVeform:INTerval {sparsing}") # interval between points
+ # Note: it is not enough to provide sparsing
+ # number of requested points needed to be asked too.
+ # However this scope is smart enough to truncate the output to
+ # physically available points, if you request more no harm is done.
+ self.write(f":WAVeform:POINt {Npnts}") # transfer all points
+
+ trRaw = Trace(f"Ch{chNum}")
+ self.write(":WAVeform:SOURce C{chNum}")
+ qstr = f":WAVeform:DATA?"
+ if self.resource.interface_type == InterfaceType.usb:
+ # Setting chunk size to 496 bytes, it seems that SDS sends data
+ # in 512 bytes chunks via USB.
+ # Which is 8 packets of 64 bytes, but each packet takes 2 bytes for a header.
+ # Thus useful payload is 512-8*2 = 496
+ # see https://patchwork.ozlabs.org/project/qemu-devel/patch/20200317095049.28486-4-kraxel@redhat.com/
+ # Setting chunk_size for a large number has *catastrophic* results
+ # on data transfer rate, since we wait for more data
+ # which is not going to come until timeout expires
+ # Setting it low is not as bad but still slows down the transfer.
+ # NOTE: I am not sure if it is a Linux driver issue or more global.
+ # The transfer rate is about
+ # 5550 kB/S, for 10k points
+ # 1400 kB/S, for 50k points
+ # 1000 kB/S, for 100k points
+ # 500 kB/S, for 500k points
+ # 160 kB/S, for 1000k points
+ # 55 kB/S, for 2.5M points
+ # It is about factor of 2 slower (for 100k points),
+ # if the scope is in the Run mode, i.e. not Stopped.
+ # FIXME find why speed depends on number of points.
+ wfRaw = self.query_binary_values(
+ qstr,
+ datatype="h",
+ header_fmt="ieee",
+ container=np.array,
+ chunk_size=496,
+ )
+ else:
+ wfRaw = self.query_binary_values(
+ qstr, datatype="h", header_fmt="ieee", container=np.array
+ )
+ trRaw.values = wfRaw.reshape(wfRaw.size, 1)
+ if decimate and sparsing != 1:
+ numtaps = 3
+ # not sure it is the best case
+ trRaw.values = scipy.signal.decimate(
+ trRaw.values, sparsing, numtaps, axis=0
+ )
+
+ trRaw.config["unit"] = "Count"
+ trRaw.config["tags"]["Decimate"] = decimate
+ trRaw.config["tags"]["rawChanConfig"] = rawChanCfg
+ return trRaw
+
if __name__ == "__main__":
import pyvisa