{ "cells": [ { "cell_type": "markdown", "id": "01804aa4-b906-4ae7-a26b-b7d758a9808a", "metadata": {}, "source": [ "# Pulse Sequencer Configuration using SCPI\n", "\n", "## Overview\n", "This document covers the basics of configuring a pulse sequencer using the SCPI interface of an OpenSync device. \n", "\n", "## Purpose\n", "This example demonstrates how to communicate an OpenSync device using its SCPI interface and configure a pulse sequencer using the raw commands. It is assumed that you have some fundamental knowledge of how SCPI commands work. The [SCPI command reference](../scpi_commands/scpi_commands_pulse.rst) describes the available commands and parameters. Additionally, OpenSync relies on a stable USB connection, so make sure one is present for this demonstration." ] }, { "cell_type": "markdown", "id": "957d6b8f-b96e-4621-832c-fa45269cc63b", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": 1, "id": "cba7fbad-6454-4c34-bed6-e471d279d9cf", "metadata": {}, "outputs": [], "source": [ "import opensync" ] }, { "cell_type": "markdown", "id": "6290f546-7af6-480d-8e64-da796161eecc", "metadata": {}, "source": [ "## Device Connection" ] }, { "cell_type": "code", "execution_count": 2, "id": "29e36c55-54c9-47f9-83d5-0baea756b051", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['COM7']" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "avail_ports = opensync.device_comm_search()\n", "avail_ports" ] }, { "cell_type": "markdown", "id": "24c9d278-a5a0-4c7d-9f6b-78fc794a1c1b", "metadata": {}, "source": [ "## Device Information" ] }, { "cell_type": "code", "execution_count": 3, "id": "29a7d47c-ee68-4691-9e51-ca87e0b397dc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['OpenPIV', 'OpenSync', '5A3F6CE6128EEB36', '0.7.3']\n" ] } ], "source": [ "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " print(opensync.device_system_version(sync_device))" ] }, { "cell_type": "markdown", "id": "a56d1e93-bb8b-475d-b1e0-8f3ba47429d8", "metadata": {}, "source": [ "## Configuring a Pulse Sequencer\n", "A pulse sequencer on an OpenSync device outputs a pulse sequencer through eight (8) output channels. It is only concerned with producing an output sequence once an internal trigger is recieved and not where that signal came from or its repetition frequency. A pulse sequencer synthesizes a pulse sequence through output states and delays to the next output state change. An output state is merely the bit pattern of high and low pins (e.g., the output channels) at a given time. The delay between output states is measured in seconds and later converted into clock cycles." ] }, { "cell_type": "markdown", "id": "d8681752-5714-4214-9c5d-8071f0ed94fa", "metadata": {}, "source": [ "Now, lets say that we want to output a pulse sequence where channel 1 (index 0) is high for 2 seconds, channel 2 (index 1) is high for 2 seconds 1 second after channel 1 turn high, and channel 2 (index 2) turns high when channels 1 and 2 go low. The following pulse sequence will look like the following.\n", "![Example Pulse Sequence](images/example_pulse_timing.png)" ] }, { "cell_type": "markdown", "id": "fc956b5a-3b6f-47d7-b130-fb901cc9ad0a", "metadata": {}, "source": [ "### Load instructions to device\n", "\n", "Each channel consists of a dedicated sequencer and can be independently programmed. This simplifies programming of the device since simple output states and delays can be used compared to earlier firmware versions of OpenSync. For instancem channel instructions simply consist of output state and delay pairs which are intuitive to visualize.\n", "\n", "Since we are working with the direct SCPI interface and not the Python wrapper API, we will be setting the units of the delays directly in the instructions. Note, you do not have to manually specify the end of a pulse as it is automaticall assumed that the output state will be off after a pulse instruction." ] }, { "cell_type": "code", "execution_count": 4, "id": "8387a99f-0e9c-4ba0-a759-1df0f09f6a9f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n", "[]\n", "[]\n" ] } ], "source": [ "commands_send = [\n", " 'pulse1:buffer on, 2 s',\n", " 'pulse2:buffer off, 1 s, on, 2 s',\n", " 'pulse3:buffer off, 3 s, on, 2 s'\n", "]\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " for command in commands_send:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "markdown", "id": "902b0571-7aa3-48fe-af3e-03c410e5eeed", "metadata": {}, "source": [ "Let's say that the pulse on channel 3 is to be 10 seconds long instead. If a delay is too long for the current clock resolution, the device will abort loading the parameters." ] }, { "cell_type": "code", "execution_count": 5, "id": "391a5665-3884-4b45-bff7-95cba0b09f3d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['**ERROR: -222', ' \"Data out of range\"']\n" ] } ], "source": [ "command = 'pulse3:buffer off, 3 s, on, 10 s'\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", "\n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "markdown", "id": "fb573bc6-82dc-49c6-85bc-083d5d9eb308", "metadata": {}, "source": [ "If we have delays of an unsupported length, we can change the clock divider so the clock resolution is lower. Here, we chose 2 which results in an 8 ns clock resolution. You can also simply split the delay between two instruction pairs which would accomplish the same thing." ] }, { "cell_type": "code", "execution_count": 6, "id": "1fab11df-3d88-4a5f-8657-096dfd012e4b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "command = 'pulse3:divider 2'\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " # Change data units to seconds for pulse sequencer 0\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " print(resp)" ] }, { "cell_type": "markdown", "id": "3d0d429f-8411-4a3d-8916-84180c8fe946", "metadata": {}, "source": [ "With the updated clock divider, we can now re-apply the new pulse instructions to sequencer 0." ] }, { "cell_type": "code", "execution_count": 7, "id": "de816fa9-d4fd-4fce-ac16-2ea150d06669", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "command = 'pulse3:buffer off, 3 s, on, 10 s'\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "markdown", "id": "093b026b-7b0f-48f2-a305-59cae3f8e8e5", "metadata": {}, "source": [ "### Activating the pulse sequencer\n", "\n", "Now that the pulse sequences have been loaded to the device, we can enable the pulse sequencer and select a sync source which triggers the pulse sequence to execute. Sync sources may be other channels as long as the primary channel is synced to `T0`. This is not currently enforced in the firmware, so beware when syncing to other channels to make sure there are no circular syncing dependencies." ] }, { "cell_type": "code", "execution_count": 8, "id": "3a7fb878-bdc7-414f-b486-c3b4c5abbca2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n", "[]\n", "[]\n" ] } ], "source": [ "commands = [\n", " 'pulse1:sync t0',\n", " 'pulse2:sync t0',\n", " 'pulse3:sync t0'\n", "]\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " for command in commands_send:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "code", "execution_count": 9, "id": "6fe4cebc-c831-45e4-ab0f-c12bcb929b2f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n", "[]\n", "[]\n" ] } ], "source": [ "commands = [\n", " 'pulse1:state on',\n", " 'pulse2:state on',\n", " 'pulse3:state on'\n", "]\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " for command in commands_send:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "markdown", "id": "f1af6756-f776-4c2c-975e-248341bbfe26", "metadata": {}, "source": [ "The pulse sequence will now be executed every time the clock sequencer sends a signal during program operation." ] }, { "cell_type": "markdown", "id": "635fd39c-62f5-469f-b547-d6cc21467987", "metadata": {}, "source": [ "## Configuring a Clock Sequencer\n", "The clock sequencer operates in a very similar manner to the pulse sequencers. While we only touched the very basics of the pulse sequencer, many of the clock sequencer functions discussed below are also available in the pulse sequencers. The purpose of the clock sequencer is to provide a reference time of which the pulse sequencers will execute. The clock sequencer consists of an internal frequency function and a trigger/gate function. The essence of the internal frequency is what will be discussed below.\n", "\n", "The frequency function can be programmed using the following commands.\n", " - `state`: Enables a clock sequencer. If disabled, the pulse sequencers will never start.\n", " - `divider`: Sets the clock divider of the sequencer.\n", " - `period`: Sets the period of the frequency.\n", " - `bcounter`: Sets the number of trigger events to accept before stopping the program.\n", " - `pcounter`: Sets the active portion of the duty cycle. It basically acts as normal, single shot, or burst mode in commercial synchronizing devices.\n", " - `ocounter`: Sets the inactive portion of the duty cycle. This is how many internal clock periods to skip before producing p counter pulses again.\n", "\n", "For our pulse sequence we programmed above, it has a period of around 5 seconds, so we will add an extra second to allow for some time between pulses. With the addition of the extra 10 second delay, the total period we will use would be something along the lines of 14 seconds. We would like to have 3 pulse sets per trigger, so we will set p counter to 3." ] }, { "cell_type": "code", "execution_count": 10, "id": "1f06e566-ebea-407a-b312-27d3e0da4d06", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "command = 'pulse0:state on'\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "code", "execution_count": 11, "id": "6372beee-18a3-462e-ba21-4feccbeee59a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "command = 'pulse0:period 14 s'\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "code", "execution_count": 12, "id": "f281a3d0-dd31-46a3-a79e-048667540280", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "command = 'pulse0:bcounter 3'\n", "\n", "with opensync.device_comm_managed(avail_ports[0]) as sync_device:\n", " # Write output states to cache\n", " resp = opensync.device_comm_write(\n", " sync_device,\n", " command\n", " )\n", " \n", " # Note, a blank response means a success.\n", " print(resp)" ] }, { "cell_type": "markdown", "id": "6ad2baed-8bed-4e08-8296-ef61638bf967", "metadata": {}, "source": [ "Now the device is ready to operate. Either push the run/stop button or fire the command `device:start` and a pulse sequence will begin." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 5 }