def __init__(self, serialNumber=None, connect=True): """Load DLLs.""" self.handle = None 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 self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) super(PS4000, self).__init__(serialNumber, connect) # check to see which model we have and use special functions if needed unit_number = self.getUnitInfo('VariantInfo') if unit_number == '4262': self.getTimestepFromTimebase = self._getTimestepFromTimebase4262 self.getTimeBaseNum = self._getTimeBaseNum4262 elif unit_number in {'4223', '4224', '4423', '4424'}: self.getTimestepFromTimebase = self._getTimestepFromTimebase4223 self.getTimeBaseNum = self._getTimeBaseNum4223 else: raise NotImplementedError('Timebase functions have not been ' 'written for the ' + unit_number)
def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") elif platform.system() == 'Darwin': from picoscope.darwin_utils import LoadLibraryDarwin self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") else: from ctypes import windll self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) super(PS3000, self).__init__(serialNumber, connect)
def __init__(self, serialNumber=None, connect=True): """Load DLLs.""" 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 self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) super(PS6000, self).__init__(serialNumber, connect)
def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") 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")) ) self.resolution = self.ADC_RESOLUTIONS["8"] super(PS5000a, self).__init__(serialNumber, connect)
def __init__(self, serialNumber=None, connect=True): """Load DLLs.""" self.handle = None 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"))) self.resolution = self.ADC_RESOLUTIONS["12"] super(PS4000a, self).__init__(serialNumber, connect)
class PS4000a(_PicoscopeBase): """The following are low-level functions for the PS4000A.""" LIBNAME = "ps4000a" MAX_VALUE = 32764 MIN_VALUE = -32764 # EXT/AUX seems to have an input impedance 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 = 8 CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7, "MaxChannels": 8} ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} 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} TIME_UNITS = {"femtoseconds": 0, "picoseconds": 1, "nanoseconds": 2, "microseconds": 3, "milliseconds": 4, "seconds": 5} def __init__(self, serialNumber=None, connect=True): """Load DLLs.""" self.handle = None 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 self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) self.resolution = self.ADC_RESOLUTIONS["12"] super(PS4000a, self).__init__(serialNumber, connect) def _lowLevelOpenUnit(self, sn): c_handle = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(str(sn)) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps4000aOpenUnit(byref(c_handle), serialNullTermStr) self.checkResult(m) self.handle = c_handle.value self.model = self.getUnitInfo('VariantInfo') def _lowLevelOpenUnitAsync(self, sn): c_status = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(sn) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps4000aOpenUnitAsync(byref(c_status), serialNullTermStr) self.checkResult(m) return c_status.value def _lowLevelOpenUnitProgress(self): complete = c_int16() progressPercent = c_int16() handle = c_int16() m = self.lib.ps4000aOpenUnitProgress(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.ps4000aCloseUnit(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.ps4000aEnumerateUnits(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.ps4000aEnumerateUnits(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): m = self.lib.ps4000aSetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps4000aStop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps4000aGetUnitInfo(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.ps4000aGetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps4000aFlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): m = self.lib.ps4000aSetSimpleTrigger( 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): timeIndisposedMs = c_int32() m = self.lib.ps4000aRunBlock( c_int16(self.handle), c_int32(numPreTrigSamples), c_int32(numPostTrigSamples), c_uint32(timebase), byref(timeIndisposedMs), c_uint32(segmentIndex), c_void_p(), c_void_p()) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps4000aIsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() sampleRate = c_float() m = self.lib.ps4000aGetTimebase2(c_int16(self.handle), c_uint32(tb), c_int32(noSamples), byref(sampleRate), byref(maxSamples), c_uint32(segmentIndex)) self.checkResult(m) return (sampleRate.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """ Convert the sample interval (float of seconds) to the corresponding integer timebase value as defined by the API. See "Timebases" section of the PS4000a programmers guide for more information. """ if self.model == '4828': maxSampleTime = (((2 ** 32 - 1) + 1) / 8E7) if sampleTimeS <= 12.5E-9: timebase = 0 else: # Otherwise in range 2^32-1 if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime timebase = math.floor((sampleTimeS * 2e7) + 1) elif self.model == '4444': maxSampleTime = (((2 ** 32 - 1) - 2) / 5.0E7) if (sampleTimeS <= 2.5E-9 and self.resolution == self.ADC_RESOLUTIONS["12"]): timebase = 0 elif (sampleTimeS <= 20E-9 and self.resolution == self.ADC_RESOLUTIONS["14"]): timebase = 3 else: # Otherwise in range 2^32-1 if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime timebase = math.floor((sampleTimeS * 5.0E7) + 2) else: # The original case from non "A" series warnings.warn("The model PS4000a you are using may not be " "fully supported", stacklevel=2) maxSampleTime = (((2 ** 32 - 1) - 4) / 2e7) if sampleTimeS <= 12.5E-9: timebase = math.floor(math.log(sampleTimeS * 8E7, 2)) timebase = max(timebase, 0) else: # Otherwise in range 2^32-1 if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime timebase = math.floor((sampleTimeS * 2e7) + 1) # is this cast needed? timebase = int(timebase) return timebase def getTimestepFromTimebase(self, timebase): """Return timebase to sampletime as seconds.""" if self.model == '4828': dt = (timebase + 1) / 8.0E7 elif self.model == '4444': if timebase < 3: dt = 2.5 ** timebase / 4.0E8 else: dt = (timebase - 2) / 5.0E7 else: # The original case from non "A" series warnings.warn("The model PS4000a you are using may not be " "fully supported", stacklevel=2) if timebase < 3: dt = 2. ** timebase / 8e7 else: dt = (timebase - 1) / 2e7 return dt return dt 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.ps4000aSetDataBuffer(c_int16(self.handle), c_enum(channel), dataPtr, c_uint32(numSamples), c_uint32(segmentIndex), c_uint32(downSampleMode)) self.checkResult(m) def _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps4000aSetDataBuffer(c_int16(self.handle), c_enum(channel), c_void_p(), c_uint32(0), 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.ps4000aGetValues( c_int16(self.handle), c_uint32(startIndex), byref(numSamplesReturned), c_uint32(downSampleRatio), c_enum(downSampleMode), c_uint16(segmentIndex), byref(overflow)) self.checkResult(m) return (numSamplesReturned.value, overflow.value) def _lowLevelSetDeviceResolution(self, resolution): self.resolution = resolution m = self.lib.ps4000aSetDeviceResolution( c_int16(self.handle), c_enum(resolution)) self.checkResult(m) def _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, downSampleRatio, downSampleMode, overflow): """Copy data from several memory segments at once.""" overflowPoint = overflow.ctypes.data_as(POINTER(c_int16)) m = self.lib.ps4000aGetValuesBulk( c_int16(self.handle), byref(c_int32(numSamples)), c_int32(fromSegment), c_int32(toSegment), c_int32(downSampleRatio), c_enum(downSampleMode), overflowPoint ) 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) #################################################################### # Untested functions below # # # #################################################################### def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, frequency, shots, triggerType, triggerSource, stopFreq, increment, dwellTime, sweepType, numSweeps): if stopFreq is None: stopFreq = frequency m = self.lib.ps4000aSetSigGenBuiltIn( 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): maxDownSampleRatio = c_uint32() m = self.lib.ps4000aGetMaxDownSampleRatio( c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), byref(maxDownSampleRatio), c_enum(downSampleRatioMode), c_uint16(segmentIndex)) self.checkResult(m) return maxDownSampleRatio.value def _lowLevelGetNoOfCaptures(self): nCaptures = c_uint32() m = self.lib.ps4000aGetNoOfCaptures(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.ps4000aGetTriggerTimeOffset64( c_int16(self.handle), byref(time), byref(timeUnits), c_uint16(segmentIndex)) self.checkResult(m) if timeUnits.value == 0: # PS4000a_FS return time.value * 1E-15 elif timeUnits.value == 1: # PS4000a_PS return time.value * 1E-12 elif timeUnits.value == 2: # PS4000a_NS return time.value * 1E-9 elif timeUnits.value == 3: # PS4000a_US return time.value * 1E-6 elif timeUnits.value == 4: # PS4000a_MS return time.value * 1E-3 elif timeUnits.value == 5: # PS4000a_S return time.value * 1E0 else: raise TypeError("Unknown timeUnits %d" % timeUnits.value) def _lowLevelMemorySegments(self, nSegments): nMaxSamples = c_uint32() m = self.lib.ps4000aMemorySegments(c_int16(self.handle), c_uint16(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.ps4000aSetDataBuffers( c_int16(self.handle), c_enum(channel), bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth)) self.checkResult(m) def _lowLevelClearDataBuffers(self, channel): m = self.lib.ps4000aSetDataBuffers( c_int16(self.handle), c_enum(channel), c_void_p(), c_void_p(), c_uint32(0)) self.checkResult(m) def _lowLevelSetNoOfCaptures(self, nCaptures): m = self.lib.ps4000aSetNoOfCaptures( c_int16(self.handle), c_uint16(nCaptures)) self.checkResult(m) # ETS Functions def _lowLevelSetEts(self): pass def _lowLevelSetEtsTimeBuffer(self): pass def _lowLevelSetEtsTimeBuffers(self): pass def _lowLevelSetExternalClock(self): pass # Complicated triggering # need to understand structs for this one to work def _lowLevelIsTriggerOrPulseWidthQualifierEnabled(self): pass def _lowLevelGetValuesTriggerTimeOffsetBulk(self): pass def _lowLevelSetTriggerChannelConditions(self): pass def _lowLevelSetTriggerChannelDirections(self): pass def _lowLevelSetTriggerChannelProperties(self): pass def _lowLevelSetPulseWidthQualifier(self): pass def _lowLevelSetTriggerDelay(self): pass # Async functions # would be nice, but we would have to learn to implement callbacks def _lowLevelGetValuesAsync(self): pass def _lowLevelGetValuesBulkAsync(self): pass # overlapped functions def _lowLevelGetValuesOverlapped(self): pass def _lowLevelGetValuesOverlappedBulk(self): pass # Streaming related functions def _lowLevelGetStreamingLatestValues(self, lpPs4000Ready, pParameter=c_void_p()): m = self.lib.ps4000aGetStreamingLatestValues( c_uint16(self.handle), lpPs4000Ready, pParameter) self.checkResult(m) def _lowLevelNoOfStreamingValues(self): noOfValues = c_uint32() m = self.lib.ps4000aNoOfStreamingValues(c_int16(self.handle), byref(noOfValues)) self.checkResult(m) return noOfValues.value def _lowLevelRunStreaming(self, sampleInterval, sampleIntervalTimeUnits, maxPreTriggerSamples, maxPostTriggerSamples, autoStop, downSampleRatio, downSampleRatioMode, overviewBufferSize): m = self.lib.ps4000aRunStreaming( c_int16(self.handle), byref(c_uint32(sampleInterval)), c_enum(sampleIntervalTimeUnits), c_uint32(maxPreTriggerSamples), c_uint32(maxPostTriggerSamples), c_int16(autoStop), c_uint32(downSampleRatio), c_enum(downSampleRatioMode), c_uint32(overviewBufferSize)) self.checkResult(m) def _lowLevelStreamingReady(self): pass
class PS5000a(_PicoscopeBase): """The following are low-level functions for the PS5000.""" LIBNAME = "ps5000a" NUM_CHANNELS = 4 CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, "External": 4, "MaxChannels": 4, "TriggerAux": 5} ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} CHANNEL_RANGE = [{"rangeV": 10E-3, "apivalue": 0, "rangeStr": "10 mV"}, {"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"}, ] CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} # has_sig_gen = True 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 AWGDACInterval = 5E-9 # in seconds AWGDACFrequency = 1 / AWGDACInterval AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} MAX_VALUE_8BIT = 32512 MIN_VALUE_8BIT = -32512 MAX_VALUE_OTHER = 32767 MIN_VALUE_OTHER = -32767 EXT_RANGE_VOLTS = 5 def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") elif platform.system() == 'Darwin': from picoscope.darwin_utils import LoadLibraryDarwin self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") else: from ctypes import windll self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) self.resolution = self.ADC_RESOLUTIONS["8"] super(PS5000a, self).__init__(serialNumber, connect) def _lowLevelOpenUnit(self, sn): c_handle = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(sn) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps5000aOpenUnit(byref(c_handle), serialNullTermStr, self.resolution) self.handle = c_handle.value # This will check if the power supply is not connected # and change the power supply accordingly # Personally (me = Mark), I don't like this # since the user should address this immediately, and we # shouldn't let this go as a soft error # but I think this should do for now if m == 0x11A: self.changePowerSource(m) else: self.checkResult(m) # B models have different AWG buffer sizes # 5242B, 5442B: 2**14 # 5243B, 5443B: 2**15 # 5444B, 5244B: 3 * 2**14 # Model 5444B identifies itself properly in VariantInfo, I will assume # the others do as well. self.model = self.getUnitInfo('VariantInfo') # print("Checking variant, found: " + str(self.model)) if self.model in ('5244B', '5444B'): self.AWGBufferAddressWidth = math.log(3 * 2**14, 2) self.AWGMaxVal = 32767 self.AWGMinVal = -32768 self.AWGMaxSamples = 49152 elif self.model in ('5243B', '5443B'): self.AWGBufferAddressWidth = 15 self.AWGMaxVal = 32767 self.AWGMinVal = -32768 self.AWGMaxSamples = 2**self.AWGBufferAddressWidth else: # This is what the previous PS5000a used for all scopes. # I am leaving it the same, although I think the AWGMaxVal and # AWGMinVal issue was fixed and should be -32768 to 32767 for all # 5000 models self.AWGBufferAddressWidth = 14 # 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 self.AWGMaxVal = 0x0FFF self.AWGMinVal = 0x0000 self.AWGMaxSamples = 2**self.AWGBufferAddressWidth def _lowLevelCloseUnit(self): m = self.lib.ps5000aCloseUnit(c_int16(self.handle)) self.checkResult(m) def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, bandwidth): m = self.lib.ps5000aSetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset)) self.checkResult(m) # The error this might through are # INVALID_HANDLE # INVALID_CHANNEL # INVALID_BANDWIDTH # Invalid bandwidth is the only case that could go wrong. # The others would be thrown above (assuming no race condition: # i.e. unplugging the scope in between this call. # I decided to keep the logic below to avoid a possible error # picobase/SetChannel should be changed to the following # Set the channel # save the new channel settings # check if ps5000a # change the bandwidth separately # changing the bandwidth would be it's own function (implemented below) if bandwidth: m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), c_enum(chNum), c_enum(1)) else: m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), c_enum(chNum), c_enum(0)) self.checkResult(m) def _lowLevelSetBandwidthFilter(self, channel, bandwidth): m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), c_enum(channel), c_enum(bandwidth)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps5000aStop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps5000aGetUnitInfo(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.ps5000aGetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps5000aFlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, timeout_ms, auto): m = self.lib.ps5000aSetSimpleTrigger( c_int16(self.handle), c_int16(enabled), c_enum(trigsrc), c_int16(threshold_adc), c_enum(direction), c_uint32(timeout_ms), c_int16(auto)) self.checkResult(m) def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, timebase, oversample, segmentIndex): # Oversample is NOT used! timeIndisposedMs = c_int32() m = self.lib.ps5000aRunBlock( c_int16(self.handle), c_uint32(numPreTrigSamples), c_uint32(numPostTrigSamples), c_uint32(timebase), byref(timeIndisposedMs), c_uint32(segmentIndex), c_void_p(), c_void_p()) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps5000aIsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowLevelPingUnit(self): m = self.lib.ps5000aPingUnit(c_int16(self.handle)) return m def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() sampleRate = c_float() m = self.lib.ps5000aGetTimebase2(c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), byref(sampleRate), byref(maxSamples), c_uint32(segmentIndex)) self.checkResult(m) return (sampleRate.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Convert sample time in S to something to pass to API Call.""" if self.resolution == self.ADC_RESOLUTIONS["8"]: maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) if sampleTimeS < 8.0E-9: st = math.floor(math.log(sampleTimeS * 1E9, 2)) st = max(st, 0) else: if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 125000000) + 2) elif self.resolution == self.ADC_RESOLUTIONS["12"]: maxSampleTime = (((2 ** 32 - 1) - 3) / 62500000) if sampleTimeS < 16.0E-9: st = math.floor(math.log(sampleTimeS * 5E8, 2)) + 1 st = max(st, 1) else: if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 62500000) + 3) elif (self.resolution == self.ADC_RESOLUTIONS["14"]) or ( self.resolution == self.ADC_RESOLUTIONS["15"]): maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 125000000) + 2) st = max(st, 3) elif self.resolution == self.ADC_RESOLUTIONS["16"]: maxSampleTime = (((2 ** 32 - 1) - 3) / 62500000) if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 62500000) + 3) st = max(st, 3) else: raise ValueError("Invalid Resolution for Device?") # is this cast needed? st = int(st) return st def getTimestepFromTimebase(self, timebase): """Return Timestep from timebase.""" if self.resolution == self.ADC_RESOLUTIONS["8"]: if timebase < 3: dt = 2. ** timebase / 1.0E9 else: dt = (timebase - 2.0) / 125000000. elif self.resolution == self.ADC_RESOLUTIONS["12"]: if timebase < 4: dt = 2. ** (timebase - 1) / 5.0E8 else: dt = (timebase - 3.0) / 62500000. elif (self.resolution == self.ADC_RESOLUTIONS["14"]) or ( self.resolution == self.ADC_RESOLUTIONS["15"]): dt = (timebase - 2.0) / 125000000. elif self.resolution == self.ADC_RESOLUTIONS["16"]: dt = (timebase - 3.0) / 62500000. return dt 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.ps5000aSetSigGenArbitrary( 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. """ dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) m = self.lib.ps5000aSetDataBuffer(c_int16(self.handle), c_enum(channel), dataPtr, c_int32(numSamples), c_uint32(segmentIndex), c_enum(downSampleMode)) 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 _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps5000aSetDataBuffer(c_int16(self.handle), c_enum(channel), c_void_p(), c_uint32(0), c_uint32(segmentIndex), 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.ps5000aGetValues( 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.ps5000aSetSigGenBuiltIn( 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 _lowLevelSetDeviceResolution(self, resolution): self.resolution = resolution m = self.lib.ps5000aSetDeviceResolution( c_int16(self.handle), c_enum(resolution)) self.checkResult(m) def _lowLevelChangePowerSource(self, powerstate): m = self.lib.ps5000aChangePowerSource( c_int16(self.handle), c_enum(powerstate)) self.checkResult(m) # Morgan's additions def _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, downSampleRatio, downSampleMode, overflow): """Copy data from several memory segments at once.""" overflowPoint = overflow.ctypes.data_as(POINTER(c_int16)) m = self.lib.ps5000aGetValuesBulk( c_int16(self.handle), byref(c_int32(numSamples)), c_int32(fromSegment), c_int32(toSegment), c_int32(downSampleRatio), c_enum(downSampleMode), overflowPoint ) self.checkResult(m) def _lowLevelSetNoOfCaptures(self, numCaptures): m = self.lib.ps5000aSetNoOfCaptures( c_int16(self.handle), c_uint32(numCaptures)) self.checkResult(m) def _lowLevelMemorySegments(self, numSegments): maxSamples = c_int32() m = self.lib.ps5000aMemorySegments( c_int16(self.handle), c_uint32(numSegments), byref(maxSamples)) self.checkResult(m) return maxSamples.value def _lowLevelGetValuesTriggerTimeOffsetBulk(self, fromSegment, toSegment): """Supposedly gets the trigger times for a bunch of segments at once. For block mode. Can't get it to work yet, however. """ import numpy as np nSegments = toSegment - fromSegment + 1 # time = c_int64() times = np.ascontiguousarray( np.zeros(nSegments, dtype=np.int64) ) timeUnits = np.ascontiguousarray( np.zeros(nSegments, dtype=np.int32) ) m = self.lib.ps5000aGetValuesTriggerTimeOffsetBulk64( c_int16(self.handle), times.ctypes.data_as(POINTER(c_int64)), timeUnits.ctypes.data_as(POINTER(c_enum)), c_uint32(fromSegment), c_uint32(toSegment) ) self.checkResult(m) # timeUnits=np.array([self.TIME_UNITS[tu] for tu in timeUnits]) return times, timeUnits
class PS3000a(_PicoscopeBase): """The following are low-level functions for the PS3000a.""" LIBNAME = "ps3000a" NUM_CHANNELS = 4 CHANNELS = { "A": 0, "B": 1, "C": 2, "D": 3, "External": 4, "MaxChannels": 4, "TriggerAux": 5 } ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} CHANNEL_RANGE = [ { "rangeV": 10E-3, "apivalue": 0, "rangeStr": "10 mV" }, { "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" }, ] CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} # has_sig_gen = True 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 = 32767 AWGMinVal = -32767 AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} MAX_VALUE_8BIT = 32512 MIN_VALUE_8BIT = -32512 MAX_VALUE_OTHER = 32767 MIN_VALUE_OTHER = -32767 EXT_RANGE_VOLTS = 5 def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") 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"))) self.resolution = self.ADC_RESOLUTIONS["8"] super(PS3000a, self).__init__(serialNumber, connect) 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.ps3000aOpenUnit(byref(c_handle), serialNumberStr) self.handle = c_handle.value # copied over from ps5000a: # This will check if the power supply is not connected # and change the power supply accordingly # Personally (me = Mark), I don't like this # since the user should address this immediately, and we # shouldn't let this go as a soft error # but I think this should do for now if m == 0x11A: self.changePowerSource("PICO_POWER_SUPPLY_NOT_CONNECTED") else: self.checkResult(m) def _lowLevelCloseUnit(self): m = self.lib.ps3000aCloseUnit(c_int16(self.handle)) self.checkResult(m) def _lowLevelEnumerateUnits(self): count = c_int16(0) m = self.lib.ps3000aEnumerateUnits(byref(count), None, None) 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.ps3000aEnumerateUnits(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): m = self.lib.ps3000aSetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset)) self.checkResult(m) m = self.lib.ps3000aSetBandwidthFilter(c_int16(self.handle), c_enum(chNum), c_enum(BWLimited)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps3000aStop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps3000aGetUnitInfo(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.ps3000aGetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps3000aFlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, auto): m = self.lib.ps3000aSetSimpleTrigger(c_int16(self.handle), c_int16(enabled), c_enum(trigsrc), c_int16(threshold_adc), c_enum(direction), c_uint32(delay), c_int16(auto)) self.checkResult(m) def _lowLevelSetNoOfCaptures(self, numCaptures): m = self.lib.ps3000aSetNoOfCaptures(c_int16(self.handle), c_uint16(numCaptures)) self.checkResult(m) def _lowLevelMemorySegments(self, numSegments): maxSamples = c_int32() m = self.lib.ps3000aMemorySegments(c_int16(self.handle), c_uint16(numSegments), byref(maxSamples)) self.checkResult(m) return maxSamples.value def _lowLevelGetMaxSegments(self): maxSegments = c_int16() m = self.lib.ps3000aGetMaxSegments(c_int16(self.handle), byref(maxSegments)) self.checkResult(m) return maxSegments.value def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, timebase, oversample, segmentIndex, callback, pParameter): # NOT: Oversample is NOT used! timeIndisposedMs = c_int32() m = self.lib.ps3000aRunBlock(c_int16(self.handle), c_uint32(numPreTrigSamples), c_uint32(numPostTrigSamples), c_uint32(timebase), c_int16(oversample), byref(timeIndisposedMs), c_uint32(segmentIndex), c_void_p(), c_void_p()) # According to the documentation, 'callback, pParameter' should work # instead of the last two c_void_p parameters. self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps3000aIsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowlevelPingUnit(self): m = self.lib.ps3000aPingUnit(c_int16(self.handle)) return m def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() intervalNanoSec = c_float() m = self.lib.ps3000aGetTimebase2(c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), byref(intervalNanoSec), c_int16(oversample), byref(maxSamples), c_uint32(segmentIndex)) self.checkResult(m) # divide by 1e9 to return interval in seconds return (intervalNanoSec.value * 1e-9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Convert sample time in S to something to pass to API Call.""" maxSampleTime = (((2**32 - 1) - 2) / 125000000) if sampleTimeS < 8.0E-9: st = math.floor(math.log(sampleTimeS * 1E9, 2)) st = max(st, 0) else: if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 125000000) + 2) # is this cast needed? st = int(st) return st def getTimestepFromTimebase(self, timebase): """Take API timestep code and returns the sampling period. API timestep is an integer from 0-32 """ if timebase < 3: dt = 2.**timebase / 1.0E9 else: dt = (timebase - 2.0) / 125000000. return dt 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.ps3000aSetSigGenArbitrary( 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. """ dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) m = self.lib.ps3000aSetDataBuffer(c_int16(self.handle), c_enum(channel), dataPtr, c_int32(numSamples), c_uint32(segmentIndex), c_enum(downSampleMode)) self.checkResult(m) def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, downSampleMode): """Set the bulk data buffer. In ps3000a, ps3000aSetDataBuffer combines the functionality of psX000YSetDataBuffer and psX000YSetDataBufferBulk. Since the rapid block functions in picoscope.py call the Bulk version, a delegator is needed. Note that the order of segmentIndex and downSampleMode is reversed. """ self._lowLevelSetDataBuffer(channel, data, downSampleMode, segmentIndex) def _lowLevelSetMultipleDataBuffers(self, channel, data, downSampleMode): max_segments = self._lowLevelGetMaxSegments() if data.shape[0] < max_segments: raise ValueError("data array has fewer rows than current number " + "of memory segments") if data.shape[1] < self.maxSamples: raise ValueError("data array has fewer columns than maxSamples") for i in range(max_segments): m = self._lowLevelSetDataBuffer(channel, data[i, :], downSampleMode, i) self.checkResult(m) def _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps3000aSetDataBuffer(c_int16(self.handle), c_enum(channel), c_void_p(), c_uint32(0), c_uint32(segmentIndex), 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.ps3000aGetValues(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 _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, downSampleRatio, downSampleMode, overflow): m = self.lib.ps3000aGetValuesBulk( c_int16(self.handle), byref(c_uint32(numSamples)), c_uint32(fromSegment), c_uint32(toSegment), c_uint32(downSampleRatio), c_int16(downSampleMode), overflow.ctypes.data_as(POINTER(c_int16))) self.checkResult(m) return overflow, numSamples 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.ps3000aSetSigGenBuiltIn( 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 _lowLevelChangePowerSource(self, powerstate): m = self.lib.ps3000aChangePowerSource(c_int16(self.handle), c_enum(powerstate)) self.checkResult(m) def _lowLevelSigGenSoftwareControl(self, triggerType): m = self.lib.ps3000aSigGenSoftwareControl(c_int16(self.handle), c_enum(triggerType)) self.checkResult(m)
class PS5000a(_PicoscopeBase): """The following are low-level functions for the PS5000.""" LIBNAME = "ps5000a" NUM_CHANNELS = 4 CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, "External": 4, "MaxChannels": 4, "TriggerAux": 5} ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} CHANNEL_RANGE = [{"rangeV": 10E-3, "apivalue": 0, "rangeStr": "10 mV"}, {"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"}, ] CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} # has_sig_gen = True 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 AWGDACInterval = 5E-9 # in seconds AWGDACFrequency = 1 / AWGDACInterval AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} MAX_VALUE_8BIT = 32512 MIN_VALUE_8BIT = -32512 MAX_VALUE_OTHER = 32767 MIN_VALUE_OTHER = -32767 EXT_RANGE_VOLTS = 5 def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") elif platform.system() == 'Darwin': from picoscope.darwin_utils import LoadLibraryDarwin self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") else: from ctypes import windll self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) self.resolution = self.ADC_RESOLUTIONS["8"] super(PS5000a, self).__init__(serialNumber, connect) def _lowLevelOpenUnit(self, sn): c_handle = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(sn) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps5000aOpenUnit(byref(c_handle), serialNullTermStr, self.resolution) self.handle = c_handle.value # This will check if the power supply is not connected # and change the power supply accordingly # Personally (me = Mark), I don't like this # since the user should address this immediately, and we # shouldn't let this go as a soft error # but I think this should do for now if m == 0x11A: self.changePowerSource(m) else: self.checkResult(m) # B models have different AWG buffer sizes # 5242B, 5442B: 2**14 # 5243B, 5443B: 2**15 # 5444B, 5244B: 3 * 2**14 # Model 5444B identifies itself properly in VariantInfo, I will assume # the others do as well. self.model = self.getUnitInfo('VariantInfo') # print("Checking variant, found: " + str(self.model)) if self.model in ('5244B', '5444B'): self.AWGBufferAddressWidth = math.log(3 * 2**14, 2) self.AWGMaxVal = 32767 self.AWGMinVal = -32768 self.AWGMaxSamples = 49152 elif self.model in ('5243B', '5443B'): self.AWGBufferAddressWidth = 15 self.AWGMaxVal = 32767 self.AWGMinVal = -32768 self.AWGMaxSamples = 2**self.AWGBufferAddressWidth else: # This is what the previous PS5000a used for all scopes. # I am leaving it the same, although I think the AWGMaxVal and # AWGMinVal issue was fixed and should be -32768 to 32767 for all # 5000 models self.AWGBufferAddressWidth = 14 # 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 self.AWGMaxVal = 0x0FFF self.AWGMinVal = 0x0000 self.AWGMaxSamples = 2**self.AWGBufferAddressWidth def _lowLevelCloseUnit(self): m = self.lib.ps5000aCloseUnit(c_int16(self.handle)) self.checkResult(m) def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, bandwidth): m = self.lib.ps5000aSetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset)) self.checkResult(m) # The error this might through are # INVALID_HANDLE # INVALID_CHANNEL # INVALID_BANDWIDTH # Invalid bandwidth is the only case that could go wrong. # The others would be thrown above (assuming no race condition: # i.e. unplugging the scope in between this call. # I decided to keep the logic below to avoid a possible error # picobase/SetChannel should be changed to the following # Set the channel # save the new channel settings # check if ps5000a # change the bandwidth separately # changing the bandwidth would be it's own function (implemented below) if bandwidth: m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), c_enum(chNum), c_enum(1)) else: m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), c_enum(chNum), c_enum(0)) self.checkResult(m) def _lowLevelSetBandwidthFilter(self, channel, bandwidth): m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), c_enum(channel), c_enum(bandwidth)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps5000aStop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps5000aGetUnitInfo(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.ps5000aGetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps5000aFlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): m = self.lib.ps5000aSetSimpleTrigger( 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): # Oversample is NOT used! timeIndisposedMs = c_int32() m = self.lib.ps5000aRunBlock( c_int16(self.handle), c_uint32(numPreTrigSamples), c_uint32(numPostTrigSamples), c_uint32(timebase), byref(timeIndisposedMs), c_uint32(segmentIndex), c_void_p(), c_void_p()) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps5000aIsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowLevelPingUnit(self): m = self.lib.ps5000aPingUnit(c_int16(self.handle)) return m def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() sampleRate = c_float() m = self.lib.ps5000aGetTimebase2(c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), byref(sampleRate), byref(maxSamples), c_uint32(segmentIndex)) self.checkResult(m) return (sampleRate.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Convert sample time in S to something to pass to API Call.""" if self.resolution == self.ADC_RESOLUTIONS["8"]: maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) if sampleTimeS < 8.0E-9: st = math.floor(math.log(sampleTimeS * 1E9, 2)) st = max(st, 0) else: if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 125000000) + 2) elif self.resolution == self.ADC_RESOLUTIONS["12"]: maxSampleTime = (((2 ** 32 - 1) - 3) / 62500000) if sampleTimeS < 16.0E-9: st = math.floor(math.log(sampleTimeS * 5E8, 2)) + 1 st = max(st, 1) else: if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 62500000) + 3) elif (self.resolution == self.ADC_RESOLUTIONS["14"]) or ( self.resolution == self.ADC_RESOLUTIONS["15"]): maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 125000000) + 2) st = max(st, 3) elif self.resolution == self.ADC_RESOLUTIONS["16"]: maxSampleTime = (((2 ** 32 - 1) - 3) / 62500000) if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 62500000) + 3) st = max(st, 3) else: raise ValueError("Invalid Resolution for Device?") # is this cast needed? st = int(st) return st def getTimestepFromTimebase(self, timebase): """Return Timestep from timebase.""" if self.resolution == self.ADC_RESOLUTIONS["8"]: if timebase < 3: dt = 2. ** timebase / 1.0E9 else: dt = (timebase - 2.0) / 125000000. elif self.resolution == self.ADC_RESOLUTIONS["12"]: if timebase < 4: dt = 2. ** (timebase - 1) / 5.0E8 else: dt = (timebase - 3.0) / 62500000. elif (self.resolution == self.ADC_RESOLUTIONS["14"]) or ( self.resolution == self.ADC_RESOLUTIONS["15"]): dt = (timebase - 2.0) / 125000000. elif self.resolution == self.ADC_RESOLUTIONS["16"]: dt = (timebase - 3.0) / 62500000. return dt 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.ps5000aSetSigGenArbitrary( 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. """ dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) m = self.lib.ps5000aSetDataBuffer(c_int16(self.handle), c_enum(channel), dataPtr, c_int32(numSamples), c_uint32(segmentIndex), c_enum(downSampleMode)) 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 _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps5000aSetDataBuffer(c_int16(self.handle), c_enum(channel), c_void_p(), c_uint32(0), c_uint32(segmentIndex), 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.ps5000aGetValues( 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.ps5000aSetSigGenBuiltIn( 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 _lowLevelSetDeviceResolution(self, resolution): self.resolution = resolution m = self.lib.ps5000aSetDeviceResolution( c_int16(self.handle), c_enum(resolution)) self.checkResult(m) def _lowLevelChangePowerSource(self, powerstate): m = self.lib.ps5000aChangePowerSource( c_int16(self.handle), c_enum(powerstate)) self.checkResult(m) # Morgan's additions def _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, downSampleRatio, downSampleMode, overflow): """Copy data from several memory segments at once.""" overflowPoint = overflow.ctypes.data_as(POINTER(c_int16)) m = self.lib.ps5000aGetValuesBulk( c_int16(self.handle), byref(c_int32(numSamples)), c_int32(fromSegment), c_int32(toSegment), c_int32(downSampleRatio), c_enum(downSampleMode), overflowPoint ) self.checkResult(m) def _lowLevelSetNoOfCaptures(self, numCaptures): m = self.lib.ps5000aSetNoOfCaptures( c_int16(self.handle), c_uint32(numCaptures)) self.checkResult(m) def _lowLevelMemorySegments(self, numSegments): maxSamples = c_int32() m = self.lib.ps5000aMemorySegments( c_int16(self.handle), c_uint32(numSegments), byref(maxSamples)) self.checkResult(m) return maxSamples.value def _lowLevelGetValuesTriggerTimeOffsetBulk(self, fromSegment, toSegment): """Supposedly gets the trigger times for a bunch of segments at once. For block mode. Can't get it to work yet, however. """ import numpy as np nSegments = toSegment - fromSegment + 1 # time = c_int64() times = np.ascontiguousarray( np.zeros(nSegments, dtype=np.int64) ) timeUnits = np.ascontiguousarray( np.zeros(nSegments, dtype=np.int32) ) m = self.lib.ps5000aGetValuesTriggerTimeOffsetBulk64( c_int16(self.handle), times.ctypes.data_as(POINTER(c_int64)), timeUnits.ctypes.data_as(POINTER(c_enum)), c_uint32(fromSegment), c_uint32(toSegment) ) self.checkResult(m) # timeUnits=np.array([self.TIME_UNITS[tu] for tu in timeUnits]) return times, timeUnits
class PS3000(_PicoscopeBase): """The following are low-level functions for the PS3000.""" LIBNAME = "ps3000" NUM_CHANNELS = 2 CHANNELS = {"A": 0, "B": 1, "MaxChannels": 2} THRESHOLD_TYPE = {"Rising": 0, "Falling": 1} 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"}, ] CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} # has_sig_gen = True WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, "RampUp": 3, "RampDown": 4, "DCVoltage": 5} SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} TIME_UNITS = {"FS": 0, "PS": 1, "NS": 2, "US": 3, "MS": 4, "S": 5} MAX_VALUE = 32767 MIN_VALUE = -32767 MAX_TIMEBASES = 19 UNIT_INFO_TYPES = {"DriverVersion": 0x0, "USBVersion": 0x1, "HardwareVersion": 0x2, "VariantInfo": 0x3, "BatchAndSerial": 0x4, "CalDate": 0x5, "ErrorCode": 0x6, "KernelVersion": 0x7} channelBuffersPtr = [c_void_p(), c_void_p()] channelBuffersLen = [0, 0] # 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} def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") elif platform.system() == 'Darwin': from picoscope.darwin_utils import LoadLibraryDarwin self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") else: from ctypes import windll self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) super(PS3000, self).__init__(serialNumber, connect) def _lowLevelOpenUnit(self, sn): if sn is not None: raise ValueError("PS3000 Doesn't Support Open by Serial Number") m = self.lib.ps3000_open_unit() if m < 0: raise IOError("Failed to Find PS3000 Unit. " + "Should you be using PS3000a driver?") self.handle = m self.suggested_time_units = self.TIME_UNITS["NS"] def _lowLevelCloseUnit(self): m = self.lib.ps3000_close_unit(c_int16(self.handle)) self.checkResult(m) def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, BWLimited): m = self.lib.ps3000_set_channel( c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps3000_stop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) m = self.lib.ps3000_get_unit_info( c_int16(self.handle), byref(s), c_int16(len(s)), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps3000_flash_led(c_int16(self.handle)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): # TODO: Fix 'auto' which is where trigger occurs in block. # Delay is not used m = self.lib.ps3000_set_trigger( c_int16(self.handle), c_enum(trigsrc), c_int16(threshold_adc), c_enum(direction), c_int16(delay), c_int16(timeout_ms)) self.checkResult(m) def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, timebase, oversample, segmentIndex): # NOT: Oversample is NOT used! # TODO: Fix 'delay' which is where trigger occurs in block if numPreTrigSamples > 0: raise ValueError("numPreTrigSamples isn't supported on PS3000") timeIndisposedMs = c_int32() m = self.lib.ps3000_run_block( c_int16(self.handle), c_uint32(numPostTrigSamples), c_uint32(timebase), c_int16(1), byref(timeIndisposedMs)) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() ready = self.lib.ps3000_ready(c_int16(self.handle)) if ready > 0: return True elif ready == 0: return False else: raise IOError("ps3000_ready returned %d" % ready.value) def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() time_interval = c_int32() time_units = c_int16() m = self.lib.ps3000_get_timebase( c_int16(self.handle), c_int16(tb), c_uint32(noSamples), byref(time_interval), byref(time_units), c_int16(1), byref(maxSamples)) self.checkResult(m) self.suggested_time_units = time_units.value return (time_interval.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """ps3000 doesn't seem to have published formula like other scopes.""" time_interval = c_int32() timebases = [None] * self.MAX_TIMEBASES # Convert to nS sampleTimenS = sampleTimeS * 1E9 tb = 0 while tb < self.MAX_TIMEBASES: rv = self.lib.ps3000_get_timebase( c_int16(self.handle), c_int16(tb), c_uint32(512), byref(time_interval), c_void_p(), c_int16(1), c_void_p()) if rv != 0: timebases[tb] = time_interval.value tb += 1 # Figure out closest option besterror = 1E99 bestindx = 0 for indx, val in enumerate(timebases): if val is not None: error = sampleTimenS - val if abs(error) < besterror: besterror = abs(error) bestindx = indx return bestindx def getTimestepFromTimebase(self, timebase): """Return timestep from Timebase.""" time_interval = c_int32() m = self.lib.ps3000_get_timebase( c_int16(self.handle), c_int16(timebase), c_uint32(512), byref(time_interval), c_void_p(), c_int16(1), c_void_p()) self.checkResult(m) return (time_interval.value / 1.0E9) def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, segmentIndex): dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) self.channelBuffersPtr[channel] = dataPtr self.channelBuffersLen[channel] = numSamples def _lowLevelClearDataBuffer(self, channel, segmentIndex): self.channelBuffersPtr[channel] = c_void_p() self.channelBuffersLen[channel] = 0 def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex): # TODO: # Check overflow in channelBuffersLen against numSamples, # but need to not raise error if channelBuffersPtr is void overflow = c_int16() rv = self.lib.ps3000_get_values( c_int16(self.handle), self.channelBuffersPtr[0], self.channelBuffersPtr[1], c_void_p(), c_void_p(), byref(overflow), c_int32(numSamples)) self.checkResult(rv) return (rv, overflow.value) def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, frequency, shots, triggerType, triggerSource, stopFreq, increment, dwellTime, sweepType, numSweeps): if stopFreq is None: stopFreq = frequency m = self.lib.ps3000_set_sig_gen_built_in( 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_uint32(numSweeps)) self.checkResult(m) def checkResult(self, ec): """Check result of function calls, raise exception if not 0.""" # PS3000 differs from other drivers in that non-zero is good if ec == 0: raise IOError('Error calling %s' % (inspect.stack()[1][3])) return 0
class PS3000(_PicoscopeBase): """The following are low-level functions for the PS3000.""" LIBNAME = "ps3000" NUM_CHANNELS = 2 CHANNELS = {"A": 0, "B": 1, "MaxChannels": 2} THRESHOLD_TYPE = {"Rising": 0, "Falling": 1} 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" }, ] CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} # has_sig_gen = True WAVE_TYPES = { "Sine": 0, "Square": 1, "Triangle": 2, "RampUp": 3, "RampDown": 4, "DCVoltage": 5 } SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} TIME_UNITS = {"FS": 0, "PS": 1, "NS": 2, "US": 3, "MS": 4, "S": 5} MAX_VALUE = 32767 MIN_VALUE = -32767 MAX_TIMEBASES = 19 UNIT_INFO_TYPES = { "DriverVersion": 0x0, "USBVersion": 0x1, "HardwareVersion": 0x2, "VariantInfo": 0x3, "BatchAndSerial": 0x4, "CalDate": 0x5, "ErrorCode": 0x6, "KernelVersion": 0x7 } channelBuffersPtr = [c_void_p(), c_void_p()] channelBuffersLen = [0, 0] # 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} def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") elif platform.system() == 'Darwin': from picoscope.darwin_utils import LoadLibraryDarwin self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") else: from ctypes import windll self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) super(PS3000, self).__init__(serialNumber, connect) def _lowLevelOpenUnit(self, sn): if sn is not None: raise ValueError("PS3000 Doesn't Support Open by Serial Number") m = self.lib.ps3000_open_unit() if m < 0: raise IOError("Failed to Find PS3000 Unit. " + "Should you be using PS3000a driver?") self.handle = m self.suggested_time_units = self.TIME_UNITS["NS"] def _lowLevelCloseUnit(self): m = self.lib.ps3000_close_unit(c_int16(self.handle)) self.checkResult(m) def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, BWLimited): m = self.lib.ps3000_set_channel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps3000_stop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) m = self.lib.ps3000_get_unit_info(c_int16(self.handle), byref(s), c_int16(len(s)), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps3000_flash_led(c_int16(self.handle)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): # TODO: Fix 'auto' which is where trigger occurs in block. # Delay is not used m = self.lib.ps3000_set_trigger(c_int16(self.handle), c_enum(trigsrc), c_int16(threshold_adc), c_enum(direction), c_int16(delay), c_int16(timeout_ms)) self.checkResult(m) def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, timebase, oversample, segmentIndex): # NOT: Oversample is NOT used! # TODO: Fix 'delay' which is where trigger occurs in block if numPreTrigSamples > 0: raise ValueError("numPreTrigSamples isn't supported on PS3000") timeIndisposedMs = c_int32() m = self.lib.ps3000_run_block(c_int16(self.handle), c_uint32(numPostTrigSamples), c_uint32(timebase), c_int16(1), byref(timeIndisposedMs)) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() ready = self.lib.ps3000_ready(c_int16(self.handle)) if ready > 0: return True elif ready == 0: return False else: raise IOError("ps3000_ready returned %d" % ready.value) def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() time_interval = c_int32() time_units = c_int16() m = self.lib.ps3000_get_timebase(c_int16(self.handle), c_int16(tb), c_uint32(noSamples), byref(time_interval), byref(time_units), c_int16(1), byref(maxSamples)) self.checkResult(m) self.suggested_time_units = time_units.value return (time_interval.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """ps3000 doesn't seem to have published formula like other scopes.""" time_interval = c_int32() timebases = [None] * self.MAX_TIMEBASES # Convert to nS sampleTimenS = sampleTimeS * 1E9 tb = 0 while tb < self.MAX_TIMEBASES: rv = self.lib.ps3000_get_timebase(c_int16(self.handle), c_int16(tb), c_uint32(512), byref(time_interval), c_void_p(), c_int16(1), c_void_p()) if rv != 0: timebases[tb] = time_interval.value tb += 1 # Figure out closest option besterror = 1E99 bestindx = 0 for indx, val in enumerate(timebases): if val is not None: error = sampleTimenS - val if abs(error) < besterror: besterror = abs(error) bestindx = indx return bestindx def getTimestepFromTimebase(self, timebase): """Return timestep from Timebase.""" time_interval = c_int32() m = self.lib.ps3000_get_timebase(c_int16(self.handle), c_int16(timebase), c_uint32(512), byref(time_interval), c_void_p(), c_int16(1), c_void_p()) self.checkResult(m) return (time_interval.value / 1.0E9) def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, segmentIndex): dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) self.channelBuffersPtr[channel] = dataPtr self.channelBuffersLen[channel] = numSamples def _lowLevelClearDataBuffer(self, channel, segmentIndex): self.channelBuffersPtr[channel] = c_void_p() self.channelBuffersLen[channel] = 0 def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, downSampleMode, segmentIndex): # TODO: # Check overflow in channelBuffersLen against numSamples, # but need to not raise error if channelBuffersPtr is void overflow = c_int16() rv = self.lib.ps3000_get_values(c_int16(self.handle), self.channelBuffersPtr[0], self.channelBuffersPtr[1], c_void_p(), c_void_p(), byref(overflow), c_int32(numSamples)) self.checkResult(rv) return (rv, overflow.value) def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, frequency, shots, triggerType, triggerSource, stopFreq, increment, dwellTime, sweepType, numSweeps): if stopFreq is None: stopFreq = frequency m = self.lib.ps3000_set_sig_gen_built_in( 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_uint32(numSweeps)) self.checkResult(m) def checkResult(self, ec): """Check result of function calls, raise exception if not 0.""" # PS3000 differs from other drivers in that non-zero is good if ec == 0: raise IOError('Error calling %s' % (inspect.stack()[1][3])) return 0
class PS4000(_PicoscopeBase): """The following are low-level functions for the PS4000.""" LIBNAME = "ps4000" MAX_VALUE = 32764 MIN_VALUE = -32764 # AWG stuff here: AWGMaxSamples = 4096 AWGDACInterval = 1 / 192000 # [s] AWGDACFrequency = 192000 # [Hz] AWGPhaseAccumulatorSize = 32 AWGMaxVal = 32767 AWGMinVal = -32768 AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} AWGBufferAddressWidth = 12 AWGMaxSamples = 2**AWGBufferAddressWidth SIGGEN_TRIGGER_TYPES = { "Rising": 0, "Falling": 1, "GateHigh": 2, "GateLow": 3 } SIGGEN_TRIGGER_SOURCES = { "None": 0, "ScopeTrig": 1, "AuxIn": 2, "ExtIn": 3, "SoftTrig": 4 } # 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, "EXT": 4} 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} def __init__(self, serialNumber=None, connect=True): """Load DLLs.""" self.handle = None 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 self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) super(PS4000, self).__init__(serialNumber, connect) # check to see which model we have and use special functions if needed unit_number = self.getUnitInfo('VariantInfo') if unit_number == '4262': self.getTimestepFromTimebase = self._getTimestepFromTimebase4262 self.getTimeBaseNum = self._getTimeBaseNum4262 elif unit_number in {'4223', '4224', '4423', '4424'}: self.getTimestepFromTimebase = self._getTimestepFromTimebase4223 self.getTimeBaseNum = self._getTimeBaseNum4223 else: raise NotImplementedError('Timebase functions have not been ' 'written for the ' + unit_number) def _lowLevelOpenUnit(self, sn): c_handle = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(sn) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps4000OpenUnit(byref(c_handle), serialNullTermStr) self.checkResult(m) self.handle = c_handle.value def _lowLevelOpenUnitAsync(self, sn): c_status = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(sn) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps4000OpenUnitAsync(byref(c_status), serialNullTermStr) self.checkResult(m) return c_status.value def _lowLevelOpenUnitProgress(self): complete = c_int16() progressPercent = c_int16() handle = c_int16() m = self.lib.ps4000OpenUnitProgress(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.ps4000CloseUnit(c_int16(self.handle)) self.checkResult(m) def _lowLevelEnumerateUnits(self): count = c_int16(0) m = self.lib.ps4000EnumerateUnits(byref(count), None, None) 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.ps4000EnumerateUnits(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): m = self.lib.ps4000SetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset), c_enum(BWLimited)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps4000Stop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps4000GetUnitInfo(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.ps4000GetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps4000FlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): m = self.lib.ps4000SetSimpleTrigger(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): timeIndisposedMs = c_int32() m = self.lib.ps4000RunBlock(c_int16(self.handle), c_uint32(numPreTrigSamples), c_uint32(numPostTrigSamples), c_uint32(timebase), c_int16(oversample), byref(timeIndisposedMs), c_uint16(segmentIndex), c_void_p(), c_void_p()) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps4000IsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() sampleRate = c_float() m = self.lib.ps4000GetTimebase2(c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), byref(sampleRate), c_int16(oversample), byref(maxSamples), c_uint16(segmentIndex)) self.checkResult(m) return (sampleRate.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Return sample time in seconds to timebase as int for API calls.""" raise NotImplementedError('Timebase functions have not been ' 'written for the scope.') def getTimestepFromTimebase(self, timebase): """Return timebase to sampletime as seconds.""" raise NotImplementedError('Timebase functions have not been ' 'written for the scope.') def _getTimeBaseNum4223(self, sampleTimeS): """Return sample time in seconds to timebase as int for API calls.""" maxSampleTime = (((2**32 - 1) - 4) / 2e7) if sampleTimeS <= 12.5E-9: timebase = math.floor(math.log(sampleTimeS * 8E7, 2)) timebase = max(timebase, 0) else: # Otherwise in range 2^32-1 if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime timebase = math.floor((sampleTimeS * 2e7) + 1) # is this cast needed? timebase = int(timebase) return timebase def _getTimestepFromTimebase4223(self, timebase): """Return timebase to sampletime as seconds.""" if timebase < 3: dt = 2.**timebase / 8e7 else: dt = (timebase - 1) / 2e7 return dt def _getTimestepFromTimebase4262(self, timebase): """Return timebase to sampletime as seconds for ps4262.""" someConstant = 10000000.0 return (timebase + 1) / someConstant def _getTimeBaseNum4262(self, sampleTimeS): """Return timebase for the ps4262. Return sample time in seconds to timebase as int for API calls for ps4262. """ someConstant = 10000000.0 maxSampleTime = ((2**30) / someConstant) minSampleTime = 1 / someConstant if sampleTimeS <= minSampleTime: timebase = 0 elif sampleTimeS >= maxSampleTime: timebase = (2**30) else: timebase = round(sampleTimeS * someConstant) - 1 # is this cast needed? timebase = int(timebase) return timebase 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.ps4000SetDataBuffer(c_int16(self.handle), c_enum(channel), dataPtr, c_uint32(numSamples)) self.checkResult(m) def _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps4000SetDataBuffer(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.ps4000GetValues(c_int16(self.handle), c_uint32(startIndex), byref(numSamplesReturned), c_uint32(downSampleRatio), c_enum(downSampleMode), c_uint16(segmentIndex), byref(overflow)) self.checkResult(m) return (numSamplesReturned.value, overflow.value) 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.ps4000SetSigGenArbitrary( 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): if stopFreq is None: stopFreq = frequency m = self.lib.ps4000SetSigGenBuiltIn( 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 _lowLevelSigGenSoftwareControl(self, triggerType): m = self.lib.ps4000SigGenSoftwareControl(c_int16(self.handle), c_enum(triggerType)) self.checkResult(m) # use VRange=5 for +/- 500mV signal range # OR # VRange=8 for +/- 5V range def _lowLevelSetExtTriggerRange(self, VRange): m = self.lib.ps4000SetExtTriggerRange(c_int16(self.handle), c_enum(VRange)) self.checkResult(m) #################################################################### # Untested functions below # # # #################################################################### def _lowLevelGetMaxDownSampleRatio(self, noOfUnaggregatedSamples, downSampleRatioMode, segmentIndex): maxDownSampleRatio = c_uint32() m = self.lib.ps4000GetMaxDownSampleRatio( c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), byref(maxDownSampleRatio), c_enum(downSampleRatioMode), c_uint16(segmentIndex)) self.checkResult(m) return maxDownSampleRatio.value def _lowLevelGetNoOfCaptures(self): nCaptures = c_uint32() m = self.lib.ps4000GetNoOfCaptures(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.ps4000GetTriggerTimeOffset64(c_int16(self.handle), byref(time), byref(timeUnits), c_uint16(segmentIndex)) self.checkResult(m) if timeUnits.value == 0: # PS4000_FS return time.value * 1E-15 elif timeUnits.value == 1: # PS4000_PS return time.value * 1E-12 elif timeUnits.value == 2: # PS4000_NS return time.value * 1E-9 elif timeUnits.value == 3: # PS4000_US return time.value * 1E-6 elif timeUnits.value == 4: # PS4000_MS return time.value * 1E-3 elif timeUnits.value == 5: # PS4000_S return time.value * 1E0 else: raise TypeError("Unknown timeUnits %d" % timeUnits.value) def _lowLevelMemorySegments(self, nSegments): nMaxSamples = c_uint32() m = self.lib.ps4000MemorySegments(c_int16(self.handle), c_uint16(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.ps4000SetDataBuffers(c_int16(self.handle), c_enum(channel), bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth)) self.checkResult(m) def _lowLevelClearDataBuffers(self, channel): m = self.lib.ps4000SetDataBuffers(c_int16(self.handle), c_enum(channel), c_void_p(), c_void_p(), c_uint32(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, downSampleMode, overflow): noOfSamples = c_uint32(numSamples) m = self.lib.ps4000GetValuesBulk( c_int16(self.handle), byref(noOfSamples), c_uint16(fromSegmentIndex), c_uint16(toSegmentIndex), 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.ps4000SetDataBufferBulk(c_int16(self.handle), c_enum(channel), bufferPtr, c_uint32(bufferLth), c_uint16(waveform)) self.checkResult(m) def _lowLevelSetNoOfCaptures(self, nCaptures): m = self.lib.ps4000SetNoOfCaptures(c_int16(self.handle), c_uint16(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.ps4000NoOfStreamingValues(c_int16(self.handle), byref(noOfValues)) self.checkResult(m) return noOfValues.value def _lowLevelRunStreaming(): pass def _lowLevelStreamingReady(): pass
class PS4000(_PicoscopeBase): """The following are low-level functions for the PS4000.""" LIBNAME = "ps4000" MAX_VALUE = 32764 MIN_VALUE = -32764 # AWG stuff here: AWGMaxSamples = 4096 AWGDACInterval = 1 / 192000 # [s] AWGDACFrequency = 192000 # [Hz] AWGPhaseAccumulatorSize = 32 AWGMaxVal = 32767 AWGMinVal = -32768 AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} AWGBufferAddressWidth = 12 AWGMaxSamples = 2 ** AWGBufferAddressWidth SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, "GateHigh": 2, "GateLow": 3} SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, "ExtIn": 3, "SoftTrig": 4} # 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, "EXT": 4} 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} def __init__(self, serialNumber=None, connect=True): """Load DLLs.""" self.handle = None 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 self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) super(PS4000, self).__init__(serialNumber, connect) # check to see which model we have and use special functions if needed unit_number = self.getUnitInfo('VariantInfo') if unit_number == '4262': self.getTimestepFromTimebase = self._getTimestepFromTimebase4262 self.getTimeBaseNum = self._getTimeBaseNum4262 elif unit_number in {'4223', '4224', '4423', '4424'}: self.getTimestepFromTimebase = self._getTimestepFromTimebase4223 self.getTimeBaseNum = self._getTimeBaseNum4223 else: raise NotImplementedError('Timebase functions have not been ' 'written for the ' + unit_number) def _lowLevelOpenUnit(self, sn): c_handle = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(sn) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps4000OpenUnit(byref(c_handle), serialNullTermStr) self.checkResult(m) self.handle = c_handle.value def _lowLevelOpenUnitAsync(self, sn): c_status = c_int16() if sn is not None: serialNullTermStr = create_string_buffer(sn) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps4000OpenUnitAsync(byref(c_status), serialNullTermStr) self.checkResult(m) return c_status.value def _lowLevelOpenUnitProgress(self): complete = c_int16() progressPercent = c_int16() handle = c_int16() m = self.lib.ps4000OpenUnitProgress(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.ps4000CloseUnit(c_int16(self.handle)) self.checkResult(m) def _lowLevelEnumerateUnits(self): count = c_int16(0) m = self.lib.ps4000EnumerateUnits(byref(count), None, None) 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.ps4000EnumerateUnits(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): m = self.lib.ps4000SetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset), c_enum(BWLimited)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps4000Stop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps4000GetUnitInfo(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.ps4000GetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps4000FlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): m = self.lib.ps4000SetSimpleTrigger( 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): timeIndisposedMs = c_int32() m = self.lib.ps4000RunBlock( c_int16(self.handle), c_uint32(numPreTrigSamples), c_uint32(numPostTrigSamples), c_uint32(timebase), c_int16(oversample), byref(timeIndisposedMs), c_uint16(segmentIndex), c_void_p(), c_void_p()) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps4000IsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() sampleRate = c_float() m = self.lib.ps4000GetTimebase2(c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), byref(sampleRate), c_int16(oversample), byref(maxSamples), c_uint16(segmentIndex)) self.checkResult(m) return (sampleRate.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Return sample time in seconds to timebase as int for API calls.""" raise NotImplementedError('Timebase functions have not been ' 'written for the scope.') def getTimestepFromTimebase(self, timebase): """Return timebase to sampletime as seconds.""" raise NotImplementedError('Timebase functions have not been ' 'written for the scope.') def _getTimeBaseNum4223(self, sampleTimeS): """Return sample time in seconds to timebase as int for API calls.""" maxSampleTime = (((2 ** 32 - 1) - 4) / 2e7) if sampleTimeS <= 12.5E-9: timebase = math.floor(math.log(sampleTimeS * 8E7, 2)) timebase = max(timebase, 0) else: # Otherwise in range 2^32-1 if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime timebase = math.floor((sampleTimeS * 2e7) + 1) # is this cast needed? timebase = int(timebase) return timebase def _getTimestepFromTimebase4223(self, timebase): """Return timebase to sampletime as seconds.""" if timebase < 3: dt = 2. ** timebase / 8e7 else: dt = (timebase - 1) / 2e7 return dt def _getTimestepFromTimebase4262(self, timebase): """Return timebase to sampletime as seconds for ps4262.""" someConstant = 10000000.0 return (timebase+1) / someConstant def _getTimeBaseNum4262(self, sampleTimeS): """Return timebase for the ps4262. Return sample time in seconds to timebase as int for API calls for ps4262. """ someConstant = 10000000.0 maxSampleTime = ((2 ** 30) / someConstant) minSampleTime = 1 / someConstant if sampleTimeS <= minSampleTime: timebase = 0 elif sampleTimeS >= maxSampleTime: timebase = (2 ** 30) else: timebase = round(sampleTimeS * someConstant) - 1 # is this cast needed? timebase = int(timebase) return timebase 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.ps4000SetDataBuffer(c_int16(self.handle), c_enum(channel), dataPtr, c_uint32(numSamples)) self.checkResult(m) def _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps4000SetDataBuffer(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.ps4000GetValues( c_int16(self.handle), c_uint32(startIndex), byref(numSamplesReturned), c_uint32(downSampleRatio), c_enum(downSampleMode), c_uint16(segmentIndex), byref(overflow)) self.checkResult(m) return (numSamplesReturned.value, overflow.value) 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.ps4000SetSigGenArbitrary( 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): if stopFreq is None: stopFreq = frequency m = self.lib.ps4000SetSigGenBuiltIn( 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 _lowLevelSigGenSoftwareControl(self, triggerType): m = self.lib.ps4000SigGenSoftwareControl( c_int16(self.handle), c_enum(triggerType)) self.checkResult(m) # use VRange=5 for +/- 500mV signal range # OR # VRange=8 for +/- 5V range def _lowLevelSetExtTriggerRange(self, VRange): m = self.lib.ps4000SetExtTriggerRange( c_int16(self.handle), c_enum(VRange)) self.checkResult(m) #################################################################### # Untested functions below # # # #################################################################### def _lowLevelGetMaxDownSampleRatio(self, noOfUnaggregatedSamples, downSampleRatioMode, segmentIndex): maxDownSampleRatio = c_uint32() m = self.lib.ps4000GetMaxDownSampleRatio( c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), byref(maxDownSampleRatio), c_enum(downSampleRatioMode), c_uint16(segmentIndex)) self.checkResult(m) return maxDownSampleRatio.value def _lowLevelGetNoOfCaptures(self): nCaptures = c_uint32() m = self.lib.ps4000GetNoOfCaptures( 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.ps4000GetTriggerTimeOffset64( c_int16(self.handle), byref(time), byref(timeUnits), c_uint16(segmentIndex)) self.checkResult(m) if timeUnits.value == 0: # PS4000_FS return time.value * 1E-15 elif timeUnits.value == 1: # PS4000_PS return time.value * 1E-12 elif timeUnits.value == 2: # PS4000_NS return time.value * 1E-9 elif timeUnits.value == 3: # PS4000_US return time.value * 1E-6 elif timeUnits.value == 4: # PS4000_MS return time.value * 1E-3 elif timeUnits.value == 5: # PS4000_S return time.value * 1E0 else: raise TypeError("Unknown timeUnits %d" % timeUnits.value) def _lowLevelMemorySegments(self, nSegments): nMaxSamples = c_uint32() m = self.lib.ps4000MemorySegments(c_int16(self.handle), c_uint16(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.ps4000SetDataBuffers( c_int16(self.handle), c_enum(channel), bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth)) self.checkResult(m) def _lowLevelClearDataBuffers(self, channel): m = self.lib.ps4000SetDataBuffers( c_int16(self.handle), c_enum(channel), c_void_p(), c_void_p(), c_uint32(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, downSampleMode, overflow): noOfSamples = c_uint32(numSamples) m = self.lib.ps4000GetValuesBulk( c_int16(self.handle), byref(noOfSamples), c_uint16(fromSegmentIndex), c_uint16(toSegmentIndex), 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.ps4000SetDataBufferBulk( c_int16(self.handle), c_enum(channel), bufferPtr, c_uint32(bufferLth), c_uint16(waveform)) self.checkResult(m) def _lowLevelSetNoOfCaptures(self, nCaptures): m = self.lib.ps4000SetNoOfCaptures( c_int16(self.handle), c_uint16(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.ps4000NoOfStreamingValues( c_int16(self.handle), byref(noOfValues)) self.checkResult(m) return noOfValues.value def _lowLevelRunStreaming(): pass def _lowLevelStreamingReady(): pass
class PS3000a(_PicoscopeBase): """The following are low-level functions for the PS3000a.""" LIBNAME = "ps3000a" NUM_CHANNELS = 4 CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, "External": 4, "MaxChannels": 4, "TriggerAux": 5} ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} CHANNEL_RANGE = [{"rangeV": 10E-3, "apivalue": 0, "rangeStr": "10 mV"}, {"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"}, ] CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} # has_sig_gen = True 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 AWGMaxVa = 32767 AWGMinVal = -32767 AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} MAX_VALUE_8BIT = 32512 MIN_VALUE_8BIT = -32512 MAX_VALUE_OTHER = 32767 MIN_VALUE_OTHER = -32767 EXT_RANGE_VOLTS = 5 def __init__(self, serialNumber=None, connect=True): """Load DLL etc.""" if platform.system() == 'Linux': from ctypes import cdll self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") elif platform.system() == 'Darwin': from picoscope.darwin_utils import LoadLibraryDarwin self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") else: from ctypes import windll self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll")) self.resolution = self.ADC_RESOLUTIONS["8"] super(PS3000a, self).__init__(serialNumber, connect) def _lowLevelOpenUnit(self, sn): c_handle = c_int16() if sn is not None: serialNullTermStr = byref(create_string_buffer(sn)) else: serialNullTermStr = None # Passing None is the same as passing NULL m = self.lib.ps3000aOpenUnit(byref(c_handle), serialNullTermStr) self.handle = c_handle.value # copied over from ps5000a: # This will check if the power supply is not connected # and change the power supply accordingly # Personally (me = Mark), I don't like this # since the user should address this immediately, and we # shouldn't let this go as a soft error # but I think this should do for now if m == 0x11A: self.changePowerSource("PICO_POWER_SUPPLY_NOT_CONNECTED") else: self.checkResult(m) def _lowLevelCloseUnit(self): m = self.lib.ps3000aCloseUnit(c_int16(self.handle)) self.checkResult(m) def _lowLevelEnumerateUnits(self): count = c_int16(0) m = self.lib.ps3000aEnumerateUnits(byref(count), None, None) 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.ps3000aEnumerateUnits(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): m = self.lib.ps3000aSetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset)) self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps3000aStop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps3000aGetUnitInfo(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.ps3000aGetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps3000aFlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, auto): m = self.lib.ps3000aSetSimpleTrigger( c_int16(self.handle), c_int16(enabled), c_enum(trigsrc), c_int16(threshold_adc), c_enum(direction), c_uint32(delay), c_int16(auto)) self.checkResult(m) def _lowLevelSetNoOfCaptures(self, numCaptures): m = self.lib.ps3000aSetNoOfCaptures(c_int16(self.handle), c_uint16(numCaptures)) self.checkResult(m) def _lowLevelMemorySegments(self, numSegments): maxSamples = c_int32() m = self.lib.ps3000aMemorySegments(c_int16(self.handle), c_uint16(numSegments), byref(maxSamples)) self.checkResult(m) return maxSamples.value def _lowLevelGetMaxSegments(self): maxSegments = c_int16() m = self.lib.ps3000aGetMaxSegments(c_int16(self.handle), byref(maxSegments)) self.checkResult(m) return maxSegments.value def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, timebase, oversample, segmentIndex): # NOT: Oversample is NOT used! timeIndisposedMs = c_int32() m = self.lib.ps3000aRunBlock( c_int16(self.handle), c_uint32(numPreTrigSamples), c_uint32(numPostTrigSamples), c_uint32(timebase), c_int16(oversample), byref(timeIndisposedMs), c_uint32(segmentIndex), c_void_p(), c_void_p()) self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps3000aIsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowlevelPingUnit(self): m = self.lib.ps3000aPingUnit(c_int16(self.handle)) return m def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() intervalNanoSec = c_float() m = self.lib.ps3000aGetTimebase2(c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), byref(intervalNanoSec), c_int16(oversample), byref(maxSamples), c_uint32(segmentIndex)) self.checkResult(m) # divide by 1e9 to return interval in seconds return (intervalNanoSec.value * 1e-9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Convert sample time in S to something to pass to API Call.""" maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) if sampleTimeS < 8.0E-9: st = math.floor(math.log(sampleTimeS * 1E9, 2)) st = max(st, 0) else: if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime st = math.floor((sampleTimeS * 125000000) + 2) # is this cast needed? st = int(st) return st def getTimestepFromTimebase(self, timebase): """Take API timestep code and returns the sampling period. API timestep is an integer from 0-32 """ if timebase < 3: dt = 2. ** timebase / 1.0E9 else: dt = (timebase - 2.0) / 125000000. return dt 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.ps3000aSetSigGenArbitrary( 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. """ dataPtr = data.ctypes.data_as(POINTER(c_int16)) numSamples = len(data) m = self.lib.ps3000aSetDataBuffer( c_int16(self.handle), c_enum(channel), dataPtr, c_int32(numSamples), c_uint32(segmentIndex), c_enum(downSampleMode)) self.checkResult(m) def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, downSampleMode): """Set the bulk data buffer. In ps3000a, ps3000aSetDataBuffer combines the functionality of psX000YSetDataBuffer and psX000YSetDataBufferBulk. Since the rapid block functions in picoscope.py call the Bulk version, a delegator is needed. Note that the order of segmentIndex and downSampleMode is reversed. """ self._lowLevelSetDataBuffer(channel, data, downSampleMode, segmentIndex) def _lowLevelSetMultipleDataBuffers(self, channel, data, downSampleMode): max_segments = self._lowLevelGetMaxSegments() if data.shape[0] < max_segments: raise ValueError("data array has fewer rows than current number " + "of memory segments") if data.shape[1] < self.maxSamples: raise ValueError("data array has fewer columns than maxSamples") for i in range(max_segments): m = self._lowLevelSetDataBuffer(channel, data[i, :], downSampleMode, i) self.checkResult(m) def _lowLevelClearDataBuffer(self, channel, segmentIndex): m = self.lib.ps3000aSetDataBuffer(c_int16(self.handle), c_enum(channel), c_void_p(), c_uint32(0), c_uint32(segmentIndex), 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.ps3000aGetValues( 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 _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, downSampleRatio, downSampleMode, overflow): m = self.lib.ps3000aGetValuesBulk( c_int16(self.handle), byref(c_uint32(numSamples)), c_uint32(fromSegment), c_uint32(toSegment), c_uint32(downSampleRatio), c_int16(downSampleMode), overflow.ctypes.data_as(POINTER(c_int16)) ) self.checkResult(m) return overflow, numSamples 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.ps3000aSetSigGenBuiltIn( 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 _lowLevelChangePowerSource(self, powerstate): m = self.lib.ps3000aChangePowerSource( c_int16(self.handle), c_enum(powerstate)) self.checkResult(m)
class PS5000(_PicoscopeBase): """The following are low-level functions for the PS5000.""" LIBNAME = "ps5000" MAX_VALUE = 32521 MIN_VALUE = -32521 # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) EXT_MAX_VALUE = 32767 EXT_MIN_VALUE = -32767 EXT_RANGE_VOLTS = 1 # The 10V and 20V ranges are only allowed in high impedence modes CHANNEL_RANGE = [{ "rangeV": 10E-3, "apivalue": 1, "rangeStr": "10 mV" }, { "rangeV": 20E-3, "apivalue": 2, "rangeStr": "20 mV" }, { "rangeV": 50E-3, "apivalue": 3, "rangeStr": "50 mV" }, { "rangeV": 100E-3, "apivalue": 4, "rangeStr": "100 mV" }, { "rangeV": 200E-3, "apivalue": 5, "rangeStr": "200 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 = {"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): """Load DLLs.""" 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(PS5000, self).__init__(serialNumber, connect) 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.ps5000OpenUnit(byref(c_handle), serialNumberStr) 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.ps5000OpenUnitAsync(byref(c_status), serialNumberStr) self.checkResult(m) return c_status.value def _lowLevelOpenUnitProgress(self): complete = c_int16() progressPercent = c_int16() handle = c_int16() m = self.lib.ps5000OpenUnitProgress(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.ps5000CloseUnit(c_int16(self.handle)) self.checkResult(m) def _lowLevelEnumerateUnits(self): count = c_int16(0) m = self.lib.ps5000EnumerateUnits(byref(count), None, None) 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.ps5000EnumerateUnits(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): m = self.lib.ps5000SetChannel(c_int16(self.handle), c_enum(chNum), c_int16(enabled), c_enum(coupling), c_enum(VRange), c_float(VOffset), c_enum(BWLimited)) # 2 for PS6404 self.checkResult(m) def _lowLevelStop(self): m = self.lib.ps5000Stop(c_int16(self.handle)) self.checkResult(m) def _lowLevelGetUnitInfo(self, info): s = create_string_buffer(256) requiredSize = c_int16(0) m = self.lib.ps6000GetUnitInfo(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.ps5000GetUnitInfo(c_int16(self.handle), byref(s), c_int16(len(s)), byref(requiredSize), c_enum(info)) self.checkResult(m) # should this bee ascii instead? # I think they are equivalent... return s.value.decode('utf-8') def _lowLevelFlashLed(self, times): m = self.lib.ps5000FlashLed(c_int16(self.handle), c_int16(times)) self.checkResult(m) def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, direction, delay, timeout_ms): m = self.lib.ps5000SetSimpleTrigger(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): timeIndisposedMs = c_int32() m = self.lib.ps5000RunBlock(c_int16(self.handle), c_uint32(numPreTrigSamples), c_uint32(numPostTrigSamples), c_uint32(timebase), c_int16(oversample), byref(timeIndisposedMs), c_uint32(segmentIndex), c_void_p(), c_void_p()) # According to the documentation, 'callback, pParameter' should work # instead of the last two c_void_p parameters. # However to avoid the potential for serious crashes, we decided to # not include them in this function call. self.checkResult(m) return timeIndisposedMs.value def _lowLevelIsReady(self): ready = c_int16() m = self.lib.ps5000IsReady(c_int16(self.handle), byref(ready)) self.checkResult(m) if ready.value: return True else: return False def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): """Return (timeIntervalSeconds, maxSamples).""" maxSamples = c_int32() sampleRate = c_float() m = self.lib.ps5000GetTimebase2(c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), byref(sampleRate), c_int16(oversample), byref(maxSamples), c_uint32(segmentIndex)) self.checkResult(m) return (sampleRate.value / 1.0E9, maxSamples.value) def getTimeBaseNum(self, sampleTimeS): """Return sample time in seconds to timebase as int for API calls.""" maxSampleTime = (((2**32 - 1) - 2) / 125000000) if sampleTimeS < 8E-9: timebase = math.floor(math.log(sampleTimeS * 1E9, 2)) timebase = max(timebase, 0) else: # Otherwise in range 2^32-1 if sampleTimeS > maxSampleTime: sampleTimeS = maxSampleTime timebase = math.floor((sampleTimeS * 125000000) + 2) # is this cast needed? timebase = int(timebase) return timebase def getTimestepFromTimebase(self, timebase): """Return timebase to sampletime as seconds.""" if timebase < 3: dt = 2.**timebase / 1E9 else: dt = (timebase - 2) / 125000000. return dt 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.ps5000SetDataBuffer(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.ps5000SetDataBuffer(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.ps5000GetValues(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) #################################################################### # Untested functions below # # # #################################################################### 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.ps5000SetSigGenArbitrary( 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 if stopFreq is None: stopFreq = frequency m = self.lib.ps5000SetSigGenBuiltIn( 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 _lowLevelGetAnalogueOffset(self, range, coupling): # TODO, populate properties with this function maximumVoltage = c_float() minimumVoltage = c_float() m = self.lib.ps5000GetAnalogueOffset(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.ps5000GetMaxDownSampleRatio( 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.ps5000GetNoOfCaptures(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.ps5000GetTriggerTimeOffset64(c_int16(self.handle), byref(time), byref(timeUnits), c_uint32(segmentIndex)) self.checkResult(m) if timeUnits.value == 0: # PS5000_FS return time.value * 1E-15 elif timeUnits.value == 1: # PS5000_PS return time.value * 1E-12 elif timeUnits.value == 2: # PS5000_NS return time.value * 1E-9 elif timeUnits.value == 3: # PS5000_US return time.value * 1E-6 elif timeUnits.value == 4: # PS5000_MS return time.value * 1E-3 elif timeUnits.value == 5: # PS5000_S return time.value * 1E0 else: raise TypeError("Unknown timeUnits %d" % timeUnits.value) def _lowLevelMemorySegments(self, nSegments): nMaxSamples = c_uint32() m = self.lib.ps5000MemorySegments(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.ps5000SetDataBuffers(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.ps5000SetDataBuffers(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.ps5000GetValuesBulk( 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.ps5000SetDataBufferBulk(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.ps5000SetDataBuffersBulk(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.ps5000SetNoOfCaptures(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.ps5000NoOfStreamingValues(c_int16(self.handle), byref(noOfValues)) self.checkResult(m) return noOfValues.value def _lowLevelRunStreaming(): pass def _lowLevelStreamingReady(): pass