Subroutines in a TCL script.

A subroutine is a group of actions that are required over and over again in a script. By making them into a subroutine your script can be shorter and easier to debug. For example setting turnouts in a route might require several actions, and is required many times in a script.

A subroutine is like a When or While statement in that it has a list of actions to execute, and any sequence of actions that can be in a When or While statement can also be in a subroutine. However a subroutine does not have a set of conditions that triggers it to run, instead it is triggered by a call to that subroutine in some other statement, or another subroutine even. After the subroutine completes running it triggers the calling statement to continue running, if required.

A trivial example of a subroutine might be:

Variables: x, y, z
SUB setup () x=1, y=2, z=3, ENDSUB
WHEN ... DO setup ()

The parentheses are required, both in the subroutine definition, and in the call. In this instance the subroutine in only of value if it is called in several places, which allows the script to be slightly shorter than it would otherwise be.

To be more useful a subroutine can take parameters, perhaps values that differ from one call to another:

Variables: x, y, z
SUB setup (xi, yi, zi) x=xi, y=yi, z=zi, ENDSUB
WHEN ... DO setup (1, 2, 3)
WHEN ... DO setup (4, 5, 6)

xi, yi and zi are variables that are local to subroutine setup. The values given in the call (such as 1, 2, 3) are copied into xi, yi and zi before the actions in the subroutine run. If you have global variables with the same names they are inaccessible in the subroutine, and the local ones are used instead. If several subroutines have local variables with the same name, then each subroutine has its own variables, even though they have the same name. In the above example it would make no difference as the local variables are all assigned values before the subroutine executes. It is allowed however to declare more local variables than parameters are passed in, for example:

Variables: x, y, z
SUB setup (xi, yi, zi, sum) x=xi, y=yi, z=zi, sum+=x+y, sum+=z, ENDSUB
WHEN ... DO setup (1, 2, 3)
WHEN ... DO setup (4, 5, 6)

In this example the three parameters (1, 2 & 3) are all added to local variable sum (which then has value 6). If the second call is then invoked then 4, 5 & 6 are added to the previous total, making 21. Note that local variables are true variables and retain their values, just like any other variable. Only the first local variables that have values passed in get changed before the subroutine runs.

Parameters are passed by value. This means that the subroutine gets a copy of the value passed and cannot change the original variable.

You can see the current contents of local variables by opening the debug window with View->Local variables. Notice that local variables are stored internally (and logged) with the subroutine name prefixed to the name you put in the script. This allows several variables of the same name to co-exist and be distinguished in the debug window and in the log.

Our example of activating turnouts might look like this:

Controls: turn1, turn2, turn3
SUB turnout (con, dir, ctc) *con=dir, $switch (ctc)=dir, ENDSUB
WHEN ... DO setup (&turn1, Left, (17,5,1))
WHEN ... DO setup (&turn2, Right, (21,7,1))

Note that the output line is passed as a pointer to the control (eg. &turn1), so that the subroutine can change the value of the line (using *con). Without the '&' the subroutine would receive the current state of the line which wouldn't be terribly useful. Also the coordinates of the turnout on a CTC diagram are passed as a single compound value. Such coordinates can be stored in variables and passed to subroutines like any other values.

If several statements try to run the same subroutine at the same time then they take turns so the following script:

Variables: route
Controls: dirSwitch, turn1, turn2, turn3
SUB pulseTurnout (turn) dirSwitch=dir, wait 0.1, &turn=1, wait 0.2, &turn=0, wait 0.1, ENDSUB
WHEN route=1 OR route=2 DO pulseTurnout (&turn1, Left)
WHEN route=1 OR route=3 DO pulseTurnout (&turn2, Right)

If route were set to 1 in loop 100 then both When statements will call pulseTurnout at once, and the actions will take place something like this:

$Session (time in seconds)

Loop number (approx)

Actions executed





Not shown in the above listing



pulseTurnout (&turn1, Left)

pulseTurnout (&turn2, Right)

In first When statement

In second When statement



dirSwitch=Left, wait 0.1

Set direction required in first call to pulseTurnout



&turn=1, wait 0.2

Pulse the output line turn1



&turn=0, wait 0.1

End output pulse




End of first subroutine call, trigger the first when statement to run again (in case any other actions followed the call) and trigger the subroutine to run with the second set of parameters.




dirSwitch=Right, wait 0.1

End of first When statement

Set direction required in second call to pulseTurnout



&turn=1, wait 0.2

Pulse the output line turn2



&turn=0, wait 0.1

End output pulse




End of second subroutine call




End of second When statement.

Subroutines may declare any number of local variables and may be passed any number of parameters. If too many parameters are passed they are ignored. If more local variables are declared than parameters passed in any call then the excess local variables retain their previous value.

Any number of calls to a subroutine may be outstanding at any time (waiting for all previous calls to complete). All outstanding calls have their own separate list of parameters saved. The parameters passed into a subroutine are the values at the time that the call was made (as opposed to the time that the subroutine eventually ran).

Be aware that statements that call subroutines may be suspended for some time waiting for the subroutine to complete all prior calls.

Last updated 30 Jul 2008

© Howard Amos 2008-2009