Skip to content

Commit

Permalink
Merge pull request #148 from charithjperera/charithjperera/ps4000a_bl…
Browse files Browse the repository at this point in the history
…ock_mode

Charithjperera/ps4000a block mode
  • Loading branch information
hmaarrfk authored Jan 25, 2019
2 parents 831bd78 + 7794584 commit e90c52d
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 69 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ around, but this one tries to improve on them via:
System has support for:
* PS6000
* PS5000A Class (PicoScope 5242A/5243A/5244A/5442A/5443A/5444A/5242B/5244B/5442B/5443B/5444B)
* PS4000A Class (PicoScope 4444/4824)
* PS3000 Class (PicoScope 3204/3205/3206/3224/3424/3425)
* PS3000A Class (PicoScope 3204A/3204B/3205A/3205B/3206A/3206B/3207A/3207B/3204/3205/3206/3404A/3404B/3405A/3405A/3406A/3406B)
* PS2000 Class (PicoScope 2104/2105/2202/2203/2204/2205/2204A/2205A)
Expand Down
45 changes: 45 additions & 0 deletions examples/test_ps4000a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from picoscope import ps4000a
import matplotlib.pyplot as plt
import numpy as np
import time

ps = ps4000a.PS4000a()

# rapid block mode

ps.setChannel(channel="A", coupling="DC", VRange=1)
ps.setChannel(channel="B", enabled=False)
ps.setChannel(channel="C", enabled=False)
ps.setChannel(channel="D", enabled=False)

n_captures = 100
sample_interval = 100e-9 # 100 ns
sample_duration = 2e-3 # 1 ms
ps.setResolution('12') # Resolution can only be set on the PS4444
ps.setSamplingInterval(sample_interval, sample_duration)
ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1)

samples_per_segment = ps.memorySegments(n_captures)
ps.setNoOfCaptures(n_captures)

data = np.zeros((n_captures, samples_per_segment), dtype=np.int16)

t1 = time.time()

ps.runBlock()
ps.waitReady()

t2 = time.time()
print("Time to record data to scope: ", str(t2 - t1))

ps.getDataRawBulk(data=data)

t3 = time.time()
print("Time to copy to RAM: ", str(t3 - t2))

plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none',
cmap=plt.cm.hot)
plt.colorbar()
plt.show()

ps.close()
7 changes: 5 additions & 2 deletions picoscope/picobase.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,8 @@ def setAWGSimpleDeltaPhase(self, waveform, deltaPhase, offsetVoltage=None,
pkToPk=None, indexMode="Single", shots=1,
triggerType="Rising",
triggerSource="ScopeTrig"):
"""Specify deltaPhase between each sample and not the total waveform duration.
"""Specify deltaPhase between each sample and not the total waveform
duration.
Returns the actual time duration of the waveform
Expand Down Expand Up @@ -829,7 +830,9 @@ def sigGenSoftwareControl(self, state=True):
self._lowLevelSigGenSoftwareControl(state)

def setResolution(self, resolution):
"""For 5000-series scopes ONLY, sets the resolution."""
"""For 5000-series or certain 4000-series scopes ONLY,
sets the resolution.
"""
self._lowLevelSetDeviceResolution(self.ADC_RESOLUTIONS[resolution])

def enumerateUnits(self):
Expand Down
188 changes: 121 additions & 67 deletions picoscope/ps4000a.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
c_int16, c_uint16, c_int32, c_uint32, c_uint64, c_void_p, c_int8
from ctypes import c_int32 as c_enum

import warnings

from picoscope.picobase import _PicoscopeBase


Expand All @@ -73,7 +75,7 @@ class PS4000a(_PicoscopeBase):
MAX_VALUE = 32764
MIN_VALUE = -32764

# EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B)
# EXT/AUX seems to have an input impedance of 50 ohm (PS6403B)
EXT_MAX_VALUE = 32767
EXT_MIN_VALUE = -32767
EXT_RANGE_VOLTS = 1
Expand All @@ -97,6 +99,8 @@ class PS4000a(_PicoscopeBase):
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,
Expand Down Expand Up @@ -129,6 +133,8 @@ def __init__(self, serialNumber=None, connect=True):
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):
Expand All @@ -142,6 +148,8 @@ def _lowLevelOpenUnit(self, sn):
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:
Expand Down Expand Up @@ -277,29 +285,77 @@ def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex):
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) - 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
""" 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.
"""

timebase = math.floor((sampleTimeS * 2e7) + 1)
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 timebase < 3:
dt = 2. ** timebase / 8e7
else:
dt = (timebase - 1) / 2e7
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,
Expand All @@ -313,11 +369,12 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode,
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),
byref(data), c_uint32(numSamples),
dataPtr, c_uint32(numSamples),
c_uint32(segmentIndex),
c_uint32(downSampleMode))
self.checkResult(m)
Expand All @@ -342,6 +399,37 @@ def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio,
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 #
# #
Expand Down Expand Up @@ -449,95 +537,61 @@ def _lowLevelClearDataBuffers(self, channel):
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.ps4000aGetValuesBulk(
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.ps4000aSetDataBufferBulk(
c_int16(self.handle),
c_enum(channel),
bufferPtr,
c_uint32(bufferLth),
c_uint16(waveform))
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():
def _lowLevelSetEts(self):
pass

def _lowLevelSetEtsTimeBuffer():
def _lowLevelSetEtsTimeBuffer(self):
pass

def _lowLevelSetEtsTimeBuffers():
def _lowLevelSetEtsTimeBuffers(self):
pass

def _lowLevelSetExternalClock():
def _lowLevelSetExternalClock(self):
pass

# Complicated triggering
# need to understand structs for this one to work
def _lowLevelIsTriggerOrPulseWidthQualifierEnabled():
def _lowLevelIsTriggerOrPulseWidthQualifierEnabled(self):
pass

def _lowLevelGetValuesTriggerTimeOffsetBulk():
def _lowLevelGetValuesTriggerTimeOffsetBulk(self):
pass

def _lowLevelSetTriggerChannelConditions():
def _lowLevelSetTriggerChannelConditions(self):
pass

def _lowLevelSetTriggerChannelDirections():
def _lowLevelSetTriggerChannelDirections(self):
pass

def _lowLevelSetTriggerChannelProperties():
def _lowLevelSetTriggerChannelProperties(self):
pass

def _lowLevelSetPulseWidthQualifier():
def _lowLevelSetPulseWidthQualifier(self):
pass

def _lowLevelSetTriggerDelay():
def _lowLevelSetTriggerDelay(self):
pass

# Async functions
# would be nice, but we would have to learn to implement callbacks
def _lowLevelGetValuesAsync():
def _lowLevelGetValuesAsync(self):
pass

def _lowLevelGetValuesBulkAsync():
def _lowLevelGetValuesBulkAsync(self):
pass

# overlapped functions
def _lowLevelGetValuesOverlapped():
def _lowLevelGetValuesOverlapped(self):
pass

def _lowLevelGetValuesOverlappedBulk():
def _lowLevelGetValuesOverlappedBulk(self):
pass

# Streaming related functions
Expand Down Expand Up @@ -575,5 +629,5 @@ def _lowLevelRunStreaming(self, sampleInterval, sampleIntervalTimeUnits,

self.checkResult(m)

def _lowLevelStreamingReady():
def _lowLevelStreamingReady(self):
pass

0 comments on commit e90c52d

Please sign in to comment.