Intro to TCL scripting

There is practically no limit to the possibilities when it comes to adding some autonomous behaviour to a model railway and as such it is impractical to design software that will 'do anything straight out of the box'.

Our railway control software Tcc is designed to allow you to do practically anything, but at a price - you have to tell it exactly what you want it to do.

This is achieved by using a TCL (Train Control Language) script which is a kind of programming language, but one designed for control applications like a model railway.

You do not need any previous experience with computer programming. In fact not having any experience can be better as TCL does not follow other languages because they are not designed for controlling lots of things at once.

In TCL everything is reduced to stating what to do and when to do it.

Each script begins with some declarations, which give names to script variables and inputs & outputs and where they are connected.

After the declarations the rest of the script is a set of statements. These statements are the body of the program and define the behaviour we want.

There are three types of statement, and these are all very similar:

WHEN

The standard when statement. When a condition becomes true the statement is executed once and once only. After the execution has finished the condition must become false and then true again for the statement to be executed again.

WHILE

Similar to the when statement except that it is executed continuously until the condition is false. The execution rate depends upon the speed of the computer and any wait or pulse actions in the statement.

ALWAYS

An extension to the while statement, except that it has no condition. The actions are executed continuously, as long as the script is running.

Example 1: Basic relay stop

To take a really simple example, one that you might consider implementing with a few gates or a flip-flop: We want a train entering a hidden siding to stop automatically at the end of the siding. On this layout trains always run slowly when approaching the sidings and so we can safely stop them with a simple relay. We can detect their presence with some kind of sensor, perhaps a light beam, an infra-red reflective unit like an IRDOT (from Heathcote electronics), a reed relay activated by a magnet on the train, an LDR (light dependant resistor) that changes resistance when light is obscured by a train, or whatever. We want the stationary train to depart again when a button on the control panel is pressed. (assume these are straight through sidings, and that the routes have been set somehow).

Suppose we have two inputs, called 'detector' (connected to whatever sensor we are using) and 'release' (connected to the button), and one output, called 'stop' (that is the relay that stops the train when energised).

We could define this behaviour with two instructions:

WHEN detector=1 DO relay=1      { stop train when detector is reached }

WHEN release=1 DO relay=0       { start train when called }

These two instructions are like edge-triggered set and reset lines on some kind of theoretical flip-flop.

Each instruction is executed once, when its condition becomes true, and then will not be executed again until the condition becomes false, and then true again.

The relay is only set to be energised when the front of the train is detected. After that the first statement is not executed again until the train has departed and another train arrives.

The relay is released when the button is first pressed. The two statements do not 'fight' to control the relay (unless the button is pressed at exactly the same moment that the rain arrives, in which case the button wins).

Example 2: A 3-Aspect signal

As another example suppose we were driving a three aspect signal, and we wanted it to be driven based upon track occupancy rather than the more complex route selection and interlocking rules.

We want the signal to show red when a train enters the section just after the signal, or when no train is approaching the signal.

We want yellow if the next section is clear, but the one after it is occupied.

And green if both following sections are clear.

OK, you might not like to drive signals that way, but it makes a simple example.

If the sections are called 'A' (before the signal), 'B' (just after the signal) and 'C' (the one after B, and we have inputs that come from an RPC FTC module (Floating Track Circuit, or 8 track occupancy detectors) with the same names. Assume we also have outputs called red, yellow and green.

One possible script to do this (including the declarations this time) could be:

Constants:
    Free=0, Occupied=1,     { define the polarity of our sensor inputs }
    On=1, Off=0             { define polarity of our outputs }

Sensors:
    A, B, C                 { current detectors attached to sections A, B & C }

Controls:
    red, yellow, green      { outputs that drive our signal }

Actions:

    WHEN A=Free                           { No train approaching }
      OR  A=Occupied, B=Occupied           { Next section occupied }
      DO  red=On, yellow=Off, green=Off

    WHEN A=Occupied, B=Free, C=Occupied
      DO  yellow=On, red=off, green=off    { Train may enter at caution }

    WHEN A=Occupied, B=Free, C=Free
      DO  green=On, red=Off, yellow=Off    { Road ahead clear }

The text in brackets are just comments, so you can leave notes to remind you how your script works - Tcc ignores them completely.

The first few lines (headed 'Constants:') simply gives names to numbers, so that we can use On and Off to mean 1 and 0. This aids readability of the script.

The lines headed 'Sensors:' and 'Controls:' give names to the inputs and outputs that connect to the railway. these are the pins on the FTC and SRO4 modules (or whatever hardware you have chosen to interface to the layout).

After the heading 'Actions:' are the instructions telling Tcc exactly what to do. Each statement consists of the following items:

'WHEN' conditions 'DO' actions

The conditions are simple tests that are anded and or-ed together (the commas may be written 'AND', but a comma abbreviates things nicely).

The actions are things we want to happen when the conditions are true.

Note that a script is not really like a programming language - the statements are not executed in order. The script is more like a written version of logic gates and registers - all the 'WHEN' statements are in essence executing in parallel. The script would do exactly the same regardless of the ordering of the three WHEN statements shown here.

The script is largely compatible with the script used by the software that CTI supplies (free trial download from their web site http://www.cti-electronics.com) and that site has an excellent introduction to these scripts. Tcc adds some advanced capabilities such as structured data and arrays, but does not yet support the ability to play audio files.

Example 3: Streetlamps

Suppose we have a single output driving a set of street lamps. The task is to light the lamps overnight. Tcc maintains a special variable holding layout time-of-day which can run faster than real-time, or could even be kept in track with your operational sequence time.

Controls:
    lamp1
Actions:
    WHEN $TIME=18:00 DO lamp1=1
    WHEN $TIME=06:00 DO lamp1=0

Taking each line in turn it means:

Controls: - Give names to the output wires
lamp1 - the first and only output is named "lamp1"
Actions: - Everything after here is what Tcc should do.
WHEN $TIME=18:00 DO lamp1=1 - At 6pm set the output "lamp1" to on.
WHEN $TIME=06:00 DO lamp1=0 - At 6am set the output "lamp1" to off.

It should be noted that regular inputs and outputs consist of a single wire and are either on or off. This is represented by zero and one in the script.

Adding more functions to this style of program is easy - generally they are just added without regard to what is already there. Suppose now we have two streetlamps that come on at different times:

Controls:
    lamp1, lamp2
Actions:
    WHEN $TIME=18:00 DO lamp1=1
    WHEN $TIME=06:00 DO lamp1=0
    WHEN $TIME=18:15 DO lamp2=1
    WHEN $TIME=05:49 DO lamp2=0

We have simply added more controls and actions.

Using 1 and 0 is rather computer-like, so lets give names to the state of lights as we had in example 2:

Constants:
    lampOn=1, lampOff=0
Controls:
    lamp1
Actions:
    WHEN $TIME=18:00 DO lamp1=lampOn
    WHEN $TIME=06:00 DO lamp1=lampOff

As an extension of this example lets assume you want an input from a switch or button called "dark" to kill all the lights until they are due to come on again. Input lines are called 'Sensors' in the script:

Constants:
    lampOn=1, lampOff=0, pressed=1
Sensors:
    dark
Controls:
    lamp1
Actions:
    WHEN $TIME=18:00 DO lamp1=lampOn
    WHEN $TIME=06:00 DO lamp1=lampOff
    WHEN dark=pressed DO lamp1=lampOff

If the switch or button connected to the input "dark" is activated then the lights get turned off.

OK, this is beginning to confuse because if $TIME is still 18:00 why doesn't "lamp1" get turned straight back on.

The rule is: When a condition (the bit between "WHEN" and "DO") first becomes true then perform the actions (the bits after "DO"). That when statement cannot run again until its condition becomes false and true again. All when statements operate this way, and it is this mechanism which makes tcl such a suitable language for our type of control applications.

Example 4: Crossing

As a final example lets suppose we wanted crossing lights to flash while a train was passing. We could put a sensor some way up the track to detect the approach of the train, and start flashing the lights at that time. To keep things simple lets assume that the train cannot stop on the crossing and the lights should stop 3 seconds after the trains has finished passing the sensor:

Sensors:
    crossing
Controls:
    lamp1, lamp2
Variables:
    flashing
Actions:
    WHEN crossing=TRUE DO flashing=1
    WHEN crossing=FALSE DO wait 3, flashing=0
    WHILE flashing=1 DO lamp1=1, wait 0.4
            lamp1=0, lamp1=1, wait 0.4
            lamp1=0

We have added a new category of data, a variable. Variables can take any integer (whole number) value and are 32 bits long which means that enormous values can be handled. In fact variables can hold data other than simple numbers, but that's a whole new ball-game for later.

Here we are using two built-in constants for sensor values: TRUE and FALSE which take values 1 and 0 respectively.

When the train first reaches the sensor called "crossing" we set the variable "flashing" to 1. When the train has finished passing the sensor we wait for 3 seconds and then set the variable "flashing" back to 0. An infra-red sensor might be suitable here.

Now we find a WHILE instead of a WHEN. With a WHEN the actions are executed only once each time the condition becomes true. With a WHILE the actions are executed as fast as Tcc runs (normally between 50 and 100 times a second) for as long as the condition is true.

So for as long as variable "flashing" is equal to 1 we perform the flashing routine. To flash the lights we turn on lamp1 for 400ms then turn it off and turn on lamp2 for 400ms, then turn it off again. As long as "flashing" is set to 1 lamp1 will immediately light again so the two lamps will flash alternately in the style of an ungated crossing in many countries.

Where do I go next?

TCC_tutorial uses Tcc to drive hardware modules and has more TCL examples
Back to index page.
List of actions available in scripts.
Configuring the serial network in Tcc to match our controls and sensors declarations.
What can Tcc and TCL be used for?

Last updated 16 April 2011

© Howard Amos 2008-2011