Programming Interface

Quick start guide and Tutorials

To get used to the programming interface (API), a quick start guide and various examples for Matlab, LabVIEW and python are provided: PulseStreamer-1.0.0.zip

It is not required to install any drivers or other software due to the Ethernet network interface used to communicate with the Pulse Streamer. Alongside with the examples provided, please have a look at the following sections to get a deeper understanding of the principles the Pulse Streamer works.

Pulse Sequences

Pulse sequences are represented as one dimensional arrays of pulses. Each pulse specifies its duration and the states of the digital and analog output channels. The C++ data type is:

struct Pulse {
    unsigned int ticks; // duration in ns
    unsigned char digi; // bit mask
    short ao0;
    short ao1;
};

The pulse duration is specified in nanoseconds.

The lowest bit in the digital bit mask “digi” corresponds to channel 0, the highest bit to channel 7. A channel is high when its corresponding bit is 1 and low otherwise.

The analog values span the full signed 16 bit integer range, i.e. -1.0 V corresponds to -0x7fff and 1.0 V corresponds to 0x7fff. Note that the DAC resolution is 12 bits, i.e., the 4 LSB are ignored.

Setting constant outputs

You can set the outputs to a constant state.

The C++ method is:

void constant(Pulse pulse={0,0,0,0})

Calling the method without a parameter will result in the default output state with all outputs set to 0V.

Running pulse sequences

Running a pulse sequence corresponds to a single function call where you pass your pulse sequence as an argument.

You can repeat a pulse sequence indefinitely or an integer number of times.

A sequence run is associated with up to three constant output states. Specifically, the instrument will assert constant outputs before and after the sequence run (the latter only if the number of repetitions is finite) and if a buffer underflow occurs. You can specify independent values for these three possible constant states.

By default the sequence is started immediately. Alternatively you can tell the system to wait for a later software start command or for an external hardware trigger applied to slow digital channel 0.

The C++ method to run a pulse sequence is:

void stream(std::vector<Pulse> sequence,
            unsigned long n_runs=-1,
            Pulse initial=Pulse{0,0,0,0},
            Pulse final=Pulse{0,0,0,0},
            Pulse underflow=Pulse{0,0,0,0},
            start_t start=IMMEDIATE
            )

sequence represents the pulse sequence

the sequence is repeated indefinitely if n_runs <=0 and a finite number of repetitions otherwise.

initial, final and underflow represent the three possible constant outputs (the tick values are ignored).

start is an enum with the mapping {IMMEDIATE:0, SOFTWARE:1, HARDWARE:2} specifying how the stream should be started.

All parameters except ‘sequence’ have default values and can be omitted.

If you have passed start=SOFTWARE, you can start the sequence at a later time using the method

void startNow()

You can set the edge type of the external trigger using the method

void setTrigger(edge_t edge=RISING)

edge is an enum with the mapping {RISING:0, FALLING:1, BOTH:2}.

If you have run a sequence and want to rerun the same sequence, you can skip uploading the sequence to memory. You can do this by calling the ‘stream’ method with the following C++ signature

void stream(Pulse initial=Pulse{0,0,0,0},
            Pulse underflow=Pulse{0,0,0,0},
            start_t start=IMMEDIATE
            )

Note that ‘n_runs’ and ‘final’ cannot be passed when rerunning the previous sequence, since these values affect the memory layout of the pulse sequence.

If you try to rerun a sequence but there is no previous sequence in memory, this will result in an error.

You can check whether a sequence is available in memory by the following method

bool hasSequence()

The method returns true if a previous sequence is available.

You can check whether the Pulse Streamer is running by calling the method

bool isRunning()

The method returns true if the Pulse Streamer is running.

Data streaming and underflows

The total sampling rate of the pulse streamer is higher than its internal data transfer rate (~6.4 Gbit / s). Thus, the instrument will run into buffer underflow conditions when the pulse sequence cannot be represented in a compressed form. This is the case e.g. for very short digital pulses with arbitrary channel masks or for arbitrary analog sequences with the full sample rate. However, in most use cases the pulse sequence can be represented in a compressed form, where the actual rate of transfered information is smaller than the sampling rate. Buffer underflows will not occurr when the average repeat value of the corresponding low level pulses is larger or equal to 3.

To treat buffer underflows gracefully, the instrument will halt the output data stream and set the output levels to a user defined underflow state as indicated above.

If you are streaming sequences at the edge of the cappability, it is good practice to check for buffer underflows on a regular basis using the programming interface.

Checking for buffer underflows

You can check for buffer underflows with a single function call. The C++ method is

int getUnderflow()

The method returns 1,2 or 3 if the digital output, the analog output or both have entered the underflow state, respectively.

Communicating with the instrument

Your Pulse Streamer 8/2 contains an embedded operating system. You connect to the embedded system over LAN through straight forward “Remote Procedure Calls” (RPC). Requests to the system are directly converted into C++ calls. This architecture gives you direct control over the system.

You can connect throught two RPC interfaces. (i) a JSON-RPC interface that is based on the well established JSON data format (http://www.json.org/) (ii) a google RPC interface (gRPC) that is based on googles data exchange format (https://developers.google.com/protocol-buffers/). Both RPC interfaces provide the same functionality.

JSON-RPC Interface

JSON-RPC libraries are available for most software languages. More information can be found on the official website http://json-rpc.org/ and on Wikpedia https://en.wikipedia.org/wiki/JSON-RPC.

The JSON-RPC URL of the Pulse Streamer is http://<pulse_streamer_ip>:8050/json-rpc, where <pulse_streamer_ip> is the IP address of your pulse streamer.

Sending Data over JSON-RPC

There is no native format for sending array data over JSON-RPC. Therefore the pulse sequence is sent as a binary string. Since the http transport layer requires string data to be base64 encoded, one conversion step is needed before sending a sequence. The JSON-RPC interface call can be performed with an array

{base64 string sequence,
 unsigned long n_runs,
 {unsigned int ticks, unsigned char digi, short ao0, short ao1},
 {unsigned int ticks, unsigned char digi, short ao0, short ao1},
 {unsigned int ticks, unsigned char digi, short ao0, short ao1},
 string / int start
 }

or with named parameters

{"sequence":base64 string sequence,
 "n_runs":unsigned long n_runs,
 "initial":{unsigned int ticks, unsigned char digi, short ao0, short ao1},
 "final":{unsigned int ticks, unsigned char digi, short ao0, short ao1},
 "underflow":{unsigned int ticks, unsigned char digi, short ao0, short ao1},
 "start":string / int start
 }

“sequence” is the array data as per above C++ data format definition packed into a binary string and converted to a base64 string. Please check out the python example for connecting to the JSON-RPC server random_pulses_json.py. All other arguments are self explanatory as per the above sections.

Most parameters are optional and the call can be used both to run a new sequence and to rerun the previous sequence.

Other remote call methods are one to one correspondances to the C++ interface.

gRPC Interface

gRPC (http://www.grpc.io/) is a new RPC interface that is based on googles well established data exchange format called Protocol Buffers (https://developers.google.com/protocol-buffers/). There are gRPC libraries available for most programming languages. Note that gRPC requires the new Protobuf3 standard and is in a beta development stage.

The gRPC server of the Pulse Streamer is <pulse_streamer_ip>:50051, where <pulse_streamer_ip> is the IP address of your pulse streamer.

Sending Data over gRPC

In gRPC, data types are defined by generic, language independent templates. The language specific implementation automatically takes care about conversion to native data types.

The Pulse Streamer interface looks like this.

package pulse_streamer;

message VoidMessage {}

message PulseMessage {
    uint32 ticks = 1;
    uint32 digi = 2;
    int32 ao0 = 3;
    int32 ao1 = 4;
}

message SequenceMessage {
    repeated PulseMessage pulse = 1;
    int64 n_runs = 2;
    PulseMessage initial = 3;
    PulseMessage final = 4;
    PulseMessage underflow = 5;
    enum Start {
        IMMEDIATE = 0;
        SOFTWARE = 1;
        HARDWARE = 2;
      }
    Start start = 6;
}

message TriggerMessage {
    enum Edge {
        RISING = 0;
        FALLING = 1;
        BOTH = 2;
      }
    Edge edge = 1;
}

message PulseStreamerReply {
    int32 value = 1;
}

service PulseStreamer {
    rpc constant (PulseMessage) returns (PulseStreamerReply) {}
    rpc stream (SequenceMessage) returns (PulseStreamerReply) {}
    rpc startNow (VoidMessage) returns (PulseStreamerReply) {}
    rpc setTrigger (TriggerMessage) returns (PulseStreamerReply) {}
    rpc isRunning (VoidMessage) returns (PulseStreamerReply) {}
    rpc hasSequence (VoidMessage) returns (PulseStreamerReply) {}
    rpc getUnderflow (VoidMessage) returns (PulseStreamerReply) {}
}

You may want to check out the source file pulse_streamer.proto.

We recommend to check out the python example for connecting to the gRPC interface random_pulses_grpc.py.

Programming examples

Further examples for Matlab, LabVIEW and python are provided with the following download: PulseStreamer-1.0.0.zip