### \
The Global Event

A special form of event exists, which is triggered anytime any of the inputs to the SIMPL+ module changes. This is simply a shortcut for having to build a compound event manually which includes all the inputs separated by commas in a `CHANGE` event declaration. Access this special event function by using the `EVENT` keyword, as follows.

EVENT\
{ // this code runs anytime anything\
  // on the input list changes\
}

Be careful when using this global event function. If the user has a SIMPL+ program in which a change on any input causes the same code to execute, this type of event is useful. However, if additional inputs are added at a later time, remember that this event function exists, and it is caused when these new inputs change as well. This may not be desirable.

\
Function Main
---------------------------------

`Main` is a special case of a user‑defined function. The `Main` function is executed when the control system initializes (boots up) and is never called again. In many cases, the `Main` function is used to initialize variables; it may not contain any statements at all.

However, in some cases, a loop may be placed in the `Main` function to perform a continuous action. Refer to the following example, and note that this example uses a `while` loop construct, which is discussed in [Controlling Program Flow: Loops].

Function Main()\
{\
Integer x;\
String LocalString[50000];

x = 0;

While (1)

{//\<do something forever\> code in here runs continuously

LocalString = GatherByLength(2, MyDelimitedString, GATHER_TIMEOUT);

If(Len(LocalString) = 0)//timeout occurred\
{\
ClearBuffer(MyDelimitedString);\
Print "Timeout occurred in Delimited string. \n");\
Break:\
}//code to work with received string}}

This loop runs continuously for as long as the control system is on. If a construct like this is used, it is recommended that a `ProcessLogic` or `Delay` function in the loop be included to allow the logic processor a chance to handle the rest of the system. If one of these statements is not included, the operating system forces a task switch at some point in time. These concepts are discussed in detail in [Understanding Processing Order].

  [Working with Strings]: ../Working_With_Strings/TP_Working-with-Strings.md
  [Working with Data (Variables)]: ../Working_With_Data/TP_Working-with-Data-(Variables).md
  [Understanding Processing Order]: ../Understanding_Processing_Order/TP_Understanding-Processing-Order.md
  [Controlling Program Flow: Loops]: ../Controlling_Program_Flow_Loops/TP_Controlling-Program-Flow-Loops.md

CHANGE ClearBuffer Controlling Data EVENT Flow GatherByLength If Len Loops Main Order Processing Program Strings Understanding Variables While Working with

Working with Strings
Working with Data (Variables)
Understanding Processing Order
Controlling Program Flow: Loops

Refer to the following example. PUSH button1\
{ // code here only runs when\
  // button1 goes high\
}

PUSH button1, button2, button3\
{ // this code runs when any of\
  // these inputs goes high\
}


Understanding Processing Order

# Understanding Processing Order

## How SIMPL+ and SIMPL Interact

Advanced SIMPL programmers should be familiar with how logic is processed in SIMPL programs. This guide does not attempt to explain this concept here, but it does detail how SIMPL+ programs fit into the picture. However, a couple of definitions may be helpful.

**Logic Wave** ‑ The act of propagating signals from the input to the output of a logic symbol. In a typical program, a single logic wave may include the processing of many symbols.

**Logic Solution** ‑ An arbitrary number of logic waves, processed until the state of all signals in the program have settled to a stable (i.e. unchanging) state.

In general, when a SIMPL+ event function is triggered, it is processed to conclusion in a single logic wave. That is, if this event caused a change to one of the output signals, that signal would be valid one logic wave after the event was triggered. In this simple case, a SIMPL+ program acts identically to a SIMPL logic symbol from a timing standpoint. In addition, for multiple SIMPL+ events triggered on the same logic wave (whether or not they are in the same module), these events multi-task (run at the same time) and complete before the next wave.

As SIMPL+ programs become more complex and processor intensive however, this general rule may no longer apply. Instead, the operating system may determine that too much time has elapsed and temporarily suspend the SIMPL+ program while it continues to process the SIMPL logic (which may also include other SIMPL+ programs). For example, if an event function must run through a loop 2000 times before completing, the processor may decide to perform a task switch and process other logic before completing the loop.

This task‑switching ability has the benefit of not freezing up the rest of the program while a particularly intensive calculation is proceeding. After the completion of the current logic solution, the SIMPL+ program that was exited continues from where it left off.

## Forcing a Task Switch

There may be times in programming when it is necessary to force a task switch to occur. For example, when a digital_output is set high, it normally is not propagated to the SIMPL program until the SIMPL+ completes. To guarantee that the digital signal is asserted, force the system to switch from the SIMPL+ module back into the SIMPL program.

There are two ways to force a task switch: with the **ProcessLogic** function or the **Delay** function. To provide an immediate task switch out of the current SIMPL+ module, use **ProcessLogic**. When the logic processor enters this module on the next logic solution, execution begins with the line immediately following. An immediate task switch also results from **Delay**, but the SIMPL+ module does not continue executing until the time specified has elapsed. The **Delay** function is discussed in greater detail in [Working with Time].

  [Working with Time]: ../Working_With_Time/TP_Working-with-Time.md

SIMPL Plus Tutorial
Understanding Processing Order
TP Understanding-Processing-Order

Order Processing Time Understanding Working with

Understanding Processing Order > Forcing a Task Switch


User Defined Functions

# User Defined Functions

The previous section details how to use system functions in SIMPL+. When programming, there may be a need to create functions of your own to simplify your programs. These functions are called user‑defined functions. User‑defined functions perform exactly like system functions with the only exception in that they must be defined before they are used.

SIMPL Plus Tutorial
User Defined Functions
TP User-Defined-Functions

User Defined Functions > Function Definitions


Function Definitions (Part 1)

## Function Definitions

Since user‑defined functions are created by the user (the SIMPL+ programmer), the user must make sure that the SIMPL+ compiler knows about these functions before the program tries to call them. This is accomplished by creating a function definition, which is different from a function call. Remember from the discussion of system functions that a function call is used to invoke a function. A function definition tells the SIMPL+ compiler what the function does.

User functions are used for several reasons. It is not desirable to create one function that performs every task for the entire program. To help better organize program modules, creating several smaller functions makes programming easier to read and understand, and debug. User‑defined functions can also be called by any other function. Rather than have the same programming logic written out in several functions, one function can be defined with this logic and then called by any other function within the module. This will also greatly reduce the module’s size.

To help the reusability of functions, any number of variables can be passed to functions. Variables are passed to functions through the function’s argument list. This is also called parameter passing, or function arguments. Function arguments can be thought of as passing a value into a function. Other variables or literal values can be passed to function arguments. Function arguments are another way of defining local variables. The difference between declaring a local variable within the function and declaring one as part of the parameter list is that the function argument will have the value of the calling function’s variable copied into it.

It is also useful for a function to return a value. A function might be written to compute a value. Another function might want to perform a task and return an error code that can be evaluated by the calling function. Functions can only return at most one value, namely integers or strings. When defining a function, the returning value will determine what type of function to declare. The different types of functions are: `FUNCTION`, `INTEGER_FUNCTION` and `STRING_FUNCTION`. For the 2‑series and newer compilers, `LONG_FUNCTION`, `SIGNED_INTEGER`, and `SIGNED_LONG_FUNCTION` are also available.

The syntax of a SIMPL+ function call is as follows:

FUNCTION MyUserFunction( [parameter1][, parameter2][, parametern] )\
{\
\<statements\>}

INTEGER_FUNCTION MyUserIntFunction( [parameter1][, parameter2][, parametern] )\
{\
\<statements\>}

STRING_FUNCTION MyUserStrFunction( [parameter1][, parameter2][, parametern] )\
{\
\<statements\>}

The `FUNCTION` keyword is used to tell the SIMPL+ compiler that what follows is the definition of a function and not a function call. The `FUNCTION` keyword also specifies that there will be no return value for this function. `INTEGER_FUNCTION` and `STRING_FUNCTION` specify that an integer or string value will be returned as the result of the function. These keywords are also called the function type.

SIMPL Plus Tutorial
User Defined Functions
TP User-Defined-Functions
Function Definitions

Definitions FUNCTION Function INTEGER_FUNCTION MyUserFunction MyUserIntFunction MyUserStrFunction Part SIGNED_INTEGER STRING_FUNCTION

These functions are called user‑defined functions. User‑defined functions perform exactly like system functions with the only exception in that they must be defined before they are used.


Function Definitions (Part 2)

The next keyword is a name provided by the SIMPL+ programmer that will become the name for this user function (called the function name). Be sure to use a unique name and not an existing SIMPL+ keyword, system function, or a previously declared variable name. Otherwise, a compile syntax error will result.

Following the function name is the function’s argument list. If no arguments are needed within this function, then the list can remain empty. Otherwise, a parameter is defined by giving a variable type and name (i.e., `INTEGER myIntArgument`). One or more functions are possible by separating each with a comma.

Function definitions are global to the SIMPL+ module in which they are defined. That is, any event function, the `Function Main`, or even another user‑defined function can call a user‑defined function that has been defined in the same SIMPL+ module. Functions defined in other modules are not accessible.

When calling a function, it is critical not only that that function has been defined in the SIMPL+ module, but also that this function definition occur before the line of code which calls the function. For example, consider the following.

INTEGER x;

PUSH someSignal\
{\
call MyUserFunction1();\
x = MyUserFunction2( x, 10 );}

FUNCTION MyUserFunction1()\
{\
print("This is MyFunction1 runnning!\n");}

INTEGER_FUNCTION MyUserFunction2( INTEGER arg1, STRING arg2 )\
{\
print("This is MyFunction2 runnning!\n");}

This code causes a compile error, because the function `MyUserFunction1` has been called before it has been defined. This can easily be remedied by reversing the order:

INTEGER x;

FUNCTION MyUserFunction1()\
{\
print("This is MyFunction1 runnning!\n");}

INTEGER_FUNCTION MyUserFunction2( INTEGER arg1, STRING arg2 )\
{\
print("This is MyFunction2 runnning!\n");}

PUSH someSignal\
{\
call MyUserFunction1();\
x = MyUserFunction2( x, 10 );}

This program compiles without any problems. Due to this dependence on order, the SIMPL+ module template that appears each time a new program is created provides a place to put function definitions. Notice that this section comes after the input/output and variable definitions, but before the event and main functions. Following the layout suggested by the template should prevent most of these errors.

Definitions FUNCTION Function INTEGER INTEGER_FUNCTION MyUserFunction1 MyUserFunction2 PUSH Part STRING

`INTEGER_FUNCTION` and `STRING_FUNCTION` specify that an integer or string value will be returned as the result of the function. These keywords are also called the function type.


Defining Local Variables In Functions

## Defining Local Variables In Functions

The concept of local variables was introduced in the section [Working with Data (Variables)]. In this section we will discuss the topic in greater detail and present a number of examples.

What is a local variable? A local variable is a variable (i.e. an integer or string) with a limited life span and limited scope. You can think of global variables as being immortal. That is, for as long as the control system is plugged in and the program is running, global variables retain their values unless a programming statement modifies them. In addition, global variables can be accessed (either to use their value or to modify them) anywhere in a SIMPL+ program. Here is a simple example:

DIGITAL_INPUT go;\
INTEGER i,j,k; // define 3 global integers

FUNCTION sillyFunction()\
{\
i = j \* 2;\
k = i ‑ 1;}

FUNCTION anotherSillyFunction()\
{\
j = i + k;}

PUSH go\
{\
i = 1;\
j = 2;\
k = 3;\
Print("i = %d, j = %d, k = %d\n", i, j, k);\
Call sillyFunction();\
Print("i = %d, j = %d, k = %d\n", i, j, k);\
Call anotherSillyFunction();\
Print("i = %d, j = %d, k = %d\n", i, j, k);}

In this program, it should be clear to see that both of the functions defined, as well as the push event, have access to the three global integers `i`, `j`, and `k`.

Local variables, on the other hand, can only be accessed within the function in which they are defined. If another user‑defined function or event function tries to access a local variable defined elsewhere, a compiler error will be generated. In addition, as soon as the function completes execution, all of the functions' local variables are destroyed. This means that any value they contained is lost. The next time this function is called the local variables are re‑created.

Creating local variables is identical to creating global variables, except that the declaration statement is placed inside of the function definition, as follows:

FUNCTION localExample()\
{\
INTEGER i, count, test;\
STRING s[100], buf[50];}

SIMPL Plus Tutorial
User Defined Functions
TP User-Defined-Functions
Defining Local Variables In Functions

DIGITAL_INPUT Data Defining FUNCTION Functions INTEGER Local PUSH Print STRING Variables

User Defined Functions > Passing Variables to Functions as Arguments

Notice that this section comes after the input/output and variable definitions, but before the event and main functions. Following the layout suggested by the template should prevent most of these errors.


## Passing Variables to Functions as Arguments

The last section describes how to make your programs easier to read and maintain by defining variables local to functions. However, this does not change the fact that by their very nature most functions require access to one or more variables that have been declared elsewhere in the program, either as global variables or as local variables in a different function. The question is, how can your function access these variables.

As we have already seen, global variables are available everywhere in a program, thus you can simply use such variables to share data between functions and the rest of the program. This is considered bad programming practice, however, and often leads to programs that are hard to read and even harder to debug. Functions can also access any input/output signals defined in the program, but as these signals can be thought of as global variables, this too is not considered good programming practice.

Instead of using global variables to share data, we instead use the concept of passing arguments (also known as parameters) into functions. Arguments can be thought of as an ordered list of variables that are passed to a function by the calling function (the term calling function simply refers to the scope of the code statement which calls the function in question). To define a function’s parameters, you list them inside the parentheses following the function name. A typical function definition would look like this:

FUNCTION some_function (INTEGER var1, INTEGER var2, STRING var3)\
{\
INTEGER localInt;\
STRING localStr[100];

var1 = var1 + 1;\
localInt = var1 + var2;\
localStr = left(var3, 10); }

Notice that the function shown above has three arguments, named `var1`, `var2`, and `var3`. `var1` and `var2` are integers, while `var3` is a string. Shown below is an example of how to call this function from elsewhere in your program:

Call some_function( intVal1, 5+1, stringVal1);

Here we are assuming that the variable `intVal1` has been defined as an integer earlier in the program, as has the string variable, `stringVal1`. Also note that the second argument is a constant, and not a variable at all. This simply means that inside `some_function`, the value of `var2` will be set to `6`.

Arguments FUNCTION Functions INTEGER Passing STRING Variables

User Defined Functions > Passing Variables to Functions as Arguments > ByRef, ByVal, and ReadOnlyByRef

The next time this function is called the local variables are re‑created. Creating local variables is identical to creating global variables, except that the declaration statement is placed inside of the function definition, as follows:

FUNCTION localExample()\
{\
INTEGER i, count, test;\
STRING s[100], buf[50];}


ByRef, ByVal, and ReadOnlyByRef

### ByRef, ByVal, and ReadOnlyByRef

When defining a function’s argument list, there are optional keywords you can use which give you greater control over the behavior of the arguments. These keywords are ByRef, ByVal, and ReadOnlyByRef.

What do these keywords mean? Essentially they describe the way that SIMPL+ passes variables to the function. When a function argument is defined as ByRef, any variable that is passed to this argument will pass enough information about itself to allow the function to modify the value of the original variable. The term ByRef is used because we say a reference to the original variable is passed to the function. This reference can be thought of the memory location where the original variable lives. When a function argument is defined as ByVal, only the value of the variable and not the variable itself is passed to the function, so the original variable cannot be modified within the function. As an example, below is a function, which takes two strings as arguments. It inserts one string into the other at a specified character location:

Note: When passing STRING arguments by value, you only need to specify the ByVal keyword for 2-series modules. 3-Series and 4-Series STRINGS are passed by value by default, unless ByRef is used.

2-Series Example:

FUNCTION insertString(ByRef STRING string1, ByVal STRING string2, ByVal INTEGER position)

{

STRING leftpart[20], rightpart[20];

leftpart = left(string1,position);

rightpart = right(string1,position);

string1 = leftpart + string2 + rightpart; }

3-series and 4-Series Example:

FUNCTION insertString(ByRef STRING string1, STRING string2, ByVal INTEGER position)

{

STRING leftpart[20], rightpart[20];

leftpart = left(string1,position);

rightpart = right(string1,position);

string1 = leftpart + string2 + rightpart; }

In this example, note that only the first string argument, string1, was defined as ByRef.

SIMPL Plus Tutorial
User Defined Functions
TP User-Defined-Functions
ByRef, ByVal, and ReadOnlyByRef

ByRef ByVal FUNCTION INTEGER ReadOnlyByRef STRING and

User Defined Functions > Functions That Return Values

Also note that the second argument is a constant, and not a variable at all. This simply means that inside `some_function`, the value of `var2` will be set to `6`.


Functions That Return Values (Part 1)

## Functions That Return Values

To this point all user‑defined functions we have discussed have had one thing in common: when the functions are finished executing they do not return a value to the calling code. This statement is ambiguous, because some of the functions do modify the values of their arguments, and thus these modified variables can be used by the calling procedure. However, the term return value is used to describe the core value, which is returned from the function to the calling procedure. Many system functions discussed earlier in this manual have return values. For example, here are some statements that use the return values of functions:

String1 = itoa(int1);\
position = find(“Artist”,CD_data);\
int3 = max(int1, int2);

For clarity, here are some example statements using system functions that do not have return values:

Print(“Variable int1 = %d\n”,int1);\
ProcessLogic();\
CancelWait(VCRWait);

It should be clear that, at least as far as system functions go, whether or not a function returns a value depends largely upon what that function is designed to do. For example, the `itoa` function would not be very valuable if it did not return a string, which could then be assigned to a variable, or used inside of an expression. On the other hand, the `Print` function simply outputs text to the console for viewing, and thus no return value is needed.

Allowing a user‑defined function to return a value is extremely useful, as it allows such functions to be used in flexible ways, such as inside of an expression. To enable a function to return a value, use a modified version of the function keyword, as shown below:

STRING_FUNCTION strfunc1();  //function returns a string\
INTEGER_FUNCTION intfunc1(); //function returns an integer\
FUNCTION func1();            //function has no return value

Clearly, functions defined using the `STRING_FUNCTION` keyword will return a string value, and those defined using the `INTEGER_FUNCTION` keyword will return an integer value. Function declared using the `FUNCTION` keyword would have no return value.

Once a function has been declared using the appropriate function type, it is the responsibility of the programmer to ensure that the proper value is returned. This is accomplished using the return function. To illustrate this, examine the following function example, which raises one number to the power determined by the second argument, and returns the result.

INTEGER_FUNCTION power(INTEGER base, INTEGER exponent)\
{\
INTEGER i, result;

if (base = 0)\
   return (0);

else if (exponent = 0)\
   return (1);

else {\
   result = 0; // initialize result\
   for (i = 1 to exponent)\
      result = result + result \* base;

   return (result);}}

To use this function in a program, simply call the function just like you would any built‑in system function. Here are a few usage examples:

Print(“5 raised to the power of 3 = %d\n”,power(5,3)); x = power(y,z);

SIMPL Plus Tutorial
User Defined Functions
TP User-Defined-Functions
Functions That Return Values

CancelWait FUNCTION Functions INTEGER INTEGER_FUNCTION Part Print ProcessLogic Return STRING_FUNCTION That Values

3-Series and 4-Series STRINGS are passed by value by default, unless ByRef is used. 2-Series Example:

FUNCTION insertString(ByRef STRING string1, ByVal STRING string2, ByVal INTEGER position)

{

STRING leftpart[20], rightpart[20];

leftpart = left(string1,position);

rightpart = right(string1,position);

string1 = leftpart + string2 + rightpart; }

3-series and 4-Series Example:

FUNCTION insertString(ByRef STRING string1, STRING string2, ByVal INTEGER position)

{

STRING leftpart[20], rightpart[20];

leftpart = left(string1,position);

rightpart = right(string1,position);

string1 = leftpart + string2 + rightpart; }

In this example, note that only the first string argument, string1, was defined as ByRef.


Functions That Return Values (Part 2)

As a second example, we shall build a function which appends a simple `checksum` byte onto the end of a string. As was mentioned earlier in this manual, `checksum`s are used by many devices to provide a basic form of error checking. In this example, the `checksum` is formed by simply adding up the values of each byte in the command string and then appending the equivalent ASCII character of the result onto the string itself. If the `checksum` value is larger than a single byte (255 decimal), we simply ignore the overflow and use the lower 8-bits of the result.

DIGITAL_INPUT control_device1, control_device2;\
STRING_OUTPUT device1_out, device2_out;\
STRING device1_cmd[20], device2_cmd[20], tempCmd[20];

STRING_FUNCTION appendChecksum(STRING command)\
{\
INTGEGER checksum, i;   // define local variables

checksum = 0;    // initialize variable

for (i = 1 to len(command)) // calculate the sum\
   checksum = checksum + byte(command,i);

return(command + chr(checksum)); //append the byte }

PUSH vcr_play\
{\
vcr_out = appendChecksum(“PLAY”);}

PUSH vcr_stop\
{\
vcr_out = appendChecksum(“STOP”);}

In this example, the system function, `byte`, is used inside the function to get the numeric value of each byte in the string. After the `checksum` has been calculated, the `chr` function is used to append the corresponding ASCII character to the end of the command string. Realize that this example is useful for just one (very simple) type of `checksum`.

DIGITAL_INPUT Functions PUSH Part Return STRING STRING_FUNCTION STRING_OUTPUT That Values

INTEGER_FUNCTION power(INTEGER base, INTEGER exponent)\
{\
INTEGER i, result;

if (base = 0)\
   return (0);

else if (exponent = 0)\
   return (1);

else {\
   result = 0; // initialize result\
   for (i = 1 to exponent)\
      result = result + result \* base;

   return (result);}}

To use this function in a program, simply call the function just like you would any built‑in system function. Here are a few usage examples:

Print(“5 raised to the power of 3 = %d\n”,power(5,3)); x = power(y,z);


Function Libraries

## Function Libraries

You are likely to find that the longer you program in SIMPL+ the more you will need to repeat code you have already written. For example, a function that converts the temperature from Celsius to Fahrenheit might come in handy in more than one job.

Clearly, code that has many applications is best placed inside of a function. Remember, however, that unlike system functions, which are globally available, user‑defined functions are only available inside of the SIMPL+ program in which they exist. If you need to use a user‑defined function in more than one SIMPL+ program, you must copy and paste it from one program to another. While this technique works, it can lead to problems when, for example, you find a bug in the function and fix it one program but forget to change it elsewhere.

To solve this problem, SIMPL+ has introduced the concept of function libraries. Simply put, a function library is a collection of user‑defined functions placed in a separate file. A library can consist of only a single function, or can consist of every function you have ever written. More likely, you will organize your libraries so that each one contains related functions. For example, you may create a string handling library, which consists of a number of functions that perform useful operations on stings.

Once a function has been included inside of a function library, it now becomes accessible to all SIMPL+ modules that are made aware of it. To make a SIMPL+ program aware of a particular library, you must use the `#USER_LIBRARY`. To include a user library within a SIMPL+ module, the syntax is as follows:

#USER_LIBRARY "MyStringFunctionLib"

Note that the file extension (.usl in this case) is left out. The above example refers to the function library called `MyStringFunctionLib.usl`. Any number of user libraries can be included within a SIMPL+ module.

Special function libraries that are created by Crestron and made available to all customers can be used in a similar manner. The only difference is the use of the `#CRESTRON_LIBRARY` compiler directive in place of `#USER_LIBRARY`. Crestron function library files end with the extension .csl.

  [Working with Data (Variables)]: ../Working_With_Data/TP_Working-with-Data-(Variables).md#All

SIMPL Plus Tutorial
User Defined Functions
TP User-Defined-Functions
Function Libraries

Data Function Libraries Variables Working with

User Defined Functions > Function Libraries

After the `checksum` has been calculated, the `chr` function is used to append the corresponding ASCII character to the end of the command string. Realize that this example is useful for just one (very simple) type of `checksum`.


Using SIMPL vs. SIMPL+

# Using SIMPL vs. SIMPL+

SIMPL+, while exciting and powerful, does present the programmer with somewhat of a dilemma, namely, when to program in SIMPL and when in SIMPL+. The answer of course is not cut-and-dry, and just about any task can be accomplished entirely in one language or the other. However, the true power of Crestron control system programming is unleashed when the strengths of both environments are harnessed simultaneously.

First, almost every program to be written will have some elements of SIMPL. Any time a button is needed to act as a toggle, or it is necessary to interlock a group of source buttons, it is generally simpler to handle these tasks with SIMPL.

SIMPL+ is advantageous for more complex and algorithmic tasks, such as building complex strings, calculating checksums, or parsing data coming from another device. In addition, complex decision-making, especially when dealing with time and date, is generally much easier to handle in SIMPL+. Finally, data storage and manipulation may be better suited to SIMPL+ than to SIMPL (though many SIMPL programs have been written to do these chores).

Of course, ultimately the decision as to how to program is up to the individual. Personal preference certainly comes in to play. With practice, a happy medium can be found that makes programming both efficient and fun.

SIMPL Plus Tutorial
Using SIMPL Windows vs. SIMPL Plus
Using SIMPL vs. SIMPL+


Using System Functions

# Using System Functions

In order to make programming in SIMPL+ simpler and more powerful, the concept of functions is introduced. A function is essentially a more complicated programming task that has been predefined and given a name. Many of the examples in previous sections of this document have used special types of functions called system functions (or built‑in functions). To employ system functions, use the following format.

returnValue = FunctionName(Parameter1, Parameter2,...);

The above syntax is called a function call, because it tells the function to cause/perform an action. Notice that there are three elements to a function call. First, it is identified by `FunctionName`, which must be unique for every system function. The function name itself is followed by a comma‑separated parameter list enclosed in parentheses. Each function may have a fixed number of parameters, a variable number of parameters, or no parameters at all. These parameters provide the means to supply information to a function. For example, the itoa function converts an integer value into its ASCII equivalent, which is very useful for creating strings to control switchers. The single parameter to this function would be the integer value that needs to be converted. Parameters can be any valid SIMPL+ expression. Consider the statements:

returnValue = itoa(154);         // constant param\
returnValue = itoa(input_num);   // variable param\
returnValue = itoa(x\*5-4);       // general expression param

All are valid function calls.

The variable to the left of the equal sign in the above function calls is used to hold the return value of the function. This value can be either an integer or a string, depending on the nature of the function. Clearly, if a given function returns an integer value, an integer variable must be used to accept this value and a string variable for those functions that return strings. In the itoa examples shown above, the return value is the ASCII string which represents the number being converted. Thus, `returnValue` in this case must be a string variable.

Some functions do not return any values at all. Thus it is not reasonable to assign a variable to a function as shown above. In addition, when using functions that do return values, sometimes the return value may not be needed. In both cases, use the `Call` keyword:

Call FunctionName(Parameter1, Parameter2, ...);

In this case, if the function being called does return a value, it is ignored.

Be aware as well that some functions may need to return more than one value. Since functions can only have at most a single return value, there are some functions that modify the values of the parameters that are passed to it.

SIMPL+ provides a large number of system functions, which are listed under a number of categories in the latest revision of the SIMPL+ Language Reference Guide. Many of these system functions are used as examples throughout this manual.

SIMPL Plus Tutorial
Using System Functions
TP Using-System-Functions


What is Needed to Use SIMPL+?

# What is Needed to Use SIMPL+?

SIMPL+ version 2.0 requires a CNX-series control processor and SIMPL v1.23 or later.

SIMPL+ version 3.0 accompanies SIMPL v2.00 or later, and may be used to program either a 2-Series control system or a CNX-series control system.

SIMPL Plus Tutorial
What is Needed to Use SIMPL Plus
What is Needed to Use SIMPL+?


What is SIMPL+?

# What is SIMPL+?

SIMPL+® is a language extension to SIMPL. It does not replace SIMPL, but instead it enhances it. With SIMPL+ it is now possible to use a procedural “C-like” language to code elements of the program that were difficult, or impossible with SIMPL alone.

A SIMPL+ program is a module that directly interacts with the control system. In order to interact with the control system, a module must contain a few essential elements. The first element is a starting point.  A starting point is needed for two reasons.

- First, it serves as a convenient place to initialize any global variables that are declared within the module.

- Second, any functionality that the module needs to perform on its own (instead of being triggered through an event), can be instantiated here.

Another element is event processing.  In order for a SIMPL+ module and a control system to interact, they must be able to send and receive signals to and from one anothe. Input and output (I/O) signals are declared within the module are then tied directly to the control system. Input signals are sent from the control system and are received within the SIMPL+ module. Output signals are sent from the SIMPL+ module to the control system. Events are functions that are triggered through input signals from the control system. I/O signals can be either digital, analog or serial and are declared within the SIMPL+ module. Events tell the SIMPL+ module that something has changed within the control system and allows the module to perform any action accordingly.

.

SIMPL Plus Tutorial
What is SIMPL Plus
What is SIMPL+?


Where Can I Get More Information?

# Where Can I Get More Information?

This guide should contain all the information needed to program in SIMPL+. For specific information about the language syntax, refer to the latest revision of the SIMPL+ Language Reference Guide .

SIMPL Plus Tutorial
Where Can I Get More Information
Where Can I Get More Information?


Working with Data (Variables)

# Working with Data (Variables)

Programming is really the manipulation of data. Examples of data in a program are the `switcher` input and output numbers, the name of the next speaker and the amount of time left before the system shuts down automatically. This section covers the different data types available in SIMPL+.

## Input/Output Types

Input/output variables are used to transfer data between SIMPL+ modules and the surrounding SIMPL program. Each input or output variable in SIMPL+ is connected directly to a signal in the SIMPL program. SIMPL programmers should already be familiar with the three signal types available in that language: digital, analog, and serial. The table below takes a closer look at the type of data conveyed by these signal types.

| Signal Type | Data                 | Example                           |
|-------------|----------------------|-----------------------------------|
| Digital     | Single bit           | Button push/release               |
| Analog      | 16-bit (0 to 65,535) | Volume level                      |
| Serial      | Up to 255 bytes      | Serial data input from a COM port |

SIMPL Signal Styles {cellspacing="0"}

This table illustrates that digital signals only transfer a single bit of information between SIMPL+ and SIMPL. Of course this makes sense, as digital signals only have two possible states (on and off). Obviously, analog and serial signals allow the transfer of much more information per signal. Depending on the application, it may be more convenient to generate an analog signal in SIMPL and connect it to a SIMPL+ program, rather than connecting a large number of digital signals and setting some variable based on which was pressed last (though both methods should work).

### Digital Inputs/Outputs

Digital signals comprise the bulk of signals in a typical SIMPL program. In SIMPL+ they are used mainly to trigger events on the rising‑ or falling‑ edge of the signal, though they can also be used in expressions.

The state (or value) of a digital signal is always either 1 or 0 (also referred to as On or Off). In SIMPL+, assigning a value of 0 to a digital signal turns it Off. Assigning it any non-zero value will turn it On (for clarity, in most cases, use the value 1).

### Analog Inputs/Outputs

Analog signals are used in SIMPL to accomplish tasks for which digital signals are inadequate. Typical examples include volume control and camera pan/tilt control. In SIMPL+, analog signals take on even greater importance since they provide an easy way of transferring data (16 bits at a time) into and out of SIMPL+ modules.

In SIMPL+, analog signals are treated much as they are in SIMPL. They are 16-bit numbers that can range between 0 and 65,535 (unsigned) or ‑32768 and +32,767 (signed). Signed and unsigned numbers are discussed in detail in [Working with Data (Variables)].

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
Working with Data (Variables)

Working with Data (Variables) > Input/Output Types > String Inputs/Outputs and Buffer Inputs


String Inputs/Outputs and Buffer Inputs

### String Inputs/Outputs and Buffer Inputs

Perhaps the greatest advantage that SIMPL+ provides is related to string handling. In SIMPL, serial data can be generated dynamically and placed on serial signals. However, one problem that arises is due to the transient nature of these signals. Simply put, serial signals are invalid except for the time between when they are created and when they reach the next symbol. With careful programming this does not cause problems, but it requires a good understanding of SIMPL.

SIMPL+ makes working with serial data much simpler by storing this data into temporary string variables. When a serial signal is connected to a SIMPL+ module and the SIMPL program causes data to be sent via this signal, the SIMPL+ program copies the data from this signal into local memory. The data is kept there until the SIMPL program changes it.

By storing the serial data into a string variable, SIMPL+ programmers can now perform tasks on strings that were difficult or impossible with SIMPL alone. For example, it is easy to evaluate a string and then add a `checksum` byte on the end to insert or remove characters from a string, or to parse information out of a string for use elsewhere. Functions that are designed to work explicitly with string signals and string variables are discussed in detail in the latest revision of the SIMPL+ Language Reference Guide.

Serial data is also unique in that unlike digital or analog signals, the data may not appear at one time, but instead it can stream in (e.g., if it comes from a COM port). This raises an interesting problem, namely, what happens if a command string coming in from a device is not picked up as one piece, but rather is broken up into two or more pieces? The problem arises in that a string input is completely replaced each time new data is detected on the input. To account for this, an alternate type of serial input type may be used, the buffer input. The buffer input differs from the string input in that serial data that comes in is appended onto data that already exists in the buffer, instead of replacing it. This type of behavior is critical for performing sophisticated string parsing and manipulation when dealing with streaming data. Refer to [Working with Strings] for a detailed discussion of buffer inputs.

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
String Inputs/Outputs and Buffer Inputs

Working with Data (Variables) > Input/Output Types > Signal Scope

They are 16-bit numbers that can range between 0 and 65,535 (unsigned) or ‑32768 and +32,767 (signed). Signed and unsigned numbers are discussed in detail in [Working with Data (Variables)].


Signal Scope (Part 1)

### Signal Scope

Signals are global throughout the entire SIMPL program and to any SIMPL+ program to which they are connected. In a SIMPL+ program, the values of digital, analog, and string inputs are read (their values can be evaluated). However, their values cannot be changed from within the SIMPL+, thus they are considered read‑only. Buffer inputs can be read from and modified.

Digital and analog output signals in SIMPL+ can be read and modified. String outputs can be modified, but cannot be read back. To understand this, the user must realize what is being seen when looking at the contents of an output variable in a SIMPL+ program. The value of any output is the value of the signal as seen by the outside SIMPL program at that instant. This is critical considering that SIMPL+ does not necessarily propagate outputs to the SIMPL program each time they are changed in the program. As a general rule, assume that analog and serial outputs are propagated at the time they are assigned new values. However, digital signals are not propagated until a task switch occurs.

This explains reading values of analog and digital outputs, but why is it that string outputs cannot be read? The reason has to do with the nature of serial signals in SIMPL. Namely that these signals do not actually store strings in them, but rather point to locations in memory where a string exists. Since the data stored at a particular location in memory can change at some later time, there is no guarantee that the string data is still there. As a result, SIMPL+ does not allow a string output to be examined.

Examine the following code example.

DIGITAL_OUTPUT d_out;\
ANALOG_OUTPUT a_out;\
STRING_OUTPUT s_out;

PUSH someEvent\
{\
d_out = 1; // set this digital output to ‘on’\
a_out = 2000; // set this analog output to 2000\
s_out = "hello"; // set this string output to "hello"

if (d_out = 1) // this WILL NOT be true until the Print ("d_out is on\n"); // next task-switch

if (a_out = 2000)       // this WILL be true Print ("a_out = 2000");

if (s_out = "hello")    // this WILL NOT be true due to the Print ("s_out is hello"); // nature of serial signals

ProcessLogic(); // force a task-switch

if (d_out = 1) // NOW this is true Print ("d_out is on\n");\
}

Function Main()   // initialization\
{\
d_out=0;\
a_out = 0;}

In this example, the digital output, `d_out`, and the analog output,` a_out`, are set to `0` on system startup in the `Function Main`. In the `push` function, the first conditional `if` statement evaluates to `False` because the digital output signal, `d_out`, is considered `Off` until this value is propagated to the SIMPL program. With digital outputs, this does not happen until the SIMPL+ program performs a task switch. The analog and string outputs, on the other hand, are propagated as soon as they are assigned new values. Thus the second `if` condition evaluates to `True` and the subsequent `print` statement is executed. The third `if` statement can still evaluate to `False`, however, due to the nature of serial signals in SIMPL, as previously described.

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
Signal Scope

ANALOG_OUTPUT DIGITAL_OUTPUT Main PUSH Part Print ProcessLogic STRING_OUTPUT Scope Signal

This type of behavior is critical for performing sophisticated string parsing and manipulation when dealing with streaming data. Refer to [Working with Strings] for a detailed discussion of buffer inputs.


Signal Scope (Part 2)

Notice the `ProcessLogic` function call in the last example. This function forces a task switch from SIMPL+ to the SIMPL logic processor. This causes the digital signal to be propagated out to the SIMPL program. The next time the logic processor passes control back to this SIMPL+ program, it picks up where it left off. As a result, the fourth `if` condition evaluates to True, thus executing the print statement.

Note: The `if` language construct is described in detail in [Controlling Program Flow: Branching]. Evaluation of True and False expressions are covered in [Operators, Expressions, and Statements].

Thus the second `if` condition evaluates to `True` and the subsequent `print` statement is executed. The third `if` statement can still evaluate to `False`, however, due to the nature of serial signals in SIMPL, as previously described.


All About Variables

## All About Variables

In addition to input and output signals, additional variables can be declared that are only used inside the SIMPL+ program. That is, the outside SIMPL program has no knowledge of these variables and no access to them. These variables are critical for use as temporary storage locations for calculations.

Unless otherwise specified with a compiler directive, all variables in SIMPL+ are volatile, which means that they do not remember their values if power is lost. The compiler directive, `#DEFAULT_NONVOLATILE`, can be used to change this behavior so that the variable's values are retained after power is lost. Notably, it is generally a good idea to explicitly initialize variables to some value before using them, except in the cases where it becomes necessary to take advantage of their non‑volatility. An obvious place to do this is in `Function Main`.

SIMPL+ allows for two different types of variables: integers and strings. In addition, variables of either type may be declared as one‑ or two‑dimensional arrays. The following sections explain these topics in detail.

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
All About Variables

Working with Data (Variables) > All About Variables > Integers

Note: The `if` language construct is described in detail in [Controlling Program Flow: Branching]. Evaluation of True and False expressions are covered in [Operators, Expressions, and Statements].


Integers (Part 1)

### Integers

Integers contain 16‑bit whole numbers. That is, they can range between 0 and 65,535 (unsigned, refer to paragraph after example) and cannot contain a decimal point. SIMPL programmers may recognize that this range is identical to that of analog signals. This is because analog signals are also treated as 16‑bit values.

Integers are declared as follows:

INTEGER \<int1\>, \<int2\>,…,\<intn\>;

Depending on how they are used, integers can either be unsigned or signed. Unsigned integers have values between 0 and 65,535. Signed integers have values between ‑32768 and +32767.

In reality, there is no difference between a signed and unsigned integer, the difference is solely in how the control system views them. That is, for any given value, that number can be thought of as either being a signed number or an unsigned number. Depending upon which operations are perform on a number, the control system decides whether to treat that number as signed or unsigned.

When an integer has a value of between 0 and 32767, it is identical whether it is considered signed or unsigned. However, numbers above 32767 may be treated as negative numbers. If they are, they will have a value of x ‑65536, where x is the unsigned value of the number. This means that the value 65,535 has a signed value of ‑1, 65534 is ‑2, etc. This scheme is referred to as two’s complement notation.

Why is all this signed/unsigned discussion important? Well, in most cases, it can be ignored and things work out fine. However, for those instances when it does make a difference, it pays to understand how to debug programs that are not working as expected.

In control system programming, often there is not a need for negative numbers (e.g., how often is a switcher switched to input number ‑12 ?). As a result, the most common operations treat integers as unsigned and it becomes necessary to use special signed operators or functions when treating numbers as signed. The table after this paragraph lists operators and functions that are unsigned and those that are signed. Any operators or functions that are not shown here do not need special consideration.

| Description | Unsigned Operators/Functions | Signed Operators/Functions |
|----|----|----|
| Less than | \< | S\< |
| Less than or equal to | \<== | S\<= |
| Greater than | \> | S\> |
| Greater than or equal to | \>= | S\>= |
| Integer division | / | S/ |
| Maximum | Max() | SMax() |
| Minimum | Min() | SMin() |

Unsigned/Signed Operators and Functions {cellspacing="0"}

Examine the following:

INTEGER j, k;

Function Main()\
{\
j = 2;\
k = -1; // this is the same as k = 65535

if (j \> k) // this will evaluate to FALSEPrint( “j is bigger as unsigned numbers\n” );

if (j S\> k) // this will evaluate to TRUEPrint( “j is bigger as signed numbers\n” );}

In this example, the first condition, `j > k`, evaluates to `False` and the `Print` statement does not execute. This is because the `>` operator performs an “unsigned greater than” operation. If both `j` and `k` are converted to unsigned values, `j` remains at `2`, but `k` becomes `65,535`, and thus `k` is obviously not smaller than `j`.

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
Integers

FALSEPrint INTEGER Integers Main Max Min Part SMax SMin TRUEPrint

In addition, variables of either type may be declared as one‑ or two‑dimensional arrays. The following sections explain these topics in detail.


Integers (Part 2)

The second condition, `j S> k`, evaluates to `True`, because this time the “signed greater than” operator was used.

Examine one more example:

INTEGER a, b, c, d;

Function Main()\
{\
a = 100;\
b = ‑4;

c = a / b; // c = 0 (100/65532)\
d = a S/ b; // d = ‑25 (100/‑4)}

Notice that `c` calculates to `zero` because the `/` operator is unsigned. It treats the variable `b` as `+65,532`. Since the `/` operator truncates the decimal portion of the result, `c` becomes `zero`. In regard to the variable `d`, since the signed division operator `S/` was used, `b` is treated as `‑4` and the result is `‑25`.

A final note regarding signed/unsigned integers, if an operation results in a number that is greater than 65,535 that number overflows and the value wraps around again starting at zero. This allows certain operators (e.g. +, ‑, and \*) to operate with no regard to sign (the result is accurate when thinking of the numbers as signed or unsigned). This also means that when trying to add (or multiply) two unsigned numbers and the result is greater than 65,535, the answer may not be what is expected.

This is because the `>` operator performs an “unsigned greater than” operation. If both `j` and `k` are converted to unsigned values, `j` remains at `2`, but `k` becomes `65,535`, and thus `k` is obviously not smaller than `j`.


Strings (Part 1)

### Strings

String variables are used to hold multiple characters in a single variable. The term string is used to illustrate the act of stringing together a number of characters to produce words, sentences, etc. Typically strings in SIMPL+ are used to hold things such as serial commands, database records, and so on.

Strings are declared as follows:

STRING \<string1[size]\>, \<string2[size]\>,…,\<stringn[size]\>;

The number in square brackets following the variable name defines the size of the string variable. When declaring strings, choose a size that is large enough to hold any amount of data that might be needed, but that is not overly large so as to waste space. That is, it is unnecessary to set the variable size to 100 characters when a given variable in an application does not contain more than 50 characters.

Working with strings is not unlike working with other variable or signal types. To assign a value to a string, for example, do the following:

STRING myString[50];\
myString = "Tiptoe, through the tulips\n";

In the example above, a variable called `myString` is declared, which can contain up to 50 characters of data. The value, `Tiptoe, through the tulips\n`, is the value being assigned to the variable. The double-quotation marks surrounding the data defines a literal string expression. That is, a string expression which is defined at compile‑time (when the program is compiled) and cannot change during run-time (while the program is running). Also note the `\n` at the end of this string literal. This represents a newline, or a carriage return followed by a line feed. This character combination is used often, so a shortcut was developed. For a complete list of similar shortcuts, refer to the latest revision of the SIMPL+ Language reference Guide. Finally, note that the square brackets were not included after the variable name, as was done when it was declared. When assigning a value to a string, that value is always assigned starting at the first character position.

It is important to note that the length of the this value does not exceed total length allocated for `myString` (in this case, 50 characters). If the declared variable is not large enough to hold the data being assigned to it, the data is truncated to as many characters as the string can hold. Also, when the program is being executed, a string overflow error will be reported within the control system’s error log.

The example above is useful, but does not really begin to tap the enormous string‑generating capabilities of SIMPL+. A common task in control system programming is the control of an audio/video matrix switch. To control such a router, often it is necessary to specify the input and output which make up the desired matrix crosspoint. As an example, assume the device to be controlled expects to see a command in a format, shown as follows:

IN\<input#\>OUT\<output#\>\<CR\>\<LF\>

This protocol allows the input and output to be specified as numbers. For example, to switch `input 4` to `output 10`, the command would be as follows:

IN4OUT10\<CR\>\<LF\>

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
Strings

Working with Data (Variables) > All About Variables > Strings


Strings (Part 2)

Where `<CR><LF>` represents the carriage return and line feed characters. Obviously, for a router of any significant size, the number of possible crosspoints can grow very large. Thus creating a literal string expression for each case would be very inefficient. Instead, build the control string dynamically as the program runs. An easy way to do this is through the use of the string concatenation operator (`+`). Note that this is identical to the addition operator, but the SIMPL+ compiler is smart enough to know whether integers are added or string expressions are concatenated.

This is addressed in the following code:

DIGITAL_INPUT do_switch;\
STRING_OUTPUT switcher_out[10];\
INTEGER input, output;

PUSH do_switch\
{\
switcher_out = "IN" + itoa(input) + "OUT" + itoa(output) + "\n";}

In this example, the `+` operator is used to concatenate multiple string expressions. The itoa function has been used, which converts an integer value (in this case from analog input signals) into a string representation of that number (e.g. 23 becomes “23”).

There is an alternate way to build strings in SIMPL+. The `MakeString` function provides functionality similar to the concatenation operator, while providing a bit more power and flexibility. The following line is equivalent to the concatenation statement above:

MakeString( switcher_out, "IN%dOUT%d\n", input, output );

This syntax is a bit more confusing. The first argument to the `MakeString `function, `switcher_out`, is the destination string. This is where the resulting string created by `MakeString` is placed. The second argument, the part embedded in double-quotation marks, is the format specification. This determines the general form of the data.

Notice how the constant parts of the string are entered directly. The interesting thing about the format specification is the `%d` sequences, which are known as type specifiers.

DIGITAL_INPUT INTEGER MakeString PUSH Part STRING_OUTPUT Strings

As an example, assume the device to be controlled expects to see a command in a format, shown as follows:

IN\<input#\>OUT\<output#\>\<CR\>\<LF\>

This protocol allows the input and output to be specified as numbers. For example, to switch `input 4` to `output 10`, the command would be as follows:

IN4OUT10\<CR\>\<LF\>


Variable Scope

### Variable Scope

Variable declarations in a SIMPL+ program can be either global or local. Global variables are defined in the Define Variables section of the code, and exist throughout the entire SIMPL+ program. This means that any event function or user‑defined function can reference and modify global variables. When the value of a global variable is being set or modified, it is reflected throughout the entire program.

Local variables are defined inside a function declaration and exist only inside that particular function. In other words, if a local variable, `byteCount`, were defined inside of a function, `CalcChecksum`, any reference to `byteCount`, outside of the scope of this function (e.g. in another function) will result in a compiler syntax error. Note that different functions can use the same variable names when defining local variables. Take a look at the following example:

// simple function to add up all the bytes in a\
// string and append the sum as a single byte\
// onto the original string.\
String_Function CalcChecksum(STRING argData)\
{\
INTEGER i, checksum;

checksum = 0;

for (i = 1 to len(argData))\
checksum = checksum + byte(argData,i);

return (argData + chr(checksum));}

In this example, `i` and `checksum` are local variables that only exist inside the function, `CalcChecksum`. This example also introduces an additional way to implement a local variable: by passing it as an argument to the function, as was done with the `STRING` variable, `argData`. The concept of local variables and argument passing is discussed in detail in the section [User Defined Functions].

While the use of global variables may seem simpler, local variables can help keep your programs better organized and easier to debug. A significant disadvantage of global variables is that you must be careful each time you use or modify a variable that it does not have an adverse effect on another part of the program. Since local variables can only be used inside of a function, this is not a concern.

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
Variable Scope

CalcChecksum INTEGER STRING Scope Variable

Notice how the constant parts of the string are entered directly. The interesting thing about the format specification is the `%d` sequences, which are known as type specifiers.


Arrays (Part 1)

## Arrays

When `INTEGER` or `STRING` variables are declared, the user may also declare them as one‑ or two‑dimensional (Integers only) arrays. An array is a group of data of the same type arranged in a table. A one‑dimensional array can be thought of as a single row with two or more columns, while a two-dimensional array can be thought of as a table with multiple rows . In SIMPL+, arrays are declared as follows.

INTEGER myArray1[15]    // 1-D integer array with 16 elements\
INTEGER myArray2[10][3] // 2-D integer array with 11x4 elements\
STRING myArray3[50][8]  // 1-D string array with 9 elements

The first two examples above define `1D` and `2D` integer arrays, respectively. The last example looks like it declares a `2D` array of strings, yet the comments states that it actually declares a `1D` array of strings. Recall that in [Working with Data (Variables)][1], it was necessary to define the maximum size of the string in square brackets, which is the same notation used for arrays. So, in the example above, nine-element array of 50‑byte strings is being declared. The user cannot declare a `2D` array of strings in SIMPL+.

Another question should have come to mind from the above examples. That is, why does declaring `myArray1[15]` create an array with 16 elements instead of 15? The answer is that array elements start at 0 and go to the declared size (15 in this case). This fact makes for an easy transition to SIMPL+ for programmers of other languages (some of which start at 0 and others which start at 1). That is, if the user is comfortable with treating arrays as starting with element 0, then the user can continue programming in this manner. If however, the user has used languages, which treat the first element in an array as element 1, then the user may want to use that notation instead.

To reference a particular element of an array when programming, use the variable name followed by the desired element in square brackets. Using the arrays declared in the example above, the following statements are all valid in SIMPL+.

j = 5;                 // set an integer variable to 5\
myArray1[3] = j;       // set the 3rd element of the array to 5\
myArray1[j\*2] = 100;   // set the 10th element of the array\
                       // to 100

myArray2[j][1] = k;    // set the j,1 element of myArray2 to\
                       // the value of k

m = myArray2[j][k-1];  // set the variable m to the value in\
                       // the j,k‑1 element of myArray2

myArray3[2] = "test";  // set the 3rd element of the string\
                       // myArray3 to "test"

SIMPL Plus Tutorial
Working With Data
TP Working-with-Data-(Variables)
Arrays

A significant disadvantage of global variables is that you must be careful each time you use or modify a variable that it does not have an adverse effect on another part of the program. Since local variables can only be used inside of a function, this is not a concern.


Arrays (Part 2)

From these examples, it should be clear that the user may use constants, variables, or expressions (discussed in [Operators, Expressions, and Statements]) inside of the brackets to access individual array elements. Array elements can appear on either side of the assignment (`=`) operator. That is they can be written to (left side) or read from (right side). Of special interest is the notation used for storing a value into the string array `myArray3`. Notice that only one set of brackets was used here even though two sets of brackets are needed when declaring the array. Remember that the first set of brackets in the declaration specified the size (in characters) of each string element. Also recall from earlier in this section, that the size field is not included when referring to strings. For example, refer to the following.

myString = "hello!";   // we do not use the size brackets here\
                       // to assign a value to a string variable

As a result, when working with string arrays, only use one set of brackets, which refer to the array element, not the string size.

  [Working with Data (Variables)]: #Integers
  [Working with Strings]: ../Working_With_Strings/TP_Working-with-Strings.md
  [Controlling Program Flow: Branching]: ../Controlling_Program_Flow_Branching/TP_Controlling-Program-Flow-Branching.md
  [Operators, Expressions, and Statements]: ../Operators,_Expressions_and_Statements/TP_Operators,%20Expressions,%20and%20Statements.md
  [User Defined Functions]: ../User_Defined_Functions/TP_User-Defined-Functions.md
  [1]: #Strings

Arrays Branching Controlling Data Defined Expressions Flow Functions Operators Part Program Statements Strings User Working and with

Working with Strings
Controlling Program Flow: Branching
Operators, Expressions, and Statements
User Defined Functions

Using the arrays declared in the example above, the following statements are all valid in SIMPL+. j = 5;                 // set an integer variable to 5\
myArray1[3] = j;       // set the 3rd element of the array to 5\
myArray1[j\*2] = 100;   // set the 10th element of the array\
                       // to 100

myArray2[j][1] = k;    // set the j,1 element of myArray2 to\
                       // the value of k

m = myArray2[j][k-1];  // set the variable m to the value in\
                       // the j,k‑1 element of myArray2

myArray3[2] = "test";  // set the 3rd element of the string\
                       // myArray3 to "test"


Working with Strings

# Working with Strings

In [Working with Data (Variables)] and [Operators, Expressions, and Statements] the concept of the **`BUFFER_INPUT`** was discussed. This section provides a more in-depth treatment of working with incoming serial data.

SIMPL Plus Tutorial
Working With Strings
TP Working-with-Strings
Working with Strings


`BUFFER_INPUT` (Part 1)

## `BUFFER_INPUT`

To review what was discussed earlier, serial data entering a SIMPL+ program may be treated as either a **`STRING_INPUT`** or as a **`BUFFER_INPUT`**. What is the difference, and which one should be used?

The difference between a **`STRING_INPUT`** and **`BUFFER_INPUT`** is quite simple. The value of a **`STRING_INPUT`** is always the last value of the serial signal that feeds it from the SIMPL program. This means that every time new data is generated on the serial signal in the SIMPL program, the **`STRING_INPUT`** variable in the SIMPL+ program changes to contain that data; any data that was previously contained in that variable is lost.

**`BUFFER_INPUT`**s on the other hand do not lose any data that was stored there previously. Instead, any new data that is generated onto the serial signal in the SIMPL program is appended to the data currently in the **`BUFFER_INPUT`** variable.

To make this concept even clearer, consider the following simple example. The SIMPL program shown below contains two Serial Send symbols, each one triggered by a button press. The outputs of these symbols are tied together so that both symbols can generate a string onto the same serial signal. Next this signal is connected in two places to the SIMPL+ module. The first input is mapped to a **`STRING_INPUT`** and the second is mapped to a **`BUFFER_INPUT`**. The declaration section for this module should appear as follows.

STRING_INPUT theString[100];\
BUFFER_INPUT theBuffer[100];

NOTE: The Serial Send symbol simply generates the static text defined in its parameter field onto the output serial signal whenever the trigger input sees a rising signal.

The table below shows the state of these two input variables in response to button presses.

| Action             | theString  | theBuffer                |
|--------------------|------------|--------------------------|
| system initializes | empty      | empty                    |
| button 1 pressed   | “Now is”   | “Now is”                 |
| button 2 pressed   | “the time” | “Now is the time”        |
| button 1 pressed   | “Now is”   | “Now is the time Now is” |

**States of Two Input Variables** {cellspacing="0"}

From this table, notice that each time the serial signal changes, **theString** assumes this value and the old data stored there is lost. On the other hand, **theBuffer** retains any old data and simply appends the new data onto the end.

Each application should dictate whether it is appropriate to use a **`STRING_INPUT`** or a **`BUFFER_INPUT`**. In general, use **`STRING_INPUT`**s when the serial signal that is feeding it is being driven from a logic symbol like a Serial Send, Analog to Serial, or Serial Gather. In these cases, the serial data is issued on a single logic wave. Therefore, it is certain that the entire string is copied into the **`STRING_INPUT`**.

NOTE: A logic wave is the time needed for a signal to propagate from the input to the output of a single logic symbol. This concept is discussed fully in [Understanding Processing Order].

SIMPL Plus Tutorial
Working With Strings
TP Working-with-Strings
`BUFFER_INPUT`


`BUFFER_INPUT` (Part 2)

If, on the other hand, the signal feeding into the SIMPL+ program comes from a streaming source such as a serial port, use a **`BUFFER_INPUT`**, which can gather up the data as it dribbles in.

To solidify this concept, consider another example. Say the program is written for a CD jukebox, which is capable of sending text strings containing the current song information. Typical data received from this device might appear as the following.

Artist=Frank Sinatra, Track=My Way, Album=Very Good Years\<CR\>

Where the **\<CR\>** at the end of the string represents a carriage return character. This is a relatively long string of data and it is quite possible, even probable, that the operating system would not remove the entire string from the serial port in one piece. This is due to the fact that the control system checks the state of the serial ports very often and removes any data that is found there. Since this data takes some time to reach the port (depending on the baud rate), it is likely that the port’s input buffer is collected before the whole string is there. If there was a serial signal called **jukebox_in** connected to the rx terminal on the COM port definition, the program might be written as follows.

first pass:

jukebox_in = "Artist=Frank Sinatra, Trac"

second pass:

jukebox_in = "k=My Way, Album=Very Good Yea"

third pass:

jukebox_in = "rs\<CR\>"

If this signal, **jukebox_in**, were then connected to a **`STRING_INPUT`** of a SIMPL+ program, it is likely that the string might not be seen as one complete piece. Thus the artist’s name, the track name, and the album name might not be parsed out for display on a touchpanel. On the other hand, if a **`BUFFER_INPUT`** were used instead, this buffer would collect the data as it arrived. Therefore, after the processor read the port the third time, this **`BUFFER_INPUT`** would contain the complete string.

NOTE: A logic wave is the time needed for a signal to propagate from the input to the output of a single logic symbol. This concept is discussed fully in [Understanding Processing Order].


Removing Data From Buffers (Part 1)

## Removing Data From Buffers

Once data has been routed into a **`BUFFER_INPUT`**, techniques are required to extract data from it. Typically the first thing to be done with data on a **`BUFFER_INPUT`** is to pull off a completed command and store it into a separate variable. For example, most data that comes from other devices are delimited with a certain character (or characters) to denote the end of the command. In many instances a carriage return (or carriage return followed by a line feed) is used.

The **getc** function is the most basic way to remove data from a buffer. Each call of **getc** pulls one character out of the buffer and returns that character’s ASCII value as the function’s return value. Characters are removed from the buffer in the order they arrived. Thus the first character in becomes the first character out. This function now provides the ability to extract data until the desired delimiter is seen. For example, the following code is read data from the buffer until a carriage return is seen.

BUFFER_INPUT data_in[100];\
INTEGER nextChar;\
STRING temp[50], line[50];

CHANGE data_in // trigger whenever a character comes in\
{\
do\
{nextChar = getc(data_in); // get the next character\
temp = temp + chr(nextChar);\
if (nextChar = 0x0D) // is it a carriage return?\
{line = temp;\
temp = "";}} until (len(data_in) = 0) // empty the buffer}

Function Main()\
{\
temp = "";}

Notice that a **do‑until** loop was used in the example above. Every time a change event is triggered for the **data_in** buffer, it is uncertain that only one character has been inserted. In fact, many characters may have been added since the last change event. Due to this possibility, continue to pull characters out of the buffer with **getc** until the buffer is empty, which is what the expression **(len(data_in) = 0)** reveals.

Also notice from the example that the extracted character is stored into an integer. This is because **getc** returns the ASCII value of the character, which is an integer. On the next line, the **chr** function is used to convert that value into a one‑byte string, which can be added to temp.

Although this example should work for real‑world applications, there is a potential problem should multiple lines of data come in on the same logic wave. Should this happen, only the last complete line is stored into **line** and the rest is lost. To account for this, make **line** into a string array and store each subsequent line into a different array element. Another possibility is that any code that is needed to further act upon the data could be built directly into this loop. Thus removing the need to store more than one line of data.

Once the data has been removed from the buffer and stored in a known format (in this case, one complete command from the device), the desired data can be extracted. Using the example above where the data was coming from a CD jukebox, the following example could be used to extract the artist, track, and album title.

BUFFER_INPUT jukebox[100];\
STRING_OUTPUT artist, track, album;\
INTEGER startPos;\
STRING searchStr[20], tempStr[100];

SIMPL Plus Tutorial
Working With Strings
TP Working-with-Strings
Removing Data From Buffers

BUFFER_INPUT Buffers CHANGE Data From INTEGER Main Part Removing STRING STRING_OUTPUT

Working with Strings > Removing Data From Buffers


Removing Data From Buffers (Part 2)

CHANGE jukebox\
{\
do\
{tempStr = tempStr + chr(getc(jukebox));\
if ( right(tempStr,1) = "\r" )\
{searchStr = "Artist=";\
startPos = Find(searchStr,tempStr);\
if (startPos) { // was the string found?\
  startPos = startPos + len(searchStr);\
  artist = mid(tempStr, startpos,\
            Find(",",tempStr,startpos) ‑ startpos);\
  searchStr = "Track=";\
  startpos = Find(searchStr,tempStr) + len(searchStr);\
  track = mid(tempStr, startpos,\
            Find("\r",tempStr,startpos) ‑ startpos);\
  searchStr = "Album=";\
  startpos = Find(searchStr,tempStr) + len(searchStr);\
  album = mid(tempStr, startpos,\
            Find("\r",tempStr,startpos) ‑ startpos);\
  tempStr = "";  }}

} until (len(jukebox) = 0);}

Function Main()\
{\
tempStr = "";}

This example introduces two new system functions, which are extremely useful for string manipulation, the **Find** and **Mid** functions. To search for the existence of a substring inside of another string, use **Find**. If it is located, the return value of the function is the character position where this string was found. If the substring was not found, then **Find** returns zero. Notice that towards the top of the example the program checked to see if the substring **Artist=** is found in the string. If it is not, then assume that the incoming data was of another format and there is no need to bother looking for the other search strings (**Track=** and **Album=**).

  [Working with Data (Variables)]: ../Working_With_Data/TP_Working-with-Data-(Variables).md
  [Operators, Expressions, and Statements]: ../Operators,_Expressions_and_Statements/TP_Operators,%20Expressions,%20and%20Statements.md#Inputs,
  [Understanding Processing Order]: ../Understanding_Processing_Order/TP_Understanding-Processing-Order.md

Buffers CHANGE Data Expressions Find From Main Operators Order Part Processing Removing Statements Understanding Variables Working and with

Working with Data (Variables)
Operators, Expressions, and Statements
Understanding Processing Order

Using the example above where the data was coming from a CD jukebox, the following example could be used to extract the artist, track, and album title. BUFFER_INPUT jukebox[100];\
STRING_OUTPUT artist, track, album;\
INTEGER startPos;\
STRING searchStr[20], tempStr[100];


Working with Time

# Working with Time

Up until now, this manual has discussed elements of the SIMPL+ language that have no concept of time. This means that each statement in a SIMPL+ program executes after the previous statement is completed. There are times when programming where there is a need to have control over exactly when the statements in the program execute. This section deals with those language constructs.

## Delay

The **Delay** function pauses the execution of the current SIMPL+ for the time specified in the parameter field. As with most time values in SIMPL+, this time value is specified in hundredths of seconds. The following program causes the program to stop for five seconds before resuming.

PUSH startMe\
{\
Print("I’m starting now...");\
Delay(500);     //this equals 5 seconds\
Print("and I’m ending 5 seconds later.\n");}

It is important to realize that the control system never allows a SIMPL+ program to lock up the rest of the system for any significant amount of time. Thus, whenever a delay function is reached, the control system performs a task switch, meaning that the execution of the current SIMPL+ module stops momentarily as the natural flow through the SIMPL program continues. In this case, even though the SIMPL+ program has stopped for five seconds, other code in the overall SIMPL program (including standard logic symbols and other SIMPL+ modules) continues to operate normally. The concept of task switching is covered in more detail in the section [Understanding Processing Order].

## Pulse

The **Pulse** function is used to drive a digital output signal high for a specified amount of time. Once again, the time is specified in hundredths of seconds. When a **Pulse** statement is executed, the digital output signal specified is driven high and a task switch occurs. Such a task switch is necessary in order for the rest of the SIMPL program to recognize that the digital signal has indeed gone high. After the time specified has expired, the digital output is driven low and another task switch occurs. The following program causes the digital output signal, **preset_1**, to be pulsed for a half a second.

#DEFINE_CONSTANT PULSE_TIME 50

DIGITAL_OUTPUT preset_1, preset_2, preset_3;

PUSH some_input\
{\
Pulse(PULSE_TIME, preset_1);}

The **Pulse** function is very similar in operation to the SIMPL One Shot symbol. In fact, in many cases, it may be more convenient (or more sensible) to simply connect a One Shot, or Multiple One Shot symbols to the output signals of a SIMPL+ module.

Also notice that unlike the **Delay** function, **Pulse** does not cause a pause in the execution of the SIMPL+ code. Therefore, the statements that follow the **Pulse** execute immediately and do not wait for the expiration of the pulse time.

SIMPL Plus Tutorial
Working With Time
TP Working-with-Time
Working with Time

DEFINE_CONSTANT DIGITAL_OUTPUT Delay PUSH Print Pulse Time Working with


Wait Events (Part 1)

## Wait Events

Wait events in SIMPL+ allow operations that are somewhat similar to the Delay SIMPL logic symbol. The syntax for a Wait event is as follows.

Wait (wait_time [, wait_name])\
{\
\<statements\>}

This syntax defines a Wait event to occur at some time in the future, defined by the value of **wait_time**. While the Wait event is pending execution, it is said to have been scheduled. The Wait event may have an optional name, which can be used to refer back to the event elsewhere in the code.

When a Wait event definition is reached during execution, the execution of the statements inside the braces (these braces are not needed if the event is only one statement long) is deferred until the time defined by **wait_time** has expired. Until this occurs, the remainder of the SIMPL+ program executes. If a Wait event definition is nested inside of a loop, it is possible that it can be reached multiple times before it even executes once. If a Wait event is pending (i.e., has been scheduled, but not executed), it is not scheduled again until it has been completed.

Once a Wait event has been scheduled to execute at some later point in time, various operations can be performed on the event before it actually executes. However, only named Wait events can be modified in this manner, since it is necessary to use the name to refer to the event. The table on the next page lists the available functions, which can operate on Wait events.

| Function | Description |
|----|----|
| CancelWait(name) | Removes the named wait from the schedule. The code never executes. |
| CancelAllWait() | Removes all pending waits from the schedule. |
| PauseWait(name) | Stops the timer for the named wait. The code does not execute until the timer is started again using ResumeWait(). |
| ResumeWait(name) | Resumes the timer for the named wait, which had been paused earlier. |
| PauseAllWait() | Similar to PauseWait(), but acts on all pending wait events. |
| ResumeAllWait() | Similar to ResumeWait(), but acts on all paused wait events. |
| RetimeWait(time, name) | Sets the time for a pending wait event to the value specified. |

Functions Available During Wait Events {cellspacing="0"}

This example shows a typical use of wait events. Here, the **SYSTEM ON** button starts a power up sequence and the **SYSTEM OFF** button likewise starts a power down sequence.

#DEFINE_CONSTANT PULSETIME 50 // half second

DIGITAL_INPUT system_on, system_off;\
DIGITAL_OUTPUT screen_up, screen_down, lift_up, lift_down;\
DIGITAL_OUTPUT vcr_on, vcr_off, dvd_on, dvd_off;\
DIGITAL_OUTPUT vproj_on, vproj_off;\
DIGITAL_OUTPUT vproj_video1, vproj_video2, vproj_rgb;\
DIGITAL_OUTPUT lights_pre_1, lights_pre_2, lights_pre_3;

PUSH system_on\
{\
CancelWait(sysOffWait); // cancel the system off wait event

Pulse(2000, screen_down); // lower screen for 20 sec.\
Pulse(9500, lift_down); // lower lift for 9.5 sec.

Wait (1000, sysOnWait1) // 10 second delay\
{Pulse(PULSETIME, vcr_on);\
Pulse(PULSETIME, dvd_on);\
Pulse(PULSETIME, lights_pre_1);\
Pulse(PULSETIME, vproj_on);}

Wait (1500, sysOnWait2) // 15 second delay pulse(PULSETIME, vproj_video);} // end of push event

PUSH system_off\
{\
CancelWait(sysOnWait1);\
CancelWait(sysOnWait2);

SIMPL Plus Tutorial
Working With Time
TP Working-with-Time
Wait Events

CancelAllWait CancelWait DEFINE_CONSTANT DIGITAL_INPUT DIGITAL_OUTPUT Events PUSH Part PauseAllWait PauseWait Pulse ResumeAllWait ResumeWait RetimeWait Wait

Also notice that unlike the **Delay** function, **Pulse** does not cause a pause in the execution of the SIMPL+ code. Therefore, the statements that follow the **Pulse** execute immediately and do not wait for the expiration of the pulse time.


Wait Events (Part 2)

Pulse(2000, screen_up);\
Pulse(PULSETIME, vproj_off);\
Pulse(PULSETIME, vcr_off);\
Pulse(PULSETIME, dvd_off);

Wait(500, sysOffWait)\
{Pulse(9500, lift_up);\
Pulse(PULSETIME, lights_pre_3); } } // end of push event

Notice that in this example, the **CancelWait** function was used to cancel any pending waits when the **SYSTEM ON** or **SYSTEM OFF** buttons were pressed. This is analogous to using the reset input on the Delay symbol in SIMPL.

  [Understanding Processing Order]: ../Understanding_Processing_Order/TP_Understanding-Processing-Order.md

Events Order Part Processing Pulse Understanding Wait

#DEFINE_CONSTANT PULSETIME 50 // half second

DIGITAL_INPUT system_on, system_off;\
DIGITAL_OUTPUT screen_up, screen_down, lift_up, lift_down;\
DIGITAL_OUTPUT vcr_on, vcr_off, dvd_on, dvd_off;\
DIGITAL_OUTPUT vproj_on, vproj_off;\
DIGITAL_OUTPUT vproj_video1, vproj_video2, vproj_rgb;\
DIGITAL_OUTPUT lights_pre_1, lights_pre_2, lights_pre_3;

PUSH system_on\
{\
CancelWait(sysOffWait); // cancel the system off wait event

Pulse(2000, screen_down); // lower screen for 20 sec.\
Pulse(9500, lift_down); // lower lift for 9.5 sec. Wait (1000, sysOnWait1) // 10 second delay\
{Pulse(PULSETIME, vcr_on);\
Pulse(PULSETIME, dvd_on);\
Pulse(PULSETIME, lights_pre_1);\
Pulse(PULSETIME, vproj_on);}

Wait (1500, sysOnWait2) // 15 second delay pulse(PULSETIME, vproj_video);} // end of push event

PUSH system_off\
{\
CancelWait(sysOnWait1);\
CancelWait(sysOnWait2);


Task Scheduling Changes and Consequences (Part 1)

# Task Scheduling Changes and Consequences

NOTE: the following applies to 2-series firmware 3.137 and higher.

The previous scheduling algorithm method for SIMPL+ events caused unexpected results when writing multiple files simultaneously to compact flash.  We will describe both the old and new algorithms.  This advanced topic assumes that you are familiar with the 2-series task-switching topic [Task Switching for 2-Series Control Systems].

NOTE: Also refer to [Task Switching for X-Generation (CNX) Control Systems] for a discussion of task switching in that environment.

Overview

One of the premises of a SIMPL+ module is that it has to appear to be the same as any other logic gate in terms of logic delays.  That means that the SIMPL+ code should transfer its inputs into outputs in one logic wave.  This is not always possible due to what the programmer has coded into the SIMPL+ module.  Because the programmer can put delays or loops in his code, the SIMPL+ code is limited in the amount of time it can run before logic resumes.  This slice of time is currently 20 milliseconds.

Initial Design

In the initial design for the 2-series, the SIMPL+ code ran at the lowest priority in the system.  This priority level was chosen to prevent SIMPL+ code from blocking other code from running.  For example, if the programmer used a “while(1)” loop without any delays, no lower priority code would ever run.  The mechanism for getting SIMPL+ to run as a logic gate required the logic processor to pause to allow for the SIMPL+ code to start running.  The logic processor would wait for a maximum of 0.5 seconds before resuming.  If the SIMPL+ task has not started after waiting 0.5 seconds, the processor generates a “SIMPL+ Event Timeout” error message.  This is caused by other parts of the system occupying the processor besides the logic processor.

Also, in the initial design, the SIMPL+ code was marked as non-preemptive.  This means that while the SIMPL+ code was running, no other process in the system could interrupt its execution.  The code ran until it finished, release control temporarily back to the control system (delay() or ProcessLogic()), or its allotted time of 20 ms ran out.  This is the portion that may interfere with the low level operations of the file system.  Low-level function calls dealing with file operations would mark the current task as non-preemptive.  The system then assumed that no other task could interrupt the current task while it was non-preemptive.  Since SIMPL+ would timeslice out and release the processor, if another SIMPL+ task performed a file operation, possible data corruption could occur.

Redesign

Changes Consequences Generation Part ProcessLogic Scheduling Task and


Task Scheduling Changes and Consequences (Part 2)

The goal of the redesign was to allow SIMPL+ to be able to run in a preemptive mode while still maintaining its logic wave propagation as well as protect against runaway user code.  The initial timeslice for SIMPL+ code will execute at a priority higher than the logic processor for code generated from events and the function Main().  This allows the signals to propagate as a logic wave.  If the SIMPL+ code either returns control back to the operating system (delay() or ProcessLogic()) or its 20 ms timeslice expires, the SIMPL+ task will then drop to the lowest priority in the system to protect against runaway SIMPL+ code.  It should be noted that Wait() events always run at the lowest priority.  There is no logic wave propagation requirement for Wait() events.

The new design resolves the possible conflicts with the low-level constructs of the system that was causing problems during file operations.  Since there was no alternative to correcting these conflicts, we needed to make the SIMPL+ tasks preemptive.

Now that the SIMPL+ code starts at a higher priority than logic, there is the possibility that SIMPL programs will operate differently.  Most notable will be SIMPL+ events fired from SIMPL+ code running at the low priority.   This could be a Wait() statement or any SIMPL+ code after it has called ProcessLogic().  For example, if a Wait()  statement set a digital output high and that signal went to trigger other SIMPL+ code, the new SIMPL+ code would run before the rest of the Wait() statement.  This will create subtle timing changes that should be negligible for most applications.

Example

The following is an example of SIMPL+ code that demonstrates a timing change caused by the new algorithm.

/\*

Comments:   This is a test to demonstate what happens when a Wait() statement

            triggers an input in the same module.  The premise behind the

            module is that a device can only process commands one at a time.

            The SIMPL program must wait until it either gets a response

            from the device or a timeout occurs.

            In this example program, there is no device attached to the com

            port so the timeout will always occur.  It is the timeout code in

            the Wait() statement that is causing the change that is seen in

            firmware 3.137.  This modules emulates talking to two devices.  The

            code for both devices is almost identical with the exception that

            device 2 code has a ProcessLogic() call before its Wait() statement.

            This reverts the scheduling to the old method.

\*/

#DEFINE_CONSTANT    MAX_NUM_RETRIES    4

#DEFINE_CONSTANT    TIMEOUT_VALUE     100        // use a 1 second timeout

#DEFAULT_NONVOLATILE

#ENABLE_STACK_CHECKING

DIGITAL_INPUT diEnable1;        // start sending commands.

DIGITAL_INPUT diTrigger1;       // Resend the last command or get the next one.

DIGITAL_INPUT diEnable2;        // start sending commands.

DIGITAL_INPUT diTrigger2;       // Resend the last command or get the next one.

STRING_INPUT siRx1$[1024];      // Holds the response from the first device.

BUFFER_INPUT biCmdQueue1[1024]; // Commands to send to the first device.

Since SIMPL+ would timeslice out and release the processor, if another SIMPL+ task performed a file operation, possible data corruption could occur. Redesign


Task Scheduling Changes and Consequences (Part 3)

STRING_INPUT siRx2$[1024];      // Holds the response from the second device.

BUFFER_INPUT biCmdQueue2[1024]; // Commands to send to the second device.

DIGITAL_OUTPUT doResend1;       // this will be wrapped around to diTrigger1

DIGITAL_OUTPUT doResend2;       // this will be wrapped around to diTrigger2

DIGITAL_OUTPUT doTest1Complete; // For thistest only, clears the Enable input

DIGITAL_OUTPUT doTest2Complete;

STRING_OUTPUT soTx1$;           // Data to send to device 1.

STRING_OUTPUT soTx2$;           // Data to send to device 2.

//  Global Variables

INTEGER   iCmdSendCnt1, iCmdSendCnt2;  // Retry counters

STRING    sCmd1[80], sCmd2[80];        // holds the last command sent

INTEGER_Function ExtractCommand1()

{

    if (Find("\n", biCmdQueue1) \> 0)

    {

    sCmd1 = Remove("\n", biCmdQueue1);

    if (len(sCmd1) \> 0)

    {

            iCmdSendCnt1 = 0;

            return (1);

        }

    }

    else  // Queue is empty.  Test is over.  This is used for our test only.

    {

        doTest1Complete = 1;

        doTest1Complete = 0;

    }

    return (0);

}

Function SendCommand1()

{

    /\* The Wait() statement pulses the Resend signal.  This will cause an PUSH

       event to be generated.  In firmware version 3.137 and above, the PUSH

       event runs before the code in the Wait() statement finished.  Since the

       trigger event calls this routine again, the Wait() statement will not

       get scheduled because it is still running in a different thread.

     \*/

    soTx1$ = sCmd1;

    iCmdSendCnt1 = iCmdSendCnt1 + 1;

    wait(TIMEOUT_VALUE, ResponseTimeout1)

    {

        doResend1 = 1;

        doResend1 = 0;

    }

}

INTEGER_Function ExtractCommand2()

{

    if (Find("\n", biCmdQueue2) \> 0)

    {

    sCmd2 = Remove("\n", biCmdQueue2);

    if (len(sCmd2) \> 0)

    {

            iCmdSendCnt2 = 0;

            return (1);

        }

    }

    else  // Queue is empty.  Test is over.  This is used for our test only.

    {

        doTest2Complete = 1;

        doTest2Complete = 0;

    }

    return (0);

}

Function SendCommand2()

{

    /\* The Wait() statement pulses the Resend signal.  This will cause an PUSH

       event to be generated.  In firmware version 3.137 and above, the PUSH

       event runs before the code in the Wait() statement finished.  By calling

       ProcessLogic(), the PUSH event task is scheduled at the same priority of

       the Wait() event task and will run after the Wait() event has finished.

       This allows the PUSH event task to schedule a new Wait() timeout for the

       new command sent.

     \*/

    soTx2$ = sCmd2;

    iCmdSendCnt2 = iCmdSendCnt2 + 1;

    ProcessLogic();

    wait(TIMEOUT_VALUE, ResponseTimeout2)

    {

        doResend2 = 1;

        doResend2 = 0;

    }

}

//  Event Handlers

PUSH diEnable1

{

    if (ExtractCommand1() = 1)

        SendCommand1();

}

PUSH diEnable2

{

    if (ExtractCommand2() = 1)

        SendCommand2();

}

/\* These RELEASE events are added to rerun the test.  Clear the buffers because

   the queue will be reprimed when the test ends

 \*/

RELEASE diEnable1

{

    ClearBuffer(biCmdQueue1);

}

RELEASE diEnable2

{

    ClearBuffer(biCmdQueue2);

}

PUSH diTrigger1

{

if (iCmdSendCnt1 \>= MAX_NUM_RETRIES)  // get the next command

{

    if (ExtractCommand1() = 1)

    {

            SendCommand1();

        }

    }

    else

    {

        SendCommand1();

    }

}

PUSH diTrigger2

{

if (iCmdSendCnt2 \>= MAX_NUM_RETRIES)  // get the next command

{

STRING_INPUT siRx1$[1024];      // Holds the response from the first device. BUFFER_INPUT biCmdQueue1[1024]; // Commands to send to the first device.


Task Scheduling Changes and Consequences (Part 4)

    if (ExtractCommand2() = 1)

    {

            SendCommand2();

        }

    }

    else

    {

        SendCommand2();

    }

}

CHANGE biCmdQueue1

{

    if (diEnable1)

    {

        SendCommand1();

    }

}

CHANGE biCmdQueue2

{

    if (diEnable2)

    {

        SendCommand2();

    }

}

CHANGE siRx1$

{

    CancelWait(ResponseTimeout1);

    // get next command

    if (ExtractCommand1() = 1)

        SendCommand1();

}

CHANGE siRx2$

{

    CancelWait(ResponseTimeout2);

    // get next command

    if (ExtractCommand2() = 1)

        SendCommand2();

}

Function Main()

{

    iCmdSendCnt1 = 0;

    iCmdSendCnt2 = 0;

}

Glossary

Preemptive – A process that will be interrupted when a higher priority process is ready to execute.

Non-preemptive – A process that will continue to run even though a higher priority process is waiting to execute.

Timeslice –  A period of time that a process is allowed to execute before relinquishing the processor for other processes.

  [Task Switching for 2-Series Control Systems]: Task_Switching_for_2-Series_Control_Systems.md
  [Task Switching for X-Generation (CNX) Control Systems]: Task_Switching_for_CNX_Control_Systems.md

CHANGE CNX CancelWait Changes Consequences Control ExtractCommand1 ExtractCommand2 Generation Main Part Scheduling SendCommand1 SendCommand2 Series Switching Systems Task and for

\*/

    soTx2$ = sCmd2;

    iCmdSendCnt2 = iCmdSendCnt2 + 1;

    ProcessLogic();

    wait(TIMEOUT_VALUE, ResponseTimeout2)

    {

        doResend2 = 1;

        doResend2 = 0;

    }

}

//  Event Handlers

PUSH diEnable1

{

    if (ExtractCommand1() = 1)

        SendCommand1();

}

PUSH diEnable2

{

    if (ExtractCommand2() = 1)

        SendCommand2();

}

/\* These RELEASE events are added to rerun the test. Clear the buffers because

   the queue will be reprimed when the test ends

 \*/

RELEASE diEnable1

{

    ClearBuffer(biCmdQueue1);

}

RELEASE diEnable2

{

    ClearBuffer(biCmdQueue2);

}

PUSH diTrigger1

{

if (iCmdSendCnt1 \>= MAX_NUM_RETRIES)  // get the next command

{

    if (ExtractCommand1() = 1)

    {

            SendCommand1();

        }

    }

    else

    {

        SendCommand1();

    }

}

PUSH diTrigger2

{

if (iCmdSendCnt2 \>= MAX_NUM_RETRIES)  // get the next command

{