Programming interface

Overview

This section defines terminology used in this documentation and provides an overview of how signals can be generated with the Pulse Streamer API.

Pulse pattern

The pulse pattern is a sequence of levels, defining the signal to generate. It is defined as an array of (duration, level) tuples, in other words, using Run-Length Encoding (RLE). In contrast to defining pulse patterns as an array of values with equal time durations, the RLE encoded pattern is more memory efficient, especially for patterns that consist of levels of both short and long durations.

The duration is always specified in nanoseconds. The level is either 0 or 1 for digital output or a real number between -1 V and +1 V for analog outputs. See the Hardware section for more details on the electrical properties of the generated signals.

../_images/pulsepattern.png

The following code shows how to define a pulse pattern similar to the one shown in the figure above. In addition, it shows an example of an analog pattern definition.

pulse_patt = [(100, 0), (200, 1), (80, 0), (300, 1), (60, 0)]
analog_patt = [( 50, 0), (100, 0.5), (200, 0.3), (50, -0.1), (10, 0)]

Creating sequences

Before a pattern can be sent for streaming to the Pulse Streamer outputs, they have to be mapped to the output channels. All these steps are performed with the Sequence object, which is created with PulseStreamer.createSequence(). The digital and analog channel assignment is done with the setDigital() and setAnalog() methods, respectively.

../_images/sequence-pattern-assignment.png
from pulsestreamer import PulseStreamer

ps = PulseStreamer('pulsestreamer')

seq = ps.createSequence()
seq.setDigital(0, pulse_patt)
seq.setDigital(2, pulse_patt)
seq.setAnalog(0, analog_patt)

Sequence transformation

Sequence transformation methods enable the creation of complex sequences from simpler sub-sequences. The sequence data can be repeated or combined with another sequence. These operations, while inherently simple, have a few edge cases that are important to know. Concatenation and repetition operations are non-destructive, meaning that they preserve original sequence objects (immutability). The result is stored in a newly created sequence object. Internally, the sequence stores a map of the channel number and the pattern data with the pattern data left unmodified. In general, this results in a sequence that consists of patterns having different durations. On concatenation or repetitions, however, it is intuitively expected that a sequence is treated as a solid unit with every pattern of the same duration. Therefore, before concatenating the sequence data, the pattern durations are padded to the common duration.

When two sub-sequences being concatenated have a different set of mapped channels, the resulting sequence will include them all. This is explained in the following example. Let’s assume we have two sequences, seq1 and seq2, which we want to concatenate. The seq1 has patterns mapped to channels (0,2), and seq2 has channels (0,1), as shown in the code below.

seq1 = ps.createSequence()
seq1.setDigital(0, patt1)
seq1.setDigital(2, patt2)

seq2 = ps.createSequence()
seq2.setDigital(0, patt3)
seq2.setDigital(1, patt4)

seq3 = seq1 + seq2   # concatenation

During the concatenation, the channel lists of the two sequences are compared and the output sequence seq3 will include them all (0,1,2). As a first step, a new sequence object seq3 will be created as a copy of seq1, and an empty pattern will be assigned to the channel 1. Next, all patterns in seq3 will be padded to the duration of the longest one, which is essentially the sequence duration. Finally, the pattern data from seq2 will be appended to the corresponding patterns of the seq3.

The duration padding is always performed with the value of the last element in the pattern. When there is no previous element, the default value is used. The repetition process behaves similarly and can be qualitatively understood as multiple concatenations of the object with itself.

Streaming

Now, any of the sequence objects created above can be sent for streaming by calling the PulseStreamer.stream() method, as shown in the following example for the sequence seq.

ps.stream(seq)
../_images/sequence-data.png

On streaming, the sequence object is converted to a hardware-specific run-length encoded data block, which can be understood as an array of Sequence steps. Every step defines the state of all channels and the duration to hold the state. Sequences support a number of useful methods, like repetition, concatenation, preview plotting, etc. With this basic set of methods, complex sequences can be built from smaller and simpler sub-sequences.

Note

Internally, the Pulse Streamer hardware always splits the sequence data into 8 nanosecond long chunks. When a sequence is shorter than 8 ns or its length is not an exact multiple of 8 ns the extra time will be padded to complete the last chunk. You can observe the effects of such padding if you try to stream a short pulse repetitively.

Example 1. Your sequence consists of a 3 ns high-level and a 2 ns low-level and you stream it with infinite repetitions, the resulting signal will have 3 ns high-level but 5 ns low-level. Therefore, the actual pulse frequency will be 125 MHz instead of 200 MHz. For continuous periodic signals, you can solve this problem by creating a sequence of repetitive pulses that has a duration which is multiple of 8 ns. One easy way to guarantee that sequence duration is a multiple of 8 ns is to repeat it 8 times using Sequence.repeat() method, which will repeat the sequence data in PC memory before sending it to the Pulse Streamer hardware.

Example 2. You want to stream a sequence that is 12345 ns long and you want to repeat it infinitely by setting n_runs=-1. Since this sequence duration is not a multiple of 8 ns (12345 ns / 8 ns = 1543.125) the Pulse Streamer will allocate 1544 chunks, and the actual sequence duration will be 1544 * 8 ns = 12352 ns, or 7 ns longer.

Sequence step

The sequence step is the smallest element of a sequence that contains information on the state of every output of the Pulse Streamer 8/2 and the duration for holding this state. The image below explains the relation between Sequence step, Sequence, and OutputState objects.

../_images/sequence-and-sequence-step.png

Warning

In a typical use of the client API, the user does not have to worry about how to create or operate on the sequence data directly. All necessary functionality is enclosed within the API presented in this article. The description of the Sequence data corresponds to the RAW data as it is required by the hardware. The internal API data structures are implemented slightly differently for each programming language, aiming at the optimization of the client performance. Furthermore, the RAW sequence data format is hardware-dependent and future Pulse Streamer models are likely to use a different format. See also: Low-level RPC interface.

Module level functions

findPulseStreamers(search_serial='')
Parameters:

search_serial (str) – Pulse Streamer serial number as a string.

Returns:

List of DeviceInfo objects.

This function searches and returns basic information about discovered Pulse Streamers. If non-empty search_serial string is provided, then information is returned only for a specific Pulse Streamer 8/2 unit.

The returned value is a list of DeviceInfo objects containing the IP address and basic information.

class DeviceInfo

This class contains read-only information about the discovered Pulse Streamer 8/2.

Property name

Example data

Description

ip

“192.168.0.2”

Device IP address

serial

“00:26:32:f0:3b:1b”

Device serial number

hostname

“pulsestreamer”

Pulse Streamer hostname

model

“Pulse Streamer 8/2”

Pulse Streamer model name

fpgaid

“123456789ABCD”

FPGA ID number

firmware

“1.2.0”

Firmware version

hardware

“1.3”

Hardware version

The discovery algorithm sends Pulse Streamer specific query packets over all available and active network interfaces and listens for responses from the connected Pulse Streamers.

Note

The findPulseStreamers() is capable of finding the devices and reporting their IP addresses even in the networks without dynamic IP assignment by a DHCP server or an improper IP address configuration. Therefore, it might happen that the reported Pulse Streamer 8/2 IP is not accessible from your network. For example, when the reported IP is 169.254.8.2 (static fallback) and your PC is configured as 192.168.1.2, you will not be able to connect to the Pulse Streamer 8/2. This is due to the way IP networks operate. However, you will still be able to discover this Pulse Streamer 8/2 and learn its IP, which is very helpful for identifying network connection problems.

PulseStreamer

The PulseStreamer class is a wrapper for the RPC interface provided by the Pulse Streamer hardware. It handles the connection to the hardware and exposes all available methods. This class is implemented in various supported programming languages with consistently named methods. However, in some languages, additional functionality common to that language is also implemented, such as callback functions in MATLAB.

class PulseStreamer
PulseStreamer(ip)

The class constructor accepts a single string argument, which can be either the IP address or a hostname through which the Pulse Streamer 8/2 can be reached on the network. The constructor fails if the ip has an incorrect value or the device is not reachable. The Pulse Streamer hardware has a static fallback address “169.254.8.2”, which allows operation when the Pulse Streamer 8/2 is directly connected to a PC network card without requiring any additional configuration.

Parameters:

ip (str) – IP address or hostname of the Pulse Streamer 8/2.

reset()

Resets the Pulse Streamer 8/2 device to the default state. All outputs are set to 0 V, and all functional configurations are set to default. The automatic rearm functionality is enabled, and the clock source is the internal clock of the device. No specific trigger functionality is enabled, which means that each sequence is streamed immediately when its upload is completed.

reboot()

Performs a soft reboot of the device without power cycling.

createSequence()

Creates a new hardware-specific Sequence object. A hardware-specific sequence object has the same functionality as Sequence and implements early checks for hardware limits. For example, an attempt to assign a pattern to a non-existing channel or to set analog voltage outside the DAC range will result in an error. The generic Sequence object’s normal behavior is to check hardware limits only when calling the PulseStreamer.stream() method.

Returns:

Hardware-specific Sequence object.

Setting constant output state

PulseStreamer.constant(state=OutputState.ZERO)

Sets the outputs to a constant state. Calling the method without a parameter will result in the default output state with all outputs set to 0 V. If you set the device to a constant output, any currently running streamed sequence is stopped. It is not possible to retrigger the last streamed sequence after setting the Pulse Streamer constant. OutputState.ZERO is a constant equal to OutputState([],0,0).

Alternatively, the state parameter can be specified as a tuple consisting of three elements ([],0,0).

ps.constant(OutputState([1, 2, 5], 0, 0))
# or
ps.constant(([1, 2, 5], 0, 0))
Parameters:

state (OutputState) – OutputState object that defines the state of outputs or a tuple.

Running pulse sequences

The Pulse Streamer 8/2 provides two modes for running pulse patterns.

  1. The first option allows you to stream a complete pulse sequence, which can be repeated either infinitely or for a specific number of times. The sequence is transferred to the Pulse Streamer 8/2 with the stream() method, which starts it immediately by default. Alternatively, you can use setTrigger() to control when the output of the sequence starts, either with software-based trigger initiated with startNow() or with an external hardware trigger.

  1. The second option is to use the continuous streaming functionality. To continuously stream sequence data, the Pulse Streamer 8/2 is equipped with two independent memory slots. While running sequences from one slot, new sequences can be uploaded in the other slot by using the upload() method, without interrupting the current streaming. Several parameters can be used to control the exact transition process between the data slots. For instance, you can either continuously run the newly uploaded sequences or configure the device to repeat the previously uploaded sequences in succession without uploading new data. Furthermore, you can specify whether the device should wait in an idle state or repeat the sequences in the current memory slot while waiting for new data to arrive in the other slot. You can start running the uploaded sequences with the start() method, which allows you to specify how many memory slots (either a fixed number or infinite) will be played.

If you want to stop a running sequence and force it to the final state specified in the function call, you can do this by calling the method forceFinal().

PulseStreamer.stream(sequence[, n_runs=PulseStreamer.REPEAT_INFINITELY[, final=OutputState.ZERO]])

Streams a complete pulse sequence to the Pulse Streamer 8/2. After the sequence has been repeated for the given n_runs, the constant state final will be reached. All parameters except sequence have default values and are optional. By default, the sequence is started immediately. Otherwise, it can be triggered using a configured software or hardware trigger. Please see the setTrigger() and startNow() methods.

If the sequence is empty, the final state will be set immediately.

The sequence parameter of the stream() method also accepts an RLE sequence defined as a list of 4 element tuples of the following format: (duration_ns, [channels_to_set_HIGH], analogV_0, analogV_1)

ps.stream([(100, [1, 2], 0, 0), (10, [2], 0, 0), (5, [], 0, 0)])
Parameters:
  • sequence (Sequence) – Sequence object or a list of tuples.

  • n_runs (int) – Number of times to repeat the sequence. Infinite repetitions if n_runs<0. There is also a symbolic constant REPEAT_INFINITELY=-1

  • final (OutputState) – OutputState object, which defines the constant output after the sequence has finished.

PulseStreamer.startNow()

Starts streaming the sequence present in the memory of the Pulse Streamer 8/2. The behavior of this method depends on the trigger configuration performed with the setTrigger() method.

If the start is TriggerStart.IMMEDIATE and the sequence has finished, then startNow() will trigger the sequence again.

If the start is TriggerStart.SOFTWARE, then the sequence starts every time the startNow() is called. In the case of the rearm=TriggerRearm.MANUAL, the method startNow() will trigger the sequence only once. Call rearm() to manually rearm the trigger.

If the start is set to one of the hardware sources, then this method does nothing.

PulseStreamer.upload(slot_nr, sequence[, n_runs=PulseStreamer.REPEAT_INFINITELY[, idle_state=OutputState.ZERO[, next_action=NextAction.SWITCH_SLOT_EXPECT_NEW_DATA[, when=When.IMMEDIATE[, on_nodata=OnNoData.ERROR]]]]])

Uploads the sequence data to the Pulse Streamer 8/2. The data is written into the memory slot defined by the corresponding parameter slot_nr. All other parameters, except sequence, have default values and are optional. Before you start running the sequences with the method start(), you can upload new sequences to one or both memory slots, overwriting the previously uploaded data.

If the memory slot is not writable because it is being accessed by the Pulse Streamer 8/2 to read the data, the method blocks and waits until the memory slot is writable again. This blocking timeout is set to 7 seconds. To avoid a timeout, you can check if a slot is ready to accept new data using the method isReadyForData().

By default, new sequences are expected to be ready in the other slot to switch immediately to that data without disruption. If next_action is set to NextAction.SWITCH_SLOT, sequences in the other slot will be run regardless they are pre-existing or new. If next_action is set to NextAction.REPEAT_SLOT, the current memory slot’s sequences will be repeated for the remaining slots_to_run defined in the start() method. Each of these repetions contains n_runs times the pulse sequence. If all slots_to_run have been played, the device enters the idle_state. If the next_action is NextAction.STOP, the device enters the idle_state when the current memory slot is completely played. After the continuous streaming ends, you can retrigger the sequence in the last active memory slot with an event of the currently active trigger start condition.

If the transition between two sequences is performed without disruption (parameters when and on_nodata set to When.IMMEDIATE and OnNoData.ERROR, respectively), the new data in the other memory slot must arrive in time. Therefore, the streamed sequence must be long enough to allow the next sequence to be uploaded on time. The upload time of a sequence is at least 30 ms and increases with the number of sequence steps up to about one second for the maximum number of allowed sequence steps (one million). Furthermore, if the data rate of the currently read sequence is high, the upload is throttled (approximately by a factor of two) to prioritize error-free reading from RAM. To ensure optimal upload performance, whenever possible, call the Sequence.getData() method of the sequence object in advance. At that point, all channel data is already merged and optimized, so it does not need to be processed during the upload() call.

If the new data does not arrive in time, an error will occur by default. You can configure the device to wait in the idle_state until the new data is ready by using OnNoData.WAIT_IDLING, or have the current data repeat until the new data becomes available by setting OnNoData.WAIT_REPEATING.

The Pulse Streamer clients support an AUTO mode for uploading sequence data. Instead of explicitly setting slot_nr to 0 or 1, you can use AUTO mode by setting slot_nr to -1 or using the symbolic constant AUTO. In this case, the client will choose the correct sequence slot. Uploading starts with slot 0 and continues with the correct slot based on the next_action setting. The method start() starts with slot 0 if AUTO mode is selected. The method isReadyForData() with AUTO as an argument returns True or False based on the availability of the net slot that AUTO mode would select.

ps.upload(slot_nr=ps.AUTO, data=seq0, n_runs=ps.REPEAT_INFINITELY, idle_state=OutputState.ZERO(), next_action=NextAction.SWITCH_SLOT_EXPECT_NEW_DATA)

ps.start(slot_nr=ps.AUTO, slots_to_run=ps.REPEAT_INFINITELY)

ps.upload(slot_nr=ps.AUTO, data=seq1, n_runs=ps.REPEAT_INFINITELY, idle_state=OutputState.ZERO(), next_action=NextAction.SWITCH_SLOT_EXPECT_NEW_DATA)

ps.upload(slot_nr=ps.AUTO, data=seq2, n_runs=ps.REPEAT_INFINITELY, idle_state=OutputState.ZERO(), next_action=NextAction.REPEAT_SLOT)
Parameters:
  • slot_nr (int) – Memory slot (0 or 1) to store the sequence data. The client supports auto mode if -1 is set for slt_nr. There is also a symbolic constant AUTO=-1.

  • sequence (Sequence) – Sequence object or a list of tuples.

  • n_runs (int) – Number of times to repeat the sequence slot. Infinite repetitions if n_runs`<0. There is also a symbolic constant `REPEAT_INFINITELY=-1

  • idle_state (OutputState) – OutputState object, which defines the constant output after the streaming has finished or no new data is present.

  • next_action (NextAction) – Defines what happens if the slot data is completely streamed.

  • when (When) – If the when is When.IMMEDIATE, the next slot is streamed without disruption. If the when is When.TRIGGER, the next slot is started with the next trigger event of the currently active trigger start condition.

  • on_nodata (OnNoData) – Defines what happens if the new sequence data is not ready on time. The behavior is defined by the enumeration OnNoData.

Returns:

0 in case of a successful upload, -1 in case of an error/timeout.

Note

As the section Streaming describes, the Pulse Streamer hardware splits the sequence data into eight nanosecond-long chunks. Therefore, if the duration of the sequence data uploaded to the Pulse Streamer is not an exact multiple of 8 ns, the extra time will be padded to complete the last chunk.

PulseStreamer.start([slot_nr=0, slots_to_run=REPEAT_INFINITELY])

Starts the streaming of the uploaded sequence data in memory slot slot_nr. If the slot data is completely streamed, the streaming process is continued as defined by the parameters of upload(). By default, the overall repetition parameter slots_to_run is indefinite.

Parameters:
  • slot_nr (int) – Memory slot (0 or 1), which contains the data to start with. In auto mode, the streaming always starts with memory slot 0.

  • slots_to_run (int) – The number of memory slots to be streamed. Streaming data completely from one memory slot includes its repetition parameter n_runs.

Returns:

0 in case the streaming has been started successfully, -1 in case the streaming could not be started.

Note

If you upload data to both memory slots in advance, the internal buffers of the Pulse Streamer 8/2 are filled before the actual streaming starts. But if you stream several slots of sequences of short duration as nested loops with next_action set to NextAction.SWITCH_SLOT, the time to access the data and refill the buffers (several µs) could exceed the sequence duration. In that case, you should use the options of OnNoData.WAIT_IDLING and OnNoData.WAIT_REPEATING to avoid an error condition.

PulseStreamer.isReadyForData([slot_nr=AUTO])
Parameters:

slot_nr (int) – Memory slot (0 or 1) to store the sequence data. The client supports auto mode if -1 is set for slot_nr. There is also a symbolic constant AUTO=-1.

Returns:

True if the dedicated slot can receive data. As long as the memory area is read or could be reread, the write to the slot is blocked and isReadyForData() returns False.

PulseStreamer.forceFinal()

Interrupts the sequence and sets the final state. This method does not modify the output state if the sequence has already finished and the Pulse Streamer is in the final state.

If no final state was declared in the current sequence, the output of the Pulse Streamer 8/2 will change to (or stay in) the last known constant state.

The recommended way to stop the Pulse Streamer 8/2 streaming is to set its output to a constant value via the method constant(), described above.

PulseStreamer.setCallbackFinished(callback_func) (MATLAB only)

Allows to set up a callback function, which will be called after the sequence streaming has finished. The callback function will be called with the following signature callbackFunction(pulseStreamer_obj). An example of such a function is shown below.

function callbackFunction(pulseStreamer)
    % this is an example of a callback function

    disp('hasFinishedCallback - Pulse Streamer finished.');

end

Configuring trigger settings

This section describes methods that allow to configure trigger properties.

PulseStreamer.setTrigger(start[, rearm=TriggerRearm.AUTO])

Defines how the uploaded sequence is triggered.

If you want to trigger the Pulse Streamer by using the external trigger input of the device, you have to set the start parameter to one of the following values: start=TriggerStart.HARDWARE_RISING (rising edge on the trigger input), start=TriggerStart.HARDWARE_FALLING (falling edge on the trigger input) or start=TriggerStart.HARDWARE_RISING_AND_FALLING (both edges are active).

If the automatic rearm functionality is enabled (rearm=TriggerRearm.AUTO), which is the default power-on state, you can re-trigger a sequence that is finished, by providing an appropriate trigger signal, depending on start argument. You can disable the automatic rearm by passing rearm=TriggerRearm.MANUAL.

If the automatic rearm functionality is disabled, you can manually rearm the Pulse Streamer by calling the method rearm()

Parameters:
  • start (TriggerStart) – Defines the source of the trigger signal

  • rearm (TriggerRearm) – Enables or disables trigger automatic rearm.

PulseStreamer.getTriggerStart()

Queries the hardware for the currently active trigger start condition.

Returns:

Returns TriggerStart value.

PulseStreamer.getTriggerRearm()

Queries the hardware for the currently active rearming method.

Returns:

Returns TriggerRearm value.

PulseStreamer.rearm()

Rearms the trigger in case the Pulse Streamer 8/2 has reached the final state of the current sequence and the trigger rearm method was set to TriggerRearm.MANUAL. Returns True on success.

Returns:

True or False

Requesting the streaming state

The following methods allow you to request whether the Pulse Streamer has a sequence in memory, whether a sequence is currently being streamed or if it has already finished.

PulseStreamer.hasSequence()

Returns True if the Pulse Streamer 8/2 memory contains a sequence.

Returns:

True or False.

PulseStreamer.isStreaming()

Returns True if the Pulse Streamer 8/2 is currently streaming a sequence. When the sequence has finished and the device remains in the final state, this method returns False again.

Returns:

True or False.

PulseStreamer.hasFinished()

Returns True if the Pulse Streamer 8/2 remains in the final state after having finished a sequence.

Returns:

True or False.

Using an external clock

The Pulse Streamer 8/2 can be clocked from three different clock sources. By default, the internal clock of the device is used. It is also possible to use an external clock of 125MHz (sampling clock) or an external 10MHz reference signal. You can choose the clock source via selectClock(). For more information on the required electrical parameters of an external clock signal, please see the section Hardware.

PulseStreamer.selectClock(source)

Sets the hardware clock source.

Parameters:

source (ClockSource) – Specifies the clock source for the Pulse Streamer hardware.

PulseStreamer.getClock()

Returns a ClockSource element with the clock source currently used by the Pulse Streamer 8/2.

Returns:

ClockSource Current clock source

Also, you can apply a continuous square wave of 125 MHz to the dedicated Pulse Streamer output channels as an external clock signal for other devices to be synchronized with the Pulse Streamer 8/2.

PulseStreamer.setSquareWave125MHz(channels=[])

Sets a persistent square wave with a frequency of 125 MHz to the selected digital outputs. The 125 MHz will remain and will not be affected by any other settings applied to this channel unless the corresponding channel is deselected via setSquareWave125MHz() or the method reset() is called. A call to this method without a parameter will disable the 125 MHz signal on all channels it was enabled before.

ps.setSquareWave125MHz(channels=[1, 2, 5])
Parameters:

channels (list) – defines to which channels the 125 MHz square wave should be applied.

Hardware identification

PulseStreamer.getSerial()
Returns:

String containing the serial number which is the same as MAC address of the Ethernet interface.

PulseStreamer.getFPGAID()
Returns:

String containing FPGA ID number.

PulseStreamer.getFirmwareVersion()
Returns:

String containing the firmware version number of the connected Pulse Streamer 8/2.

PulseStreamer.getHardwareVersion()
Returns:

String containing the hardware revision number of the connected Pulse Streamer 8/2.

PulseStreamer.setHostname(hostname)
Parameters:

hostname (str) – Hostname string to set for the connected Pulse Streamer 8/2.

Sets hostname of the connected Pulse Streamer 8/2.

Note

Depending on your network environment, this setting may not affect how your Pulse Streamer 8/2 is identified in the network. However, the stored hostname will be returned when you call getHostname().

PulseStreamer.getHostname()
Returns:

String containing the hostname of the connected Pulse Streamer.

Calibrating the analog outputs

Pulse Streamer 8/2 devices shipped with firmware version v1.3.0 or later (published in July 2020) already include calibration data for analog outputs, and no further user action is required. Devices shipped with older firmware require analog output calibration in order to achieve specified accuracy, see Hardware. You can perform this calibration yourself by following the steps described below. The calibration requires a sufficiently accurate multimeter (not an oscilloscope) connected to the analog outputs (can be done one channel at a time).

Calibration procedure

  1. Connect the multimeter to the analog output of the Pulse Streamer 8/2. The measurement has to be performed at 50 Ω load, so you will need to attach 50 Ω termination.

  2. Using the Pulse Streamer API, set the analog output to several values and record multimeter readings. This has to be done at least for -0.9 V and +0.9 V output values.

  3. Calculate the slope

    slope = \frac{voltage_{+0.9V} - voltage_{-0.9V}}{1.8}

  4. Calculate the offset

    offset = voltage_{+0.9V} - slope*0.9

  5. Perform the steps 1 to 4 for each analog output.

  6. Call the setAnalogCalibration() and specify both offsets and slopes.

  7. Reboot the Pulse Streamer 8/2 to apply the new calibration data (from firmware v1.5.0 on, the device is rebooted automatically).

Warning

If you perform repeated calibration, you have to reset the slope and offset to values 1 and 0, respectively. Failing to do so will lead to invalid calibration data.

PulseStreamer.setAnalogCalibration(dc_offset_a0=0, dc_offset_a1=0, slope_a0=1, slope_a1=1)

Sends the DC-offset and slope of each analog channel to the Pulse Streamer 8/2 and stores it to internal memory. These values will be applied after reboot. With firmware version v1.5.0 or later, this is done automatically. If you use a former firmware of the Pulse Streamer 8/2, you will have to power cycle your device.

Parameters:
  • dc_offset_a0 – The DC offset of analog channel 0

  • dc_offset_a1 – The DC offset of analog channel 1

  • slope_a0 – The gradient of the transfer function of analog channel 0

  • slope_a1 – The gradient of the transfer function of analog channel 1

If you need help during the calibration procedure, please contact support@swabianinstruments.com.

PulseStreamer.getAnalogCalibration()

Returns the stored calibration values of your Pulse Streamer. These values will be rounded according to the DAC resolution. If you call this method immediately after setAnalogCalibration(), the returned data will not reflect the actual calibration state.

Returns:

structure of the four calibration values rounded to the DAC resolution.

Modify the network configuration

By default, the Pulse Streamer 8/2 will attempt to acquire an IP address via DHCP. If you want to assign a specific IP address to your Pulse Streamer, you can disable DHCP and configure a static IP instead. We recommend using our graphical user interface (Windows only) to modify the Pulse Streamer 8/2 network configuration. For more information, please have a look at Network Connection. Alternatively, the following methods allow you to configure the device’s network settings via our client software interfaces.

PulseStreamer.setNetworkConfiguration(dhcp, ip='', netmask='', gateway='', testmode=True)

Enables DHCP or sets static IP address, Netmask and Standard Gateway. If testmode=True, the configuration is applied temporarily. Power-cycling will restore the former configuration. If testmode=False, the configuration will be applied permanently and the device is rebooted.

Parameters:
  • dhcp (bool) – DHCP enable/disable True/False

  • ip (str) – static IP address (If dhcp=True, this value is ignored)

  • netmask (str) – Netmask for static IP address configuration (If dhcp=True, this value is ignored)

  • gateway (str) – Standard gateway for static IP address configuration (If dhcp=True, this value is ignored)

  • testmode (bool) – If True, the configuration is applied temporarily. Power-cycling will restore the former configuration. If False, the configuration will be applied permanently and the device is rebooted.

PulseStreamer.getNetworkConfiguration(permanent=False)
Parameters:

permanent (bool) – If True, the method returns network settings stored in the device’s configuration file. If False, the method returns the current network settings of the device.

Returns:

structure of the current or stored network settings

PulseStreamer.applyNetworkConfiguration()

Applies current (and successfully tested) network configuration permanently and the device is rebooted.

Enumerations

class ClockSource(enumeration)

This enumeration describes the selectable clock sources of the Pulse Streamer 8/2

INTERNAL

Use internal clock generator (default)

EXT_125MHZ

Use external 125 MHz clock signal

EXT_10MHZ

Derive clock from external 10 MHz reference signal

class TriggerStart(enumeration)

This enumeration describes the selectable start modes of the Pulse Streamer 8/2

IMMEDIATE

Trigger immediately after a sequence is uploaded. (default)

SOFTWARE

Trigger by calling startNow() method.

HARDWARE_RISING

External trigger on the rising edge.

HARDWARE_FALLING

External trigger on the falling edge.

HARDWARE_RISING_AND_FALLING

External trigger on rising and falling edges.

class TriggerRearm(enumeration)

This enumeration describes the rearm functionality of the Pulse Streamer 8/2

AUTO

Trigger immediately after a sequence is uploaded. (default)

MANUAL

Trigger once only and do not rearm automatically. Rearm manually via the rearm() method.

class NextAction(enumeration)

This enumeration defines the transition behaviour during continuous streaming of the Pulse Streamer 8/2

STOP

Streaming will stop after finishing the current sequence slot

SWITCH_SLOT

After the data in the current memory slot is finished, the data in the other slot is run. If this slot has already been run once, the existing sequences will be run again.

SWITCH_SLOT_EXPECT_NEW_DATA

After the data in the current memory slot is finished, the data in the other slot is run. New data must be available in that slot.

REPEAT_SLOT

Current sequence slot is repeated after it has been completely streamed. The overall repetition parameter slots_to_run defines the number of remaining repetitions.

class When(enumeration)
IMMEDIATE

Action defined by next_action is performed immediately after the previous data slot has finished.

TRIGGER

Action defined by next_action is performed with the next trigger event of the currently active trigger start condition.

class OnNoData(enumeration)
ERROR

Device enters an error state if new data does not arrive in time.

WAIT_IDLING

Device waits in idle_state till the data of the following slot is present in the device buffers.

WAIT_REPEATING

Device repeats the current data slot till the data of the following slot is present in the device buffers.

Sequence

The Sequence contains information about the patterns and channel assignments. It also handles the mapping of patterns (see Pulse pattern) to output channels and has a number of built-in methods that operate on the whole sequence, like concatenation, repetitions, visualization, etc.

Warning

While the same pattern can be mapped to one or more channels, successive mappings to the same channel will overwrite the previous mapping.

class Sequence
Sequence()

Class constructor. The Sequence is a generic class without early hardware limit checks. Since this class is not aware of hardware-specific limitations, like available channels or analog range, the validation will be performed only during an attempt to stream this Sequence object.

Note

If you want to have early limit checks, channel number validation, please create a hardware-specific Sequence object with PulseStreamer.createSequence() method. This, however, requires an active connection to the hardware.

setDigital(channels, pattern)

Assigns a pattern to a digital output. The same pattern can be assigned to one or more channels simultaneously.

sequence.setDigital(0, patt1)
sequence.setDigital([1,2,6], patt2)
Parameters:
  • channels (list[int]) – Digital channel number(s) as labeled on the Pulse Streamer 8/2 connectors panel.

  • pattern (list) – A pattern to be assigned to the channel.

invertDigital(channels)

Inverts level values in the stored pattern for the specified channel.

sequence.setDigital(1, [(10, 0), (20, 1), (80, 0)])
sequence.invertDigital(1)
# is equivalent to
sequence.setDigital(1, [(10, 1), (20, 0), (80, 1)])
Parameters:

channel (int) – Digital channel number.

setAnalog(channels, pattern)

Assigns a pattern to an analog output. The same pattern can be assigned to one or more channels simultaneously.

sequence.setAnalog([0,1], patt2)
Parameters:
  • channels (list[int]) – Analog channel number(s) as labeled on the Pulse Streamer 8/2 connectors panel.

  • pattern (list) – A pattern to be assigned to the channel.

invertAnalog(channel)

Inverts level values in the pattern for the specified channel.

sequence.setAnalog(0, [(100, -0.1), (200, 0), (800,  0.5)])
sequence.invertAnalog(0)
# is equivalent to
sequence.setAnalog(0, [(100,  0.1), (200, 0), (800, -0.5)])
Parameters:

channel (int) – Analog channel number.

Properties

Sequence.isEmpty()
Sequence.isempty() (in MATLAB)

Returns True if the sequence is empty.

Sequence.getDuration()

Returns sequence duration in nanoseconds.

Sequence.getLastState()

Returns the last state in the sequence as an OutputState object.

Returns:

OutputState Last state of the sequence.

Sequence.getData()

Returns the run-length encoded (RLE) sequence data. This method is called automatically by the PulseStreamer.stream() method.

Returns:

Run-length encoded data.

Transformation

static Sequence.repeat(sequence, n_times)

Returns the sequence data duplicated n_times. The sequence data in the original object remains unmodified. In case the Sequence object consists of patterns with different durations, they will be padded to the longest one, which defines sequence duration as returned by Sequence.getDuration() method. In this context, a Sequence shall be understood as a set of patterns all of the same duration.

In Python, you can repeat sequences similarly to lists by multiplying them by a number.

# The following three lines are fully equivalent
seq1 = Sequence.repeat(seq, 5)
seq1 = seq * 5
seq1 = 5 * seq
Parameters:
  • sequence (Sequence) – Sequence object to be repeated.

  • n_times (int) – Number of times the sequence is repeated.

Returns:

Returns a new Sequence object with data duplicated n_times.

static Sequence.concatenate(sequence1, sequence2)

Creates a new Sequence object with a sequence of sequence2 object appended at the end of this sequence. Both object, sequence1 and sequence2, remain unmodified. In case the sequence1 sequence consists of patterns with different durations, they will be padded to the longest one, which defines sequence duration as returned by Sequence.getDuration() method. In this context, a Sequence shall be understood as a set of patterns all of the same duration.

This method is exposed directly in LabVIEW. However, in MATLAB and Python, it is exposed only as the override of the concatenation operator. This allows for transparent use of any function of these languages that implicitly use concatenation. See the example code for each language.

seq3 = seq1 + seq2
Returns:

Returns a new Sequence object with concatenated data.

static Sequence.split(sequence, at_times)

Returns a list of sequences, which are the split partitions of the original sequence defined by the split points in at_times. The sequence data in the original object remains unmodified.

array_of_seq = Sequence.split(seq, [400, 900])
Parameters:
  • sequence (Sequence) – Sequence object to be split.

  • at_times (list[int]) – List with timestamps in nanoseconds where the sequence is split.

Returns:

Returns a list with new Sequence objects, which are the split partitions of the original sequence.

Visualization

Sequence.plotDigital()

Plots the sequence data for digital outputs. Plotting is done into the current axes. (Only in MATLAB and LabView)

Sequence.plotAnalog()

Plots sequence data for analog outputs. Plotting is done into the current axes. (Only in MATLAB and LabView)

Sequence.plot()

Calls plotDigital() and plotAnalog() and shows the results as subplots in a single figure. Plotting is done in the current figure.

An example of the plot() output is shown in the image below.

../_images/example-sequence-plot.png

Note

(Python only)

Since the Python Client release v1.6.1, importing the module matplotlib for plot() is optional. If you want to use visualization with Python, please ensure you have the package matplotlib installed.

OutputState

The OutputState is a simple value class that contains information on the state of every output of the Pulse Streamer 8/2.

class OutputState
OutputState(channels, A0, A1)

Class constructor. Input parameters specify the state of each output of the Pulse Streamer 8/2. Digital and analog output values are specified differently. In order to set a HIGH level at the digital channel, add the channel number to the channels list, for example channels=[0,2,3] will set HIGH level on the channels 0, 2 and 3. All other digital channels will be set to LOW. Output values at each of two analog outputs are specified with corresponding parameters A0 and A1.

Parameters:
  • channels (list) – List of digital channels to be set HIGH.

  • A0 (float) – Analog output 0 voltage in volts.

  • A1 (float) – Analog output 1 voltage in volts.

ZERO

This is a helper constant equal to OutputState([], 0, 0).

Advanced (Beta) features

Synchronized Pulse Streamer 8/2 (Python only)

With our programming examples in Python, we provide the class SyncPulseStreamer as a wrapper class for the client interface of the Pulse Streamer 8/2. It combines two Pulse Streamer 8/2 (requires firmware version v1.4.0 or later) and is currently only available for the Python client.

Synchronization concept and setup

One Pulse Streamer 8/2 master generates the clock signal and trigger for one Pulse Streamer 8/2 slave. The only necessary preparation is that digital channel 6 of the master must be connected to the external clock input of the slave as well as digital channel 7 of the master must be connected to the trigger input of the slave. To avoid race conditions between the trigger and trigger-sampling clock-edge, we recommend using cables of equal length.

The features of the resulting SyncPulseStreamer object can be described as follows:

  • 14 digital channels (6 of the master, 8 of the slave)

  • 4 analog channels (2 in each case master/slave)

  • The slave is delayed by a constant time offset of ~70 ns (internal + cable)

  • Increased ({\sim}x*\sqrt{2}) RMS jitter of the 8 digital channels of the slave (< 75 ps)

  • In this configuration, devices with hardware Version 2.x (devices before 2021) have a 100 mV ripple on the digital channels of the slave due to the external clock signal. The analog outputs are not affected.

Usage and sequence generation

The Pulse Streamer synchronization wrapper offers the same API-structure as the original Pulse Streamer client if possible. For a detailed description of the Pulse Streamer API, please have a look at the Programming interface section and the provided Python example.

When it comes to sequence generation, unlike the original method createSequence(), the equivalent method of the sync-wrapper returns two sequence objects, one for the Pulse Streamer 8/2 master and one for the Pulse Streamer 8/2 slave. You can set the digital and analog channels of both sequences independently. Setting Channel 6 (clock signal) of the master will be ignored, and channel 7 (trigger) of the master will be overwritten by the stream method).

If you want to use a parameter n_runs>1 with the stream() function, you should ensure that the two sequences are of equal duration. Therefore the stream() prints a warning message if the duration of the two sequences differs.

To compensate for the delay of the Pulse Streamer 8/2 slave, you can add an empty pulse as a first pulse to each channel of the master sequence, as it is shown in the Python code example. In that case, you have to take into account that if you want to use n_runs>1, you have to subtract the delay from the last pulses of the master-sequence, or you can accordingly pad the slave-sequence. The drawback is that this either requires the same state at the beginning and the end of the channel pattern, or you have to deal with the padding. Furthermore, you have to take into account that when a sequence is given to the method stream() of the Pulse Streamer 8/2 or to the transformation-methods (see Transformation) of the Sequence class, all channels are padded with the last value to the longest channel duration. For further information, please have a look at the sections Creating sequences and Sequence.

Just to give you the complete information of the padding issue: Due to the internal design of the Pulse Streamer 8/2, the device itself will pad the last sequence step to a duration that the sequence duration as a whole is a multiple of 8ns (which means a prolongation between 0 and 7 ns). This step is executed before the parameter n_runs is applied (see Streaming).

Further Limitations

  1. If you use the synchronized Pulse Streamer 8/2 with a fast external trigger, it is possible that the Pulse Streamer 8/2*master is ready for retriggering and the slave is not and vice versa. Therefore, you should always poll the method :meth:`~PulseStreamer.hasFinished` before retriggering the *Pulse Streamer 8/2 with the external trigger.

  2. If you stream a second pulse pattern into an already running sequence, you previously should set the Pulse Streamer 8/2 to a constant state by using the methods constant() or reset(). Otherwise, the new sequence of the Pulse Streamer 8/2 slave might already be triggered by the still running stream of the Pulse Streamer 8/2 master.