"""
Base class for the PicoScopes that have a header file which ends with \\*Api.h.
Namely, ps2000aApi, ps3000aApi, ps4000Api, ps4000aApi, ps5000Api, ps5000aApi and ps6000Api.
"""
from ctypes import (c_int8, c_int16, c_uint16, c_int32, c_uint32, c_int64,
c_float, c_void_p, byref, cast, POINTER)
import numpy as np
from msl.equipment.resources.picotech import c_enum
from .enums import PicoScopeInfoApi
from .picoscope import PicoScope
from ..errors import (
PICO_OK,
PICO_BUSY,
PICO_POWER_SUPPLY_CONNECTED,
PICO_POWER_SUPPLY_NOT_CONNECTED,
ERROR_CODES_API
)
[docs]class PicoScopeApi(PicoScope):
def __init__(self, record, func_ptrs):
"""Base class for the PicoScopes that have a header file which ends with \\*Api.h.
Use the PicoScope SDK to communicate with the ps2000a, ps3000a, ps4000, ps4000a,
ps5000, ps5000a and ps6000 oscilloscopes.
Do not instantiate this class directly. Use the :meth:`~.EquipmentRecord.connect`
method to connect to the equipment.
Parameters
----------
record : :class:`~.EquipmentRecord`
A record from an :ref:`equipment-database`.
func_ptrs : :mod:`.functions`
The appropriate function-pointer list for the SDK.
"""
super(PicoScopeApi, self).__init__(record, func_ptrs)
self.enPicoScopeInfo = PicoScopeInfoApi
self._buffer_size = None
# check the equipment_record.connection.properties dictionary to see how to initialize the PicoScope
properties = self.equipment_record.connection.properties
self._auto_select_power = properties.get('auto_select_power', True)
resolution = properties.get('resolution', '8BIT')
open_unit = properties.get('open', True)
open_unit_async = properties.get('open_async', None)
if open_unit and open_unit_async is None:
self.open_unit(self._auto_select_power, resolution)
elif open_unit_async:
self.open_unit_async(self._auto_select_power, resolution)
[docs] def errcheck_api(self, result, func, args):
"""The SDK function returns PICO_OK if the function call was successful."""
self.log_errcheck(result, func, args)
if result == PICO_BUSY:
self.log_info('%s is busy...', self._base_msg)
return result
if result != PICO_OK:
conn = self.equipment_record.connection
error_name, msg = ERROR_CODES_API.get(result, ('UnhandledError', 'Error code 0x{:x}'.format(result)))
error_msg = msg.format(
model=conn.model,
serial=conn.serial,
sdk_filename=self.SDK_FILENAME,
sdk_filename_upper=self.SDK_FILENAME.upper()
)
self.raise_exception('{}: {}'.format(error_name, error_msg))
return result
[docs] def change_power_source(self, power_state):
"""Change the power source.
This function selects the power supply mode. You must call this function if any of the
following conditions arises:
* USB power is required
* the AC power adapter is connected or disconnected during use
* a USB 3.0 scope is plugged into a USB 2.0 port (indicated if any function returns
the ``PICO_USB3_0_DEVICE_NON_USB3_0_PORT`` status code)
This function is only valid for ps3000a, ps4000a and ps5000a.
"""
return self.ChangePowerSource(self._handle, power_state)
[docs] def current_power_source(self):
"""
This function returns the current power state of the device.
This function is only valid for ps3000a, ps4000a and ps5000a.
"""
ret = self.CurrentPowerSource(self._handle)
if ret == PICO_POWER_SUPPLY_CONNECTED:
return 'AC adaptor'
elif ret == PICO_POWER_SUPPLY_NOT_CONNECTED:
return 'USB cable'
else:
self.errcheck_api(ret, self.current_power_source, ())
[docs] def flash_led(self, action):
"""
This function flashes the LED on the front of the scope without blocking the calling
thread. Calls to :meth:`~.PicoScope.run_streaming` and :meth:`~.PicoScope.run_block` cancel any flashing
started by this function. It is not possible to set the LED to be constantly illuminated,
as this state is used to indicate that the scope has not been initialized.
"""
return self.FlashLed(self._handle, action)
[docs] def get_analogue_offset(self, voltage_range, coupling):
"""
This function is used to get the maximum and minimum allowable analog offset for a
specific voltage range.
This function is invalid for ps4000 and ps5000.
"""
coupling_ = self.convert_to_enum(coupling, self.enCoupling, to_upper=True)
v_range = self.convert_to_enum(voltage_range, self.enRange, prefix='R_', to_upper=True)
maximum_voltage = c_float()
minimum_voltage = c_float()
self.GetAnalogueOffset(self._handle, v_range, coupling_, byref(maximum_voltage), byref(minimum_voltage))
return maximum_voltage.value, minimum_voltage.value
[docs] def get_max_down_sample_ratio(self, num_unaggreated_samples, mode='None', segment_index=0):
"""
Returns
-------
:class:`int`
This function returns the maximum down-sampling ratio that can be used for a given
number of samples in a given down-sampling mode.
"""
mode_ = self.convert_to_enum(mode, self.enRatioMode, to_upper=True)
max_down_sample_ratio = c_uint32()
self.GetMaxDownSampleRatio(self._handle, num_unaggreated_samples, byref(max_down_sample_ratio),
mode_, segment_index)
return max_down_sample_ratio.value
[docs] def get_max_segments(self):
"""This function is valid for ps2000a, ps3000a, ps4000a and ps5000a.
Returns
-------
:class:`int`
This function returns the maximum number of segments allowed for the opened
device. This number is the maximum value of ``nsegments`` that can be passed to
:meth:`memory_segments`.
"""
max_segments = c_uint16() if self.IS_PS2000A else c_uint32()
self.GetMaxSegments(self._handle, byref(max_segments))
return max_segments.value
[docs] def get_no_of_captures(self):
"""
This function finds out how many captures are available in rapid block mode after
:meth:`~.PicoScope.run_block` has been called when either the collection completed or the
collection of waveforms was interrupted by calling :meth:`~.PicoScope.stop`. The returned value
(``nCaptures``) can then be used to iterate through the number of segments using
:meth:`get_values`, or in a single call to :meth:`get_values_bulk` where it is used
to calculate the ``toSegmentIndex`` parameter.
Not valid for ps5000.
"""
n_captures = c_uint16() if self.IS_PS4000 else c_uint32()
self.GetNoOfCaptures(self._handle, byref(n_captures))
return n_captures.value
[docs] def get_num_of_processed_captures(self):
"""
This function finds out how many captures in rapid block mode have been processed
after :meth:`~.PicoScope.run_block` has been called when either the collection completed or the
collection of waveforms was interrupted by calling :meth:`~.PicoScope.stop`. The returned value
(``nCaptures``) can then be used to iterate through the number of segments using
:meth:`get_values`, or in a single call to :meth:`get_values_bulk` where it is used
to calculate the ``toSegmentIndex`` parameter.
Not valid for ps4000 and ps5000.
"""
n_processed_captures = c_uint32()
self.GetNoOfProcessedCaptures(self._handle, byref(n_processed_captures))
return n_processed_captures.value
[docs] def get_streaming_latest_values(self, lp_ps):
"""
This function instructs the driver to return the next block of values to your
:mod:`StreamingReady callback <msl.equipment.resources.picotech.picoscope.callbacks>`.
You must have previously called :meth:`~.PicoScope.run_streaming` beforehand to set up streaming.
"""
p_parameter = c_void_p()
self.GetStreamingLatestValues(self._handle, lp_ps, byref(p_parameter))
[docs] def get_timebase(self, timebase, num_samples=0, segment_index=0, oversample=0):
"""
Since Python supports the :class:`float` data type, this function returns
:meth:`get_timebase2`. The timebase that is returned is in **seconds** (not ns).
This function calculates the sampling rate and maximum number of samples for a
given timebase under the specified conditions. The result will depend on the number of
channels enabled by the last call to :meth:`~.PicoScope.set_channel`.
The `oversample` argument is only used by ps4000, ps5000 and ps6000.
"""
return self.get_timebase2(timebase, num_samples, segment_index, oversample)
[docs] def get_timebase2(self, timebase, num_samples=0, segment_index=0, oversample=0):
"""
This function is an upgraded version of :meth:`get_timebase`, and returns the time
interval as a :class:`float` rather than an :class:`int`. This allows it to return
sub-nanosecond time intervals. See :meth:`get_timebase` for a full description.
The timebase that is returned is in **seconds** (not ns).
The `oversample` argument is only used by ps4000, ps5000 and ps6000.
"""
num_samples = int(num_samples)
time_interval_nanoseconds = c_float()
max_samples = c_uint32() if self.IS_PS6000 else c_int32()
if self.IS_PS4000A or self.IS_PS5000A:
self.GetTimebase2(self._handle, timebase, num_samples, byref(time_interval_nanoseconds),
byref(max_samples), segment_index)
else:
self.GetTimebase2(self._handle, timebase, num_samples, byref(time_interval_nanoseconds),
oversample, byref(max_samples), segment_index)
return time_interval_nanoseconds.value*1e-9, max_samples.value
[docs] def get_trigger_time_offset(self, segment_index=0):
"""
This function gets the time, as two 4-byte values, at which the trigger occurred. Call it
after block-mode data has been captured or when data has been retrieved from a
previous block-mode capture. A 64-bit version of this function,
:meth:`get_trigger_time_offset64`, is also available.
"""
time_upper = c_uint32()
time_lower = c_uint32()
time_units = c_enum()
self.GetTriggerTimeOffset(self._handle, byref(time_upper), byref(time_lower), byref(time_units), segment_index)
return time_upper.value, time_lower.value, self.enTimeUnits(time_units.value)
[docs] def get_trigger_time_offset64(self, segment_index=0):
"""
This function gets the time, as a single 64-bit value, at which the trigger occurred. Call
it after block-mode data has been captured or when data has been retrieved from a
previous block-mode capture. A 32-bit version of this function,
:meth:`get_trigger_time_offset`, is also available.
"""
time = c_int64()
time_units = c_enum()
self.GetTriggerTimeOffset64(self._handle, byref(time), byref(time_units), segment_index)
return time.value, self.enTimeUnits(time_units.value)
[docs] def get_values(self, num_samples=None, start_index=0, factor=1, ratio_mode='None', segment_index=0):
"""
This function returns block-mode data, with or without down sampling, starting at the
specified sample number. It is used to get the stored data from the driver after data
collection has stopped.
Parameters
----------
num_samples : :class:`int` or :data:`None`, optional
The number of samples required. If :data:`None` then automatically determine the
number of samples to retrieve.
start_index : :class:`int`, optional
A zero-based index that indicates the start point for data collection.
It is measured in sample intervals from the start of the buffer.
factor : :class:`int`, optional
The down-sampling factor that will be applied to the raw data.
ratio_mode : :class:`enum.IntEnum`, optional
Which down-sampling mode to use. A ``RatioMode`` enum.
segment_index : :class:`int`, optional
The zero-based number of the memory segment where the data is stored.
"""
overflow = c_int16()
if num_samples is None:
num_samples = self._num_samples
n_samples = c_uint32(num_samples)
mode = self.convert_to_enum(ratio_mode, self.enRatioMode, to_upper=True)
self.GetValues(self._handle, start_index, byref(n_samples), factor, mode, segment_index, byref(overflow))
return n_samples.value, overflow.value
[docs] def get_values_async(self, lp_data_ready, num_samples=None, start_index=0, factor=1, ratio_mode='None',
segment_index=0):
"""
This function returns data either with or without down sampling, starting at the
specified sample number. It is used to get the stored data from the scope after data
collection has stopped. It returns the data using the ``lp_data_ready`` callback.
Parameters
----------
lp_data_ready : :mod:`callback <msl.equipment.resources.picotech.picoscope.callbacks>`
A :mod:`DataReady callback <msl.equipment.resources.picotech.picoscope.callbacks>` function.
num_samples : :class:`int`, optional
The number of samples required. If :data:`None` then automatically determine the
number of samples to retrieve.
start_index : :class:`int`, optional
A zero-based index that indicates the start point for data collection.
It is measured in sample intervals from the start of the buffer.
factor : :class:`int`, optional
The downsampling factor that will be applied to the raw data.
ratio_mode : :class:`enum.IntEnum`, optional
Which down-sampling mode to use. A ``RatioMode`` enum.
segment_index : :class:`int`, optional
The zero-based number of the memory segment where the data is stored.
"""
p_parameter = c_void_p()
if num_samples is None:
num_samples = self._num_samples
mode = self.convert_to_enum(ratio_mode, self.enRatioMode, to_upper=True)
self.GetValuesAsync(self._handle, start_index, num_samples, factor, mode,
segment_index, lp_data_ready, byref(p_parameter))
[docs] def get_values_bulk(self, from_segment_index=0, to_segment_index=None, factor=1, ratio_mode='None'):
"""
This function retrieves waveforms captured using rapid block mode. The waveforms
must have been collected sequentially and in the same run.
The `down_sample_ratio` and `down_sample_ratio_mode` arguments are ignored for
ps4000 and ps5000.
"""
assert self.get_no_of_captures() == self._num_captures
if to_segment_index is None:
to_segment_index = self._num_captures - 1
num_segments = to_segment_index - from_segment_index + 1
no_of_samples = c_uint32(self._num_samples * self._num_captures)
overflow = (c_int16 * num_segments)()
mode = self.convert_to_enum(ratio_mode, self.enRatioMode, to_upper=True)
if self.IS_PS4000 or self.IS_PS5000:
self.GetValuesBulk(self._handle, byref(no_of_samples), from_segment_index,
to_segment_index, overflow)
else:
self.GetValuesBulk(self._handle, byref(no_of_samples), from_segment_index,
to_segment_index, factor, mode, overflow)
return no_of_samples.value, [v for v in overflow]
[docs] def get_values_overlapped(self, start_index, down_sample_ratio, down_sample_ratio_mode, segment_index):
"""
This function allows you to make a deferred data-collection request, which will later be
executed, and the arguments validated, when you call :meth:`~.PicoScope.run_block` in block
mode. The advantage of this function is that the driver makes contact with the scope
only once, when you call :meth:`~.PicoScope.run_block`, compared with the two contacts that
occur when you use the conventional :meth:`~.PicoScope.run_block`, :meth:`get_values` calling
sequence. This slightly reduces the dead time between successive captures in block
mode.
This function is invalid for ps4000 and ps5000.
"""
no_of_samples = c_uint32()
overflow = c_int16()
self.GetValuesOverlapped(self._handle, start_index, byref(no_of_samples), down_sample_ratio,
down_sample_ratio_mode, segment_index, byref(overflow))
return no_of_samples.value, overflow.value
[docs] def get_values_overlapped_bulk(self, start_index, down_sample_ratio, down_sample_ratio_mode,
from_segment_index, to_segment_index):
"""
This function allows you to make a deferred data-collection request, which will later be
executed, and the arguments validated, when you call :meth:`~.PicoScope.run_block` in rapid
block mode. The advantage of this method is that the driver makes contact with the
scope only once, when you call :meth:`~.PicoScope.run_block`, compared with the two contacts
that occur when you use the conventional :meth:`~.PicoScope.run_block`,
:meth:`get_values_bulk` calling sequence. This slightly reduces the dead time
between successive captures in rapid block mode.
This function is invalid for ps4000 and ps5000.
"""
no_of_samples = c_uint32()
overflow = c_int16()
self.GetValuesOverlappedBulk(self._handle, start_index, byref(no_of_samples), down_sample_ratio,
down_sample_ratio_mode, from_segment_index, to_segment_index, byref(overflow))
return no_of_samples.value, overflow.value
[docs] def get_values_trigger_time_offset_bulk(self, from_segment_index, to_segment_index):
"""
This function retrieves the time offsets, as lower and upper 32-bit values, for
waveforms obtained in rapid block mode.
This function is provided for use in programming environments that do not support 64-
bit integers. If your programming environment supports this data type, it is easier to
use :meth:`get_values_trigger_time_offset_bulk64`.
"""
times_upper = c_uint32()
times_lower = c_uint32()
time_units = c_enum()
self.GetValuesTriggerTimeOffsetBulk(self._handle, byref(times_upper), byref(times_lower),
byref(time_units), from_segment_index, to_segment_index)
return times_upper.value, times_lower.value, self.enTimeUnits(time_units.value)
[docs] def get_values_trigger_time_offset_bulk64(self, from_segment_index=0, to_segment_index=None):
"""
This function retrieves the 64-bit time offsets for waveforms captured in rapid block mode.
A 32-bit version of this function, :meth:`get_values_trigger_time_offset_bulk`, is
available for use with programming languages that do not support 64-bit integers
"""
if to_segment_index is None:
to_segment_index = self.get_no_of_captures() - 1
n = to_segment_index - from_segment_index + 1
times = (c_int64 * n)()
units = (c_enum * n)()
self.GetValuesTriggerTimeOffsetBulk64(self._handle, times, units, from_segment_index, to_segment_index)
return list(times), [self.enTimeUnits(u) for u in units]
[docs] def hold_off(self, holdoff, holdoff_type):
"""
This function is for backward compatibility only and is not currently used.
This function is only defined for ps2000a, ps3000a and ps4000.
"""
return self.HoldOff(self._handle, holdoff, holdoff_type)
[docs] def is_led_flashing(self):
"""
This function reports whether or not the LED is flashing.
This function is supported by ps4000, ps4000a and ps5000.
"""
status = c_int16()
self.IsLedFlashing(self._handle, byref(status))
return status.value
def _is_ready(self):
"""
This function may be used instead of a callback function to receive data from
:meth:`~.PicoScope.run_block`. To use this method, pass a NULL pointer as the lpReady
argument to :meth:`~.PicoScope.run_block`. You must then poll the driver to see if it has finished
collecting the requested samples.
"""
ready = c_int16()
self.IsReady(self._handle, byref(ready))
return bool(ready.value)
[docs] def is_trigger_or_pulse_width_qualifier_enabled(self):
"""
This function discovers whether a trigger, or pulse width triggering, is enabled.
"""
trigger_enabled = c_int16()
pulse_width_qualifier_enabled = c_int16()
self.IsTriggerOrPulseWidthQualifierEnabled(self._handle, byref(trigger_enabled),
byref(pulse_width_qualifier_enabled))
return trigger_enabled.value, pulse_width_qualifier_enabled.value
[docs] def memory_segments(self, num_segments):
"""
This function sets the number of memory segments that the scope will use.
When the scope is opened, the number of segments defaults to 1, meaning that each
capture fills the scopes available memory. This function allows you to divide the
memory into a number of segments so that the scope can store several waveforms
sequentially.
"""
num_max_samples = c_uint32() if self.IS_PS6000 else c_int32()
self.MemorySegments(self._handle, num_segments, byref(num_max_samples))
return num_max_samples.value
[docs] def no_of_streaming_values(self):
"""
This function returns the number of samples available after data collection in
streaming mode. Call it after calling :meth:`~.PicoScope.stop`.
"""
no_of_values = c_uint32()
self.NoOfStreamingValues(self._handle, byref(no_of_values))
return no_of_values.value
def _get_optional_open_args(self, resolution):
"""Helper function for open_unit and open_unit_async"""
# must call "convert_to_enum" before the "serial_ptr cast", otherwise we corrupt the pointer's memory
if self.IS_PS5000A:
res_enum = self.convert_to_enum(resolution, self.enDeviceResolution, prefix='RES_', to_upper=True)
if self.equipment_record.serial:
serial_ptr = cast(self.equipment_record.serial.encode(self.ENCODING), POINTER(c_int8))
else:
serial_ptr = None
args = ()
if not (self.IS_PS4000 or self.IS_PS5000):
args += (serial_ptr,) # these scopes take the serial as the second argument
if self.IS_PS5000A:
args += (res_enum,) # the ps5000a takes a resolution as the third argument
return args
[docs] def open_unit(self, auto_select_power=True, resolution='8Bit'):
"""Open the PicoScope for communication.
This function opens a PicoScope attached to the computer. The maximum number of
units that can be opened depends on the operating system, the kernel driver
and the computer.
Parameters
----------
auto_select_power : :class:`bool`, optional
PicoScopes that can be powered by either DC power
or by USB power may raise ``PICO_POWER_SUPPLY_NOT_CONNECTED`` if the DC power
supply is not connected. Passing in :data:`True` will automatically switch to
the USB power source.
resolution : :class:`str`, optional
The ADC resolution: 8, 12, 14, 15 or 16Bit. Only used by the PS5000A Series
and it is ignored for all other PicoScope Series.
"""
if self._handle is not None:
self.log_warning('%s is already open', self._base_msg)
return
handle = c_int16()
args = (byref(handle), ) + self._get_optional_open_args(resolution)
ret = self.OpenUnit(*args)
if handle.value > 0:
self._handle = handle
if ret == PICO_OK:
self.log_debug('%s.open_unit%s', self.__class__.__name__, args)
return ret
if auto_select_power and ret == PICO_POWER_SUPPLY_NOT_CONNECTED:
self.log_debug('%s.open_unit%s', self.__class__.__name__, args)
return self.change_power_source(ret) # the ret value is the correct power_state value
# raise the exception
self.errcheck_api(ret, self.open_unit, args)
[docs] def open_unit_async(self, auto_select_power=True, resolution='8Bit'):
"""
This function opens a scope without blocking the calling thread. You can find out when
it has finished by periodically calling :meth:`open_unit_progress` until that function
returns a value of 100.
Parameters
----------
auto_select_power : :class:`bool`, optional
PicoScopes that can be powered by either DC power
or by USB power may raise ``PICO_POWER_SUPPLY_NOT_CONNECTED`` if the DC power
supply is not connected. Passing in :data:`True` will automatically switch to
the USB power source.
resolution : :class:`str`, optional
The ADC resolution: 8, 12, 14, 15 or 16Bit. Only used by the PS5000A Series
and it is ignored for all other PicoScope Series.
"""
if self._handle is not None:
self.log_warning('%s is already open', self._base_msg)
return
status = c_int16()
opts = self._get_optional_open_args(resolution)
self.OpenUnitAsync(byref(status), *opts)
self._auto_select_power = auto_select_power
if status.value == 0:
self.raise_exception(
'The open async operation was disallowed because another open operation is in progress'
)
return status.value
[docs] def open_unit_progress(self):
"""
This function checks on the progress of a request made to :meth:`open_unit_async` to
open a scope. The return value is from 0 to 100, where 100 implies
that the operation is complete.
"""
handle = c_int16()
progress_percent = c_int16()
complete = c_int16()
ret = self.OpenUnitProgress(byref(handle), byref(progress_percent), byref(complete))
if handle.value > 0:
self._handle = handle
if self._auto_select_power and ret == PICO_POWER_SUPPLY_NOT_CONNECTED:
ret = self.change_power_source(ret) # the ret value is the correct power_state value
if ret == PICO_OK:
return 100 if complete.value else progress_percent.value
else:
# raise the exception
self.errcheck_api(ret, self.open_unit_progress, ())
def _run_streaming(self, sample_interval, time_units, max_pre_trigger_samples, max_post_trigger_samples,
auto_stop, down_sample_ratio, down_sample_ratio_mode):
"""
This function tells the oscilloscope to start collecting data in streaming mode. When
data has been collected from the device it is down sampled if necessary and then
delivered to the application. Call :meth:`get_streaming_latest_values` to retrieve the
data. See Using streaming mode for a step-by-step guide to this process.
When a trigger is set, the total number of samples stored in the driver is the sum of
`max_pre_trigger_samples` and `max_post_trigger_samples`. If `auto_stop` is false then
this will become the maximum number of samples without down sampling.
The `down_sample_ratio_mode` argument is ignored for ps4000 and ps5000.
"""
units = self.convert_to_enum(time_units, self.enTimeUnits, to_upper=True)
mode = self.convert_to_enum(down_sample_ratio_mode, self.enRatioMode, to_upper=True)
interval = c_uint32(sample_interval)
if self.IS_PS4000 or self.IS_PS5000:
self.RunStreaming(self._handle, byref(interval), units,
max_pre_trigger_samples, max_post_trigger_samples, auto_stop, down_sample_ratio,
self._buffer_size)
else:
self.RunStreaming(self._handle, byref(interval), units,
max_pre_trigger_samples, max_post_trigger_samples, auto_stop, down_sample_ratio,
mode, self._buffer_size)
return interval.value
[docs] def set_bandwidth_filter(self, channel, bandwidth):
"""
This function is reserved for future use.
This function is only valid for ps3000a, ps4000a and ps5000a.
"""
ch = self.convert_to_enum(channel, self.enChannel, to_upper=True)
bw = self.convert_to_enum(bandwidth, self.enBandwidthLimiter, prefix='BW_', to_upper=True)
return self.SetBandwidthFilter(self._handle, ch, bw)
[docs] def set_data_buffer(self, channel, buffer=None, mode='None', segment_index=0):
"""Set the data buffer for the specified channel.
The `mode` argument is ignored for ps4000 and ps5000.
The `segment_index` argument is ignored for ps4000, ps5000 and ps6000.
Parameters
----------
channel : :class:`enum.IntEnum`
An enum value or member name from ``Channel``.
buffer : :class:`numpy.ndarray`, optional
A int16, numpy array. If :data:`None` then use a pre-allocated array.
mode : :class:`enum.IntEnum`, optional
An enum value or member name from ``RatioMode``.
segment_index : :class:`int`, optional
The zero-based number of the memory segment where the data is stored.
"""
ch = self.convert_to_enum(channel, self.enChannel, to_upper=True)
ratio_mode = self.convert_to_enum(mode, self.enRatioMode, to_upper=True)
if buffer is None:
buffer = self.channel[ch.name].buffer
self._buffer_size = buffer.size
if self.IS_PS4000 or self.IS_PS5000:
return self.SetDataBuffer(self._handle, ch, buffer, buffer.size)
elif self.IS_PS6000:
return self.SetDataBuffer(self._handle, ch, buffer, buffer.size, ratio_mode)
else:
return self.SetDataBuffer(self._handle, ch, buffer, buffer.size, segment_index, ratio_mode)
[docs] def set_data_buffer_bulk(self, channel, buffer, waveform, mode='None'):
"""
This function allows you to associate a buffer with a specified waveform number and
input channel in rapid block mode. The number of waveforms captured is determined
by the ``nCaptures`` argument sent to :meth:`set_no_of_captures`. There is only one
buffer for each waveform because the only down-sampling mode that requires two
buffers, aggregation mode, is not available in rapid block mode. Call one of the
GetValues functions to retrieve the data after capturing.
This function is only valid for ps4000, ps5000 and ps6000.
The `down_sample_ratio_mode` argument is ignored for ps4000 and ps5000.
"""
ch = self.convert_to_enum(channel, self.enChannel, to_upper=True)
ratio_mode = self.convert_to_enum(mode, self.enRatioMode, to_upper=True)
if self.IS_PS4000 or self.IS_PS5000:
return self.SetDataBufferBulk(self._handle, ch, buffer, buffer.size, waveform)
else:
return self.SetDataBufferBulk(self._handle, ch, buffer, buffer.size, waveform, ratio_mode)
[docs] def set_data_buffers(self, channel, buffer_length, mode, segment_index):
"""
This function tells the driver where to store the data, either unprocessed or
down sampled, that will be returned after the next call to one of the GetValues
functions. The function allows you to specify only a single buffer, so for aggregation
mode, which requires two buffers, you need to call :meth:`set_data_buffers` instead.
You must allocate memory for the buffer before calling this function.
The `mode` argument is ignored for ps4000 and ps5000.
The `segment_index` argument is ignored for ps4000, ps5000 and ps6000.
"""
buffer_max = c_int16()
buffer_min = c_int16()
if self.IS_PS4000 or self.IS_PS5000:
self.SetDataBuffers(self._handle, channel, byref(buffer_max), byref(buffer_min), buffer_length)
elif self.IS_PS6000:
self.SetDataBuffers(self._handle, channel, byref(buffer_max), byref(buffer_min), buffer_length, mode)
else:
self.SetDataBuffers(self._handle, channel, byref(buffer_max), byref(buffer_min), buffer_length,
segment_index, mode)
return buffer_max.value, buffer_min.value
[docs] def set_digital_port(self, port, enabled, logic_level):
"""
This function is used to enable the digital port and set the logic level (the voltage at
which the state transitions from 0 to 1).
This function is only used by ps2000a and ps3000a.
"""
return self.SetDigitalPort(self._handle, port, enabled, logic_level)
[docs] def set_ets(self, mode, ets_cycles, ets_interleave):
"""
This function is used to enable or disable ETS (equivalent-time sampling) and to set
the ETS parameters. See ETS overview for an explanation of ETS mode.
"""
mode_ = self.convert_to_enum(mode, self.enEtsMode, to_upper=True)
sample_time_picoseconds = c_int32()
self.SetEts(self._handle, mode_, ets_cycles, ets_interleave, byref(sample_time_picoseconds))
return sample_time_picoseconds.value
[docs] def set_ets_time_buffer(self, buffer):
"""
This function tells the driver where to find your applications ETS time buffers. These
buffers contain the 64-bit timing information for each ETS sample after you run a
block-mode ETS capture.
Parameters
----------
buffer : :class:`ctypes.c_longlong`
An array of 64-bit words (:class:`ctypes.c_int64`), each representing the time,
in picoseconds, at which the sample was captured.
"""
return self.SetEtsTimeBuffer(self._handle, byref(buffer), len(buffer))
[docs] def set_ets_time_buffers(self, time_upper, time_lower):
"""
This function tells the driver where to find your applications ETS time buffers. These
buffers contain the timing information for each ETS sample after you run a blockmode
ETS capture. There are two buffers containing the upper and lower 32-bit parts
of the timing information, to allow programming languages that do not support 64-bit
data to retrieve the timings.
"""
if len(time_upper) != len(time_lower):
msg = 'len(time_upper) != len(time_lower) -- {} != {}'.format(len(time_upper) != len(time_lower))
self.raise_exception(msg)
return self.SetEtsTimeBuffers(self._handle, byref(time_upper), byref(time_lower), len(time_upper))
[docs] def set_frequency_counter(self, channel, enabled, range, threshold_major, threshold_minor):
"""
This function is only define in the header file and it is not in the manual.
This function is only valid for ps4000 and ps4000a.
"""
return self.SetFrequencyCounter(self._handle, channel, enabled, range, threshold_major, threshold_minor)
[docs] def set_no_of_captures(self, n_captures):
"""
This function sets the number of captures to be collected in one run of rapid block
mode. If you do not call this function before a run, the driver will capture only one
waveform. Once a value has been set, the value remains constant unless changed.
"""
ret = self.SetNoOfCaptures(self._handle, n_captures)
self._num_captures = n_captures
self._allocate_buffer_memory()
return ret
[docs] def set_sig_gen_arbitrary(self, waveform, repetition_rate=None, offset_voltage=0.0, pk_to_pk=None,
start_delta_phase=None, stop_delta_phase=None, delta_phase_increment=0,
dwell_count=None, sweep_type='up', operation='off', index_mode='single',
shots=None, sweeps=None, trigger_type='rising', trigger_source='None',
ext_in_threshold=0):
"""
This function programs the signal generator to produce an arbitrary waveform.
Parameters
----------
waveform : :class:`numpy.ndarray`
The arbitrary waveform, in volts.
repetition_rate : :class:`float`, optional
The requested repetition rate (frequency) of the entire arbitrary waveform. The actual
repetition rate that is used may be different based on the specifications of the AWG.
If specified then the :meth:`sig_gen_frequency_to_phase` method is called to determine
the value of `start_delta_phase`.
offset_voltage : :class:`float`, optional
The voltage offset, in volts, to be applied to the waveform.
pk_to_pk : :class:`float`, optional
The peak-to-peak voltage, in volts, of the waveform signal. If :data:`None` then uses
the maximum value of the waveform to determine the peak-to-peak voltage.
start_delta_phase : :class:`int`, optional
The initial value added to the phase accumulator as the generator begins
to step through the waveform buffer.
stop_delta_phase : :class:`int`, optional
The final value added to the phase accumulator before the generator restarts or reverses
the sweep. When frequency sweeping is not required, set equal to `start_delta_phase`.
delta_phase_increment : :class:`int`, optional
The amount added to the delta phase value every time the `dwell_count` period expires.
This determines the amount by which the generator sweeps the output frequency in each
dwell period. When frequency sweeping is not required, set to zero.
dwell_count : :class:`int`, optional
The time, in units of ``dacPeriod``, between successive additions of `delta_phase_increment`
to the delta phase accumulator. This determines the rate at which the generator sweeps the
output frequency.
sweep_type : :class:`enum.IntEnum`, optional
Whether the frequency will sweep from `start_frequency` to `stop_frequency`, or
in the opposite direction, or repeatedly reverse direction. One of: ``UP``, ``DOWN``,
``UPDOWN``, ``DOWNUP``
operation : :class:`enum.IntEnum`, optional
The type of waveform to be produced, specified by one of the following enumerated
types (B models only): ``OFF``, ``WHITENOISE``, ``PRBS``
index_mode : :class:`enum.IntEnum`, optional
Specifies how the signal will be formed from the arbitrary waveform data. Possible values
are ``SINGLE`` or ``DUAL``.
shots : :class:`int`, optional
If :data:`None` then start and run continuously after trigger occurs.
sweeps : :class:`int`, optional
If :data:`None` then start a sweep and continue after trigger occurs.
trigger_type : :class:`enum.IntEnum`, optional
The type of trigger that will be applied to the signal generator.
One of: ``RISING``, ``FALLING``, ``GATE_HIGH``, ``GATE_LOW``.
trigger_source : :class:`enum.IntEnum`, optional
The source that will trigger the signal generator. If :data:`None` then run
without waiting for trigger.
ext_in_threshold : :class:`int`, optional
Used to set trigger level for external trigger.
Returns
-------
:class:`numpy.ndarray`
The arbitrary waveform, in ADU.
Raises
------
~msl.equipment.exceptions.PicoTechError
If the value of an input parameter is invalid.
"""
min_value, max_value, min_size, max_size = self.sig_gen_arbitrary_min_max_values()
if waveform.size < min_size:
self.raise_exception('The waveform size is {}, must be >= {}'.format(waveform.size, min_size))
if waveform.size > max_size:
self.raise_exception('The waveform size is {}, must be <= {}'.format(waveform.size, max_size))
sweep_typ = self.convert_to_enum(sweep_type, self.enSweepType, to_upper=True)
extra_ops = self.convert_to_enum(operation, self.enExtraOperations, to_upper=True)
mode = self.convert_to_enum(index_mode, self.enIndexMode, to_upper=True)
trig_typ = self.convert_to_enum(trigger_type, self.enSigGenTrigType, to_upper=True)
trig_source = self.convert_to_enum(trigger_source, self.enSigGenTrigSource, to_upper=True)
if start_delta_phase is None and repetition_rate is None:
self.raise_exception('Must specify either "start_delta_phase" or "repetition_rate"')
if start_delta_phase is None:
start_delta_phase = self.sig_gen_frequency_to_phase(repetition_rate, mode, waveform.size)
if stop_delta_phase is None:
stop_delta_phase = start_delta_phase
if dwell_count is None:
dwell_count = self.MIN_DWELL_COUNT
if shots is None:
shots = self.SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN
if sweeps is None:
sweeps = self.SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN
# convert the waveform from volts to analog-to-digital units
waveform = waveform.copy()
max_waveform_value = np.max(np.absolute(waveform))
waveform /= max_waveform_value # the waveform must be within the range -1.0 to 1.0
waveform *= max_value
waveform.round(out=waveform)
waveform = waveform.astype(np.int16)
if pk_to_pk is None:
pk_to_pk = max_waveform_value * 2.0
offset = int(round(offset_voltage * 1e6))
pk2pk = int(round(pk_to_pk * 1e6))
self.SetSigGenArbitrary(self._handle, offset, pk2pk, start_delta_phase, stop_delta_phase,
delta_phase_increment, dwell_count, waveform, waveform.size,
sweep_typ, extra_ops, mode, shots, sweeps, trig_typ, trig_source,
ext_in_threshold)
return waveform
[docs] def set_sig_gen_builtin(self, offset_voltage, pk_to_pk, wave_type, start_frequency, stop_frequency, increment,
dwell_time, sweep_type, operation, shots, sweeps, trigger_type, trigger_source,
ext_in_threshold):
"""
This function sets up the signal generator to produce a signal from a list of built-in
waveforms. If different start and stop frequencies are specified, the device will sweep
either up, down or up and down. Call :meth:`set_sig_gen_builtin_v2` instead, which uses
double-precision arguments.
"""
return self.SetSigGenBuiltIn(self._handle, offset_voltage, pk_to_pk, wave_type, start_frequency,
stop_frequency, increment, dwell_time, sweep_type, operation, shots, sweeps,
trigger_type, trigger_source, ext_in_threshold)
[docs] def set_sig_gen_builtin_v2(self, offset_voltage=0.0, pk_to_pk=1.0, wave_type='sine',
start_frequency=1.0, stop_frequency=None, increment=0.1, dwell_time=1.0,
sweep_type='up', operation='off', shots=None, sweeps=None,
trigger_type='rising', trigger_source='None', ext_in_threshold=0):
"""
This function is an upgraded version of :meth:`set_sig_gen_builtin` with double-precision
frequency arguments for more precise control at low frequencies.
This function is invalid for ps4000 and ps4000a.
Parameters
----------
offset_voltage : :class:`float`, optional
The voltage offset, in volts, to be applied to the waveform.
pk_to_pk : :class:`float`, optional
The peak-to-peak voltage, in volts, of the waveform signal.
wave_type : :class:`enum.IntEnum`, optional
The type of waveform to be generated. A ``WaveType`` enum.
start_frequency : :class:`float`, optional
The frequency that the signal generator will initially produce.
stop_frequency : :class:`float`, optional
The frequency at which the sweep reverses direction or returns to the initial frequency.
increment : :class:`float`, optional
The amount of frequency increase or decrease in sweep mode.
dwell_time : :class:`float`, optional
The time, in seconds, for which the sweep stays at each frequency.
sweep_type : :class:`enum.IntEnum`, optional
Whether the frequency will sweep from `start_frequency` to `stop_frequency`, or
in the opposite direction, or repeatedly reverse direction. One of: ``UP``, ``DOWN``,
``UPDOWN``, ``DOWNUP``
operation : :class:`enum.IntEnum`, optional
The type of waveform to be produced, specified by one of the following enumerated
types (B models only): ``OFF``, ``WHITENOISE``, ``PRBS``
shots : :class:`int`, optional
If :data:`None` then start and run continuously after trigger occurs.
sweeps : :class:`int`, optional
If :data:`None` then start a sweep and continue after trigger occurs.
trigger_type : :class:`enum.IntEnum`, optional
The type of trigger that will be applied to the signal generator.
One of: ``RISING``, ``FALLING``, ``GATE_HIGH``, ``GATE_LOW``.
trigger_source : :class:`enum.IntEnum`, optional
The source that will trigger the signal generator. If :data:`None` then run
without waiting for trigger.
ext_in_threshold : :class:`int`, optional
Used to set trigger level for external trigger.
"""
offset = int(round(offset_voltage*1e6))
pk2pk = int(round(pk_to_pk*1e6))
wave_typ = self.convert_to_enum(wave_type, self.enWaveType, to_upper=True)
sweep_typ = self.convert_to_enum(sweep_type, self.enSweepType, to_upper=True)
extra_ops = self.convert_to_enum(operation, self.enExtraOperations, to_upper=True)
trig_typ = self.convert_to_enum(trigger_type, self.enSigGenTrigType, to_upper=True)
trig_source = self.convert_to_enum(trigger_source, self.enSigGenTrigSource, to_upper=True)
if stop_frequency is None:
stop_frequency = start_frequency
if shots is None:
shots = self.SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN
if sweeps is None:
sweeps = self.SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN
return self.SetSigGenBuiltInV2(self._handle, offset, pk2pk, wave_typ, start_frequency,
stop_frequency, increment, dwell_time, sweep_typ, extra_ops, shots, sweeps,
trig_typ, trig_source, ext_in_threshold)
[docs] def set_sig_gen_properties_arbitrary(self, start_delta_phase, stop_delta_phase, delta_phase_increment,
dwell_count, sweep_type, shots, sweeps, trigger_type, trigger_source,
ext_in_threshold, offset_voltage=0, pk_to_pk=-1):
"""
This function reprograms the arbitrary waveform generator. All values can be
reprogrammed while the signal generator is waiting for a trigger.
The `offset_voltage` and `pk_to_pk` arguments are only used for ps6000.
This function is invalid for ps4000 and ps5000.
"""
if self.IS_PS6000:
return self.SetSigGenPropertiesArbitrary(self._handle, offset_voltage, pk_to_pk,
start_delta_phase, stop_delta_phase, delta_phase_increment,
dwell_count, sweep_type, shots, sweeps, trigger_type,
trigger_source, ext_in_threshold)
else:
return self.SetSigGenPropertiesArbitrary(self._handle,
start_delta_phase, stop_delta_phase, delta_phase_increment,
dwell_count, sweep_type, shots, sweeps, trigger_type,
trigger_source, ext_in_threshold)
[docs] def set_sig_gen_properties_builtin(self, start_frequency, stop_frequency, increment, dwell_time,
sweep_type, shots, sweeps, trigger_type, trigger_source,
ext_in_threshold, offset_voltage=0, pk_to_pk=-1):
"""
This function reprograms the signal generator. Values can be changed while the signal
generator is waiting for a trigger.
The `offset_voltage` and `pk_to_pk` arguments are only used for ps6000.
This function is invalid for ps4000 and ps5000.
"""
if self.IS_PS6000:
return self.SetSigGenPropertiesBuiltIn(self._handle, offset_voltage, pk_to_pk,
start_frequency, stop_frequency, increment, dwell_time,
sweep_type, shots, sweeps, trigger_type, trigger_source,
ext_in_threshold)
else:
return self.SetSigGenPropertiesBuiltIn(self._handle,
start_frequency, stop_frequency, increment, dwell_time,
sweep_type, shots, sweeps, trigger_type, trigger_source,
ext_in_threshold)
[docs] def set_simple_trigger(self, enable, source, threshold, direction, delay, auto_trigger_ms):
"""
This function simplifies arming the trigger. It supports only the LEVEL trigger types
and does not allow more than one channel to have a trigger applied to it. Any previous
pulse width qualifier is cancelled.
"""
return self.SetSimpleTrigger(self._handle, enable, source, threshold, direction, delay, auto_trigger_ms)
[docs] def set_trigger_channel_directions(self, a='rising', b='rising', c='rising', d='rising',
ext='rising', aux='rising'):
"""
This function sets the direction of the trigger for each channel.
ps4000a overrides this method because it has a different implementation.
"""
a_ = self.convert_to_enum(a, self.enThresholdDirection, to_upper=True)
b_ = self.convert_to_enum(b, self.enThresholdDirection, to_upper=True)
c_ = self.convert_to_enum(c, self.enThresholdDirection, to_upper=True)
d_ = self.convert_to_enum(d, self.enThresholdDirection, to_upper=True)
ext_ = self.convert_to_enum(ext, self.enThresholdDirection, to_upper=True)
aux_ = self.convert_to_enum(aux, self.enThresholdDirection, to_upper=True)
return self.SetTriggerChannelDirections(self._handle, a_, b_, c_, d_, ext_, aux_)
[docs] def set_trigger_delay(self, delay):
"""
This function sets the post-trigger delay, which causes capture to start a defined time
after the trigger event.
"""
return self.SetTriggerDelay(self._handle, delay)
[docs] def sig_gen_arbitrary_min_max_values(self):
"""
This function returns the range of possible sample values and waveform buffer sizes
that can be supplied to :meth:`set_sig_gen_arbitrary` for setting up the arbitrary
waveform generator (AWG).
"""
min_value = c_int16()
max_value = c_int16()
min_size = c_uint32()
max_size = c_uint32()
self.SigGenArbitraryMinMaxValues(self._handle, byref(min_value), byref(max_value),
byref(min_size), byref(max_size))
return min_value.value, max_value.value, min_size.value, max_size.value
[docs] def sig_gen_frequency_to_phase(self, repetition_rate, index_mode, buffer_length):
"""
This function converts a frequency to a phase count for use with the arbitrary
waveform generator (AWG). The value returned depends on the length of the buffer,
the index mode passed and the device model. The phase count can then be sent to the
driver through :meth:`set_sig_gen_arbitrary` or :meth:`set_sig_gen_properties_arbitrary`.
Parameters
----------
repetition_rate : :class:`float`
The requested repetition rate (frequency) of the entire arbitrary waveform.
index_mode : :class:`enum.IntEnum`
An ``IndexMode`` enum value or member name.
buffer_length : :class:`int`
The size (number of samples) of the waveform.
Returns
-------
:class:`int`
The phase count.
Raises
------
~msl.equipment.exceptions.PicoTechError
If the value of an input parameter is invalid.
"""
mode = self.convert_to_enum(index_mode, self.enIndexMode, to_upper=True)
phase = c_uint32()
self.SigGenFrequencyToPhase(self._handle, repetition_rate, mode, buffer_length, byref(phase))
if phase.value < 1:
self.raise_exception('The delta phase value is < 1. Increase the repetition rate value.')
return phase.value
[docs] def sig_gen_software_control(self, state):
"""
This function causes a trigger event, or starts and stops gating. It is used when the
signal generator is set to ``SOFT_TRIG``.
"""
return self.SigGenSoftwareControl(self._handle, state)
[docs] def trigger_within_pre_trigger_samples(self, state):
"""
This function is in the header file, but it is not in the manual.
This function is only valid for ps4000 and ps5000a.
"""
return self.TriggerWithinPreTriggerSamples(self._handle, state)
[docs] def set_trigger_channel_conditions(self, conditions, info='clear'):
"""
This function sets up trigger conditions on the scope's inputs. The trigger is defined by
one or more ``TriggerConditions`` structures, which are found in the
:mod:`~msl.equipment.resources.picotech.picoscope.structs` module, that are then ``ORed``
together. Each structure is itself the ``AND`` of the states of one or more of the inputs.
This ``AND-OR`` logic allows you to create any possible Boolean function of the scope's
inputs.
The `info` parameter is only used for ps4000a and it is a ``PS4000AConditionsInfo`` enum.
"""
if self.IS_PS4000A:
info = self.convert_to_enum(info, self.enConditionsInfo, to_upper=True)
ret = self.sdk.ps4000aSetTriggerChannelConditions(self._handle, byref(conditions),
len(conditions), info)
else:
ret = self.SetTriggerChannelConditions(self._handle, byref(conditions), len(conditions))
return ret
[docs] def set_trigger_channel_properties(self, channel_properties, timeout=0.1, aux_output_enable=0):
"""
This function is used to enable or disable triggering and set its parameters.
Parameters
----------
channel_properties : :class:`list` of ``TriggerChannelProperties`` :mod:`~msl.equipment.resources.picotech.picoscope.structs`
A list of ``TriggerChannelProperties`` structures describing the requested properties.
timeout : :class:`float`, optional
The time, in seconds, for which the scope device will wait before collecting data
if no trigger event occurs. If this is set to zero, the scope device will wait
indefinitely for a trigger.
aux_output_enable : :class:`int`, optional
Zero configures the AUXIO connector as a trigger input. Any other value configures
it as a trigger output. Only used by ps5000.
"""
auto_trigger_ms = int(round(max(0.0, timeout * 1e3)))
return self.SetTriggerChannelProperties(self._handle, byref(channel_properties),
len(channel_properties), aux_output_enable, auto_trigger_ms)