Wiki Home > User Manual > Modules > StateScript
Table of Contents
[TOC]
*Taken directly from http://spikegadgets.com/software/statescript.html
#StateScript
A simple, yet powerful scripting language to control the timing of environmental input and output events with high temporal resolution in hardware.
StateScript allows you to define temporally precise sequences of events and their triggers, which is useful with devices such as lights, levers, beam breaks, lasers, stimulation sources, audio, and solenoid pumps. Scripts are sent to our Environmental Control Unit and compiled to run in real-time in hardware. Controlling the experimental environment has never been so easy.
##A simple example
This complete example shows how to define variables, callbacks, and ‘if .. else’ blocks.
#!text
%all variables are global
int count = 0
int countThreshold = 10
%this block is executed if digital input 1 goes from low to high
callback portin[1] up
if (count < countThreshold) do
%digital output 1 is turned to a high state
portout[1] = 1
count = count + 1
%this block is scheduled to be exectuted in 500ms.
%The 'in' keyword after 'do' is used to schedule for later.
do in 500
portout[myPort] = 0
end
%a message is sent back to the computer.
%This is executed before the 500ms delay above is finished
disp('Lever press')
else do
disp('Ten presses completed') %a message is sent back to the computer
count = 0 %reset the counter
end
end;%semicolons are used to compile/execute everything since the last semicolon.
##Language overview ###1. Callbacks
Callbacks are executed when the hardware input state changes. The user can create callbacks for individual digital input ports. A callback may not be nested inside another block.
#!text
%this callback is executed if digital input 2 goes from low to high
callback portin[2] up
portout[1] = 1
end
%this callback is executed if digital input 2 goes from high to low
callback portin[2] down
portout[1] = 0
end;
###2. Functions
Functions are blocks that are executed with the ‘trigger()’ command. A trigger may not be nested inside another block.
#!text
%function 1 (exectuted with 'trigger(1)')
function 1
portout[1] = flip %'flip' will switch the binary output state
end
%function 2 (exectuted with 'trigger(2)')
function 2
portout[2] = flip
end
%this callback is executed if digital input 2 goes from low to high
callback portin[2] up
trigger(1) %this will execute function 1
end;
###3. Variables
Variables are global, which means they are accessible from all blocks. They must be defined outside all blocks. Currently only integer variables are supported.
#!text
%Variables are declared outside all blocks.
int currentPort = 1;
function 1
portout[currentPort] = flip %variables can be used in portout[]
currentPort = currentPort+1
end;
###4. ‘Do in’ blocks
The workhorse of StateScript timing control, ‘do in’ blocks allow you to schedule an entire block of code to be executed later with 1 ms precision.
#!text
int blinkDelay = 500
callback portin[1] up
portout[1] = 1 %turn on light now
do in blinkDelay
portout[1] = 0 %turn off light in 500 ms
end
end;
###5. ‘If…else’ blocks
The if…else block in StateScript allows conditional block execution. Standard boolean logic notation (&&, | , <, >, <=, >=, ==) are used to define conditions. ‘If’ statements can be combined with ‘do in’ statements. |
#!text
int currentCorrectChoice = 1
int numberOfCorrect = 0
callback portin[1] up
%Combo if, do in statement
if (currentCorrectChoice == 1 && numberOfCorrect > 3 ) do in 500
numberOfCorrect = numberOfCorrect+1
portout[1] = 1
%turn off reward pump 1500ms after this block is executed
%(2000 ms after callback execution)
do in 1500
portout[1] = 0
end
else do
numberOfCorrect = 0
end
end;
###6. ‘While…then’ blocks
The while…then block is used to repeatedly schedule a block of code at regular intervals until a condition is no longer true. Every ‘while’ statement must be accompanied by a ‘do every’ statement.
#!text
int count
int loopInterval
int done
function 1
%only start blinking if it is not already blinking
if (done == 1) do
count = 0
done = 0
loopInterval = 500
%blink the light 16 times
%as long as the condition is true, the block is re-scheduled
%every loopInterval milliseconds
while count < 16 do every loopInterval
portout[1] = flip
count = count+1
%we can make the loop interval change as the while loop is going
loopInterval = loopInterval-10
%when the condition is no longer true, the 'then' statement
%is exectuted to reset conditions
then do
portout[1] = 1
done = 1
loopInterval = 500
end
end;
###7. Output control
Currently only digital port output is supported. Analog output control coming soon.
#!text
portout[1] = 1 %turn output port 1 to a high state
portout[currentBlinkingPort] = 0 %turn a variable-defined port to a low state
portout[currentBlinkingPort] = flip %flip a port state
###8. Built-in functions
Built-in functions are included to allow extra features and custom hardware behaviors.
#!text
%'clock()' is used to get the current clock value
a = clock()
a = clock(reset) %used to reset the system clock to 0
%'disp()' is used to send text back to the computer interface
disp(a) %display a variable
disp('Rewarded!') %display a string
%'random()' is used to generate random numbers
a = random(99) %returns a random number between 0 and 99
%'sound()' is used to play an audio file stored on an SD card
sound('beep1')
sound(stop) %stops audio playback before it is done
volume(10) %change the current audio volume
%'trigger()' is used to trigger a defined function
trigger(2) %execute function 2
%By default, hardware port status updates
%are sent to the computer every time a digital port changes
updates off %turn all DIO auto status updates off
updates off 1 %turn all DIO auto status updates off for input port 1
updates on %turn all DIO auto status updates on
###9. The semicolon ( ; )
In most of the examples above, a semicolon, followed by a carriage return, is placed after the last line of the script. When a script is sent to the ECU, the ECU stores multiple lines until a semicolon is given. This tells the ECU to compile everything up to that point (since the last semicolon). Semicolons should not be placed inside any blocks. A single command followed by a semicolon is executed immediately (portout[1] = 1;). The hardware will return ‘~~~’ if everything compiled ok. Otherwise it will return an error message.
##Considerations
###Asynchronous execution
StateScript execution is asynchronous, which can be confusing to programmers who are accustomed to synchronous execution. When a program executes asynchronously, it does not wait for one line of code to finish before moving on to the next line. Using a ‘do’ block without an ‘in’ statement forces immediate, synchronous execution of the block, but a ‘do in X’ statement simply adds the block of code for later execution. The compiler moves on to executing lines that follow the scheduled block before the scheduled block is executed.
#!text
%What is the final state of the output port?
function 1
portout[1] = 1 %exectuted 1st
do in 500
portout[1] = flip %exectuted 3rd
end
portout[1] = 0 %exectuted 2nd
end;
Considering this asynchronous behavior is especially important for while…then loops. These loops are not the same as traditional while loops, where code following the loop block is not executed until the loop is finished. Instead, only the first iteration of the loop is executed immediately (and synchronously), at which point the second iteration is scheduled. When the next scheduled iteration exectutes, the condition is first checked, and if the condition is true the block is executed and the next iteration is scheduled. Therefore, any variables that are counting the number of iterations, etc., need to be reset in the while loops’ ‘then’ section, which is executed once the condition is no longer true.
Shown is an example with two while loops, one nested inside the other. The counters for these loops are managed in the ‘then’ statements:
#!text
int pulseCounter = 0
int trainCounter = 0
callback portin[1] up
%create 10 pulse trains, where the trains
%start every 100 ms and each individual
%train contains 5 pulses at 10 ms intervals
%outer while loop 10 pulse trains
while trainCounter < 10 do every 100
%nested inner while loop for each train
while pulseCounter < 5 do every 10
%create a single 1 ms pulse
portout[1] = 1
do in 1
portout[1] = 0
end
pulseCounter = pulseCounter + 1
then do %cleanup for inner loop
%reset inner loop counter
pulseCounter = 0
%iterate outer loop counter here
trainCounter = trainCounter + 1
end
then do %cleanup for outer loop
%reset outer loop counter
trainCounter = 0
end
end;