Skip to content

Commit e90c52d

Browse files
authored
Merge pull request #148 from charithjperera/charithjperera/ps4000a_block_mode
Charithjperera/ps4000a block mode
2 parents 831bd78 + 7794584 commit e90c52d

File tree

4 files changed

+172
-69
lines changed

4 files changed

+172
-69
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ around, but this one tries to improve on them via:
1212
System has support for:
1313
* PS6000
1414
* PS5000A Class (PicoScope 5242A/5243A/5244A/5442A/5443A/5444A/5242B/5244B/5442B/5443B/5444B)
15+
* PS4000A Class (PicoScope 4444/4824)
1516
* PS3000 Class (PicoScope 3204/3205/3206/3224/3424/3425)
1617
* PS3000A Class (PicoScope 3204A/3204B/3205A/3205B/3206A/3206B/3207A/3207B/3204/3205/3206/3404A/3404B/3405A/3405A/3406A/3406B)
1718
* PS2000 Class (PicoScope 2104/2105/2202/2203/2204/2205/2204A/2205A)

examples/test_ps4000a.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from picoscope import ps4000a
2+
import matplotlib.pyplot as plt
3+
import numpy as np
4+
import time
5+
6+
ps = ps4000a.PS4000a()
7+
8+
# rapid block mode
9+
10+
ps.setChannel(channel="A", coupling="DC", VRange=1)
11+
ps.setChannel(channel="B", enabled=False)
12+
ps.setChannel(channel="C", enabled=False)
13+
ps.setChannel(channel="D", enabled=False)
14+
15+
n_captures = 100
16+
sample_interval = 100e-9 # 100 ns
17+
sample_duration = 2e-3 # 1 ms
18+
ps.setResolution('12') # Resolution can only be set on the PS4444
19+
ps.setSamplingInterval(sample_interval, sample_duration)
20+
ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1)
21+
22+
samples_per_segment = ps.memorySegments(n_captures)
23+
ps.setNoOfCaptures(n_captures)
24+
25+
data = np.zeros((n_captures, samples_per_segment), dtype=np.int16)
26+
27+
t1 = time.time()
28+
29+
ps.runBlock()
30+
ps.waitReady()
31+
32+
t2 = time.time()
33+
print("Time to record data to scope: ", str(t2 - t1))
34+
35+
ps.getDataRawBulk(data=data)
36+
37+
t3 = time.time()
38+
print("Time to copy to RAM: ", str(t3 - t2))
39+
40+
plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none',
41+
cmap=plt.cm.hot)
42+
plt.colorbar()
43+
plt.show()
44+
45+
ps.close()

picoscope/picobase.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,8 @@ def setAWGSimpleDeltaPhase(self, waveform, deltaPhase, offsetVoltage=None,
646646
pkToPk=None, indexMode="Single", shots=1,
647647
triggerType="Rising",
648648
triggerSource="ScopeTrig"):
649-
"""Specify deltaPhase between each sample and not the total waveform duration.
649+
"""Specify deltaPhase between each sample and not the total waveform
650+
duration.
650651
651652
Returns the actual time duration of the waveform
652653
@@ -829,7 +830,9 @@ def sigGenSoftwareControl(self, state=True):
829830
self._lowLevelSigGenSoftwareControl(state)
830831

831832
def setResolution(self, resolution):
832-
"""For 5000-series scopes ONLY, sets the resolution."""
833+
"""For 5000-series or certain 4000-series scopes ONLY,
834+
sets the resolution.
835+
"""
833836
self._lowLevelSetDeviceResolution(self.ADC_RESOLUTIONS[resolution])
834837

835838
def enumerateUnits(self):

picoscope/ps4000a.py

Lines changed: 121 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
c_int16, c_uint16, c_int32, c_uint32, c_uint64, c_void_p, c_int8
6363
from ctypes import c_int32 as c_enum
6464

65+
import warnings
66+
6567
from picoscope.picobase import _PicoscopeBase
6668

6769

@@ -73,7 +75,7 @@ class PS4000a(_PicoscopeBase):
7375
MAX_VALUE = 32764
7476
MIN_VALUE = -32764
7577

76-
# EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B)
78+
# EXT/AUX seems to have an input impedance of 50 ohm (PS6403B)
7779
EXT_MAX_VALUE = 32767
7880
EXT_MIN_VALUE = -32767
7981
EXT_RANGE_VOLTS = 1
@@ -97,6 +99,8 @@ class PS4000a(_PicoscopeBase):
9799
CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6,
98100
"H": 7, "MaxChannels": 8}
99101

102+
ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4}
103+
100104
CHANNEL_COUPLINGS = {"DC50": 2, "DC": 1, "AC": 0}
101105

102106
WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2,
@@ -129,6 +133,8 @@ def __init__(self, serialNumber=None, connect=True):
129133
from ctypes import windll
130134
self.lib = windll.LoadLibrary(str(self.LIBNAME + ".dll"))
131135

136+
self.resolution = self.ADC_RESOLUTIONS["12"]
137+
132138
super(PS4000a, self).__init__(serialNumber, connect)
133139

134140
def _lowLevelOpenUnit(self, sn):
@@ -142,6 +148,8 @@ def _lowLevelOpenUnit(self, sn):
142148
self.checkResult(m)
143149
self.handle = c_handle.value
144150

151+
self.model = self.getUnitInfo('VariantInfo')
152+
145153
def _lowLevelOpenUnitAsync(self, sn):
146154
c_status = c_int16()
147155
if sn is not None:
@@ -277,29 +285,77 @@ def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex):
277285
return (sampleRate.value / 1.0E9, maxSamples.value)
278286

279287
def getTimeBaseNum(self, sampleTimeS):
280-
"""Return sample time in seconds to timebase as int for API calls."""
281-
maxSampleTime = (((2 ** 32 - 1) - 4) / 2e7)
282-
283-
if sampleTimeS <= 12.5E-9:
284-
timebase = math.floor(math.log(sampleTimeS * 8E7, 2))
285-
timebase = max(timebase, 0)
286-
else:
287-
# Otherwise in range 2^32-1
288-
if sampleTimeS > maxSampleTime:
289-
sampleTimeS = maxSampleTime
288+
""" Convert the sample interval (float of seconds) to the
289+
corresponding integer timebase value as defined by the API.
290+
See "Timebases" section of the PS4000a programmers guide
291+
for more information.
292+
"""
290293

291-
timebase = math.floor((sampleTimeS * 2e7) + 1)
294+
if self.model == '4828':
295+
maxSampleTime = (((2 ** 32 - 1) + 1) / 8E7)
296+
297+
if sampleTimeS <= 12.5E-9:
298+
timebase = 0
299+
else:
300+
# Otherwise in range 2^32-1
301+
if sampleTimeS > maxSampleTime:
302+
sampleTimeS = maxSampleTime
303+
304+
timebase = math.floor((sampleTimeS * 2e7) + 1)
305+
306+
elif self.model == '4444':
307+
maxSampleTime = (((2 ** 32 - 1) - 2) / 5.0E7)
308+
309+
if (sampleTimeS <= 2.5E-9 and
310+
self.resolution == self.ADC_RESOLUTIONS["12"]):
311+
timebase = 0
312+
elif (sampleTimeS <= 20E-9 and
313+
self.resolution == self.ADC_RESOLUTIONS["14"]):
314+
timebase = 3
315+
else:
316+
# Otherwise in range 2^32-1
317+
if sampleTimeS > maxSampleTime:
318+
sampleTimeS = maxSampleTime
319+
320+
timebase = math.floor((sampleTimeS * 5.0E7) + 2)
321+
322+
else: # The original case from non "A" series
323+
warnings.warn("The model PS4000a you are using may not be "
324+
"fully supported", stacklevel=2)
325+
maxSampleTime = (((2 ** 32 - 1) - 4) / 2e7)
326+
327+
if sampleTimeS <= 12.5E-9:
328+
timebase = math.floor(math.log(sampleTimeS * 8E7, 2))
329+
timebase = max(timebase, 0)
330+
else:
331+
# Otherwise in range 2^32-1
332+
if sampleTimeS > maxSampleTime:
333+
sampleTimeS = maxSampleTime
334+
335+
timebase = math.floor((sampleTimeS * 2e7) + 1)
292336

293337
# is this cast needed?
294338
timebase = int(timebase)
295339
return timebase
296340

297341
def getTimestepFromTimebase(self, timebase):
298342
"""Return timebase to sampletime as seconds."""
299-
if timebase < 3:
300-
dt = 2. ** timebase / 8e7
301-
else:
302-
dt = (timebase - 1) / 2e7
343+
if self.model == '4828':
344+
dt = (timebase + 1) / 8.0E7
345+
elif self.model == '4444':
346+
if timebase < 3:
347+
dt = 2.5 ** timebase / 4.0E8
348+
else:
349+
dt = (timebase - 2) / 5.0E7
350+
351+
else: # The original case from non "A" series
352+
warnings.warn("The model PS4000a you are using may not be "
353+
"fully supported", stacklevel=2)
354+
if timebase < 3:
355+
dt = 2. ** timebase / 8e7
356+
else:
357+
dt = (timebase - 1) / 2e7
358+
return dt
303359
return dt
304360

305361
def _lowLevelSetDataBuffer(self, channel, data, downSampleMode,
@@ -313,11 +369,12 @@ def _lowLevelSetDataBuffer(self, channel, data, downSampleMode,
313369
segmentIndex is unused, but required by other versions of the API
314370
(eg PS5000a)
315371
"""
372+
dataPtr = data.ctypes.data_as(POINTER(c_int16))
316373
numSamples = len(data)
317374

318375
m = self.lib.ps4000aSetDataBuffer(c_int16(self.handle),
319376
c_enum(channel),
320-
byref(data), c_uint32(numSamples),
377+
dataPtr, c_uint32(numSamples),
321378
c_uint32(segmentIndex),
322379
c_uint32(downSampleMode))
323380
self.checkResult(m)
@@ -342,6 +399,37 @@ def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio,
342399
self.checkResult(m)
343400
return (numSamplesReturned.value, overflow.value)
344401

402+
def _lowLevelSetDeviceResolution(self, resolution):
403+
self.resolution = resolution
404+
m = self.lib.ps4000aSetDeviceResolution(
405+
c_int16(self.handle),
406+
c_enum(resolution))
407+
self.checkResult(m)
408+
409+
def _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment,
410+
downSampleRatio, downSampleMode, overflow):
411+
"""Copy data from several memory segments at once."""
412+
overflowPoint = overflow.ctypes.data_as(POINTER(c_int16))
413+
m = self.lib.ps4000aGetValuesBulk(
414+
c_int16(self.handle),
415+
byref(c_int32(numSamples)),
416+
c_int32(fromSegment),
417+
c_int32(toSegment),
418+
c_int32(downSampleRatio),
419+
c_enum(downSampleMode),
420+
overflowPoint
421+
)
422+
self.checkResult(m)
423+
424+
def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex,
425+
downSampleMode):
426+
"""Just calls setDataBuffer with argument order changed.
427+
428+
For compatibility with current picobase.py.
429+
"""
430+
self._lowLevelSetDataBuffer(channel, data, downSampleMode,
431+
segmentIndex)
432+
345433
####################################################################
346434
# Untested functions below #
347435
# #
@@ -449,95 +537,61 @@ def _lowLevelClearDataBuffers(self, channel):
449537
c_uint32(0))
450538
self.checkResult(m)
451539

452-
# Bulk values.
453-
# These would be nice, but the user would have to provide us
454-
# with an array.
455-
# we would have to make sure that it is contiguous amonts other things
456-
def _lowLevelGetValuesBulk(self,
457-
numSamples,
458-
fromSegmentIndex,
459-
toSegmentIndex,
460-
downSampleRatio,
461-
downSampleMode,
462-
overflow):
463-
noOfSamples = c_uint32(numSamples)
464-
465-
m = self.lib.ps4000aGetValuesBulk(
466-
c_int16(self.handle),
467-
byref(noOfSamples),
468-
c_uint16(fromSegmentIndex), c_uint16(toSegmentIndex),
469-
overflow.ctypes.data_as(POINTER(c_int16)))
470-
self.checkResult(m)
471-
return noOfSamples.value
472-
473-
def _lowLevelSetDataBufferBulk(self, channel, buffer, waveform,
474-
downSampleRatioMode):
475-
bufferPtr = buffer.ctypes.data_as(POINTER(c_int16))
476-
bufferLth = len(buffer)
477-
478-
m = self.lib.ps4000aSetDataBufferBulk(
479-
c_int16(self.handle),
480-
c_enum(channel),
481-
bufferPtr,
482-
c_uint32(bufferLth),
483-
c_uint16(waveform))
484-
self.checkResult(m)
485-
486540
def _lowLevelSetNoOfCaptures(self, nCaptures):
487541
m = self.lib.ps4000aSetNoOfCaptures(
488542
c_int16(self.handle),
489543
c_uint16(nCaptures))
490544
self.checkResult(m)
491545

492546
# ETS Functions
493-
def _lowLevelSetEts():
547+
def _lowLevelSetEts(self):
494548
pass
495549

496-
def _lowLevelSetEtsTimeBuffer():
550+
def _lowLevelSetEtsTimeBuffer(self):
497551
pass
498552

499-
def _lowLevelSetEtsTimeBuffers():
553+
def _lowLevelSetEtsTimeBuffers(self):
500554
pass
501555

502-
def _lowLevelSetExternalClock():
556+
def _lowLevelSetExternalClock(self):
503557
pass
504558

505559
# Complicated triggering
506560
# need to understand structs for this one to work
507-
def _lowLevelIsTriggerOrPulseWidthQualifierEnabled():
561+
def _lowLevelIsTriggerOrPulseWidthQualifierEnabled(self):
508562
pass
509563

510-
def _lowLevelGetValuesTriggerTimeOffsetBulk():
564+
def _lowLevelGetValuesTriggerTimeOffsetBulk(self):
511565
pass
512566

513-
def _lowLevelSetTriggerChannelConditions():
567+
def _lowLevelSetTriggerChannelConditions(self):
514568
pass
515569

516-
def _lowLevelSetTriggerChannelDirections():
570+
def _lowLevelSetTriggerChannelDirections(self):
517571
pass
518572

519-
def _lowLevelSetTriggerChannelProperties():
573+
def _lowLevelSetTriggerChannelProperties(self):
520574
pass
521575

522-
def _lowLevelSetPulseWidthQualifier():
576+
def _lowLevelSetPulseWidthQualifier(self):
523577
pass
524578

525-
def _lowLevelSetTriggerDelay():
579+
def _lowLevelSetTriggerDelay(self):
526580
pass
527581

528582
# Async functions
529583
# would be nice, but we would have to learn to implement callbacks
530-
def _lowLevelGetValuesAsync():
584+
def _lowLevelGetValuesAsync(self):
531585
pass
532586

533-
def _lowLevelGetValuesBulkAsync():
587+
def _lowLevelGetValuesBulkAsync(self):
534588
pass
535589

536590
# overlapped functions
537-
def _lowLevelGetValuesOverlapped():
591+
def _lowLevelGetValuesOverlapped(self):
538592
pass
539593

540-
def _lowLevelGetValuesOverlappedBulk():
594+
def _lowLevelGetValuesOverlappedBulk(self):
541595
pass
542596

543597
# Streaming related functions
@@ -575,5 +629,5 @@ def _lowLevelRunStreaming(self, sampleInterval, sampleIntervalTimeUnits,
575629

576630
self.checkResult(m)
577631

578-
def _lowLevelStreamingReady():
632+
def _lowLevelStreamingReady(self):
579633
pass

0 commit comments

Comments
 (0)