From ad2e4ac8956bdcb05d19643f7b427e0fc2d3173f Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Tue, 19 Oct 2021 13:43:02 +0200 Subject: [PATCH 01/18] Adding an initial version for the ps6000a series. --- picoscope/ps6000a.py | 721 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 721 insertions(+) create mode 100644 picoscope/ps6000a.py diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py new file mode 100644 index 0000000..04a328c --- /dev/null +++ b/picoscope/ps6000a.py @@ -0,0 +1,721 @@ +# This is the instrument-specific file for the ps6000A series of instruments. +# +# pico-python is Copyright (c) 2013-2014 By: +# Colin O'Flynn +# Mark Harfouche +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. 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. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 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. + +""" +This is the low level driver file for a specific Picoscope. + +By this, I mean if parameters want to get passed as strings, they should be +handled by PSBase +All functions here should take things as close to integers as possible, the +only exception here is for array parameters. Array parameters should be passed +in a pythonic way through numpy since the PSBase class should not be aware of +the specifics behind how the clib is called. + +The functions should not have any default values as these should be handled +by PSBase. + +""" + +# 3.0 compatibility +# see http://docs.python.org/2/library/__future__.html +from __future__ import division +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import math + +# to load the proper dll +import platform + +# Do not import or use ill definied data types +# such as short int or long +# use the values specified in the h file +# float is always defined as 32 bits +# double is defined as 64 bits +from ctypes import byref, POINTER, create_string_buffer, c_float, \ + c_int8, c_int16, c_int32, c_uint32, c_uint64, c_void_p, CFUNCTYPE +from ctypes import c_int32 as c_enum + +from picoscope.picobase import _PicoscopeBase + + +# Decorators for callback functions. PICO_STATUS is uint32_t. +def blockReady(function): + """typedef void (*ps4000aBlockReady) + ( + int16_t handle, + PICO_STATUS status, + void * pParameter + ) + """ + callback = CFUNCTYPE(c_void_p, c_int16, c_uint32, c_void_p) + return callback(function) + + +def dataReady(function): + """typedef void (*ps4000aDataReady) + ( + int16_t handle, + PICO_STATUS status, + uint32_t noOfSamples, + int16_t overflow, + void * pParameter + ) + """ + callback = CFUNCTYPE(c_void_p, + c_int16, c_uint32, c_uint32, c_int16, c_void_p) + return callback(function) + + +def streamingReady(function): + """typedef void (*ps4000aStreamingReady) + ( + int16_t handle, + int32_t noOfSamples, + uint32_t startIndex, + int16_t overflow, + uint32_t triggerAt, + int16_t triggered, + int16_t autoStop, + void * pParameter + ) + """ + callback = CFUNCTYPE(c_void_p, c_int16, c_int32, c_uint32, c_int16, + c_uint32, c_int16, c_int16, c_void_p) + return callback + + +class PS6000a(_PicoscopeBase): + """The following are low-level functions for the ps6000A.""" + + LIBNAME = "ps6000a" + + # Resolution in bit + ADC_RESOLUTIONS = {"8": 0, "10": 10, "12": 1} + + # TODO verify values below + MAX_VALUE = 32764 + MIN_VALUE = -32764 + + # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) + EXT_MAX_VALUE = 32767 + EXT_MIN_VALUE = -32767 + EXT_RANGE_VOLTS = 1 + + # I don't think that the 50V range is allowed, but I left it there anyway + # The 10V and 20V ranges are only allowed in high impedence modes + CHANNEL_RANGE = [{"rangeV": 20E-3, "apivalue": 1, "rangeStr": "20 mV"}, + {"rangeV": 50E-3, "apivalue": 2, "rangeStr": "50 mV"}, + {"rangeV": 100E-3, "apivalue": 3, "rangeStr": "100 mV"}, + {"rangeV": 200E-3, "apivalue": 4, "rangeStr": "200 mV"}, + {"rangeV": 500E-3, "apivalue": 5, "rangeStr": "500 mV"}, + {"rangeV": 1.0, "apivalue": 6, "rangeStr": "1 V"}, + {"rangeV": 2.0, "apivalue": 7, "rangeStr": "2 V"}, + {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, + {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, + {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, + {"rangeV": 50.0, "apivalue": 11, "rangeStr": "50 V"}, + ] + + NUM_CHANNELS = 4 + CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, + "External": 4, "MaxChannels": 4, "TriggerAux": 5} + + CHANNEL_COUPLINGS = {"DC50": 2, "DC": 1, "AC": 0} + + WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, + "RampUp": 3, "RampDown": 4, + "Sinc": 5, "Gaussian": 6, "HalfSine": 7, "DCVoltage": 8, + "WhiteNoise": 9} + + SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} + + SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, + "GateHigh": 2, "GateLow": 3} + SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, + "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} + + # This is actually different depending on the AB/CD models + # I wonder how we could detect the difference between the oscilloscopes + # I believe we can obtain this information from the setInfo function + # by readign the hardware version + # for the PS6403B version, the hardware version is "1 1", + # an other possibility is that the PS6403B shows up as 6403 when using + # VARIANT_INFO and others show up as PS6403X where X = A,C or D + + AWGPhaseAccumulatorSize = 32 + AWGBufferAddressWidth = 14 + AWGMaxSamples = 2 ** AWGBufferAddressWidth + + AWGDACInterval = 5E-9 # in seconds + AWGDACFrequency = 1 / AWGDACInterval + + # Note this is NOT what is written in the Programming guide as of version + # 10_5_0_28 + # This issue was acknowledged in this thread + # http://www.picotech.com/support/topic13217.html + AWGMaxVal = 0x0FFF + AWGMinVal = 0x0000 + + AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} + + def __init__(self, serialNumber=None, connect=True, resolution="8"): + """Load DLLs.""" + self.handle = None + self.resolution = self.ADC_RESOLUTIONS.get(resolution) + + if platform.system() == 'Linux': + from ctypes import cdll + # ok I don't know what is wrong with my installer, + # but I need to include .so.2 + self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so.2") + elif platform.system() == 'Darwin': + from picoscope.darwin_utils import LoadLibraryDarwin + self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") + else: + from ctypes import windll + from ctypes.util import find_library + self.lib = windll.LoadLibrary( + find_library(str(self.LIBNAME + ".dll")) + ) + + super(PS6000a, self).__init__(serialNumber, connect) + + ########################### + # TODO test functions below + ########################### + def _lowLevelOpenUnit(self, serialNumber): + c_handle = c_int16() + if serialNumber is not None: + serialNumberStr = create_string_buffer(bytes(serialNumber, + encoding='utf-8')) + else: + serialNumberStr = None + # Passing None is the same as passing NULL + m = self.lib.ps6000aOpenUnit(byref(c_handle), serialNumberStr, + self.resolution) + self.checkResult(m) + self.handle = c_handle.value + + def _lowLevelOpenUnitAsync(self, serialNumber): + c_status = c_int16() + if serialNumber is not None: + serialNumberStr = create_string_buffer(bytes(serialNumber, + encoding='utf-8')) + else: + serialNumberStr = None + # Passing None is the same as passing NULL + m = self.lib.ps6000aOpenUnitAsync(byref(c_status), serialNumberStr, + self.resolution) + self.checkResult(m) + + return c_status.value + + def _lowLevelOpenUnitProgress(self): + complete = c_int16() + progressPercent = c_int16() + handle = c_int16() + + m = self.lib.ps6000aOpenUnitProgress(byref(handle), + byref(progressPercent), + byref(complete)) + self.checkResult(m) + + if complete.value != 0: + self.handle = handle.value + + # if we only wanted to return one value, we could do somethign like + # progressPercent = progressPercent * (1 - 0.1 * complete) + return (progressPercent.value, complete.value) + + def _lowLevelCloseUnit(self): + m = self.lib.ps6000aCloseUnit(c_int16(self.handle)) + self.checkResult(m) + + def _lowLevelEnumerateUnits(self): + count = c_int16(0) + serials = c_int8(0) + serialLth = c_int16(0) + m = self.lib.ps6000aEnumerateUnits(byref(count), byref(serials), + byref(serialLth)) + self.checkResult(m) + # a serial number is rouhgly 8 characters + # an extra character for the comma + # and an extra one for the space after the comma? + # the extra two also work for the null termination + serialLth = c_int16(count.value * (8 + 2)) + serials = create_string_buffer(serialLth.value + 1) + + m = self.lib.ps6000aEnumerateUnits(byref(count), serials, + byref(serialLth)) + self.checkResult(m) + + serialList = str(serials.value.decode('utf-8')).split(',') + + serialList = [x.strip() for x in serialList] + + return serialList + + def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, + BWLimited): + # TODO verify that it is really "On"/"Off" + if enabled: + m = self.lib.ps6000aSetChannelOn(c_int16(self.handle), + c_enum(chNum), c_enum(coupling), + c_enum(VRange), c_float(VOffset), + c_enum(BWLimited)) + else: + m = self.lib.ps6000aSetChannelOff(c_int16(self.handle), + c_enum(chNum)) + self.checkResult(m) + + def _lowLevelStop(self): + m = self.lib.ps6000aStop(c_int16(self.handle)) + self.checkResult(m) + + def _lowLevelGetUnitInfo(self, info): + s = create_string_buffer(256) + requiredSize = c_int16(0) + + m = self.lib.ps6000aGetUnitInfo(c_int16(self.handle), byref(s), + c_int16(len(s)), byref(requiredSize), + c_enum(info)) + self.checkResult(m) + if requiredSize.value > len(s): + s = create_string_buffer(requiredSize.value + 1) + m = self.lib.ps6000aGetUnitInfo(c_int16(self.handle), byref(s), + c_int16(len(s)), + byref(requiredSize), c_enum(info)) + self.checkResult(m) + + # should this be ascii instead? + # I think they are equivalent... + return s.value.decode('utf-8') + + def _lowLevelFlashLed(self, times): + m = self.lib.ps6000aFlashLed(c_int16(self.handle), c_int16(times)) + self.checkResult(m) + + def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, + direction, delay, timeout_ms): + m = self.lib.ps6000aSetSimpleTrigger( + c_int16(self.handle), c_int16(enabled), + c_enum(trigsrc), c_int16(threshold_adc), + c_enum(direction), c_uint32(delay), c_int16(timeout_ms)) + self.checkResult(m) + + def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, + timebase, oversample, segmentIndex, callback, + pParameter): + # Hold a reference to the callback so that the Python + # function pointer doesn't get free'd. + self._c_runBlock_callback = blockReady(callback) + timeIndisposedMs = c_int32() + m = self.lib.ps6000aRunBlock( + c_int16(self.handle), c_uint32(numPreTrigSamples), + c_uint32(numPostTrigSamples), c_uint32(timebase), + c_int16(oversample), byref(timeIndisposedMs), + c_uint32(segmentIndex), self._c_runBlock_callback, c_void_p()) + self.checkResult(m) + return timeIndisposedMs.value + + def _lowLevelIsReady(self): + ready = c_int16() + m = self.lib.ps6000aIsReady(c_int16(self.handle), byref(ready)) + self.checkResult(m) + if ready.value: + return True + else: + return False + + def _lowLevelGetTimebase(self, timebase, noSamples, oversample, + segmentIndex): + """ + Calculate the sampling interval and maximum number of samples. + + timebase + Number of the selected timebase. + noSamples + Number of required samples. + oversample + Not used. + segmentIndex + Index of the segment to save samples in + + Return + ------- + timeIntervalSeconds : float + Time interval between two samples in s. + maxSamples : int + maximum number of samples available depending on channels + and timebase chosen. + """ + maxSamples = c_int32() + timeIntervalSeconds = c_float() + + m = self.lib.ps6000aGetTimebase(c_int16(self.handle), + c_uint32(timebase), + c_int32(noSamples), + byref(timeIntervalSeconds), + byref(maxSamples), + c_uint32(segmentIndex)) + self.checkResult(m) + + return (timeIntervalSeconds.value / 1.0E9, maxSamples.value) + + def getTimeBaseNum(self, sampleTimeS): + """Convert `sampleTimeS` in s to the integer timebase number.""" + # TODO + maxSampleTime = (((2 ** 32 - 1) - 4) / 156250000) + + if sampleTimeS < 6.4E-9: + timebase = math.floor(math.log(sampleTimeS * 5E9, 2)) + timebase = max(timebase, 0) + else: + # Otherwise in range 2^32-1 + if sampleTimeS > maxSampleTime: + sampleTimeS = maxSampleTime + + timebase = math.floor((sampleTimeS * 156250000) + 4) + + return timebase + + def getTimestepFromTimebase(self, timebase): + """Convert `timebase` index to sampletime in seconds.""" + if timebase < 5: + dt = 2 ** timebase / 5E9 + else: + dt = (timebase - 4) / 156250000. + return dt + + ################################## + # TODO verify functions below here in the manual + # TODO ensure all relevant functions are in here + ################################## + + def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, + offsetVoltage, pkToPk, indexMode, + shots, triggerType, triggerSource): + """Waveform should be an array of shorts.""" + waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) + + m = self.lib.ps6000aSetSigGenArbitrary( + c_int16(self.handle), + c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts + c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts + c_uint32(int(deltaPhase)), # startDeltaPhase + c_uint32(int(deltaPhase)), # stopDeltaPhase + c_uint32(0), # deltaPhaseIncrement + c_uint32(0), # dwellCount + waveformPtr, # arbitraryWaveform + c_int32(len(waveform)), # arbitraryWaveformSize + c_enum(0), # sweepType for deltaPhase + c_enum(0), # operation (adding random noise and whatnot) + c_enum(indexMode), # single, dual, quad + c_uint32(shots), + c_uint32(0), # sweeps + c_uint32(triggerType), + c_uint32(triggerSource), + c_int16(0)) # extInThreshold + self.checkResult(m) + + def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, + segmentIndex): + """Set the data buffer. + + Be sure to call _lowLevelClearDataBuffer + when you are done with the data array + or else subsequent calls to GetValue will still use the same array. + + segmentIndex is unused, but required by other versions of the API + (eg PS5000a) + """ + dataPtr = data.ctypes.data_as(POINTER(c_int16)) + numSamples = len(data) + + m = self.lib.ps6000aSetDataBuffer(c_int16(self.handle), + c_enum(channel), + dataPtr, c_uint32(numSamples), + c_enum(downSampleMode)) + self.checkResult(m) + + def _lowLevelClearDataBuffer(self, channel, segmentIndex): + m = self.lib.ps6000aSetDataBuffer(c_int16(self.handle), + c_enum(channel), + c_void_p(), c_uint32(0), c_enum(0)) + self.checkResult(m) + + def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, + downSampleMode, segmentIndex): + numSamplesReturned = c_uint32() + numSamplesReturned.value = numSamples + overflow = c_int16() + m = self.lib.ps6000aGetValues( + c_int16(self.handle), c_uint32(startIndex), + byref(numSamplesReturned), c_uint32(downSampleRatio), + c_enum(downSampleMode), c_uint32(segmentIndex), + byref(overflow)) + self.checkResult(m) + return (numSamplesReturned.value, overflow.value) + + def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, + frequency, shots, triggerType, + triggerSource, stopFreq, increment, + dwellTime, sweepType, numSweeps): + # TODO, I just noticed that V2 exists + # Maybe change to V2 in the future + + if stopFreq is None: + stopFreq = frequency + + m = self.lib.ps6000aSetSigGenBuiltIn( + c_int16(self.handle), + c_int32(int(offsetVoltage * 1000000)), + c_int32(int(pkToPk * 1000000)), + c_int16(waveType), + c_float(frequency), c_float(stopFreq), + c_float(increment), c_float(dwellTime), + c_enum(sweepType), c_enum(0), + c_uint32(shots), c_uint32(numSweeps), + c_enum(triggerType), c_enum(triggerSource), + c_int16(0)) + self.checkResult(m) + + def _lowLevelPingUnit(self): + """Check connection to picoscope and return the error.""" + return self.lib.ps6000aPingUnit(c_int16(self.handle)) + + #################################################################### + # Untested functions below # + # # + #################################################################### + def _lowLevelGetAnalogueOffset(self, range, coupling): + # TODO, populate properties with this function + maximumVoltage = c_float() + minimumVoltage = c_float() + + m = self.lib.ps6000aGetAnalogueOffset( + c_int16(self.handle), c_enum(range), c_enum(coupling), + byref(maximumVoltage), byref(minimumVoltage)) + self.checkResult(m) + + return (maximumVoltage.value, minimumVoltage.value) + + def _lowLevelGetMaxDownSampleRatio(self, noOfUnaggregatedSamples, + downSampleRatioMode, segmentIndex): + maxDownSampleRatio = c_uint32() + + m = self.lib.ps6000aGetMaxDownSampleRatio( + c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), + byref(maxDownSampleRatio), + c_enum(downSampleRatioMode), c_uint32(segmentIndex)) + self.checkResult(m) + + return maxDownSampleRatio.value + + def _lowLevelGetNoOfCaptures(self): + nCaptures = c_uint32() + + m = self.lib.ps6000aGetNoOfCaptures(c_int16(self.handle), + byref(nCaptures)) + self.checkResult(m) + + return nCaptures.value + + def _lowLevelGetTriggerTimeOffset(self, segmentIndex): + time = c_uint64() + timeUnits = c_enum() + + m = self.lib.ps6000aGetTriggerTimeOffset64( + c_int16(self.handle), byref(time), + byref(timeUnits), c_uint32(segmentIndex)) + self.checkResult(m) + + if timeUnits.value == 0: # ps6000a_FS + return time.value * 1E-15 + elif timeUnits.value == 1: # ps6000a_PS + return time.value * 1E-12 + elif timeUnits.value == 2: # ps6000a_NS + return time.value * 1E-9 + elif timeUnits.value == 3: # ps6000a_US + return time.value * 1E-6 + elif timeUnits.value == 4: # ps6000a_MS + return time.value * 1E-3 + elif timeUnits.value == 5: # ps6000a_S + return time.value * 1E0 + else: + raise TypeError("Unknown timeUnits %d" % timeUnits.value) + + def _lowLevelMemorySegments(self, nSegments): + nMaxSamples = c_uint32() + + m = self.lib.ps6000aMemorySegments(c_int16(self.handle), + c_uint32(nSegments), + byref(nMaxSamples)) + self.checkResult(m) + + return nMaxSamples.value + + def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, + downSampleRatioMode): + bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) + bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) + bufferLth = len(bufferMax) + + m = self.lib.ps6000aSetDataBuffers(c_int16(self.handle), + c_enum(channel), + bufferMaxPtr, bufferMinPtr, + c_uint32(bufferLth), + c_enum(downSampleRatioMode)) + self.checkResult(m) + + def _lowLevelClearDataBuffers(self, channel): + m = self.lib.ps6000aSetDataBuffers( + c_int16(self.handle), c_enum(channel), + c_void_p(), c_void_p(), c_uint32(0), c_enum(0)) + self.checkResult(m) + + # Bulk values. + # These would be nice, but the user would have to provide us + # with an array. + # we would have to make sure that it is contiguous amonts other things + def _lowLevelGetValuesBulk(self, + numSamples, fromSegmentIndex, toSegmentIndex, + downSampleRatio, downSampleRatioMode, + overflow): + noOfSamples = c_uint32(numSamples) + + m = self.lib.ps6000aGetValuesBulk( + c_int16(self.handle), + byref(noOfSamples), + c_uint32(fromSegmentIndex), c_uint32(toSegmentIndex), + c_uint32(downSampleRatio), c_enum(downSampleRatioMode), + overflow.ctypes.data_as(POINTER(c_int16)) + ) + self.checkResult(m) + return noOfSamples.value + + def _lowLevelSetDataBufferBulk(self, channel, buffer, waveform, + downSampleRatioMode): + bufferPtr = buffer.ctypes.data_as(POINTER(c_int16)) + bufferLth = len(buffer) + + m = self.lib.ps6000aSetDataBufferBulk( + c_int16(self.handle), + c_enum(channel), bufferPtr, c_uint32(bufferLth), + c_uint32(waveform), c_enum(downSampleRatioMode)) + self.checkResult(m) + + def _lowLevelSetDataBuffersBulk(self, channel, bufferMax, bufferMin, + waveform, downSampleRatioMode): + bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) + bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) + + bufferLth = len(bufferMax) + + m = self.lib.ps6000aSetDataBuffersBulk( + c_int16(self.handle), c_enum(channel), + bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth), + c_uint32(waveform), c_enum(downSampleRatioMode)) + self.checkResult(m) + + def _lowLevelSetNoOfCaptures(self, nCaptures): + m = self.lib.ps6000aSetNoOfCaptures(c_int16(self.handle), + c_uint32(nCaptures)) + self.checkResult(m) + + # ETS Functions + def _lowLevelSetEts(): + pass + + def _lowLevelSetEtsTimeBuffer(): + pass + + def _lowLevelSetEtsTimeBuffers(): + pass + + def _lowLevelSetExternalClock(): + pass + + # Complicated triggering + # need to understand structs for this one to work + def _lowLevelIsTriggerOrPulseWidthQualifierEnabled(): + pass + + def _lowLevelGetValuesTriggerTimeOffsetBulk(): + pass + + def _lowLevelSetTriggerChannelConditions(): + pass + + def _lowLevelSetTriggerChannelDirections(): + pass + + def _lowLevelSetTriggerChannelProperties(): + pass + + def _lowLevelSetPulseWidthQualifier(): + pass + + def _lowLevelSetTriggerDelay(): + pass + + # Async functions + # would be nice, but we would have to learn to implement callbacks + def _lowLevelGetValuesAsync(): + pass + + def _lowLevelGetValuesBulkAsync(): + pass + + # overlapped functions + def _lowLevelGetValuesOverlapped(): + pass + + def _lowLevelGetValuesOverlappedBulk(): + pass + + # Streaming related functions + def _lowLevelGetStreamingLatestValues(): + pass + + def _lowLevelNoOfStreamingValues(self): + noOfValues = c_uint32() + + m = self.lib.ps6000aNoOfStreamingValues(c_int16(self.handle), + byref(noOfValues)) + self.checkResult(m) + + return noOfValues.value + + def _lowLevelRunStreaming(): + pass + + def _lowLevelStreamingReady(): + pass From 15dd1e4e904329f0316d9e9f067a24048a605ac3 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:35:29 +0100 Subject: [PATCH 02/18] Typos fixed. --- picoscope/picobase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picoscope/picobase.py b/picoscope/picobase.py index 5109dbb..64fe1dd 100644 --- a/picoscope/picobase.py +++ b/picoscope/picobase.py @@ -347,12 +347,12 @@ def setSamplingInterval(self, sampleInterval, duration, oversample=0, Parameters ---------- sampleInterval - Desired interval in seconds vetween two samples. + Desired interval in seconds between two samples. duration Desired duration of measurement. oversample Average over several measurements in the sample interval. - Not many picoscopes are capable of oversampling + Not many picoscopes are capable of oversampling. segmentIndex Index of the memory segment to store data into. From 72fae37e84f63e815a784ab600dbffa856eb473c Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Mon, 24 Jan 2022 20:00:01 +0100 Subject: [PATCH 03/18] Methods ordered and revised. Adjustment of the parameters data type. Often they are 64 bit. --- picoscope/ps6000a.py | 280 +++++++++++++++++++++++-------------------- 1 file changed, 149 insertions(+), 131 deletions(-) diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 04a328c..78ae6ea 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -59,7 +59,7 @@ # float is always defined as 32 bits # double is defined as 64 bits from ctypes import byref, POINTER, create_string_buffer, c_float, \ - c_int8, c_int16, c_int32, c_uint32, c_uint64, c_void_p, CFUNCTYPE + c_int8, c_int16, c_int32, c_uint32, c_int64, c_uint64, c_void_p, CFUNCTYPE from ctypes import c_int32 as c_enum from picoscope.picobase import _PicoscopeBase @@ -74,6 +74,8 @@ def blockReady(function): void * pParameter ) """ + if function is None: + return None callback = CFUNCTYPE(c_void_p, c_int16, c_uint32, c_void_p) return callback(function) @@ -210,6 +212,8 @@ def __init__(self, serialNumber=None, connect=True, resolution="8"): ########################### # TODO test functions below ########################### + + # General unit calls def _lowLevelOpenUnit(self, serialNumber): c_handle = c_int16() if serialNumber is not None: @@ -234,7 +238,6 @@ def _lowLevelOpenUnitAsync(self, serialNumber): m = self.lib.ps6000aOpenUnitAsync(byref(c_status), serialNumberStr, self.resolution) self.checkResult(m) - return c_status.value def _lowLevelOpenUnitProgress(self): @@ -246,10 +249,8 @@ def _lowLevelOpenUnitProgress(self): byref(progressPercent), byref(complete)) self.checkResult(m) - if complete.value != 0: self.handle = handle.value - # if we only wanted to return one value, we could do somethign like # progressPercent = progressPercent * (1 - 0.1 * complete) return (progressPercent.value, complete.value) @@ -282,23 +283,6 @@ def _lowLevelEnumerateUnits(self): return serialList - def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, - BWLimited): - # TODO verify that it is really "On"/"Off" - if enabled: - m = self.lib.ps6000aSetChannelOn(c_int16(self.handle), - c_enum(chNum), c_enum(coupling), - c_enum(VRange), c_float(VOffset), - c_enum(BWLimited)) - else: - m = self.lib.ps6000aSetChannelOff(c_int16(self.handle), - c_enum(chNum)) - self.checkResult(m) - - def _lowLevelStop(self): - m = self.lib.ps6000aStop(c_int16(self.handle)) - self.checkResult(m) - def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) @@ -322,37 +306,35 @@ def _lowLevelFlashLed(self, times): m = self.lib.ps6000aFlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) - def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, - direction, delay, timeout_ms): - m = self.lib.ps6000aSetSimpleTrigger( - c_int16(self.handle), c_int16(enabled), - c_enum(trigsrc), c_int16(threshold_adc), - c_enum(direction), c_uint32(delay), c_int16(timeout_ms)) - self.checkResult(m) + def _lowLevelPingUnit(self): + """Check connection to picoscope and return the error.""" + return self.lib.ps6000aPingUnit(c_int16(self.handle)) - def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, - timebase, oversample, segmentIndex, callback, - pParameter): - # Hold a reference to the callback so that the Python - # function pointer doesn't get free'd. - self._c_runBlock_callback = blockReady(callback) - timeIndisposedMs = c_int32() - m = self.lib.ps6000aRunBlock( - c_int16(self.handle), c_uint32(numPreTrigSamples), - c_uint32(numPostTrigSamples), c_uint32(timebase), - c_int16(oversample), byref(timeIndisposedMs), - c_uint32(segmentIndex), self._c_runBlock_callback, c_void_p()) + # Configure measurement + def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, + BWLimited): + # TODO verify that it is really "On"/"Off" + if enabled: + m = self.lib.ps6000aSetChannelOn(c_int16(self.handle), + c_enum(chNum), c_enum(coupling), + c_enum(VRange), c_float(VOffset), + c_enum(BWLimited)) + else: + m = self.lib.ps6000aSetChannelOff(c_int16(self.handle), + c_enum(chNum)) self.checkResult(m) - return timeIndisposedMs.value - def _lowLevelIsReady(self): - ready = c_int16() - m = self.lib.ps6000aIsReady(c_int16(self.handle), byref(ready)) + def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, + direction, delay, timeout_ms): + m = self.lib.ps6000aSetSimpleTrigger( + c_int16(self.handle), + c_int16(enabled), + c_enum(trigsrc), + c_int16(threshold_adc), + c_enum(direction), + c_uint64(delay), + c_uint32(timeout_ms)) self.checkResult(m) - if ready.value: - return True - else: - return False def _lowLevelGetTimebase(self, timebase, noSamples, oversample, segmentIndex): @@ -376,22 +358,21 @@ def _lowLevelGetTimebase(self, timebase, noSamples, oversample, maximum number of samples available depending on channels and timebase chosen. """ - maxSamples = c_int32() + maxSamples = c_uint64() timeIntervalSeconds = c_float() m = self.lib.ps6000aGetTimebase(c_int16(self.handle), c_uint32(timebase), - c_int32(noSamples), + c_uint64(noSamples), byref(timeIntervalSeconds), byref(maxSamples), - c_uint32(segmentIndex)) + c_uint64(segmentIndex)) self.checkResult(m) return (timeIntervalSeconds.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Convert `sampleTimeS` in s to the integer timebase number.""" - # TODO maxSampleTime = (((2 ** 32 - 1) - 4) / 156250000) if sampleTimeS < 6.4E-9: @@ -414,37 +395,40 @@ def getTimestepFromTimebase(self, timebase): dt = (timebase - 4) / 156250000. return dt - ################################## - # TODO verify functions below here in the manual - # TODO ensure all relevant functions are in here - ################################## - - def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, - offsetVoltage, pkToPk, indexMode, - shots, triggerType, triggerSource): - """Waveform should be an array of shorts.""" - waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) + # Start / stop measurement + def _lowLevelStop(self): + m = self.lib.ps6000aStop(c_int16(self.handle)) + self.checkResult(m) - m = self.lib.ps6000aSetSigGenArbitrary( + def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, + timebase, oversample, segmentIndex, callback, + pParameter): + # Hold a reference to the callback so that the Python + # function pointer doesn't get free'd. + self._c_runBlock_callback = blockReady(callback) + timeIndisposedMs = c_int32() + m = self.lib.ps6000aRunBlock( c_int16(self.handle), - c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts - c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts - c_uint32(int(deltaPhase)), # startDeltaPhase - c_uint32(int(deltaPhase)), # stopDeltaPhase - c_uint32(0), # deltaPhaseIncrement - c_uint32(0), # dwellCount - waveformPtr, # arbitraryWaveform - c_int32(len(waveform)), # arbitraryWaveformSize - c_enum(0), # sweepType for deltaPhase - c_enum(0), # operation (adding random noise and whatnot) - c_enum(indexMode), # single, dual, quad - c_uint32(shots), - c_uint32(0), # sweeps - c_uint32(triggerType), - c_uint32(triggerSource), - c_int16(0)) # extInThreshold + c_uint64(numPreTrigSamples), + c_uint64(numPostTrigSamples), + c_uint32(timebase), + byref(timeIndisposedMs), + c_uint64(segmentIndex), + self._c_runBlock_callback, + c_void_p()) self.checkResult(m) + return timeIndisposedMs.value + def _lowLevelIsReady(self): + ready = c_int16() + m = self.lib.ps6000aIsReady(c_int16(self.handle), byref(ready)) + self.checkResult(m) + if ready.value: + return True + else: + return False + + # Acquire data def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, segmentIndex): """Set the data buffer. @@ -452,38 +436,114 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, Be sure to call _lowLevelClearDataBuffer when you are done with the data array or else subsequent calls to GetValue will still use the same array. - - segmentIndex is unused, but required by other versions of the API - (eg PS5000a) """ dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) m = self.lib.ps6000aSetDataBuffer(c_int16(self.handle), c_enum(channel), - dataPtr, c_uint32(numSamples), - c_enum(downSampleMode)) + dataPtr, + c_int32(numSamples), + dataType, # TODO + c_uint64(segmentIndex), + c_enum(downSampleMode), + action # TODO + ) self.checkResult(m) def _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps6000aSetDataBuffer(c_int16(self.handle), c_enum(channel), - c_void_p(), c_uint32(0), c_enum(0)) + c_void_p(), + c_int32(0), + dataType, # TODO + c_uint64(segmentIndex), + c_enum(0), + action) # TODO self.checkResult(m) def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex): - numSamplesReturned = c_uint32() + numSamplesReturned = c_uint64() numSamplesReturned.value = numSamples overflow = c_int16() m = self.lib.ps6000aGetValues( - c_int16(self.handle), c_uint32(startIndex), - byref(numSamplesReturned), c_uint32(downSampleRatio), - c_enum(downSampleMode), c_uint32(segmentIndex), + c_int16(self.handle), + c_uint64(startIndex), + byref(numSamplesReturned), + c_uint64(downSampleRatio), + c_enum(downSampleMode), + c_uint64(segmentIndex), byref(overflow)) self.checkResult(m) return (numSamplesReturned.value, overflow.value) + def _lowLevelMemorySegments(self, nSegments): + nMaxSamples = c_uint64() + m = self.lib.ps6000aMemorySegments(c_int16(self.handle), + c_uint64(nSegments), + byref(nMaxSamples)) + self.checkResult(m) + return nMaxSamples.value + + def _lowLevelGetTriggerTimeOffset(self, segmentIndex): + time = c_int64() + timeUnits = c_enum() + + m = self.lib.ps6000aGetTriggerTimeOffset64( + c_int16(self.handle), + byref(time), + byref(timeUnits), + c_uint64(segmentIndex)) + self.checkResult(m) + + if timeUnits.value == 0: # ps6000a_FS + return time.value * 1E-15 + elif timeUnits.value == 1: # ps6000a_PS + return time.value * 1E-12 + elif timeUnits.value == 2: # ps6000a_NS + return time.value * 1E-9 + elif timeUnits.value == 3: # ps6000a_US + return time.value * 1E-6 + elif timeUnits.value == 4: # ps6000a_MS + return time.value * 1E-3 + elif timeUnits.value == 5: # ps6000a_S + return time.value * 1E0 + else: + raise TypeError("Unknown timeUnits %d" % timeUnits.value) + + ################################## + # TODO verify functions below here in the manual + # TODO ensure all relevant functions are in here + ################################## + + def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, + offsetVoltage, pkToPk, indexMode, + shots, triggerType, triggerSource): + """Waveform should be an array of shorts.""" + # TODO not in the manual! + waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) + + m = self.lib.ps6000aSetSigGenArbitrary( + c_int16(self.handle), + c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts + c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts + c_uint32(int(deltaPhase)), # startDeltaPhase + c_uint32(int(deltaPhase)), # stopDeltaPhase + c_uint32(0), # deltaPhaseIncrement + c_uint32(0), # dwellCount + waveformPtr, # arbitraryWaveform + c_int32(len(waveform)), # arbitraryWaveformSize + c_enum(0), # sweepType for deltaPhase + c_enum(0), # operation (adding random noise and whatnot) + c_enum(indexMode), # single, dual, quad + c_uint32(shots), + c_uint32(0), # sweeps + c_uint32(triggerType), + c_uint32(triggerSource), + c_int16(0)) # extInThreshold + self.checkResult(m) + def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, frequency, shots, triggerType, triggerSource, stopFreq, increment, @@ -507,14 +567,6 @@ def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, c_int16(0)) self.checkResult(m) - def _lowLevelPingUnit(self): - """Check connection to picoscope and return the error.""" - return self.lib.ps6000aPingUnit(c_int16(self.handle)) - - #################################################################### - # Untested functions below # - # # - #################################################################### def _lowLevelGetAnalogueOffset(self, range, coupling): # TODO, populate properties with this function maximumVoltage = c_float() @@ -548,40 +600,6 @@ def _lowLevelGetNoOfCaptures(self): return nCaptures.value - def _lowLevelGetTriggerTimeOffset(self, segmentIndex): - time = c_uint64() - timeUnits = c_enum() - - m = self.lib.ps6000aGetTriggerTimeOffset64( - c_int16(self.handle), byref(time), - byref(timeUnits), c_uint32(segmentIndex)) - self.checkResult(m) - - if timeUnits.value == 0: # ps6000a_FS - return time.value * 1E-15 - elif timeUnits.value == 1: # ps6000a_PS - return time.value * 1E-12 - elif timeUnits.value == 2: # ps6000a_NS - return time.value * 1E-9 - elif timeUnits.value == 3: # ps6000a_US - return time.value * 1E-6 - elif timeUnits.value == 4: # ps6000a_MS - return time.value * 1E-3 - elif timeUnits.value == 5: # ps6000a_S - return time.value * 1E0 - else: - raise TypeError("Unknown timeUnits %d" % timeUnits.value) - - def _lowLevelMemorySegments(self, nSegments): - nMaxSamples = c_uint32() - - m = self.lib.ps6000aMemorySegments(c_int16(self.handle), - c_uint32(nSegments), - byref(nMaxSamples)) - self.checkResult(m) - - return nMaxSamples.value - def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, downSampleRatioMode): bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) From ed94b9a1bc9431a8799516a7c6a83a18f15a52a1 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Tue, 25 Jan 2022 13:22:14 +0100 Subject: [PATCH 04/18] Checked for completeness, methods improved. All methods of the manual are present (at least as stubs). Methods copied from the ps5000 are marked as not present. Some methods, values are improved, according to the manual. Order improved. Some new methods (updates etc.) included. A test-file was included in order to test the methods with the picoscope. --- examples/test_ps6000a.py | 107 +++++++ picoscope/picobase.py | 6 +- picoscope/ps6000a.py | 618 ++++++++++++++++++++++++++------------- 3 files changed, 533 insertions(+), 198 deletions(-) create mode 100644 examples/test_ps6000a.py diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py new file mode 100644 index 0000000..09005b9 --- /dev/null +++ b/examples/test_ps6000a.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +Testing the ps6000a Series + +Created on Tue Jan 25 12:20:14 2022 by Benedikt Moneke +""" + +# Imports +import matplotlib.pyplot as plt +import numpy as np +from picoscope import ps6000a +import time + + +def test_general_unit_calls(): + global ps + assert ps.openUnitProgress() + ps.flashLed() + print(ps.getAllUnitInfo()) + assert not ps.ping() + + +def test_timebase(): + global ps + # Preparation + ps.memorySegments(1) + # Tests + data = ((200e-12, 0), + (800e-12, 2), + (3.2e-9, 4), + (6.4e-9, 5), + (10e-9, 5), + (15e-9, 6)) + for (time, timebase) in data: + assert ps.getTimeBaseNum(time) == timebase + data = ((200e-12, 0), + (800e-12, 2), + (3.2e-9, 4), + (6.4e-9, 5), + (12.8-9, 6), + (3.84e-8, 10), + (6.144e-7, 100)) + for (time, timebase) in data: + assert ps.getTimestepFromTimebase(timebase) == time + timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) + assert timestep == time + + +def test_deviceResolution(): + global ps + ps.setResolution("12") + assert ps.resolution == "12" + assert ps.getResolution() == "12" + assert ps.MIN_VALUE == -32736 + assert ps.MAX_VALUE == 32736 + + +def test_rapid_block_mode(n_captures=100, + sample_interval=100e-9, # 100 ns + sample_duration=2e-3, # 1 ms + ): + """Test the rapid block mode.""" + global ps + # Configuration of Picoscope + ps.setChannel(channel="A", coupling="DC", VRange=1) + ps.setChannel(channel="B", enabled=False) + + ps.setResolution('12') + ps.setSamplingInterval(sample_interval, sample_duration) + ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) + + samples_per_segment = ps.memorySegments(n_captures) + ps.setNoOfCaptures(n_captures) + + data = np.zeros((n_captures, samples_per_segment), dtype=np.int16) + + # Measurement + t1 = time.time() + + ps.runBlock() + ps.waitReady() + + t2 = time.time() + print("Time to record data to scope: ", str(t2 - t1)) + + ps.getDataRawBulk(data=data) + + t3 = time.time() + print("Time to copy to RAM: ", str(t3 - t2)) + + plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none', + cmap=plt.cm.hot) + plt.colorbar() + plt.show() + + +if __name__ == "__main__": + # Initialize the picoscope + ps = ps6000a.PS6000a() + + # Run tests. + test_general_unit_calls() + test_deviceResolution() + test_rapid_block_mode() + + # Close the connection + ps.close() diff --git a/picoscope/picobase.py b/picoscope/picobase.py index 64fe1dd..d71bfcc 100644 --- a/picoscope/picobase.py +++ b/picoscope/picobase.py @@ -1082,11 +1082,15 @@ def sigGenSoftwareControl(self, state=True): self._lowLevelSigGenSoftwareControl(state) def setResolution(self, resolution): - """For 5000-series or certain 4000-series scopes ONLY, + """For 6000-series, 5000-series or certain 4000-series scopes ONLY, sets the resolution. """ self._lowLevelSetDeviceResolution(self.ADC_RESOLUTIONS[resolution]) + def getResolution(self): + """Get the currently set resolution.""" + return self._lowLevelGetDeviceResolution() + def enumerateUnits(self): """Return connected units as serial numbers as list of strings.""" return self._lowLevelEnumerateUnits() diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 78ae6ea..3114aed 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -58,8 +58,8 @@ # use the values specified in the h file # float is always defined as 32 bits # double is defined as 64 bits -from ctypes import byref, POINTER, create_string_buffer, c_float, \ - c_int8, c_int16, c_int32, c_uint32, c_int64, c_uint64, c_void_p, CFUNCTYPE +from ctypes import byref, POINTER, create_string_buffer, c_float, c_int8, c_int16,\ + c_uint16, c_int32, c_uint32, c_int64, c_uint64, c_void_p, CFUNCTYPE from ctypes import c_int32 as c_enum from picoscope.picobase import _PicoscopeBase @@ -113,6 +113,19 @@ def streamingReady(function): return callback +def updateFirmwareProgress(function): + """typedef void (*PicoUpdateFirmwareProgress) + ( + int16_t handle, + uint16_t progress + ) + """ + if function is None: + return None + callback = CFUNCTYPE(c_void_p, c_int16, c_uint16) + return callback(function) + + class PS6000a(_PicoscopeBase): """The following are low-level functions for the ps6000A.""" @@ -121,17 +134,43 @@ class PS6000a(_PicoscopeBase): # Resolution in bit ADC_RESOLUTIONS = {"8": 0, "10": 10, "12": 1} - # TODO verify values below - MAX_VALUE = 32764 - MIN_VALUE = -32764 + NUM_CHANNELS = 4 + CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, + "External": 4, "MaxChannels": 4, "TriggerAux": 5} - # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) - EXT_MAX_VALUE = 32767 - EXT_MIN_VALUE = -32767 - EXT_RANGE_VOLTS = 1 + CHANNEL_COUPLINGS = {"DC50": 2, "DC": 1, "AC": 0} - # I don't think that the 50V range is allowed, but I left it there anyway - # The 10V and 20V ranges are only allowed in high impedence modes + ACTIONS = { # PICO_ACTION they can be combined with bitwise OR. + # TODO decide on names + 'clear_all': 0x00000001, # PICO_CLEAR_ALL + 'add': 0x00000002, # PICO_ADD + 'clear_this': 0x00001000, # PICO_CLEAR_THIS_DATA_BUFFER + 'clear_waveform': 0x00002000, # PICO_CLEAR_WAVEFORM_DATA_BUFFERS + 'clear_waveform_read': 0x00004000, # PICO_CLEAR_WAVEFORM_READ_DATA_BUFFERS + } + + DATA_TYPES = { # PICO_DATA_TYPE + 'int8': 0, # PICO_INT8_T + 'int16': 1, # PICO_INT16_T + 'int32': 2, # PICO_INT32_T + 'uint32': 3, # PICO_UINT32_T + 'int64': 4, # PICO_INT64_T + } + + TIME_UNITS = [ # PICO_TIME_UNITS + 1e-15, # PICO_FS + 1e-12, # PICO_PS + 1e-9, # PICO_NS + 1e-6, # PICO_US + 1e-3, # PICO_MS + 1, # PICO_S + ] + + # Only at 8 bit, use GetAdcLimits for other resolutions. + MAX_VALUE = 32512 + MIN_VALUE = -32512 + + # 10V and 20V are only allowed in high impedence modes. CHANNEL_RANGE = [{"rangeV": 20E-3, "apivalue": 1, "rangeStr": "20 mV"}, {"rangeV": 50E-3, "apivalue": 2, "rangeStr": "50 mV"}, {"rangeV": 100E-3, "apivalue": 3, "rangeStr": "100 mV"}, @@ -142,14 +181,14 @@ class PS6000a(_PicoscopeBase): {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, - {"rangeV": 50.0, "apivalue": 11, "rangeStr": "50 V"}, ] - NUM_CHANNELS = 4 - CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, - "External": 4, "MaxChannels": 4, "TriggerAux": 5} + # TODO verify values below - CHANNEL_COUPLINGS = {"DC50": 2, "DC": 1, "AC": 0} + # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) + EXT_MAX_VALUE = 32767 + EXT_MIN_VALUE = -32767 + EXT_RANGE_VOLTS = 1 WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, "RampUp": 3, "RampDown": 4, @@ -213,7 +252,9 @@ def __init__(self, serialNumber=None, connect=True, resolution="8"): # TODO test functions below ########################### - # General unit calls + "General unit calls" + + # Open / close unit def _lowLevelOpenUnit(self, serialNumber): c_handle = c_int16() if serialNumber is not None: @@ -226,6 +267,8 @@ def _lowLevelOpenUnit(self, serialNumber): self.resolution) self.checkResult(m) self.handle = c_handle.value + mi, ma = self._lowLevelGetAdcLimits(self.resolution) + self.MIN_VALUE, self.MAX_VALUE = mi, ma def _lowLevelOpenUnitAsync(self, serialNumber): c_status = c_int16() @@ -251,14 +294,15 @@ def _lowLevelOpenUnitProgress(self): self.checkResult(m) if complete.value != 0: self.handle = handle.value - # if we only wanted to return one value, we could do somethign like - # progressPercent = progressPercent * (1 - 0.1 * complete) + mi, ma = self._lowLevelGetAdcLimits(self.resolution) + self.MIN_VALUE, self.MAX_VALUE = mi, ma return (progressPercent.value, complete.value) def _lowLevelCloseUnit(self): m = self.lib.ps6000aCloseUnit(c_int16(self.handle)) self.checkResult(m) + # Misc def _lowLevelEnumerateUnits(self): count = c_int16(0) serials = c_int8(0) @@ -283,6 +327,10 @@ def _lowLevelEnumerateUnits(self): return serialList + def _lowLevelFlashLed(self, times): + m = self.lib.ps6000aFlashLed(c_int16(self.handle), c_int16(times)) + self.checkResult(m) + def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) @@ -302,40 +350,13 @@ def _lowLevelGetUnitInfo(self, info): # I think they are equivalent... return s.value.decode('utf-8') - def _lowLevelFlashLed(self, times): - m = self.lib.ps6000aFlashLed(c_int16(self.handle), c_int16(times)) - self.checkResult(m) - def _lowLevelPingUnit(self): """Check connection to picoscope and return the error.""" return self.lib.ps6000aPingUnit(c_int16(self.handle)) - # Configure measurement - def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, - BWLimited): - # TODO verify that it is really "On"/"Off" - if enabled: - m = self.lib.ps6000aSetChannelOn(c_int16(self.handle), - c_enum(chNum), c_enum(coupling), - c_enum(VRange), c_float(VOffset), - c_enum(BWLimited)) - else: - m = self.lib.ps6000aSetChannelOff(c_int16(self.handle), - c_enum(chNum)) - self.checkResult(m) - - def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, - direction, delay, timeout_ms): - m = self.lib.ps6000aSetSimpleTrigger( - c_int16(self.handle), - c_int16(enabled), - c_enum(trigsrc), - c_int16(threshold_adc), - c_enum(direction), - c_uint64(delay), - c_uint32(timeout_ms)) - self.checkResult(m) + "Measurement" + # Timebase def _lowLevelGetTimebase(self, timebase, noSamples, oversample, segmentIndex): """ @@ -395,6 +416,78 @@ def getTimestepFromTimebase(self, timebase): dt = (timebase - 4) / 156250000. return dt + # Device resolution + def _lowLevelGetDeviceResolution(self): + """Return the vertical resolution of the oscilloscope.""" + resolution = c_uint32() + m = self.lib.ps6000aGetDeviceResolution(c_int16(self.handle), + byref(resolution)) + self.checkResult(m) + for key in self.ADC_RESOLUTIONS.keys(): + if self.ADC_RESOLUTIONS[key] == resolution: + self.resolution = key + return key + raise TypeError(f"Unknown resolution {resolution}.") + + def _lowLevelSetDeviceResolution(self, resolution): + """ + Set the sampling resolution of the device. + + At 10-bit and higher resolutions, the maximum capture buffer length is + half that of 8-bit mode. When using 12-bit resolution only 2 channels + can be enabled to capture data. + """ + if type(resolution) is str: + resolution = self.ADC_RESOLUTIONS(resolution) + m = self.lib.ps6000aSetDeviceResolution( + c_int16(self.handle), + resolution) + self.checkResult(m) + self.resolution = resolution + self.MIN_VALUE, self.MAX_VALUE = self._lowLevelGetAdcLimits(resolution) + + def _lowLevelGetAdcLimits(self, resolution): + """ + This function gets the maximum and minimum sample values that the ADC + can produce at a given resolution. + """ + minimum = c_int16() + maximum = c_int16() + m = self.lib.ps6000aGetAdcLimits( + c_int16(self.handle), + self.ADC_RESOLUTIONS(resolution), + byref(minimum), + byref(maximum)) + self.checkResult(m) + return minimum, maximum + + # Channel + def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, + BWLimited): + # TODO verify that it is really "On"/"Off" + if enabled: + m = self.lib.ps6000aSetChannelOn(c_int16(self.handle), + c_enum(chNum), c_enum(coupling), + c_enum(VRange), c_float(VOffset), + c_enum(BWLimited)) + else: + m = self.lib.ps6000aSetChannelOff(c_int16(self.handle), + c_enum(chNum)) + self.checkResult(m) + + # Trigger + def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, + direction, delay, timeout_ms): + m = self.lib.ps6000aSetSimpleTrigger( + c_int16(self.handle), + c_int16(enabled), + c_enum(trigsrc), + c_int16(threshold_adc), + c_enum(direction), + c_uint64(delay), + c_uint32(timeout_ms)) + self.checkResult(m) + # Start / stop measurement def _lowLevelStop(self): m = self.lib.ps6000aStop(c_int16(self.handle)) @@ -429,6 +522,14 @@ def _lowLevelIsReady(self): return False # Acquire data + def _lowLevelMemorySegments(self, nSegments): + nMaxSamples = c_uint64() + m = self.lib.ps6000aMemorySegments(c_int16(self.handle), + c_uint64(nSegments), + byref(nMaxSamples)) + self.checkResult(m) + return nMaxSamples.value + def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, segmentIndex): """Set the data buffer. @@ -436,7 +537,15 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, Be sure to call _lowLevelClearDataBuffer when you are done with the data array or else subsequent calls to GetValue will still use the same array. + + action + the method to use when creating the buffer. The buffers are added + to a unique list for the channel, data type and segment. Therefore + you must use PICO_CLEAR_ALL to remove all buffers already written. + PICO_ACTION values can be ORed together to allow clearing and + adding in one call. """ + # TODO understand SetDataBuffer with action and dataType dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) @@ -444,11 +553,10 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, c_enum(channel), dataPtr, c_int32(numSamples), - dataType, # TODO + self.DATA_TYPES['int16'], c_uint64(segmentIndex), c_enum(downSampleMode), - action # TODO - ) + self.ACTIONS['add']) self.checkResult(m) def _lowLevelClearDataBuffer(self, channel, segmentIndex): @@ -456,12 +564,21 @@ def _lowLevelClearDataBuffer(self, channel, segmentIndex): c_enum(channel), c_void_p(), c_int32(0), - dataType, # TODO + self.DATA_TYPES['int16'], c_uint64(segmentIndex), c_enum(0), - action) # TODO + self.ACTIONS['clear_this']) self.checkResult(m) + def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, + downSampleMode): + """Just calls setDataBuffer with argument order changed. + + For compatibility with current picobase.py. + """ + self._lowLevelSetDataBuffer(channel, data, downSampleMode, + segmentIndex) + def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex): numSamplesReturned = c_uint64() @@ -478,14 +595,7 @@ def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, self.checkResult(m) return (numSamplesReturned.value, overflow.value) - def _lowLevelMemorySegments(self, nSegments): - nMaxSamples = c_uint64() - m = self.lib.ps6000aMemorySegments(c_int16(self.handle), - c_uint64(nSegments), - byref(nMaxSamples)) - self.checkResult(m) - return nMaxSamples.value - + # Misc def _lowLevelGetTriggerTimeOffset(self, segmentIndex): time = c_int64() timeUnits = c_enum() @@ -497,109 +607,82 @@ def _lowLevelGetTriggerTimeOffset(self, segmentIndex): c_uint64(segmentIndex)) self.checkResult(m) - if timeUnits.value == 0: # ps6000a_FS - return time.value * 1E-15 - elif timeUnits.value == 1: # ps6000a_PS - return time.value * 1E-12 - elif timeUnits.value == 2: # ps6000a_NS - return time.value * 1E-9 - elif timeUnits.value == 3: # ps6000a_US - return time.value * 1E-6 - elif timeUnits.value == 4: # ps6000a_MS - return time.value * 1E-3 - elif timeUnits.value == 5: # ps6000a_S - return time.value * 1E0 - else: + try: + return time.value * self.TIME_UNITS[timeUnits.value] + except KeyError: raise TypeError("Unknown timeUnits %d" % timeUnits.value) - ################################## - # TODO verify functions below here in the manual - # TODO ensure all relevant functions are in here - ################################## + "Updates" - def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, - offsetVoltage, pkToPk, indexMode, - shots, triggerType, triggerSource): - """Waveform should be an array of shorts.""" - # TODO not in the manual! - waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) + def _lowLevelCheckForUpdate(self): + """ + Check whether a firmware update for the device is available. - m = self.lib.ps6000aSetSigGenArbitrary( - c_int16(self.handle), - c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts - c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts - c_uint32(int(deltaPhase)), # startDeltaPhase - c_uint32(int(deltaPhase)), # stopDeltaPhase - c_uint32(0), # deltaPhaseIncrement - c_uint32(0), # dwellCount - waveformPtr, # arbitraryWaveform - c_int32(len(waveform)), # arbitraryWaveformSize - c_enum(0), # sweepType for deltaPhase - c_enum(0), # operation (adding random noise and whatnot) - c_enum(indexMode), # single, dual, quad - c_uint32(shots), - c_uint32(0), # sweeps - c_uint32(triggerType), - c_uint32(triggerSource), - c_int16(0)) # extInThreshold + Returns + ------- + firmwareInfos : None + not implemented: A struct with information. + number : int + Number of elements in the structure. + required : bool + Whether an update is required or not. + """ + # TODO what to do with the struct? + number = c_int16() + required = c_uint16() + m = self.lib.ps6000aCheckForUpdate(c_int16(self.handle), c_void_p(), + byref(number), byref(required)) self.checkResult(m) + return None, number, required - def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, - frequency, shots, triggerType, - triggerSource, stopFreq, increment, - dwellTime, sweepType, numSweeps): - # TODO, I just noticed that V2 exists - # Maybe change to V2 in the future + def _lowLevelStartFirmwareUpdate(self, function): + callback = updateFirmwareProgress(function) + m = self.lib.ps6000aStartFirmwareUpdate(c_int16(self.handle), callback) + self.checkResult(m) - if stopFreq is None: - stopFreq = frequency + "Misc" - m = self.lib.ps6000aSetSigGenBuiltIn( - c_int16(self.handle), - c_int32(int(offsetVoltage * 1000000)), - c_int32(int(pkToPk * 1000000)), - c_int16(waveType), - c_float(frequency), c_float(stopFreq), - c_float(increment), c_float(dwellTime), - c_enum(sweepType), c_enum(0), - c_uint32(shots), c_uint32(numSweeps), - c_enum(triggerType), c_enum(triggerSource), - c_int16(0)) - self.checkResult(m) + ################################## + # TODO verify functions below here in the manual + # TODO ensure all relevant functions are in here + ################################## - def _lowLevelGetAnalogueOffset(self, range, coupling): - # TODO, populate properties with this function - maximumVoltage = c_float() - minimumVoltage = c_float() + "Measurement" - m = self.lib.ps6000aGetAnalogueOffset( - c_int16(self.handle), c_enum(range), c_enum(coupling), - byref(maximumVoltage), byref(minimumVoltage)) - self.checkResult(m) + # Complicated triggering + # need to understand structs for some of this to work + def _lowLevelGetValuesTriggerTimeOffsetBulk(): + pass - return (maximumVoltage.value, minimumVoltage.value) + def _lowLevelSetTriggerChannelConditions(): + pass - def _lowLevelGetMaxDownSampleRatio(self, noOfUnaggregatedSamples, - downSampleRatioMode, segmentIndex): - maxDownSampleRatio = c_uint32() + def _lowLevelSetTriggerChannelDirections(): + pass - m = self.lib.ps6000aGetMaxDownSampleRatio( - c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), - byref(maxDownSampleRatio), - c_enum(downSampleRatioMode), c_uint32(segmentIndex)) - self.checkResult(m) + def _lowLevelSetTriggerChannelProperties(): + pass - return maxDownSampleRatio.value + def _lowLevelSetTriggerDelay(): + pass - def _lowLevelGetNoOfCaptures(self): - nCaptures = c_uint32() + def _lowLevelSetTriggerDigitalPortProperties(self): + pass - m = self.lib.ps6000aGetNoOfCaptures(c_int16(self.handle), - byref(nCaptures)) - self.checkResult(m) + # Optional input triggering: PulseWidthQualifier + def _lowLevelSetPulseWidthQualifierConditions(self): + pass - return nCaptures.value + def _lowLevelSetPulseWidthQualifierDirections(self): + pass + + def _lowLevelSetPulseWidthQualifierProperties(self): + pass + def _lowLevelTriggerWithinPreTriggerSamples(self): + pass + + # Data acquisition def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, downSampleRatioMode): bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) @@ -639,101 +722,242 @@ def _lowLevelGetValuesBulk(self, self.checkResult(m) return noOfSamples.value - def _lowLevelSetDataBufferBulk(self, channel, buffer, waveform, - downSampleRatioMode): - bufferPtr = buffer.ctypes.data_as(POINTER(c_int16)) - bufferLth = len(buffer) + def _lowLevelSetNoOfCaptures(self, nCaptures): + m = self.lib.ps6000aSetNoOfCaptures(c_int16(self.handle), + c_uint32(nCaptures)) + self.checkResult(m) - m = self.lib.ps6000aSetDataBufferBulk( - c_int16(self.handle), - c_enum(channel), bufferPtr, c_uint32(bufferLth), - c_uint32(waveform), c_enum(downSampleRatioMode)) + # Async functions + # would be nice, but we would have to learn to implement callbacks + def _lowLevelGetValuesAsync(): + pass + + def _lowLevelGetValuesBulkAsync(): + pass + + # overlapped functions + def _lowLevelGetValuesOverlapped(): + pass + + # Streaming related functions + def _lowLevelGetStreamingLatestValues(): + pass + + def _lowLevelNoOfStreamingValues(self): + noOfValues = c_uint32() + + m = self.lib.ps6000aNoOfStreamingValues(c_int16(self.handle), + byref(noOfValues)) self.checkResult(m) - def _lowLevelSetDataBuffersBulk(self, channel, bufferMax, bufferMin, - waveform, downSampleRatioMode): - bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) - bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) + return noOfValues.value - bufferLth = len(bufferMax) + def _lowLevelRunStreaming(): + pass - m = self.lib.ps6000aSetDataBuffersBulk( - c_int16(self.handle), c_enum(channel), - bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth), - c_uint32(waveform), c_enum(downSampleRatioMode)) + "alphabetically" + + def _lowLevelChannelCombinationsStateless(self): + """ + Return a list of the possible channel combinations given a proposed + configuration (resolution and timebase) of the oscilloscope. It does + not change the configuration of the oscilloscope. + """ + # TODO + pass + + def _lowLevelGetAnalogueOffsetLimits(self, range, coupling): + # TODO, populate properties with this function + maximumVoltage = c_float() + minimumVoltage = c_float() + + m = self.lib.ps6000aGetAnalogueOffsetLimits( + c_int16(self.handle), c_enum(range), c_enum(coupling), + byref(maximumVoltage), byref(minimumVoltage)) self.checkResult(m) - def _lowLevelSetNoOfCaptures(self, nCaptures): - m = self.lib.ps6000aSetNoOfCaptures(c_int16(self.handle), - c_uint32(nCaptures)) + return (maximumVoltage.value, minimumVoltage.value) + + def _lowLevelGetMaximumAvailableMemory(self): + pass + + def _lowLevelMinimumTimebaseStateless(self): + pass + + def _lowLevelGetNoOfCaptures(self): + nCaptures = c_uint32() + + m = self.lib.ps6000aGetNoOfCaptures(c_int16(self.handle), + byref(nCaptures)) self.checkResult(m) - # ETS Functions - def _lowLevelSetEts(): + return nCaptures.value + + def _lowLevelGetNoOfProcessedCaptures(self): pass - def _lowLevelSetEtsTimeBuffer(): + def _lowLevelGetTriggerInfo(self): pass - def _lowLevelSetEtsTimeBuffers(): + def _lowLevelMemorySegmentsBySamples(self): pass - def _lowLevelSetExternalClock(): + def _lowLevelNearestSampleIntervalStateless(self): pass - # Complicated triggering - # need to understand structs for this one to work - def _lowLevelIsTriggerOrPulseWidthQualifierEnabled(): + def _lowLevelQueryMaxSegmentsBySamples(self): pass - def _lowLevelGetValuesTriggerTimeOffsetBulk(): + def _lowLevelQueryOutputEdgeDetect(self): pass - def _lowLevelSetTriggerChannelConditions(): + def _lowLevelSetDigitalPortOff(self): pass - def _lowLevelSetTriggerChannelDirections(): + def _lowLevelSetDigitalPortOn(self): pass - def _lowLevelSetTriggerChannelProperties(): + def _lowLevelSetOutputEdgeDetect(self): pass - def _lowLevelSetPulseWidthQualifier(): + # Signal Generator + def _lowLevelSigGenApply(self): pass - def _lowLevelSetTriggerDelay(): + def _lowLevelSigGenClockManual(self): pass - # Async functions - # would be nice, but we would have to learn to implement callbacks - def _lowLevelGetValuesAsync(): + def _lowLevelSigGenFilter(self): pass - def _lowLevelGetValuesBulkAsync(): + def _lowLevelSigGenFrequency(self): pass - # overlapped functions - def _lowLevelGetValuesOverlapped(): + def _lowLevelSigGenFrequencyLimits(self): pass - def _lowLevelGetValuesOverlappedBulk(): + def _lowLevelSigGenFrequencySweep(self): pass - # Streaming related functions - def _lowLevelGetStreamingLatestValues(): + def _lowLevelSigGenLimits(self): pass - def _lowLevelNoOfStreamingValues(self): - noOfValues = c_uint32() + def _lowLevelSigGenPause(self): + pass - m = self.lib.ps6000aNoOfStreamingValues(c_int16(self.handle), - byref(noOfValues)) - self.checkResult(m) + def _lowLevelSigGenPhase(self): + pass - return noOfValues.value + def _lowLevelSigGenPhaseSweep(self): + pass - def _lowLevelRunStreaming(): + def _lowLevelSigGenRange(self): + pass + + def _lowLevelSigGenRestart(self): + pass + + def _lowLevelSigGenSoftwareTriggerControl(self): + pass + + def _lowLevelSigGenTrigger(self): pass - def _lowLevelStreamingReady(): + def _lowLevelSigGenWaveform(self): pass + + def _lowLevelSigGenWaveformDutyCycle(self): + pass + + ###################################################### + # TODO methods below are not in the programmer's guide + ###################################################### + def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, + offsetVoltage, pkToPk, indexMode, + shots, triggerType, triggerSource): + """Waveform should be an array of shorts.""" + raise NotImplementedError() + waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) + + m = self.lib.ps6000aSetSigGenArbitrary( + c_int16(self.handle), + c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts + c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts + c_uint32(int(deltaPhase)), # startDeltaPhase + c_uint32(int(deltaPhase)), # stopDeltaPhase + c_uint32(0), # deltaPhaseIncrement + c_uint32(0), # dwellCount + waveformPtr, # arbitraryWaveform + c_int32(len(waveform)), # arbitraryWaveformSize + c_enum(0), # sweepType for deltaPhase + c_enum(0), # operation (adding random noise and whatnot) + c_enum(indexMode), # single, dual, quad + c_uint32(shots), + c_uint32(0), # sweeps + c_uint32(triggerType), + c_uint32(triggerSource), + c_int16(0)) # extInThreshold + self.checkResult(m) + + def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, + frequency, shots, triggerType, + triggerSource, stopFreq, increment, + dwellTime, sweepType, numSweeps): + # TODO, I just noticed that V2 exists + # Maybe change to V2 in the future + raise NotImplementedError() + + if stopFreq is None: + stopFreq = frequency + + m = self.lib.ps6000aSetSigGenBuiltIn( + c_int16(self.handle), + c_int32(int(offsetVoltage * 1000000)), + c_int32(int(pkToPk * 1000000)), + c_int16(waveType), + c_float(frequency), c_float(stopFreq), + c_float(increment), c_float(dwellTime), + c_enum(sweepType), c_enum(0), + c_uint32(shots), c_uint32(numSweeps), + c_enum(triggerType), c_enum(triggerSource), + c_int16(0)) + self.checkResult(m) + + def _lowLevelGetMaxDownSampleRatio(self, noOfUnaggregatedSamples, + downSampleRatioMode, segmentIndex): + raise NotImplementedError() + maxDownSampleRatio = c_uint32() + + m = self.lib.ps6000aGetMaxDownSampleRatio( + c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), + byref(maxDownSampleRatio), + c_enum(downSampleRatioMode), c_uint32(segmentIndex)) + self.checkResult(m) + + return maxDownSampleRatio.value + + def _lowLevelSetDataBufferBulk(self, channel, buffer, waveform, + downSampleRatioMode): + raise NotImplementedError() + bufferPtr = buffer.ctypes.data_as(POINTER(c_int16)) + bufferLth = len(buffer) + + m = self.lib.ps6000aSetDataBufferBulk( + c_int16(self.handle), + c_enum(channel), bufferPtr, c_uint32(bufferLth), + c_uint32(waveform), c_enum(downSampleRatioMode)) + self.checkResult(m) + + def _lowLevelSetDataBuffersBulk(self, channel, bufferMax, bufferMin, + waveform, downSampleRatioMode): + raise NotImplementedError() + bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) + bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) + + bufferLth = len(bufferMax) + + m = self.lib.ps6000aSetDataBuffersBulk( + c_int16(self.handle), c_enum(channel), + bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth), + c_uint32(waveform), c_enum(downSampleRatioMode)) + self.checkResult(m) From 528bdb5982fb4be8a2faf4bababbe4e35348d3f9 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Tue, 25 Jan 2022 15:13:28 +0100 Subject: [PATCH 05/18] Raise errors on not implemented methods. Also catch not defined callbacks. Change readme to include ps6000a. --- README.md | 1 + picoscope/picobase.py | 6 +- picoscope/ps6000a.py | 154 ++++++++++++++++++------------------------ 3 files changed, 71 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 27f102f..c1ea5c6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ around, but this one tries to improve on them via: * Supports both Windows and Linux and Mac System has support for: + * PS6000A Class (Picoscope 6xxxE) * PS6000 * PS5000A Class (PicoScope 5242A/5243A/5244A/5442A/5443A/5444A/5242B/5244B/5442B/5443B/5444B) * PS4000A Class (PicoScope 4444/4824) diff --git a/picoscope/picobase.py b/picoscope/picobase.py index d71bfcc..947970f 100644 --- a/picoscope/picobase.py +++ b/picoscope/picobase.py @@ -866,9 +866,9 @@ def setAWGSimple(self, waveform, duration, offsetVoltage=None, Set the AWG to output your desired waveform. If you require precise control of the timestep increment, you should - use setSigGenAritrarySimpleDelaPhase instead + use setSigGenAritrarySimpleDeltaPhase instead - Check setSigGenAritrarySimpleDelaPhase for parameter explanation + Check setSigGenAritrarySimpleDeltaPhase for parameter explanation Returns: The actual duration of the waveform. @@ -911,7 +911,7 @@ def setAWGSimpleDeltaPhase(self, waveform, deltaPhase, offsetVoltage=None, This should in theory minimize the quantization error in the ADC. - else, the waveform shoudl be a numpy int16 type array with the + else, the waveform should be a numpy int16 type array with the containing waveform For the Quad mode, if offset voltage is not provided, then waveform[0] diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 3114aed..c3bedd7 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -58,8 +58,8 @@ # use the values specified in the h file # float is always defined as 32 bits # double is defined as 64 bits -from ctypes import byref, POINTER, create_string_buffer, c_float, c_int8, c_int16,\ - c_uint16, c_int32, c_uint32, c_int64, c_uint64, c_void_p, CFUNCTYPE +from ctypes import byref, POINTER, create_string_buffer, c_float, c_int8, \ + c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64, c_void_p, CFUNCTYPE from ctypes import c_int32 as c_enum from picoscope.picobase import _PicoscopeBase @@ -90,6 +90,8 @@ def dataReady(function): void * pParameter ) """ + if function is None: + return None callback = CFUNCTYPE(c_void_p, c_int16, c_uint32, c_uint32, c_int16, c_void_p) return callback(function) @@ -108,6 +110,8 @@ def streamingReady(function): void * pParameter ) """ + if function is None: + return None callback = CFUNCTYPE(c_void_p, c_int16, c_int32, c_uint32, c_int16, c_uint32, c_int16, c_int16, c_void_p) return callback @@ -146,7 +150,8 @@ class PS6000a(_PicoscopeBase): 'add': 0x00000002, # PICO_ADD 'clear_this': 0x00001000, # PICO_CLEAR_THIS_DATA_BUFFER 'clear_waveform': 0x00002000, # PICO_CLEAR_WAVEFORM_DATA_BUFFERS - 'clear_waveform_read': 0x00004000, # PICO_CLEAR_WAVEFORM_READ_DATA_BUFFERS + 'clear_waveform_read': 0x00004000, + # PICO_CLEAR_WAVEFORM_READ_DATA_BUFFERS } DATA_TYPES = { # PICO_DATA_TYPE @@ -636,8 +641,12 @@ def _lowLevelCheckForUpdate(self): return None, number, required def _lowLevelStartFirmwareUpdate(self, function): - callback = updateFirmwareProgress(function) - m = self.lib.ps6000aStartFirmwareUpdate(c_int16(self.handle), callback) + # Hold a reference to the callback so that the Python + # function pointer doesn't get free'd. + self._c_updateFirmware_callback = updateFirmwareProgress(function) + m = self.lib.ps6000aStartFirmwareUpdate( + c_int16(self.handle), + self._c_updateFirmware_callback) self.checkResult(m) "Misc" @@ -652,39 +661,40 @@ def _lowLevelStartFirmwareUpdate(self, function): # Complicated triggering # need to understand structs for some of this to work def _lowLevelGetValuesTriggerTimeOffsetBulk(): - pass + raise NotImplementedError() def _lowLevelSetTriggerChannelConditions(): - pass + raise NotImplementedError() def _lowLevelSetTriggerChannelDirections(): - pass + raise NotImplementedError() def _lowLevelSetTriggerChannelProperties(): - pass + raise NotImplementedError() def _lowLevelSetTriggerDelay(): - pass + raise NotImplementedError() def _lowLevelSetTriggerDigitalPortProperties(self): - pass + raise NotImplementedError() # Optional input triggering: PulseWidthQualifier def _lowLevelSetPulseWidthQualifierConditions(self): - pass + raise NotImplementedError() def _lowLevelSetPulseWidthQualifierDirections(self): - pass + raise NotImplementedError() def _lowLevelSetPulseWidthQualifierProperties(self): - pass + raise NotImplementedError() def _lowLevelTriggerWithinPreTriggerSamples(self): - pass + raise NotImplementedError() # Data acquisition def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, downSampleRatioMode): + raise NotImplementedError() bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) bufferLth = len(bufferMax) @@ -697,6 +707,7 @@ def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, self.checkResult(m) def _lowLevelClearDataBuffers(self, channel): + raise NotImplementedError() m = self.lib.ps6000aSetDataBuffers( c_int16(self.handle), c_enum(channel), c_void_p(), c_void_p(), c_uint32(0), c_enum(0)) @@ -710,6 +721,7 @@ def _lowLevelGetValuesBulk(self, numSamples, fromSegmentIndex, toSegmentIndex, downSampleRatio, downSampleRatioMode, overflow): + raise NotImplementedError() noOfSamples = c_uint32(numSamples) m = self.lib.ps6000aGetValuesBulk( @@ -730,20 +742,21 @@ def _lowLevelSetNoOfCaptures(self, nCaptures): # Async functions # would be nice, but we would have to learn to implement callbacks def _lowLevelGetValuesAsync(): - pass + raise NotImplementedError() def _lowLevelGetValuesBulkAsync(): - pass + raise NotImplementedError() # overlapped functions def _lowLevelGetValuesOverlapped(): - pass + raise NotImplementedError() # Streaming related functions def _lowLevelGetStreamingLatestValues(): - pass + raise NotImplementedError() def _lowLevelNoOfStreamingValues(self): + raise NotImplementedError() noOfValues = c_uint32() m = self.lib.ps6000aNoOfStreamingValues(c_int16(self.handle), @@ -753,7 +766,7 @@ def _lowLevelNoOfStreamingValues(self): return noOfValues.value def _lowLevelRunStreaming(): - pass + raise NotImplementedError() "alphabetically" @@ -764,9 +777,10 @@ def _lowLevelChannelCombinationsStateless(self): not change the configuration of the oscilloscope. """ # TODO - pass + raise NotImplementedError() def _lowLevelGetAnalogueOffsetLimits(self, range, coupling): + raise NotImplementedError() # TODO, populate properties with this function maximumVoltage = c_float() minimumVoltage = c_float() @@ -779,12 +793,13 @@ def _lowLevelGetAnalogueOffsetLimits(self, range, coupling): return (maximumVoltage.value, minimumVoltage.value) def _lowLevelGetMaximumAvailableMemory(self): - pass + raise NotImplementedError() def _lowLevelMinimumTimebaseStateless(self): - pass + raise NotImplementedError() def _lowLevelGetNoOfCaptures(self): + raise NotImplementedError() nCaptures = c_uint32() m = self.lib.ps6000aGetNoOfCaptures(c_int16(self.handle), @@ -794,80 +809,81 @@ def _lowLevelGetNoOfCaptures(self): return nCaptures.value def _lowLevelGetNoOfProcessedCaptures(self): - pass + raise NotImplementedError() def _lowLevelGetTriggerInfo(self): - pass + raise NotImplementedError() def _lowLevelMemorySegmentsBySamples(self): - pass + raise NotImplementedError() def _lowLevelNearestSampleIntervalStateless(self): - pass + raise NotImplementedError() def _lowLevelQueryMaxSegmentsBySamples(self): - pass + raise NotImplementedError() def _lowLevelQueryOutputEdgeDetect(self): - pass + raise NotImplementedError() def _lowLevelSetDigitalPortOff(self): - pass + raise NotImplementedError() def _lowLevelSetDigitalPortOn(self): - pass + raise NotImplementedError() def _lowLevelSetOutputEdgeDetect(self): - pass + raise NotImplementedError() # Signal Generator + # TODO add signal generator, at least in a simple version. def _lowLevelSigGenApply(self): - pass + raise NotImplementedError() def _lowLevelSigGenClockManual(self): - pass + raise NotImplementedError() def _lowLevelSigGenFilter(self): - pass + raise NotImplementedError() def _lowLevelSigGenFrequency(self): - pass + raise NotImplementedError() def _lowLevelSigGenFrequencyLimits(self): - pass + raise NotImplementedError() def _lowLevelSigGenFrequencySweep(self): - pass + raise NotImplementedError() def _lowLevelSigGenLimits(self): - pass + raise NotImplementedError() def _lowLevelSigGenPause(self): - pass + raise NotImplementedError() def _lowLevelSigGenPhase(self): - pass + raise NotImplementedError() def _lowLevelSigGenPhaseSweep(self): - pass + raise NotImplementedError() def _lowLevelSigGenRange(self): - pass + raise NotImplementedError() def _lowLevelSigGenRestart(self): - pass + raise NotImplementedError() def _lowLevelSigGenSoftwareTriggerControl(self): - pass + raise NotImplementedError() def _lowLevelSigGenTrigger(self): - pass + raise NotImplementedError() def _lowLevelSigGenWaveform(self): - pass + raise NotImplementedError() def _lowLevelSigGenWaveformDutyCycle(self): - pass + raise NotImplementedError() ###################################################### # TODO methods below are not in the programmer's guide @@ -876,6 +892,7 @@ def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, offsetVoltage, pkToPk, indexMode, shots, triggerType, triggerSource): """Waveform should be an array of shorts.""" + # TODO called by picobase. raise NotImplementedError() waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) @@ -903,6 +920,8 @@ def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, frequency, shots, triggerType, triggerSource, stopFreq, increment, dwellTime, sweepType, numSweeps): + # TODO add. Called by picobase. + # TODO, I just noticed that V2 exists # Maybe change to V2 in the future raise NotImplementedError() @@ -922,42 +941,3 @@ def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, c_enum(triggerType), c_enum(triggerSource), c_int16(0)) self.checkResult(m) - - def _lowLevelGetMaxDownSampleRatio(self, noOfUnaggregatedSamples, - downSampleRatioMode, segmentIndex): - raise NotImplementedError() - maxDownSampleRatio = c_uint32() - - m = self.lib.ps6000aGetMaxDownSampleRatio( - c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), - byref(maxDownSampleRatio), - c_enum(downSampleRatioMode), c_uint32(segmentIndex)) - self.checkResult(m) - - return maxDownSampleRatio.value - - def _lowLevelSetDataBufferBulk(self, channel, buffer, waveform, - downSampleRatioMode): - raise NotImplementedError() - bufferPtr = buffer.ctypes.data_as(POINTER(c_int16)) - bufferLth = len(buffer) - - m = self.lib.ps6000aSetDataBufferBulk( - c_int16(self.handle), - c_enum(channel), bufferPtr, c_uint32(bufferLth), - c_uint32(waveform), c_enum(downSampleRatioMode)) - self.checkResult(m) - - def _lowLevelSetDataBuffersBulk(self, channel, bufferMax, bufferMin, - waveform, downSampleRatioMode): - raise NotImplementedError() - bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) - bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) - - bufferLth = len(bufferMax) - - m = self.lib.ps6000aSetDataBuffersBulk( - c_int16(self.handle), c_enum(channel), - bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth), - c_uint32(waveform), c_enum(downSampleRatioMode)) - self.checkResult(m) From 89b4e307c098da3e0b9d93287baa6459e3b5f2cf Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:40:47 +0100 Subject: [PATCH 06/18] Async tests added. Error in the callback definition fixed. --- examples/test_ps6000a.py | 74 +++++++++++++++++++---- picoscope/ps6000a.py | 124 +++++++++++++++++---------------------- 2 files changed, 116 insertions(+), 82 deletions(-) diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py index 09005b9..a3e2e73 100644 --- a/examples/test_ps6000a.py +++ b/examples/test_ps6000a.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Testing the ps6000a Series +Testing the ps6000a Series. Created on Tue Jan 25 12:20:14 2022 by Benedikt Moneke """ @@ -13,15 +13,15 @@ def test_general_unit_calls(): - global ps assert ps.openUnitProgress() ps.flashLed() print(ps.getAllUnitInfo()) assert not ps.ping() + print("General unit calls test passed.") def test_timebase(): - global ps + """Test the timebase methods.""" # Preparation ps.memorySegments(1) # Tests @@ -44,15 +44,17 @@ def test_timebase(): assert ps.getTimestepFromTimebase(timebase) == time timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) assert timestep == time + print("Timebase test passed.") def test_deviceResolution(): - global ps + """Test setting/getting device resolution, including ADC limits.""" ps.setResolution("12") assert ps.resolution == "12" assert ps.getResolution() == "12" assert ps.MIN_VALUE == -32736 assert ps.MAX_VALUE == 32736 + print("Device resolution test passed.") def test_rapid_block_mode(n_captures=100, @@ -60,7 +62,6 @@ def test_rapid_block_mode(n_captures=100, sample_duration=2e-3, # 1 ms ): """Test the rapid block mode.""" - global ps # Configuration of Picoscope ps.setChannel(channel="A", coupling="DC", VRange=1) ps.setChannel(channel="B", enabled=False) @@ -92,16 +93,65 @@ def test_rapid_block_mode(n_captures=100, cmap=plt.cm.hot) plt.colorbar() plt.show() + print("Rapid block mode test passed.") + + +def data_ready(handle, status, noOfSamples, overflow, pParameter=None): + """Show the asynchronously received data.""" + if status == 0: + print(f"{noOfSamples} samples received with overflow: {overflow}") + plt.plot(data) + plt.show() + print("Data reading asynchronously test passed.") + else: + print(f"Data receiving error {status}.") + + +def test_read_async(handle=None, status=0, pParameter=None): + """Test reading data asynchronously.""" + if status == 0: + print("Block is ready and can be read.") + channel, numSamples = config + global data + data = np.empty(numSamples, dtype=np.int16) + ps._lowLevelSetDataBuffer(channel, data, 0, 0) + ps._lowLevelGetValuesAsync(numSamples, 0, 1, 0, 0, data_ready) + print("callback started, waiting 0.5 s.") + time.wait(0.5) + else: + print("Data is not ready. RunBlock had an error.") + + +def test_runBlock_async(channel="A", sample_interval=100e-9, + sample_duration=2e-3): + """Test running a block asynchronously.""" + # Configuration of Picoscope + ps.setChannel(channel=channel, coupling="DC", VRange=1) + ps.memorySegments(1) + ps.setResolution('12') + i, samples, m = ps.setSamplingInterval(sample_interval, sample_duration) + ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) + + global config + config = channel, samples + # Run the block + ps.runBlock(callback=test_read_async) + print("Run Block started, waiting 0.5 s.") + time.wait(0.5) if __name__ == "__main__": + """Run all the tests.""" # Initialize the picoscope ps = ps6000a.PS6000a() - # Run tests. - test_general_unit_calls() - test_deviceResolution() - test_rapid_block_mode() - - # Close the connection - ps.close() + try: + # Run tests. + test_general_unit_calls() + test_deviceResolution() + test_rapid_block_mode() + test_runBlock_async() + finally: + # Close the connection + ps.close() + print("All tests passed.") diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index c3bedd7..8fe5982 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -67,7 +67,7 @@ # Decorators for callback functions. PICO_STATUS is uint32_t. def blockReady(function): - """typedef void (*ps4000aBlockReady) + """typedef void (*ps6000aBlockReady) ( int16_t handle, PICO_STATUS status, @@ -81,42 +81,22 @@ def blockReady(function): def dataReady(function): - """typedef void (*ps4000aDataReady) + """typedef void (*ps6000aDataReady) ( int16_t handle, PICO_STATUS status, - uint32_t noOfSamples, + uint64_t noOfSamples, int16_t overflow, void * pParameter ) """ if function is None: return None - callback = CFUNCTYPE(c_void_p, - c_int16, c_uint32, c_uint32, c_int16, c_void_p) + callback = CFUNCTYPE(c_void_p, c_int16, c_uint32, c_uint64, c_int16, + c_void_p) return callback(function) -def streamingReady(function): - """typedef void (*ps4000aStreamingReady) - ( - int16_t handle, - int32_t noOfSamples, - uint32_t startIndex, - int16_t overflow, - uint32_t triggerAt, - int16_t triggered, - int16_t autoStop, - void * pParameter - ) - """ - if function is None: - return None - callback = CFUNCTYPE(c_void_p, c_int16, c_int32, c_uint32, c_int16, - c_uint32, c_int16, c_int16, c_void_p) - return callback - - def updateFirmwareProgress(function): """typedef void (*PicoUpdateFirmwareProgress) ( @@ -444,9 +424,8 @@ def _lowLevelSetDeviceResolution(self, resolution): """ if type(resolution) is str: resolution = self.ADC_RESOLUTIONS(resolution) - m = self.lib.ps6000aSetDeviceResolution( - c_int16(self.handle), - resolution) + m = self.lib.ps6000aSetDeviceResolution(c_int16(self.handle), + resolution) self.checkResult(m) self.resolution = resolution self.MIN_VALUE, self.MAX_VALUE = self._lowLevelGetAdcLimits(resolution) @@ -458,11 +437,10 @@ def _lowLevelGetAdcLimits(self, resolution): """ minimum = c_int16() maximum = c_int16() - m = self.lib.ps6000aGetAdcLimits( - c_int16(self.handle), - self.ADC_RESOLUTIONS(resolution), - byref(minimum), - byref(maximum)) + m = self.lib.ps6000aGetAdcLimits(c_int16(self.handle), + self.ADC_RESOLUTIONS(resolution), + byref(minimum), + byref(maximum)) self.checkResult(m) return minimum, maximum @@ -483,14 +461,13 @@ def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, # Trigger def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): - m = self.lib.ps6000aSetSimpleTrigger( - c_int16(self.handle), - c_int16(enabled), - c_enum(trigsrc), - c_int16(threshold_adc), - c_enum(direction), - c_uint64(delay), - c_uint32(timeout_ms)) + m = self.lib.ps6000aSetSimpleTrigger(c_int16(self.handle), + c_int16(enabled), + c_enum(trigsrc), + c_int16(threshold_adc), + c_enum(direction), + c_uint64(delay), + c_uint32(timeout_ms)) self.checkResult(m) # Start / stop measurement @@ -505,15 +482,14 @@ def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, # function pointer doesn't get free'd. self._c_runBlock_callback = blockReady(callback) timeIndisposedMs = c_int32() - m = self.lib.ps6000aRunBlock( - c_int16(self.handle), - c_uint64(numPreTrigSamples), - c_uint64(numPostTrigSamples), - c_uint32(timebase), - byref(timeIndisposedMs), - c_uint64(segmentIndex), - self._c_runBlock_callback, - c_void_p()) + m = self.lib.ps6000aRunBlock(c_int16(self.handle), + c_uint64(numPreTrigSamples), + c_uint64(numPostTrigSamples), + c_uint32(timebase), + byref(timeIndisposedMs), + c_uint64(segmentIndex), + self._c_runBlock_callback, + c_void_p()) self.checkResult(m) return timeIndisposedMs.value @@ -526,7 +502,7 @@ def _lowLevelIsReady(self): else: return False - # Acquire data + # Setup data acquisition. def _lowLevelMemorySegments(self, nSegments): nMaxSamples = c_uint64() m = self.lib.ps6000aMemorySegments(c_int16(self.handle), @@ -584,32 +560,44 @@ def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, self._lowLevelSetDataBuffer(channel, data, downSampleMode, segmentIndex) + # Acquire data. def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex): numSamplesReturned = c_uint64() numSamplesReturned.value = numSamples overflow = c_int16() - m = self.lib.ps6000aGetValues( - c_int16(self.handle), - c_uint64(startIndex), - byref(numSamplesReturned), - c_uint64(downSampleRatio), - c_enum(downSampleMode), - c_uint64(segmentIndex), - byref(overflow)) + m = self.lib.ps6000aGetValues(c_int16(self.handle), + c_uint64(startIndex), + byref(numSamplesReturned), + c_uint64(downSampleRatio), + c_enum(downSampleMode), + c_uint64(segmentIndex), + byref(overflow)) self.checkResult(m) return (numSamplesReturned.value, overflow.value) + def _lowLevelGetValuesAsync(self, numSamples, startIndex, downSampleRatio, + downSampleMode, segmentIndex, callback, pPar): + self._c_getValues_callback = dataReady(callback) + m = self.lib.ps6000aGetValuesAsync(c_int16(self.handle), + c_uint64(startIndex), + c_uint64(numSamples), + c_uint64(downSampleRatio), + c_enum(downSampleMode), + c_uint64(segmentIndex), + self._c_getValues_callback, + c_void_p()) + self.checkResult(m) + # Misc def _lowLevelGetTriggerTimeOffset(self, segmentIndex): time = c_int64() timeUnits = c_enum() - m = self.lib.ps6000aGetTriggerTimeOffset64( - c_int16(self.handle), - byref(time), - byref(timeUnits), - c_uint64(segmentIndex)) + m = self.lib.ps6000aGetTriggerTimeOffset64(c_int16(self.handle), + byref(time), + byref(timeUnits), + c_uint64(segmentIndex)) self.checkResult(m) try: @@ -740,10 +728,6 @@ def _lowLevelSetNoOfCaptures(self, nCaptures): self.checkResult(m) # Async functions - # would be nice, but we would have to learn to implement callbacks - def _lowLevelGetValuesAsync(): - raise NotImplementedError() - def _lowLevelGetValuesBulkAsync(): raise NotImplementedError() @@ -892,7 +876,7 @@ def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, offsetVoltage, pkToPk, indexMode, shots, triggerType, triggerSource): """Waveform should be an array of shorts.""" - # TODO called by picobase. + # TODO called by picobase, add a combination of low level functions. raise NotImplementedError() waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) @@ -920,7 +904,7 @@ def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, frequency, shots, triggerType, triggerSource, stopFreq, increment, dwellTime, sweepType, numSweeps): - # TODO add. Called by picobase. + # TODO called by picobase, add a combination of low level functions. # TODO, I just noticed that V2 exists # Maybe change to V2 in the future From 9386089f0ca221b08eb203583292e7a9f4c50dca Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Wed, 26 Jan 2022 13:51:06 +0100 Subject: [PATCH 07/18] Tests improved after trials with a ps4000a series. --- examples/test_ps6000a.py | 83 +++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py index a3e2e73..b021ae5 100644 --- a/examples/test_ps6000a.py +++ b/examples/test_ps6000a.py @@ -1,6 +1,18 @@ # -*- coding: utf-8 -*- """ -Testing the ps6000a Series. +Testing the ps6000a series software-device interaction. +====================================================== + +This file provides tests in order to verify that the software does with the +oscilloscope, what it is supposed to do. Additionally the tests serve as +examples on how to do certain tasks. + +Usage +----- + +- Run this file in order to execute all software tests on your device. +- Import this file and execute the tests you want with your already opened + oscilloscope device. Created on Tue Jan 25 12:20:14 2022 by Benedikt Moneke """ @@ -12,15 +24,15 @@ import time -def test_general_unit_calls(): - assert ps.openUnitProgress() +def test_general_unit_calls(ps): + """Test general unit calls""" ps.flashLed() print(ps.getAllUnitInfo()) - assert not ps.ping() + assert not ps.ping(), "Ping failed." print("General unit calls test passed.") -def test_timebase(): +def test_timebase(ps): """Test the timebase methods.""" # Preparation ps.memorySegments(1) @@ -32,7 +44,8 @@ def test_timebase(): (10e-9, 5), (15e-9, 6)) for (time, timebase) in data: - assert ps.getTimeBaseNum(time) == timebase + text = f"Time {time} does not give timebase {timebase}" + assert ps.getTimeBaseNum(time) == timebase, text data = ((200e-12, 0), (800e-12, 2), (3.2e-9, 4), @@ -41,23 +54,25 @@ def test_timebase(): (3.84e-8, 10), (6.144e-7, 100)) for (time, timebase) in data: - assert ps.getTimestepFromTimebase(timebase) == time + text = f"time {time} does not fit timebase {timebase}." + assert ps.getTimestepFromTimebase(timebase) == time, "Timestep " + text timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) - assert timestep == time + assert timestep == time, f"LowLevel: {timestep} != {time}" print("Timebase test passed.") -def test_deviceResolution(): +def test_deviceResolution(ps): """Test setting/getting device resolution, including ADC limits.""" ps.setResolution("12") - assert ps.resolution == "12" - assert ps.getResolution() == "12" - assert ps.MIN_VALUE == -32736 - assert ps.MAX_VALUE == 32736 + assert ps.resolution == "12", "Picoscope variable was not set." + assert ps.getResolution() == "12", "Picoscope resolution is wrong." + assert ps.MIN_VALUE == -32736, "Minimum adc value is wrong." + assert ps.MAX_VALUE == 32736, "Maximum adc value is wrong." print("Device resolution test passed.") -def test_rapid_block_mode(n_captures=100, +def test_rapid_block_mode(ps, + n_captures=100, sample_interval=100e-9, # 100 ns sample_duration=2e-3, # 1 ms ): @@ -108,36 +123,52 @@ def data_ready(handle, status, noOfSamples, overflow, pParameter=None): def test_read_async(handle=None, status=0, pParameter=None): - """Test reading data asynchronously.""" + """ + Test reading data asynchronously. + + If you call it manually instead of using it as a callback, use pParameter + for handing over the picoscope instance. + """ + if pParameter is not None: + psg = pParameter if status == 0: print("Block is ready and can be read.") + # config is a global variable written by the caller. channel, numSamples = config + # Define data globally for data_ready. global data data = np.empty(numSamples, dtype=np.int16) - ps._lowLevelSetDataBuffer(channel, data, 0, 0) - ps._lowLevelGetValuesAsync(numSamples, 0, 1, 0, 0, data_ready) - print("callback started, waiting 0.5 s.") - time.wait(0.5) + if not isinstance(channel, int): + channel = ps.CHANNELS[channel] + psg._lowLevelSetDataBuffer(channel, data, 0, 0) + psg._lowLevelGetValuesAsync(numSamples, 0, 1, 0, 0, data_ready, None) + print("Get values async started.") else: print("Data is not ready. RunBlock had an error.") -def test_runBlock_async(channel="A", sample_interval=100e-9, +def test_runBlock_async(ps, channel="A", sample_interval=100e-9, sample_duration=2e-3): """Test running a block asynchronously.""" + # Define psg globally for test_read_async. + global psg + psg = ps # Configuration of Picoscope ps.setChannel(channel=channel, coupling="DC", VRange=1) ps.memorySegments(1) + ps.setNoOfCaptures(1) ps.setResolution('12') i, samples, m = ps.setSamplingInterval(sample_interval, sample_duration) ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) + # Define config globally for test_read_async. global config config = channel, samples # Run the block ps.runBlock(callback=test_read_async) - print("Run Block started, waiting 0.5 s.") - time.wait(0.5) + print("Run block started, waiting 1 s.") + time.sleep(1) + print("Run block finished.") if __name__ == "__main__": @@ -147,10 +178,10 @@ def test_runBlock_async(channel="A", sample_interval=100e-9, try: # Run tests. - test_general_unit_calls() - test_deviceResolution() - test_rapid_block_mode() - test_runBlock_async() + test_general_unit_calls(ps) + test_deviceResolution(ps) + test_rapid_block_mode(ps) + test_runBlock_async(ps) finally: # Close the connection ps.close() From 116f3832dd09947ffb1c94f99896233b9f6fab88 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Mon, 12 Sep 2022 18:26:37 +0200 Subject: [PATCH 08/18] Error codes expanded. --- picoscope/error_codes.py | 142 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/picoscope/error_codes.py b/picoscope/error_codes.py index 47cd7c0..2802d4a 100644 --- a/picoscope/error_codes.py +++ b/picoscope/error_codes.py @@ -232,4 +232,144 @@ "The number of channels which can be enabled is limited in " + "15 and 16-bit modes"], [0x122, "PICO_CHANNEL_DISABLED_DUE_TO_USB_POWERED", - "USB Power not sufficient to power all channels."]] + "USB Power not sufficient to power all channels."], + [0x123, "PICO_SIGGEN_DC_VOLTAGE_NOT_CONFIGURABLE", ""], + [0x124, "PICO_NO_TRIGGER_ENABLED_FOR_TRIGGER_IN_PRE_TRIG", ""], + [0x125, "PICO_TRIGGER_WITHIN_PRE_TRIG_NOT_ARMED", ""], + [0x126, "PICO_TRIGGER_WITHIN_PRE_NOT_ALLOWED_WITH_DELAY", ""], + [0x127, "PICO_TRIGGER_INDEX_UNAVAILABLE", ""], + [0x128, "PICO_AWG_CLOCK_FREQUENCY", ""], + [0x129, "PICO_TOO_MANY_CHANNELS_IN_USE", ""], + [0x12A, "PICO_NULL_CONDITIONS", ""], + [0x12B, "PICO_DUPLICATE_CONDITION_SOURCE", ""], + [0x12C, "PICO_INVALID_CONDITION_INFO", ""], + [0x12D, "PICO_SETTINGS_READ_FAILED", ""], + [0x12E, "PICO_SETTINGS_WRITE_FAILED", ""], + [0x12F, "PICO_ARGUMENT_OUT_OF_RANGE", ""], + [0x130, "PICO_HARDWARE_VERSION_NOT_SUPPORTED", ""], + [0x131, "PICO_DIGITAL_HARDWARE_VERSION_NOT_SUPPORTED", ""], + [0x132, "PICO_ANALOGUE_HARDWARE_VERSION_NOT_SUPPORTED", ""], + [0x133, "PICO_UNABLE_TO_CONVERT_TO_RESISTANCE", ""], + [0x134, "PICO_DUPLICATED_CHANNEL", ""], + [0x135, "PICO_INVALID_RESISTANCE_CONVERSION", ""], + [0x136, "PICO_INVALID_VALUE_IN_MAX_BUFFER", ""], + [0x137, "PICO_INVALID_VALUE_IN_MIN_BUFFER", ""], + [0x138, "PICO_SIGGEN_FREQUENCY_OUT_OF_RANGE", ""], + [0x139, "PICO_EEPROM2_CORRUPT", ""], + [0x13A, "PICO_EEPROM2_FAIL", ""], + [0x13B, "PICO_SERIAL_BUFFER_TOO_SMALL", ""], + [0x13C, "PICO_SIGGEN_TRIGGER_AND_EXTERNAL_CLOCK_CLASH", ""], + [0x13D, "PICO_WARNING_SIGGEN_AUXIO_TRIGGER_DISABLED", ""], + [0x13E, "PICO_SIGGEN_GATING_AUXIO_NOT_AVAILABLE", ""], + [0x13F, "PICO_SIGGEN_GATING_AUXIO_ENABLED", ""], + [0x140, "PICO_RESOURCE_ERROR", ""], + [0x141, "PICO_TEMPERATURE_TYPE_INVALID", ""], + [0x142, "PICO_TEMPERATURE_TYPE_NOT_SUPPORTED", ""], + [0x143, "PICO_TIMEOUT", ""], + [0x144, "PICO_DEVICE_NOT_FUNCTIONING", ""], + [0x145, "PICO_INTERNAL_ERROR", ""], + [0x146, "PICO_MULTIPLE_DEVICES_FOUND", ""], + [0x147, "PICO_WARNING_NUMBER_OF_SEGMENTS_REDUCED", ""], + [0x148, "PICO_CAL_PINS_STATES", ""], + [0x149, "PICO_CAL_PINS_FREQUENCY", ""], + [0x14A, "PICO_CAL_PINS_AMPLITUDE", ""], + [0x14B, "PICO_CAL_PINS_WAVETYPE", ""], + [0x14C, "PICO_CAL_PINS_OFFSET", ""], + [0x14D, "PICO_PROBE_FAULT", ""], + [0x14E, "PICO_PROBE_IDENTITY_UNKNOWN", ""], + [0x14F, "PICO_PROBE_POWER_DC_POWER_SUPPLE_REQUIRED", ""], + [0x150, "PICO_PROBE_NOT_POWERED_THROUGH_DC_POWER_SUPPLY", ""], + [0x151, "PICO_PROBE_CONFIG_FAILURE", ""], + [0x152, "PICO_PROBE_INTERACTION_CALLBACK", ""], + [0x153, "PICO_UNKNOWN_INTELLIGENT_PROBE", ""], + [0x154, "PICO_INTELLIGENT_PROBE_CORRUPT", ""], + [0x155, "PICO_PROBE_COLLECTION_NOT_STARTED", ""], + [0x156, "PICO_PROBE_POWER_CONSUMPTION_EXCEEDED", ""], + [0x157, "PICO_WARNING_PROBE_CHANNEL_OUT_OF_SYNC", ""], + [0x158, "PICO_ENDPOINT_MISSING", ""], + [0x159, "PICO_UNKNOWN_ENDPOINT_REQUEST", ""], + [0x15A, "PICO_ADC_TYPE_ERROR", ""], + [0x15B, "PICO_FPGA2_FAILED", ""], + [0x15C, "PICO_FPGA2_DEVICE_STATUS", ""], + [0x15D, "PICO_ENABLED_PROGRAM_FPGA2_FAILED", ""], + [0x15E, "PICO_NO_CANNELS_OR_PORTS_ENABLED", ""], + [0x15F, "PICO_INVALID_RATIO_MODE", ""], + [0x160, "PICO_READS_NOT_SUPPORTED_IN_CURRENT_CAPTURE_MODE", ""], + [0x161, "PICO_TRIGGER_READ_SELECTION_CHECK_FAILED", ""], + [0x162, "PICO_DATA_READ1_SELECTION_CHECK_FAILED", ""], + [0x164, "PICO_DATA_READ2_SELECTION_CHECK_FAILED", ""], + [0x168, "PICO_DATA_READ3_SELECTION_CHECK_FAILED", ""], + [0x170, "PICO_READ_SELECTION_OUT_OF_RANGE", ""], + [0x171, "PICO_MULTIPLE_RATIO_MODES", ""], + [0x172, "PICO_NO_SAMPLES_READ", ""], + [0x173, "PICO_RATIO_MODE_NOT_REQUESTED", ""], + [0x174, "PICO_NO_USER_READ_REQUESTS", ""], + [0x175, "PICO_ZERO_SAMPLES_INVALID", ""], + [0x176, "PICO_ANALOGUE_HARDWARE_MISSING", ""], + [0x177, "PICO_ANALOGUE_HARDWARE_PINS", ""], + [0x178, "PICO_ANALOGUE_SMPS_FAULT", ""], + [0x179, "PICO_DIGITAL_ANALOGUE_HARDWARE_CONFLICT", ""], + [0x17A, "PICO_RATIO_MODE_BUFFER_NOT_SET", ""], + [0x17B, "PICO_RESOLUTION_NOT_SUPPORTED_BY_VARIENT", ""], + [0x17C, "PICO_THRESHOLD_OUT_OF_RANGE", ""], + [0x17D, "PICO_INVALID_SIMPLE_TRIGGER_DIRECTION", ""], + [0x17E, "PICO_AUX_NOT_SUPPORTED", ""], + [0x17F, "PICO_NULL_DIRECTIONS", ""], + [0x180, "PICO_NULL_CHANNEL_PROPERTIES", ""], + [0x181, "PICO_TRIGGER_CHANNEL_NOT_ENABLED", ""], + [0x182, "PICO_CONDITION_HAS_NO_TRIGGER_PROPERTY", ""], + [0x183, "PICO_RATIO_MODE_TRIGGER_MASKING_INVALID", ""], + [0x184, "PICO_TRIGGER_DATA_REQUIRES_MIN_BUFFER_SIZE_OF_40_SAMPLES", ""], + [0x185, "PICO_NO_OF_CAPTURES_OUT_OF_RANGE", ""], + [0x186, "PICO_RATIO_MODE_SEGMENT_HEADER_DOES_NOT_REQUIRE_BUFFERS", ""], + [0x187, "PICO_FOR_SEGMENT_HEADER_USE_GETTRIGGERINFO", ""], + [0x188, "PICO_READ_NOT_SET", ""], + [0x189, "PICO_ADC_SETTING_MISMATCH", ""], + [0x18A, "PICO_DATATYPE_INVALID", ""], + [0x18B, "PICO_RATIO_MODE_DOES_NOT_SUPPORT_DATATYPE", ""], + [0x18C, "PICO_CHANNEL_COMBINATION_NOT_VALID_IN_THIS_RESOLUTION", ""], + [0x18D, "PICO_USE_8BIT_RESOLUTION", ""], + [0x18E, "PICO_AGGREGATE_BUFFERS_SAME_POINTER", ""], + [0x18F, "PICO_OVERLAPPED_READ_VALUES_OUT_OF_RANGE", ""], + [0x190, "PICO_OVERLAPPED_READ_SEGMENTS_OUT_OF_RANGE", ""], + [0x191, "PICO_CHANNELFLAGSCOMBINATIONS_ARRAY_SIZE_TOO_SMALL", ""], + [0x192, "PICO_CAPTURES_EXCEEDS_NO_OF_SUPPORTED_SEGMENTS", ""], + [0x193, "PICO_TIME_UNITS_OUT_OF_RANGE", ""], + [0x194, "PICO_NO_SAMPLES_REQUESTED", ""], + [0x195, "PICO_INVALID_ACTION", ""], + [0x196, "PICO_NO_OF_SAMPLES_NEED_TO_BE_EQUAL_WHEN_ADDING_BUFFERS", ""], + [0x197, "PICO_WAITING_FOR_DATA_BUFFERS", ""], + [0x198, "PICO_STREAMING_ONLY_SUPPORTS_ONE_READ", ""], + [0x199, "PICO_CLEAR_DATA_BUFFER_INVALID", ""], + [0x19A, "PICO_INVALID_ACTION_FLAGS_COMBINATION", ""], + [0x19B, "PICO_PICO_MOTH_MIN_AND_MAX_NULL_BUFFERS_CANNOT_BE_ADDED", ""], + [0x19C, "PICO_CONFLICT_IN_SET_DATA_BUFFERS_CALL_REMOVE_DATA_BUFFER_TO_RESET", ""], + [0x19D, "PICO_REMOVING_DATA_BUFFER_ENTRIES_NOT_ALLOWED_WHILE_DATA_PROCESSING", ""], + [0x200, "PICO_CYUSB_REQUEST_FAILED", ""], + [0x201, "PICO_STREAMING_DATA_REQUIRED", ""], + [0x202, "PICO_INVALID_NUMBER_OF_SAMPLES", ""], + [0x203, "PICO_INALID_DISTRIBUTION", ""], + [0x204, "PICO_BUFFER_LENGTH_GREATER_THAN_INT32_T", ""], + [0x209, "PICO_PLL_MUX_OUT_FAILED", ""], + [0x20A, "PICO_ONE_PULSE_WIDTH_DIRECTION_ALLOWED", ""], + [0x20B, "PICO_EXTERNAL_TRIGGER_NOT_SUPPORTED", ""], + [0x20C, "PICO_NO_TRIGGER_CONDITIONS_SET", ""], + [0x20D, "PICO_NO_OF_CHANNEL_TRIGGER_PROPERTIES_OUT_OF_RANGE", ""], + [0x20E, "PICO_PROBE_COMPNENT_ERROR", ""], + [0x210, "PICO_INVALID_TRIGGER_CHANNELS_FOR_ETS", ""], + [0x211, "PICO_NOT_AVALIABLE_WHEN_STREAMING_IS_RUNNING", ""], + [0x212, "PICO_INVALID_TRIGGER_WITHIN_PRE_TRIGGER_STATE", ""], + [0x213, "PICO_ZERO_NUMBER_OF_CAPTURES_INVALID", ""], + [0x300, "PICO_TRIGGER_DELAY_OUT_OF_RANGE", ""], + [0x301, "PICO_INVALID_THRESHOLD_DIRECTION", ""], + [0x302, "PICO_INVALID_THRESGOLD_MODE", ""], + [0x1000000, "PICO_DEVICE_TIME_STAMP_RESET", ""], + [0x10000000, "PICO_WATCHDOGTIMER", ""], + [0x10000001, "PICO_IPP_NOT_FOUND", ""], + [0x10000002, "PICO_IPP_NO_FUNCTION", ""], + [0x10000003, "PICO_IPP_ERROR", ""], + [0x10000004, "PICO_SHADOW_CAL_NOT_AVAILABLE", ""], + [0x10000005, "PICO_SHADOW_CAL_DISABLED", ""], + [0x10000006, "PICO_SHADOW_CAL_ERROR", ""], + [0x10000007, "PICO_SHADOW_CAL_CORRUPT", ""], +] From 719b2115cf0b04ec8fdf47c527b8e64d8cf307de Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Mon, 12 Sep 2022 18:57:16 +0200 Subject: [PATCH 09/18] Bug fixes and tests. downsampleMode = "none" (0) is illegal, therefore changed to "raw". --- examples/test_ps6000a.py | 114 +++++++++++++++++++++---------------- picoscope/error_codes.py | 6 ++ picoscope/ps6000a.py | 118 ++++++++++++++++++++++++--------------- 3 files changed, 145 insertions(+), 93 deletions(-) diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py index b021ae5..b886652 100644 --- a/examples/test_ps6000a.py +++ b/examples/test_ps6000a.py @@ -46,17 +46,22 @@ def test_timebase(ps): for (time, timebase) in data: text = f"Time {time} does not give timebase {timebase}" assert ps.getTimeBaseNum(time) == timebase, text - data = ((200e-12, 0), + data = (#(200e-12, 0), (800e-12, 2), (3.2e-9, 4), (6.4e-9, 5), - (12.8-9, 6), + #(12.8-9, 6), + (19.2e-9, 7), (3.84e-8, 10), (6.144e-7, 100)) for (time, timebase) in data: - text = f"time {time} does not fit timebase {timebase}." - assert ps.getTimestepFromTimebase(timebase) == time, "Timestep " + text - timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) + text = f"{time} s does not fit timebase {timebase}." + assert ps.getTimestepFromTimebase(timebase) == time, "Timestep: " + text + try: + timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) + except Exception: + print(f"getTimebase failed at time {time}, timebase {timebase}.") + raise assert timestep == time, f"LowLevel: {timestep} != {time}" print("Timebase test passed.") @@ -64,7 +69,7 @@ def test_timebase(ps): def test_deviceResolution(ps): """Test setting/getting device resolution, including ADC limits.""" ps.setResolution("12") - assert ps.resolution == "12", "Picoscope variable was not set." + assert ps.resolution == 1, "Picoscope variable was not set." assert ps.getResolution() == "12", "Picoscope resolution is wrong." assert ps.MIN_VALUE == -32736, "Minimum adc value is wrong." assert ps.MAX_VALUE == 32736, "Maximum adc value is wrong." @@ -80,6 +85,8 @@ def test_rapid_block_mode(ps, # Configuration of Picoscope ps.setChannel(channel="A", coupling="DC", VRange=1) ps.setChannel(channel="B", enabled=False) + ps.setChannel(channel="C", enabled=False) + ps.setChannel(channel="D", enabled=False) ps.setResolution('12') ps.setSamplingInterval(sample_interval, sample_duration) @@ -99,7 +106,8 @@ def test_rapid_block_mode(ps, t2 = time.time() print("Time to record data to scope: ", str(t2 - t1)) - ps.getDataRawBulk(data=data) + # downSampleMode raw (no downsampling) is 0x80000000. 0 is invalid! + ps.getDataRawBulk(data=data, downSampleMode=0x80000000) t3 = time.time() print("Time to copy to RAM: ", str(t3 - t2)) @@ -107,65 +115,72 @@ def test_rapid_block_mode(ps, plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none', cmap=plt.cm.hot) plt.colorbar() + plt.title("rapid block mode") plt.show() print("Rapid block mode test passed.") -def data_ready(handle, status, noOfSamples, overflow, pParameter=None): - """Show the asynchronously received data.""" - if status == 0: - print(f"{noOfSamples} samples received with overflow: {overflow}") - plt.plot(data) - plt.show() - print("Data reading asynchronously test passed.") - else: - print(f"Data receiving error {status}.") - - -def test_read_async(handle=None, status=0, pParameter=None): - """ - Test reading data asynchronously. - - If you call it manually instead of using it as a callback, use pParameter - for handing over the picoscope instance. - """ - if pParameter is not None: - psg = pParameter - if status == 0: - print("Block is ready and can be read.") - # config is a global variable written by the caller. - channel, numSamples = config - # Define data globally for data_ready. - global data - data = np.empty(numSamples, dtype=np.int16) - if not isinstance(channel, int): - channel = ps.CHANNELS[channel] - psg._lowLevelSetDataBuffer(channel, data, 0, 0) - psg._lowLevelGetValuesAsync(numSamples, 0, 1, 0, 0, data_ready, None) - print("Get values async started.") - else: - print("Data is not ready. RunBlock had an error.") +class Handler: + + def print(self, text): + print(text) + + def data_ready(self, handle, status, noOfSamples, overflow, pParameter=None): + """Show the asynchronously received data.""" + if status == 0: + self.print(f"{noOfSamples} samples received with overflow: {overflow}") + plt.plot(self.data) + plt.title("async") + plt.show() + self.print("Data reading asynchronously test passed.") + else: + self.print(f"Data receiving error {status}.") + + def test_read_async(self, handle=None, status=0, pParameter=None): + """ + Test reading data asynchronously. + + If you call it manually instead of using it as a callback, use pParameter + for handing over the picoscope instance. + """ + if pParameter is not None: + ps = pParameter + else: + ps = self.ps + if status == 0: + self.print("Block is ready and can be read.") + # config is a global variable written by the caller. + channel, numSamples = self.config + # Define data for data_ready. + self.data = np.zeros(ps.noSamples, dtype=np.int16) + if not isinstance(channel, int): + channel = ps.CHANNELS[channel] + ps._lowLevelClearDataBufferAll(channel, 0) + ps._lowLevelSetDataBuffer(channel, self.data, downSampleMode=0x80000000, segmentIndex=0) + ps._lowLevelGetValuesAsync(ps.noSamples, 0, 1, 0x80000000, 0, self.data_ready, None) + self.print("Get values async started.") + else: + self.print("Data is not ready. RunBlock had an error.") def test_runBlock_async(ps, channel="A", sample_interval=100e-9, sample_duration=2e-3): """Test running a block asynchronously.""" - # Define psg globally for test_read_async. - global psg - psg = ps + # Define a handler to exchange data + global handler + handler = Handler() + handler.ps = ps # Configuration of Picoscope ps.setChannel(channel=channel, coupling="DC", VRange=1) ps.memorySegments(1) ps.setNoOfCaptures(1) ps.setResolution('12') - i, samples, m = ps.setSamplingInterval(sample_interval, sample_duration) + interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, sample_duration) ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) - # Define config globally for test_read_async. - global config - config = channel, samples + handler.config = channel, samples # Run the block - ps.runBlock(callback=test_read_async) + ps.runBlock(callback=handler.test_read_async) print("Run block started, waiting 1 s.") time.sleep(1) print("Run block finished.") @@ -179,6 +194,7 @@ def test_runBlock_async(ps, channel="A", sample_interval=100e-9, try: # Run tests. test_general_unit_calls(ps) + test_timebase(ps) test_deviceResolution(ps) test_rapid_block_mode(ps) test_runBlock_async(ps) diff --git a/picoscope/error_codes.py b/picoscope/error_codes.py index 2802d4a..1341dd2 100644 --- a/picoscope/error_codes.py +++ b/picoscope/error_codes.py @@ -363,6 +363,12 @@ [0x300, "PICO_TRIGGER_DELAY_OUT_OF_RANGE", ""], [0x301, "PICO_INVALID_THRESHOLD_DIRECTION", ""], [0x302, "PICO_INVALID_THRESGOLD_MODE", ""], + [0x6000, "PICO_HARDWARE_CAPTURE_TIMEOUT", + "waiting for the device to capture timed out"], + [0x6001, "PICO_HARDWARE_READY_TIMEOUT", + "waiting for the device be ready for capture timed out"], + [0x6002, "PICO_HARDWARE_CAPTURING_CALL_STOP", + "the driver is performing a capture requested by RunStreaming or RunBlock to interrupt this capture call Stop on the device first"], [0x1000000, "PICO_DEVICE_TIME_STAMP_RESET", ""], [0x10000000, "PICO_WATCHDOGTIMER", ""], [0x10000001, "PICO_IPP_NOT_FOUND", ""], diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 8fe5982..4a3d84d 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -58,7 +58,7 @@ # use the values specified in the h file # float is always defined as 32 bits # double is defined as 64 bits -from ctypes import byref, POINTER, create_string_buffer, c_float, c_int8, \ +from ctypes import byref, POINTER, create_string_buffer, c_float, c_int8, c_double,\ c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64, c_void_p, CFUNCTYPE from ctypes import c_int32 as c_enum @@ -132,7 +132,7 @@ class PS6000a(_PicoscopeBase): 'clear_waveform': 0x00002000, # PICO_CLEAR_WAVEFORM_DATA_BUFFERS 'clear_waveform_read': 0x00004000, # PICO_CLEAR_WAVEFORM_READ_DATA_BUFFERS - } + } DATA_TYPES = { # PICO_DATA_TYPE 'int8': 0, # PICO_INT8_T @@ -140,7 +140,7 @@ class PS6000a(_PicoscopeBase): 'int32': 2, # PICO_INT32_T 'uint32': 3, # PICO_UINT32_T 'int64': 4, # PICO_INT64_T - } + } TIME_UNITS = [ # PICO_TIME_UNITS 1e-15, # PICO_FS @@ -149,7 +149,7 @@ class PS6000a(_PicoscopeBase): 1e-6, # PICO_US 1e-3, # PICO_MS 1, # PICO_S - ] + ] # Only at 8 bit, use GetAdcLimits for other resolutions. MAX_VALUE = 32512 @@ -168,6 +168,14 @@ class PS6000a(_PicoscopeBase): {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, ] + RATIO_MODE = {"aggregate": 1, # max and min of every n data. + "decimate": 2, # Take every n data. + "average": 4, # Average of every n data. + "trigger": 0x40000000, # 20 samples either side of the trigger. This cannot be combined with any other ratio mode + "raw": 0x80000000, # No downsampling + "none": 0x80000000, # for compatibility + } + # TODO verify values below # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) @@ -187,14 +195,6 @@ class PS6000a(_PicoscopeBase): SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} - # This is actually different depending on the AB/CD models - # I wonder how we could detect the difference between the oscilloscopes - # I believe we can obtain this information from the setInfo function - # by readign the hardware version - # for the PS6403B version, the hardware version is "1 1", - # an other possibility is that the PS6403B shows up as 6403 when using - # VARIANT_INFO and others show up as PS6403X where X = A,C or D - AWGPhaseAccumulatorSize = 32 AWGBufferAddressWidth = 14 AWGMaxSamples = 2 ** AWGBufferAddressWidth @@ -233,10 +233,6 @@ def __init__(self, serialNumber=None, connect=True, resolution="8"): super(PS6000a, self).__init__(serialNumber, connect) - ########################### - # TODO test functions below - ########################### - "General unit calls" # Open / close unit @@ -313,6 +309,7 @@ def _lowLevelEnumerateUnits(self): return serialList def _lowLevelFlashLed(self, times): + # TODO verify as it does not work m = self.lib.ps6000aFlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) @@ -365,19 +362,20 @@ def _lowLevelGetTimebase(self, timebase, noSamples, oversample, and timebase chosen. """ maxSamples = c_uint64() - timeIntervalSeconds = c_float() + timeIntervalNanoSeconds = c_double() m = self.lib.ps6000aGetTimebase(c_int16(self.handle), c_uint32(timebase), c_uint64(noSamples), - byref(timeIntervalSeconds), + byref(timeIntervalNanoSeconds), byref(maxSamples), c_uint64(segmentIndex)) self.checkResult(m) - return (timeIntervalSeconds.value / 1.0E9, maxSamples.value) + return (timeIntervalNanoSeconds.value / 1.0e9, maxSamples.value) - def getTimeBaseNum(self, sampleTimeS): + @staticmethod + def getTimeBaseNum(sampleTimeS): """Convert `sampleTimeS` in s to the integer timebase number.""" maxSampleTime = (((2 ** 32 - 1) - 4) / 156250000) @@ -393,7 +391,8 @@ def getTimeBaseNum(self, sampleTimeS): return timebase - def getTimestepFromTimebase(self, timebase): + @staticmethod + def getTimestepFromTimebase(timebase): """Convert `timebase` index to sampletime in seconds.""" if timebase < 5: dt = 2 ** timebase / 5E9 @@ -408,9 +407,9 @@ def _lowLevelGetDeviceResolution(self): m = self.lib.ps6000aGetDeviceResolution(c_int16(self.handle), byref(resolution)) self.checkResult(m) - for key in self.ADC_RESOLUTIONS.keys(): - if self.ADC_RESOLUTIONS[key] == resolution: - self.resolution = key + self.resolution = resolution.value + for key, value in self.ADC_RESOLUTIONS.items(): + if value == self.resolution: return key raise TypeError(f"Unknown resolution {resolution}.") @@ -423,7 +422,7 @@ def _lowLevelSetDeviceResolution(self, resolution): can be enabled to capture data. """ if type(resolution) is str: - resolution = self.ADC_RESOLUTIONS(resolution) + resolution = self.ADC_RESOLUTIONS[resolution] m = self.lib.ps6000aSetDeviceResolution(c_int16(self.handle), resolution) self.checkResult(m) @@ -435,19 +434,21 @@ def _lowLevelGetAdcLimits(self, resolution): This function gets the maximum and minimum sample values that the ADC can produce at a given resolution. """ + # TODO verify + if type(resolution) is str: + resolution = self.ADC_RESOLUTIONS[resolution] minimum = c_int16() maximum = c_int16() m = self.lib.ps6000aGetAdcLimits(c_int16(self.handle), - self.ADC_RESOLUTIONS(resolution), + resolution, byref(minimum), byref(maximum)) self.checkResult(m) - return minimum, maximum + return minimum.value, maximum.value # Channel def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, BWLimited): - # TODO verify that it is really "On"/"Off" if enabled: m = self.lib.ps6000aSetChannelOn(c_int16(self.handle), c_enum(chNum), c_enum(coupling), @@ -467,7 +468,7 @@ def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, c_int16(threshold_adc), c_enum(direction), c_uint64(delay), - c_uint32(timeout_ms)) + c_uint32(timeout_ms * 1000)) self.checkResult(m) # Start / stop measurement @@ -526,7 +527,8 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, PICO_ACTION values can be ORed together to allow clearing and adding in one call. """ - # TODO understand SetDataBuffer with action and dataType + if downSampleMode == 0: + downSampleMode = self.RATIO_MODE['raw'] dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) @@ -540,17 +542,32 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, self.ACTIONS['add']) self.checkResult(m) - def _lowLevelClearDataBuffer(self, channel, segmentIndex): + def _lowLevelClearDataBuffer(self, channel, segmentIndex, downSampleMode=0): + """Clear the buffer for the chosen channel, segment, downSampleMode.""" + if downSampleMode == 0: + downSampleMode = self.RATIO_MODE['raw'] m = self.lib.ps6000aSetDataBuffer(c_int16(self.handle), c_enum(channel), c_void_p(), c_int32(0), self.DATA_TYPES['int16'], c_uint64(segmentIndex), - c_enum(0), + c_enum(downSampleMode), self.ACTIONS['clear_this']) self.checkResult(m) + def _lowLevelClearDataBufferAll(self, channel, segmentIndex): + """Clear all the stored buffers.""" + m = self.lib.ps6000aSetDataBuffer(c_int16(self.handle), + c_enum(channel), + c_void_p(), + c_int32(0), + self.DATA_TYPES['int16'], + c_uint64(segmentIndex), + c_enum(0), + self.ACTIONS['clear_all']) + self.checkResult(m) + def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, downSampleMode): """Just calls setDataBuffer with argument order changed. @@ -563,6 +580,8 @@ def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, # Acquire data. def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex): + if downSampleMode == 0: + downSampleMode = self.RATIO_MODE['raw'] numSamplesReturned = c_uint64() numSamplesReturned.value = numSamples overflow = c_int16() @@ -578,6 +597,8 @@ def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, def _lowLevelGetValuesAsync(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex, callback, pPar): + if downSampleMode == 0: + downSampleMode = self.RATIO_MODE['raw'] self._c_getValues_callback = dataReady(callback) m = self.lib.ps6000aGetValuesAsync(c_int16(self.handle), c_uint64(startIndex), @@ -589,6 +610,10 @@ def _lowLevelGetValuesAsync(self, numSamples, startIndex, downSampleRatio, c_void_p()) self.checkResult(m) + ########################### + # TODO test functions below + ########################### + # Misc def _lowLevelGetTriggerTimeOffset(self, segmentIndex): time = c_int64() @@ -681,7 +706,9 @@ def _lowLevelTriggerWithinPreTriggerSamples(self): # Data acquisition def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, - downSampleRatioMode): + downSampleMode): + if downSampleMode == 0: + downSampleMode = self.RATIO_MODE['raw'] raise NotImplementedError() bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) @@ -691,7 +718,7 @@ def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, c_enum(channel), bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth), - c_enum(downSampleRatioMode)) + c_enum(downSampleMode)) self.checkResult(m) def _lowLevelClearDataBuffers(self, channel): @@ -707,20 +734,23 @@ def _lowLevelClearDataBuffers(self, channel): # we would have to make sure that it is contiguous amonts other things def _lowLevelGetValuesBulk(self, numSamples, fromSegmentIndex, toSegmentIndex, - downSampleRatio, downSampleRatioMode, + downSampleRatio, downSampleMode, overflow): - raise NotImplementedError() - noOfSamples = c_uint32(numSamples) - + if downSampleMode == 0: + downSampleMode = self.RATIO_MODE['raw'] + # TODO this method works. + overflowPoint = overflow.ctypes.data_as(POINTER(c_int16)) m = self.lib.ps6000aGetValuesBulk( c_int16(self.handle), - byref(noOfSamples), - c_uint32(fromSegmentIndex), c_uint32(toSegmentIndex), - c_uint32(downSampleRatio), c_enum(downSampleRatioMode), - overflow.ctypes.data_as(POINTER(c_int16)) - ) + c_uint64(0), # startIndex + byref(c_int64(numSamples)), + c_int64(fromSegmentIndex), + c_int64(toSegmentIndex), + c_int64(downSampleRatio), + c_enum(downSampleMode), + overflowPoint + ) self.checkResult(m) - return noOfSamples.value def _lowLevelSetNoOfCaptures(self, nCaptures): m = self.lib.ps6000aSetNoOfCaptures(c_int16(self.handle), From fc8b42dcd3218f48e620e313d824f5e93eac032f Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Tue, 20 Sep 2022 10:09:42 +0200 Subject: [PATCH 10/18] Tests expanded to use different downsampling methods. --- examples/test_ps6000a.py | 44 +++++++++++++++++++++++++++++++++++++++- picoscope/ps6000a.py | 16 +++++++-------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py index b886652..77ebe16 100644 --- a/examples/test_ps6000a.py +++ b/examples/test_ps6000a.py @@ -50,7 +50,7 @@ def test_timebase(ps): (800e-12, 2), (3.2e-9, 4), (6.4e-9, 5), - #(12.8-9, 6), + (12.8e-9, 6), (19.2e-9, 7), (3.84e-8, 10), (6.144e-7, 100)) @@ -132,6 +132,7 @@ def data_ready(self, handle, status, noOfSamples, overflow, pParameter=None): plt.plot(self.data) plt.title("async") plt.show() + self.ps._lowLevelClearDataBuffer(self.config[0], 0, downSampleMode=0x80000000) self.print("Data reading asynchronously test passed.") else: self.print(f"Data receiving error {status}.") @@ -155,6 +156,7 @@ def test_read_async(self, handle=None, status=0, pParameter=None): self.data = np.zeros(ps.noSamples, dtype=np.int16) if not isinstance(channel, int): channel = ps.CHANNELS[channel] + self.config = channel, numSamples ps._lowLevelClearDataBufferAll(channel, 0) ps._lowLevelSetDataBuffer(channel, self.data, downSampleMode=0x80000000, segmentIndex=0) ps._lowLevelGetValuesAsync(ps.noSamples, 0, 1, 0x80000000, 0, self.data_ready, None) @@ -186,6 +188,45 @@ def test_runBlock_async(ps, channel="A", sample_interval=100e-9, print("Run block finished.") +def test_downsampling(ps, + sample_interval=100e-9, # 100 ns + sample_duration=2e-3, # 1 ms + ): + """Test for different downsampling methods.""" + ps._lowLevelClearDataBufferAll() + ps.setChannel(channel="A", coupling="DC", VRange=1) + ps.setChannel(channel="B", enabled=False) + ps.setChannel(channel="C", enabled=False) + ps.setChannel(channel="D", enabled=False) + + ps.setResolution('12') + ps.memorySegments(1) + ps.setNoOfCaptures(1) + interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, sample_duration) + ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) + + ps.runBlock() + ps.waitReady() + + data0 = np.zeros(ps.noSamples, dtype=np.int16) + data1 = np.zeros(ps.noSamples, dtype=np.int16) + data2 = np.zeros(ps.noSamples, dtype=np.int16) + + # downSampleMode raw (no downsampling) is 0x80000000. 0 is invalid! + ps.getDataRaw(data=data0, downSampleMode=0x80000000) + ps.getDataRaw(data=data1, downSampleMode=ps.RATIO_MODE['decimate'], downSampleRatio=10) + ps.getDataRaw(data=data2, downSampleMode=ps.RATIO_MODE['average'], downSampleRatio=10) + + samplesReduced = len(data0) // 10 + plt.plot(data0, label="raw") + plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], data1[:samplesReduced], label="decimate") + plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], data2[:samplesReduced], label="average") + plt.title("downsampling") + plt.legend() + plt.show() + print("Downsampling test passed.") + + if __name__ == "__main__": """Run all the tests.""" # Initialize the picoscope @@ -197,6 +238,7 @@ def test_runBlock_async(ps, channel="A", sample_interval=100e-9, test_timebase(ps) test_deviceResolution(ps) test_rapid_block_mode(ps) + test_downsampling(ps) test_runBlock_async(ps) finally: # Close the connection diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 4a3d84d..8c7eed0 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -111,7 +111,11 @@ def updateFirmwareProgress(function): class PS6000a(_PicoscopeBase): - """The following are low-level functions for the ps6000A.""" + """The following are low-level functions for the ps6000A. + + Due to the new nature of the setDataBuffer method with actions, you have to + clear all configured buffers before using a different readout method. + """ LIBNAME = "ps6000a" @@ -125,7 +129,6 @@ class PS6000a(_PicoscopeBase): CHANNEL_COUPLINGS = {"DC50": 2, "DC": 1, "AC": 0} ACTIONS = { # PICO_ACTION they can be combined with bitwise OR. - # TODO decide on names 'clear_all': 0x00000001, # PICO_CLEAR_ALL 'add': 0x00000002, # PICO_ADD 'clear_this': 0x00001000, # PICO_CLEAR_THIS_DATA_BUFFER @@ -202,10 +205,6 @@ class PS6000a(_PicoscopeBase): AWGDACInterval = 5E-9 # in seconds AWGDACFrequency = 1 / AWGDACInterval - # Note this is NOT what is written in the Programming guide as of version - # 10_5_0_28 - # This issue was acknowledged in this thread - # http://www.picotech.com/support/topic13217.html AWGMaxVal = 0x0FFF AWGMinVal = 0x0000 @@ -434,7 +433,6 @@ def _lowLevelGetAdcLimits(self, resolution): This function gets the maximum and minimum sample values that the ADC can produce at a given resolution. """ - # TODO verify if type(resolution) is str: resolution = self.ADC_RESOLUTIONS[resolution] minimum = c_int16() @@ -556,8 +554,8 @@ def _lowLevelClearDataBuffer(self, channel, segmentIndex, downSampleMode=0): self.ACTIONS['clear_this']) self.checkResult(m) - def _lowLevelClearDataBufferAll(self, channel, segmentIndex): - """Clear all the stored buffers.""" + def _lowLevelClearDataBufferAll(self, channel=1, segmentIndex=0): + """Clear all the stored buffers for all channels.""" m = self.lib.ps6000aSetDataBuffer(c_int16(self.handle), c_enum(channel), c_void_p(), From 3c410c85756d797b46a5ec3079f61b1ddf6cfebe Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Thu, 19 Jan 2023 18:54:06 +0100 Subject: [PATCH 11/18] Values adjusted according to headers.h --- picoscope/ps6000a.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 8c7eed0..dd4f6d8 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -124,7 +124,7 @@ class PS6000a(_PicoscopeBase): NUM_CHANNELS = 4 CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, - "External": 4, "MaxChannels": 4, "TriggerAux": 5} + "External": 1000, "MaxChannels": 4, "TriggerAux": 1001} CHANNEL_COUPLINGS = {"DC50": 2, "DC": 1, "AC": 0} @@ -184,7 +184,7 @@ class PS6000a(_PicoscopeBase): # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) EXT_MAX_VALUE = 32767 EXT_MIN_VALUE = -32767 - EXT_RANGE_VOLTS = 1 + EXT_RANGE_VOLTS = 5 WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, "RampUp": 3, "RampDown": 4, From 90e4fcbe2ab3802fc4d5ca93b118d1fc33f1fe68 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Fri, 20 Jan 2023 08:28:09 +0100 Subject: [PATCH 12/18] Lines made shorter for flake8. --- picoscope/ps6000a.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 8c7eed0..337ec17 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -58,8 +58,9 @@ # use the values specified in the h file # float is always defined as 32 bits # double is defined as 64 bits -from ctypes import byref, POINTER, create_string_buffer, c_float, c_int8, c_double,\ - c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64, c_void_p, CFUNCTYPE +from ctypes import byref, POINTER, create_string_buffer, c_float, c_int8,\ + c_double, c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64,\ + c_void_p, CFUNCTYPE from ctypes import c_int32 as c_enum from picoscope.picobase import _PicoscopeBase @@ -174,7 +175,8 @@ class PS6000a(_PicoscopeBase): RATIO_MODE = {"aggregate": 1, # max and min of every n data. "decimate": 2, # Take every n data. "average": 4, # Average of every n data. - "trigger": 0x40000000, # 20 samples either side of the trigger. This cannot be combined with any other ratio mode + "trigger": 0x40000000, # 20 samples either side of the + # trigger. This cannot be combined with any other ratio mode "raw": 0x80000000, # No downsampling "none": 0x80000000, # for compatibility } @@ -540,7 +542,8 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, self.ACTIONS['add']) self.checkResult(m) - def _lowLevelClearDataBuffer(self, channel, segmentIndex, downSampleMode=0): + def _lowLevelClearDataBuffer(self, channel, segmentIndex, + downSampleMode=0): """Clear the buffer for the chosen channel, segment, downSampleMode.""" if downSampleMode == 0: downSampleMode = self.RATIO_MODE['raw'] From 8d0eaedf5ec7e40ea495fee0322cdc5b00ab6050 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Fri, 20 Jan 2023 09:43:31 +0100 Subject: [PATCH 13/18] Code cleaned up. --- picoscope/ps6000a.py | 102 ++++++++++--------------------------------- 1 file changed, 22 insertions(+), 80 deletions(-) diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 1deaa3d..827c6f1 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -114,6 +114,9 @@ def updateFirmwareProgress(function): class PS6000a(_PicoscopeBase): """The following are low-level functions for the ps6000A. + The 'TriggerAux' channel (trigger input at backside) works with + setSimpleTrigger. + Due to the new nature of the setDataBuffer method with actions, you have to clear all configured buffers before using a different readout method. """ @@ -181,13 +184,11 @@ class PS6000a(_PicoscopeBase): "none": 0x80000000, # for compatibility } - # TODO verify values below - - # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) EXT_MAX_VALUE = 32767 EXT_MIN_VALUE = -32767 EXT_RANGE_VOLTS = 5 + # TODO verify AWG values WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, "RampUp": 3, "RampDown": 4, "Sinc": 5, "Gaussian": 6, "HalfSine": 7, "DCVoltage": 8, @@ -596,6 +597,24 @@ def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, self.checkResult(m) return (numSamplesReturned.value, overflow.value) + def _lowLevelGetValuesBulk(self, numSamples, fromSegmentIndex, + toSegmentIndex, downSampleRatio, downSampleMode, + overflow): + if downSampleMode == 0: + downSampleMode = self.RATIO_MODE['raw'] + overflowPoint = overflow.ctypes.data_as(POINTER(c_int16)) + m = self.lib.ps6000aGetValuesBulk( + c_int16(self.handle), + c_uint64(0), # startIndex + byref(c_int64(numSamples)), + c_int64(fromSegmentIndex), + c_int64(toSegmentIndex), + c_int64(downSampleRatio), + c_enum(downSampleMode), + overflowPoint + ) + self.checkResult(m) + def _lowLevelGetValuesAsync(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex, callback, pPar): if downSampleMode == 0: @@ -733,26 +752,6 @@ def _lowLevelClearDataBuffers(self, channel): # These would be nice, but the user would have to provide us # with an array. # we would have to make sure that it is contiguous amonts other things - def _lowLevelGetValuesBulk(self, - numSamples, fromSegmentIndex, toSegmentIndex, - downSampleRatio, downSampleMode, - overflow): - if downSampleMode == 0: - downSampleMode = self.RATIO_MODE['raw'] - # TODO this method works. - overflowPoint = overflow.ctypes.data_as(POINTER(c_int16)) - m = self.lib.ps6000aGetValuesBulk( - c_int16(self.handle), - c_uint64(0), # startIndex - byref(c_int64(numSamples)), - c_int64(fromSegmentIndex), - c_int64(toSegmentIndex), - c_int64(downSampleRatio), - c_enum(downSampleMode), - overflowPoint - ) - self.checkResult(m) - def _lowLevelSetNoOfCaptures(self, nCaptures): m = self.lib.ps6000aSetNoOfCaptures(c_int16(self.handle), c_uint32(nCaptures)) @@ -899,60 +898,3 @@ def _lowLevelSigGenWaveform(self): def _lowLevelSigGenWaveformDutyCycle(self): raise NotImplementedError() - - ###################################################### - # TODO methods below are not in the programmer's guide - ###################################################### - def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, - offsetVoltage, pkToPk, indexMode, - shots, triggerType, triggerSource): - """Waveform should be an array of shorts.""" - # TODO called by picobase, add a combination of low level functions. - raise NotImplementedError() - waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) - - m = self.lib.ps6000aSetSigGenArbitrary( - c_int16(self.handle), - c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts - c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts - c_uint32(int(deltaPhase)), # startDeltaPhase - c_uint32(int(deltaPhase)), # stopDeltaPhase - c_uint32(0), # deltaPhaseIncrement - c_uint32(0), # dwellCount - waveformPtr, # arbitraryWaveform - c_int32(len(waveform)), # arbitraryWaveformSize - c_enum(0), # sweepType for deltaPhase - c_enum(0), # operation (adding random noise and whatnot) - c_enum(indexMode), # single, dual, quad - c_uint32(shots), - c_uint32(0), # sweeps - c_uint32(triggerType), - c_uint32(triggerSource), - c_int16(0)) # extInThreshold - self.checkResult(m) - - def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, - frequency, shots, triggerType, - triggerSource, stopFreq, increment, - dwellTime, sweepType, numSweeps): - # TODO called by picobase, add a combination of low level functions. - - # TODO, I just noticed that V2 exists - # Maybe change to V2 in the future - raise NotImplementedError() - - if stopFreq is None: - stopFreq = frequency - - m = self.lib.ps6000aSetSigGenBuiltIn( - c_int16(self.handle), - c_int32(int(offsetVoltage * 1000000)), - c_int32(int(pkToPk * 1000000)), - c_int16(waveType), - c_float(frequency), c_float(stopFreq), - c_float(increment), c_float(dwellTime), - c_enum(sweepType), c_enum(0), - c_uint32(shots), c_uint32(numSweeps), - c_enum(triggerType), c_enum(triggerSource), - c_int16(0)) - self.checkResult(m) From 904ae5fae9018c02060dc469272627aace4d7743 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Fri, 20 Jan 2023 09:44:23 +0100 Subject: [PATCH 14/18] Check Channel Combinations added. --- picoscope/error_codes.py | 5 +++- picoscope/ps6000a.py | 56 +++++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/picoscope/error_codes.py b/picoscope/error_codes.py index 1341dd2..126116b 100644 --- a/picoscope/error_codes.py +++ b/picoscope/error_codes.py @@ -368,7 +368,10 @@ [0x6001, "PICO_HARDWARE_READY_TIMEOUT", "waiting for the device be ready for capture timed out"], [0x6002, "PICO_HARDWARE_CAPTURING_CALL_STOP", - "the driver is performing a capture requested by RunStreaming or RunBlock to interrupt this capture call Stop on the device first"], + ("the driver is performing a capture requested by RunStreaming or " + "RunBlock to interrupt this capture call Stop on the device first")], + [0x2000001, "PICO_TRIGGER_TIME_NOT_REQUESTED", + "Requesting the TriggerTimeOffset, the trigger time has not been set."] [0x1000000, "PICO_DEVICE_TIME_STAMP_RESET", ""], [0x10000000, "PICO_WATCHDOGTIMER", ""], [0x10000001, "PICO_IPP_NOT_FOUND", ""], diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 827c6f1..a3a387f 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -630,19 +630,15 @@ def _lowLevelGetValuesAsync(self, numSamples, startIndex, downSampleRatio, c_void_p()) self.checkResult(m) - ########################### - # TODO test functions below - ########################### - # Misc def _lowLevelGetTriggerTimeOffset(self, segmentIndex): time = c_int64() timeUnits = c_enum() - m = self.lib.ps6000aGetTriggerTimeOffset64(c_int16(self.handle), - byref(time), - byref(timeUnits), - c_uint64(segmentIndex)) + m = self.lib.ps6000aGetTriggerTimeOffset(c_int16(self.handle), + byref(time), + byref(timeUnits), + c_uint64(segmentIndex)) self.checkResult(m) try: @@ -650,6 +646,10 @@ def _lowLevelGetTriggerTimeOffset(self, segmentIndex): except KeyError: raise TypeError("Unknown timeUnits %d" % timeUnits.value) + ########################### + # TODO test functions below + ########################### + "Updates" def _lowLevelCheckForUpdate(self): @@ -665,13 +665,14 @@ def _lowLevelCheckForUpdate(self): required : bool Whether an update is required or not. """ - # TODO what to do with the struct? + # TODO raises PICO_STRING_BUFFER_TOO_SMALL + firmware_info = create_string_buffer(25600000) number = c_int16() required = c_uint16() - m = self.lib.ps6000aCheckForUpdate(c_int16(self.handle), c_void_p(), + m = self.lib.ps6000aCheckForUpdate(c_int16(self.handle), byref(firmware_info), byref(number), byref(required)) self.checkResult(m) - return None, number, required + return firmware_info, number, required def _lowLevelStartFirmwareUpdate(self, function): # Hold a reference to the callback so that the Python @@ -784,14 +785,39 @@ def _lowLevelRunStreaming(): "alphabetically" - def _lowLevelChannelCombinationsStateless(self): + def _lowLevelChannelCombinationsStateless(self, resolution, timebase): """ Return a list of the possible channel combinations given a proposed - configuration (resolution and timebase) of the oscilloscope. It does + configuration (`resolution` and `timebase` number) of the oscilloscope. It does not change the configuration of the oscilloscope. + + Bit values of the different flags in a channel combination: + PICO_CHANNEL_A_FLAGS = 1, + PICO_CHANNEL_B_FLAGS = 2, + PICO_CHANNEL_C_FLAGS = 4, + PICO_CHANNEL_D_FLAGS = 8, + PICO_CHANNEL_E_FLAGS = 16, + PICO_CHANNEL_F_FLAGS = 32, + PICO_CHANNEL_G_FLAGS = 64, + PICO_CHANNEL_H_FLAGS = 128, + PICO_PORT0_FLAGS = 65536, + PICO_PORT1_FLAGS = 131072, + PICO_PORT2_FLAGS = 262144, + PICO_PORT3_FLAGS = 524288, """ - # TODO - raise NotImplementedError() + # TODO raises PICO_CHANNELFLAGSCOMBINATIONS_ARRAY_SIZE_TOO_SMALL + ChannelCombinations = create_string_buffer(b"", 100000) + nChannelCombinations = c_uint32() + if isinstance(resolution, str): + resolution = self.ADC_RESOLUTIONS[resolution] + m = self.lib.ps6000aChannelCombinationsStateless(c_int16(self.handle), + ChannelCombinations, + byref(nChannelCombinations), + c_uint32(resolution), + c_uint32(timebase), + ) + self.checkResult(m) + return ChannelCombinations def _lowLevelGetAnalogueOffsetLimits(self, range, coupling): raise NotImplementedError() From 617e9748d63d5d1c0241448f3ff7fea8bfccad8f Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Fri, 20 Jan 2023 10:49:02 +0100 Subject: [PATCH 15/18] Linelengths changed for flake. --- examples/freqmeasure.py | 2 +- examples/specgram_plot.py | 2 +- examples/test_ps6000a.py | 71 +++++++++++++++++++++++---------------- picoscope/error_codes.py | 7 ++-- picoscope/ps6000a.py | 9 ++--- 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/examples/freqmeasure.py b/examples/freqmeasure.py index b101c85..0f66ffa 100644 --- a/examples/freqmeasure.py +++ b/examples/freqmeasure.py @@ -50,7 +50,7 @@ def freq_from_crossings(self, sig): def measure(self): print("Waiting for trigger") - while(self.ps.isReady() is False): + while not self.ps.isReady(): time.sleep(0.01) print("Sampling Done") data = self.ps.getDataV("A", 50000) diff --git a/examples/specgram_plot.py b/examples/specgram_plot.py index 11aa7da..7f0f713 100644 --- a/examples/specgram_plot.py +++ b/examples/specgram_plot.py @@ -41,7 +41,7 @@ def examplePS6000(): for i in range(0, 50): ps.runBlock() - while(ps.isReady() is False): + while not ps.isReady(): time.sleep(0.01) print("Sampling Done") diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py index 77ebe16..a0612ff 100644 --- a/examples/test_ps6000a.py +++ b/examples/test_ps6000a.py @@ -43,26 +43,27 @@ def test_timebase(ps): (6.4e-9, 5), (10e-9, 5), (15e-9, 6)) - for (time, timebase) in data: - text = f"Time {time} does not give timebase {timebase}" - assert ps.getTimeBaseNum(time) == timebase, text - data = (#(200e-12, 0), - (800e-12, 2), - (3.2e-9, 4), - (6.4e-9, 5), - (12.8e-9, 6), - (19.2e-9, 7), - (3.84e-8, 10), - (6.144e-7, 100)) - for (time, timebase) in data: - text = f"{time} s does not fit timebase {timebase}." - assert ps.getTimestepFromTimebase(timebase) == time, "Timestep: " + text + for (t, timebase) in data: + text = f"Time {t} does not give timebase {timebase}" + assert ps.getTimeBaseNum(t) == timebase, text + data = ( + (800e-12, 2), + (3.2e-9, 4), + (6.4e-9, 5), + (12.8e-9, 6), + (19.2e-9, 7), + (3.84e-8, 10), + (6.144e-7, 100), + ) + for (t, timebase) in data: + text = f"{t} s does not fit timebase {timebase}." + assert ps.getTimestepFromTimebase(timebase) == t, "Timestep: " + text try: timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) except Exception: - print(f"getTimebase failed at time {time}, timebase {timebase}.") + print(f"getTimebase failed at time {t}, timebase {timebase}.") raise - assert timestep == time, f"LowLevel: {timestep} != {time}" + assert timestep == t, f"LowLevel: {timestep} != {t}" print("Timebase test passed.") @@ -125,14 +126,17 @@ class Handler: def print(self, text): print(text) - def data_ready(self, handle, status, noOfSamples, overflow, pParameter=None): + def data_ready(self, handle, status, noOfSamples, overflow, + pParameter=None): """Show the asynchronously received data.""" if status == 0: - self.print(f"{noOfSamples} samples received with overflow: {overflow}") + self.print( + f"{noOfSamples} samples received with overflow: {overflow}") plt.plot(self.data) plt.title("async") plt.show() - self.ps._lowLevelClearDataBuffer(self.config[0], 0, downSampleMode=0x80000000) + self.ps._lowLevelClearDataBuffer(self.config[0], 0, + downSampleMode=0x80000000) self.print("Data reading asynchronously test passed.") else: self.print(f"Data receiving error {status}.") @@ -141,8 +145,8 @@ def test_read_async(self, handle=None, status=0, pParameter=None): """ Test reading data asynchronously. - If you call it manually instead of using it as a callback, use pParameter - for handing over the picoscope instance. + If you call it manually instead of using it as a callback, use + pParameter for handing over the picoscope instance. """ if pParameter is not None: ps = pParameter @@ -158,8 +162,11 @@ def test_read_async(self, handle=None, status=0, pParameter=None): channel = ps.CHANNELS[channel] self.config = channel, numSamples ps._lowLevelClearDataBufferAll(channel, 0) - ps._lowLevelSetDataBuffer(channel, self.data, downSampleMode=0x80000000, segmentIndex=0) - ps._lowLevelGetValuesAsync(ps.noSamples, 0, 1, 0x80000000, 0, self.data_ready, None) + ps._lowLevelSetDataBuffer(channel, self.data, + downSampleMode=0x80000000, + segmentIndex=0) + ps._lowLevelGetValuesAsync(ps.noSamples, 0, 1, 0x80000000, 0, + self.data_ready, None) self.print("Get values async started.") else: self.print("Data is not ready. RunBlock had an error.") @@ -177,7 +184,8 @@ def test_runBlock_async(ps, channel="A", sample_interval=100e-9, ps.memorySegments(1) ps.setNoOfCaptures(1) ps.setResolution('12') - interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, sample_duration) + interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, + sample_duration) ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) handler.config = channel, samples @@ -202,7 +210,8 @@ def test_downsampling(ps, ps.setResolution('12') ps.memorySegments(1) ps.setNoOfCaptures(1) - interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, sample_duration) + interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, + sample_duration) ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) ps.runBlock() @@ -214,13 +223,17 @@ def test_downsampling(ps, # downSampleMode raw (no downsampling) is 0x80000000. 0 is invalid! ps.getDataRaw(data=data0, downSampleMode=0x80000000) - ps.getDataRaw(data=data1, downSampleMode=ps.RATIO_MODE['decimate'], downSampleRatio=10) - ps.getDataRaw(data=data2, downSampleMode=ps.RATIO_MODE['average'], downSampleRatio=10) + ps.getDataRaw(data=data1, downSampleMode=ps.RATIO_MODE['decimate'], + downSampleRatio=10) + ps.getDataRaw(data=data2, downSampleMode=ps.RATIO_MODE['average'], + downSampleRatio=10) samplesReduced = len(data0) // 10 plt.plot(data0, label="raw") - plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], data1[:samplesReduced], label="decimate") - plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], data2[:samplesReduced], label="average") + plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], + data1[:samplesReduced], label="decimate") + plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], + data2[:samplesReduced], label="average") plt.title("downsampling") plt.legend() plt.show() diff --git a/picoscope/error_codes.py b/picoscope/error_codes.py index 126116b..4c651d9 100644 --- a/picoscope/error_codes.py +++ b/picoscope/error_codes.py @@ -343,8 +343,11 @@ [0x199, "PICO_CLEAR_DATA_BUFFER_INVALID", ""], [0x19A, "PICO_INVALID_ACTION_FLAGS_COMBINATION", ""], [0x19B, "PICO_PICO_MOTH_MIN_AND_MAX_NULL_BUFFERS_CANNOT_BE_ADDED", ""], - [0x19C, "PICO_CONFLICT_IN_SET_DATA_BUFFERS_CALL_REMOVE_DATA_BUFFER_TO_RESET", ""], - [0x19D, "PICO_REMOVING_DATA_BUFFER_ENTRIES_NOT_ALLOWED_WHILE_DATA_PROCESSING", ""], + [0x19C, + "PICO_CONFLICT_IN_SET_DATA_BUFFERS_CALL_REMOVE_DATA_BUFFER_TO_RESET", ""], + [0x19D, + "PICO_REMOVING_DATA_BUFFER_ENTRIES_NOT_ALLOWED_WHILE_DATA_PROCESSING", + ""], [0x200, "PICO_CYUSB_REQUEST_FAILED", ""], [0x201, "PICO_STREAMING_DATA_REQUIRED", ""], [0x202, "PICO_INVALID_NUMBER_OF_SAMPLES", ""], diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index a3a387f..25ef438 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -669,7 +669,8 @@ def _lowLevelCheckForUpdate(self): firmware_info = create_string_buffer(25600000) number = c_int16() required = c_uint16() - m = self.lib.ps6000aCheckForUpdate(c_int16(self.handle), byref(firmware_info), + m = self.lib.ps6000aCheckForUpdate(c_int16(self.handle), + byref(firmware_info), byref(number), byref(required)) self.checkResult(m) return firmware_info, number, required @@ -788,8 +789,8 @@ def _lowLevelRunStreaming(): def _lowLevelChannelCombinationsStateless(self, resolution, timebase): """ Return a list of the possible channel combinations given a proposed - configuration (`resolution` and `timebase` number) of the oscilloscope. It does - not change the configuration of the oscilloscope. + configuration (`resolution` and `timebase` number) of the oscilloscope. + It does not change the configuration of the oscilloscope. Bit values of the different flags in a channel combination: PICO_CHANNEL_A_FLAGS = 1, @@ -812,7 +813,7 @@ def _lowLevelChannelCombinationsStateless(self, resolution, timebase): resolution = self.ADC_RESOLUTIONS[resolution] m = self.lib.ps6000aChannelCombinationsStateless(c_int16(self.handle), ChannelCombinations, - byref(nChannelCombinations), + nChannelCombinations, c_uint32(resolution), c_uint32(timebase), ) From 07062d7b555e10ff99747fe6c12ba8fd6ef11799 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Fri, 20 Jan 2023 11:15:28 +0100 Subject: [PATCH 16/18] Make f string python 2 compatible. --- examples/test_ps6000a.py | 16 +++++++++------- picoscope/ps6000a.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py index a0612ff..77dc122 100644 --- a/examples/test_ps6000a.py +++ b/examples/test_ps6000a.py @@ -44,7 +44,7 @@ def test_timebase(ps): (10e-9, 5), (15e-9, 6)) for (t, timebase) in data: - text = f"Time {t} does not give timebase {timebase}" + text = "Time {} does not give timebase {}".format(t, timebase) assert ps.getTimeBaseNum(t) == timebase, text data = ( (800e-12, 2), @@ -56,14 +56,16 @@ def test_timebase(ps): (6.144e-7, 100), ) for (t, timebase) in data: - text = f"{t} s does not fit timebase {timebase}." + text = "{} s does not fit timebase {}.".format(t, timebase) assert ps.getTimestepFromTimebase(timebase) == t, "Timestep: " + text try: timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) except Exception: - print(f"getTimebase failed at time {t}, timebase {timebase}.") + print( + "getTimebase failed at time {}, timebase {}.".format(t, + timebase)) raise - assert timestep == t, f"LowLevel: {timestep} != {t}" + assert timestep == t, "LowLevel: {} != {}".format(timestep, t) print("Timebase test passed.") @@ -130,8 +132,8 @@ def data_ready(self, handle, status, noOfSamples, overflow, pParameter=None): """Show the asynchronously received data.""" if status == 0: - self.print( - f"{noOfSamples} samples received with overflow: {overflow}") + self.print("{} samples received with overflow: {}".format( + noOfSamples, overflow)) plt.plot(self.data) plt.title("async") plt.show() @@ -139,7 +141,7 @@ def data_ready(self, handle, status, noOfSamples, overflow, downSampleMode=0x80000000) self.print("Data reading asynchronously test passed.") else: - self.print(f"Data receiving error {status}.") + self.print("Data receiving error {}.".format(status)) def test_read_async(self, handle=None, status=0, pParameter=None): """ diff --git a/picoscope/ps6000a.py b/picoscope/ps6000a.py index 25ef438..71bf0b6 100644 --- a/picoscope/ps6000a.py +++ b/picoscope/ps6000a.py @@ -413,7 +413,7 @@ def _lowLevelGetDeviceResolution(self): for key, value in self.ADC_RESOLUTIONS.items(): if value == self.resolution: return key - raise TypeError(f"Unknown resolution {resolution}.") + raise TypeError("Unknown resolution {}.".format(resolution)) def _lowLevelSetDeviceResolution(self, resolution): """ From 205b11c902490d81fb9b252274c712a7c1be1603 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Fri, 20 Jan 2023 12:46:38 +0100 Subject: [PATCH 17/18] Comma forgotten. --- picoscope/error_codes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picoscope/error_codes.py b/picoscope/error_codes.py index 4c651d9..4610384 100644 --- a/picoscope/error_codes.py +++ b/picoscope/error_codes.py @@ -374,7 +374,7 @@ ("the driver is performing a capture requested by RunStreaming or " "RunBlock to interrupt this capture call Stop on the device first")], [0x2000001, "PICO_TRIGGER_TIME_NOT_REQUESTED", - "Requesting the TriggerTimeOffset, the trigger time has not been set."] + "Requesting the TriggerTimeOffset, the trigger time has not been set."], [0x1000000, "PICO_DEVICE_TIME_STAMP_RESET", ""], [0x10000000, "PICO_WATCHDOGTIMER", ""], [0x10000001, "PICO_IPP_NOT_FOUND", ""], From 22a44066cd3e651a35957d36a7dff09533bce114 Mon Sep 17 00:00:00 2001 From: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com> Date: Fri, 20 Jan 2023 13:15:18 +0100 Subject: [PATCH 18/18] More python2 problems. --- examples/test_ps6000a.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/test_ps6000a.py b/examples/test_ps6000a.py index 77dc122..e13d606 100644 --- a/examples/test_ps6000a.py +++ b/examples/test_ps6000a.py @@ -125,23 +125,23 @@ def test_rapid_block_mode(ps, class Handler: - def print(self, text): + def printing(self, text): print(text) def data_ready(self, handle, status, noOfSamples, overflow, pParameter=None): """Show the asynchronously received data.""" if status == 0: - self.print("{} samples received with overflow: {}".format( + self.printing("{} samples received with overflow: {}".format( noOfSamples, overflow)) plt.plot(self.data) plt.title("async") plt.show() self.ps._lowLevelClearDataBuffer(self.config[0], 0, downSampleMode=0x80000000) - self.print("Data reading asynchronously test passed.") + self.printing("Data reading asynchronously test passed.") else: - self.print("Data receiving error {}.".format(status)) + self.printing("Data receiving error {}.".format(status)) def test_read_async(self, handle=None, status=0, pParameter=None): """ @@ -155,7 +155,7 @@ def test_read_async(self, handle=None, status=0, pParameter=None): else: ps = self.ps if status == 0: - self.print("Block is ready and can be read.") + self.printing("Block is ready and can be read.") # config is a global variable written by the caller. channel, numSamples = self.config # Define data for data_ready. @@ -169,9 +169,9 @@ def test_read_async(self, handle=None, status=0, pParameter=None): segmentIndex=0) ps._lowLevelGetValuesAsync(ps.noSamples, 0, 1, 0x80000000, 0, self.data_ready, None) - self.print("Get values async started.") + self.printing("Get values async started.") else: - self.print("Data is not ready. RunBlock had an error.") + self.printing("Data is not ready. RunBlock had an error.") def test_runBlock_async(ps, channel="A", sample_interval=100e-9,