Clock Setup#

Overview#

This document describes the procedures and methods to setup a clock sequencer which is used to trigger a pulse sequence at a fixed frequency or event cycle. Clock sequencers are the backbone of measurement aquisition systems since they determine how often and when measurements are taken. As such, it is important to understand all the different configurations for clock sequencers, especially in the case for OpenSync.

Purpose#

To help users reach an optimal clock sequencer config, a set of examples and explanations are given for each major configuration mode. Since clock sequencer parameters are the main topic of this discussion, please note that the loading and executing of clock configs will not be discussed in this document.

Creating the Clock Config#

A basic clock config can be obtained from calling the function get_clock_params from the opensync namespace. All clock parameters are contained within this dictionary. It is typically not recommended to modify the dictionary of clock parameters and instead one should prioritize using helepr functions which check for invalid inputs.

[1]:
import opensync
[2]:
clock_config = opensync.get_clock_params()
clock_config
[2]:
{'state': False,
 'divider': 1,
 'period': 0.0,
 'bcounter': 0,
 'pcounter': 1,
 'ocounter': 0,
 'trigger_mode': 'disabled',
 'trigger_edge': 'rising',
 'gate_mode': 'disabled',
 'gate_level': 'low'}

As you can see, there is a lot of different ways a clock sequencer can be configured. A clock sequencer is enabled by setting its state to True. The divider controlls the clock resolution of the clock sequencer. The repetition frequency is determined by its period, or how quickly it repeats. For instance, a one millisecond period results in a one kilohertz frequency.

The clock sequencer contains three counters which determine how the clock sequencer runs. The number of acquisition events that are accepted is dictated by bcounter. This is particularly important for external trigger configurations as it allows one to select the number of triggers to accept before finishing program operation. pcounter is the number of pulses to perform per trigger. A pcounter of 10,0000 would perform 10,000 pulse sequencer triggers at the desired repetition frequency per accepted trigger. ocounter if paired with pcounter and is the number of internal events to skip that makes up the overal duty cycle program. Having a pcounter of 5 and ocounter of 3 would produce a total internal pulse train 8 pulses long where only the first 5 produces a trigger for the pulse sequencers.

There are also external trigger and gate modes. trigger_mode selects where the internal trigger event occur. If set to disabled, the trigger can be generated via firmware. Conversly, setting the mode to triggered will require an external trigger to start each clock sequence. Gating can be used at the clock level to prevent signals from reaching the pulse sequencers. A gate mode of pulse ignores each trigger event until the logic level is satisfied. It does not decrement bcounter since the clock sequencer program is not executed. However, a gate mode of output will decrement bcounter as it inhibits the output of the trigger signal instead of inhibiting the program itself. Setting the gate mode to channel places the gating responsiblity to the pulse sequencers instead of the clock sequencers. The gate logic level can be selected as either high or low. Likewise, external trigger edges can also be rising or falling.

Example 1: Freerun Aquisition at 1 kHz for 1,000 Iterations#

For a simple particle image velocimetry (PIV) experiment, many do not use external triggers and simply want to capture a set of image pairs. In this example, 1,000 pulse sequence signals are sent at a frequency of one (1) kilohertz. With a suitable pulse config, this would result in 1,000 image pairs of high temporal resolution. The entire aquisition time would be around a single second.

[3]:
# Get default clock config
clock_config = opensync.get_clock_params()
[4]:
# Enable the clock sequencer
clock_config = opensync.config_clock_state(
    clock_config,
    state=True
)
[5]:
# Then change the clock frequency to 1 kHz
clock_config = opensync.config_clock_period(
    clock_config,
    period=1.0, #0.001 second period = 1 kHz
    units='ms'
)
[6]:
# Then change the total amount of clock reps to 1,000
clock_config = opensync.config_clock_pcounter(
    clock_config,
    count=1000
)
[7]:
# Make sure trigger mode is disabled
clock_config = opensync.config_clock_trigger_mode(
    clock_config,
    trigger_mode='disabled'
)
[8]:
# Make sure gate mode is disabled
clock_config = opensync.config_clock_gate_mode(
    clock_config,
    gate_mode='disabled'
)

Example 2: Freerun Aquisition at 1 kHz for 1,000 Iterations Only When Gate is High#

For a slighly more complicated experiment, there may be times where one does not want to aquire images while the internal clock is still running. For instance, lets say there is an experiment where a submersible is moving upstream in a water channel. When the submersible is not in the field of view of the measurement system, it may be a good idea to not acquire images in order to save memeory space. As such, an external trigger gated to the internal clock can be used to disable and enable pulse sequencer fire signals. This allows the acquisition system to only capture images/measurements when the submersible is in view and otherwise

Expanding upon the previous example, we will be setting gate_mode to pulse and gate_level to high. Of course we may want a different acquisition frequence since one kilohertz is a little fast, but that is outside the scope of this example.

[9]:
# Enable gating for the trigger signal
clock_config = opensync.config_clock_gate_mode(
    clock_config,
    gate_mode='pulse'
)
[10]:
# Only gate inhibit on high
clock_config = opensync.config_clock_gate_level(
    clock_config,
    level='high'
)

Example 3: Triggered Aquisition at 1 kHz for 1,000 Iterations Only When Gate is High#

The final examples expands upon the last two examples by using an external trigger to start the clock sequencer program. Only 1,000 triggers would be accepted before signaling that the acquisition is complete.

[11]:
# Enable external triggering
clock_config = opensync.config_clock_trigger_mode(
    clock_config,
    trigger_mode='triggered'
)
[12]:
# Set external trigger edge to rising
clock_config = opensync.config_clock_trigger_edge(
    clock_config,
    edge='rising'
)
[13]:
# Set bcounter to 1,000 to only that amount of triggers are accepted
clock_config = opensync.config_clock_bcounter(
    clock_config,
    count=1000
)
[14]:
# Set pcounter to 1 so only one pulse happens per trigger
clock_config = opensync.config_clock_pcounter(
    clock_config,
    count=1
)