Signals on the Kansas City Subdivision

Part 1 - Research  Part 2 - Design   Part 3 - Logic

Updated 1 October 2008

In the past two parts of this series, I described the Research and Design of my signal system for my layout, The Milwaukee Road Kansas City Subdivision.

I have included the diagram of the control panel for reference when you read along with the article. schematic

Hardware connections

As previously discussed, every piece of hardware in the system is connected to a SMINI node. Full details of these connections are described in Bruce Chubb's book "C/MRI User's Manual".

Basic operation of the software

The software is written using Microsoft Visual Basic. Bruce Chubb provides a library of ready-made routines to perform reading and writing to and from the SMINI nodes.

All that is required in my program is to specify which node I want to use, then call the C/MRI library routine.

All of the devices have meaningful names. These names are called 'variables' in programming terminology. It is easiest to think of a 'variable' as a place in the computers memory to store a bit of information. We can put something into a variable and we can read something out of the variable later. Since variables are part of the computer's memory, their values are lost when the program finishes.

All of my variables are set by inputs from the railroad (SMINI nodes) and by the program logic, so it doesn't matter that the variables 'disappear' when the program finishes.

By having symbolic names for the different things you want to test, you can have very readable computer code. We will see this as we progress down the page. The computer software that controls the CTC system does the following basic operations:-
  1. Read all of the inputs and assign them to meaningful names.
  2. Decide what to do using the inputs.
  3. Write all of the outputs to the railroad.
  4. Go back to A and repeat.

Doing block detection

For block detection, the program reads the 9 block inputs from the railroad and assigns them to variables BlockInput1, BlockInput2, BlockInput3, etc. Blocks can have the value 'OCC' for occupied or 'CLR' for clear. The blocks are then set to occupied or clear depending on the value from the detectors. The block status is used when I check turnouts for locking and when deciding if signals should be set to stop.

Example:-

  If Block3 = OCC Then
    Signal24Right = Stop
    Signal24Left = Stop
    Turnout23Lock = Locked
    BlockIndicator3 = On
  End If
In English, If block 3 is occupied, then make signal 24 left and right show 'stop' and lock turnout 23 so it cannot move. My code is a little more compact than the above. Here is how we set the turnout lock on number 3. SwitchLockIndicator3 = Abs(Block9 Or (SignalIndicator4 <> SignalIndicatorStop)) This is simply short cut code for:-
IF Block9 = OCC OR
  SignalIndicator4 <> SignalIndicatorStop THEN
    SwitchLockIndicator3 = Locked
ELSE
  SwitchLockIndicator3 = Unlocked
END IF
In English again, if block 9 is occupied or signal 4 is not at stop then lock turnout 3.

Controlling turnouts

Mainline turnouts are interlocked with the block detectors and signal indications. It is not possible to change a turnout that has a train sitting on it or has a signal cleared across it. Here is the code for Turnout #3:-
     ' Turnout 3, protected by signal 4 and block 9
     ' Ignore switch lever input unless it has changed.
     If SwitchLeverInput3 <> SwitchLeverState3 Then
       SwitchLeverState3 = SwitchLeverInput3
       ' Switch Lock logic taken care of by TurnoutLockCheck
       If SwitchLockIndicator3 = 0 Then
           Turnout3InTransit = Timer
           If SwitchLeverState3 = SWN Then TurnoutOutput3 = TUN
           If SwitchLeverState3 = SWR Then TurnoutOutput3 = TUR
       End If
     End If
Since I model an interlocking machine, it is possible that the operator has changed the lever position when it is not allowed to change. So the first part ignores the input unless it has changed. To do this, you need two variables, one to keep track of the Input and one to keep track of the last state of the input. The Input (SwitchLeverInput3) is set by reading the SMINI node that the panel switch is connected to. The State (SwitchLeverState3) is set by the program.

So, if the lever is in a different position than it was last time we checked, then we need to do a further check to see if we have to change the turnout position. We then make the State the same as the Input (so we can check the State next time around - remember this code is executed every .001 seconds forever).

The next part says 'If the turnout is NOT locked, then change its position.' If SwitchLockIndicator3 = 0 (if it is not locked) Then Set the position. This is accomplished by looking at the SwitchLeverState.

If the state is 'SWN' (which is Normal), then the TurnoutOuput is set to 'TUN' (which is Normal). If the state is 'SWR' (which is Reverse), then the TurnoutOuput is set to 'TUR' (which is Reverse).

So we only change the turnout when it is safe to do so. The lines beginning with apostrophes (') are comments and are there to remind me what the code does.

Controlling signals

Signals are controlled by the operator using panel levers. The Union Switch and Signal design used opposing signals as Left and Right. One lever controls both signals. This works because the signal can only be cleared in one direction.

Another feature that I chose to implement is 'Running Time'. This is a feature of signal systems that helps to prevent problems when the operator has cleared a signal and needs to put the signal back to stop in the face of an approaching train.

Before the operator can change turnouts or clear a different signal, they must wait for 5 minutes before the plant 'unlocks'.

Here is the code for Signal 4.

     If Signal4RunTime = 0 Then
       If SignalLeverInput4 <> SignalLeverState4 Then
         ' if signal was dropped by operator, then begin running time.
         If SignalLeverState4 <> SignalLeverStop _
           And SignalIndicator4 <> SignalIndicatorStop Then
           Signal4RunTime = Timer
         End If
         SignalLeverState4 = SignalLeverInput4
         If SignalLeverState4 = SignalLeverStop Then
           SignalIndicator4 = SignalIndicatorStop ' put Signal 4 to stop
         Else
           If SignalLeverState4 = SignalLeverLeft Then
             Signal4LeftLogic
           Else
             Signal4RightLogic
           End If
         End If
       End If
     Else
       SignalLeverState4 = SignalLeverInput4
     End If
Lets take a look at the outermost IF statement.
   If Signal4RunTime = 0 Then
   	code
   Else
      SignalLeverState4 = SignalLeverInput4
   End if
This is a check for 'Running Time'. A value greater than zero means that the signal is locked from being changed. If it is running time, then we just record the lever state. If it is not running time, then we will perform the rest of the checks.

Now lets look at the code inside the first IF statement.

If SignalLeverInput4 <> SignalLeverState4 Then
(This is the same as the turnout lever check earlier) We will only perform this code if the operator has actually changed the lever.

The next part checks to see if the operator has put the signal to stop and starts the running time timer. I use the built in timer function in Visual Basic. There is another part of the program which checks the timer to see if the time limit has expired - I have it configured for 30 seconds.

Next we record the lever input state (SignalLeverState4 = SignalLeverInput4) If the signal lever is at stop, then we put the signal indicator to stop. Other code reads the signal indicator to decide what to display on the signal on the railroad. If the signal lever is anywhere else but stop, then check if it is Left or Right.

In order to make this code more readable, I have the logic for each Left and Right signal in its own routine. These are the Signal4LeftLogic and Signal4RightLogic routines. When the operator requests the Left signal to be cleared, this is the logic that is performed:-

   Sub Signal4LeftLogic()
      If Block9 = CLR Then
       	SignalIndicator4 = SignalIndicatorLeft
     	End If
   End Sub
This is pretty straight-forward, if the block that the signal protects is clear, then go ahead and set it to Left. Other code will actually set the signal head based on the SignalIndicator4 and the turnout position. The code for the Right signal is more involved, as we should check block 9 and if there is a signal already cleared in the opposite direction.
   Sub Signal4RightLogic()
     ' can clear if block9 is clear
     If Block9 = CLR Then
       ' if turnout 7 is normal, then okay to clear right
       If TurnoutIndicator7 = TurnoutIndicatorNormal Then
         SignalIndicator4 = SignalIndicatorRight
       Else
         ' turnout 7 is reverse, check if signal 8 is left (against 4)
         If SignalIndicator8 <> SignalIndicatorLeft Then
           SignalIndicator4 = SignalIndicatorRight
         End If
       End If
     End If
   End Sub
First, only clear the signal if block 9 is clear. If turnout 7 is normal then there can't be a conflicting signal cleared (If signal 8 Left is cleared, it won't conflict because the train for signal 8 left will be going 'straight' through turnout 7 and turnout 7 is protected by signal 8 Right.) So it's okay to clear, again setting the SignalIndicator4.

However, if Turnout 7 is reversed, then we have to check if signal 8 has been cleared left. This could cause a collision! Only if Signal 8 Left is not cleared (SignalIndicator8 is not equal to SignalIndicatorLeft), can we clear Signal 4 Right.

We can see on the diagram that there are 4 signal heads in Signal 4. These are 4Ra (Signal 4 Right A), 4Rb (Signal 4 Right B), 4Lab (Signal 4 Left A and B). These designations are also a Union Switch and Signal standard that I chose to adopt. Notice that the Signal 10 Right has 4 heads (10Rabc 10Rd).

Once we have determined that it is safe to give a proceed signal, we then need to decide what indication we will show. Again, I have defined symbolic names for the numbers that will make the signals the right colour. The information on how to do this is in the C/MRI users manual.

The routine is Signal4HeadLogic, which looks like this:-

   Sub Signal4HeadLogic()
     ' Signal 4 Left Head logic
     ' A (GRNRED) if turnout 3 is normal
     ' B (REDYEL) if turnout 3 is reverse
     If SignalIndicator4 = SignalIndicatorLeft Then
       If TurnoutIndicator3 = TurnoutIndicatorNormal Then
         Signal4LeftHead = GRNRED
       Else
         Signal4LeftHead = REDGRN
       End If
     Else
       Signal4LeftHead = REDRED
     End If
     ' Signal 4 Right Head logic
     ' A = turnout 3 normal
     ' B = turnout 3 reverse
     If SignalIndicator4 = SignalIndicatorRight Then
       If TurnoutIndicator3 = TurnoutIndicatorNormal Then
         Signal4RightHeadA = GRN
         Signal4RightHeadB = RED
       Else
         Signal4RightHeadA = RED
         Signal4RightHeadB = GRN
       End If
     Else
       Signal4RightHeadA = RED
       Signal4RightHeadB = RED
     End If
   End Sub
This is not too difficult to understand. First we check for the Left head. If the Signal Indicator for 4 is set to Left then we will check the turnout next. In conclusion, all of the decisions made on the system are very much like the proceeding code. There is more code for performing traffic direction monitoring and running timers for 'running time' locks. Once all of the logic is completed, then the outputs are written to the railroad and we go back to the beginning and do all of it again. It looks like a lot of code to get through, but it is all running at very high speed.

The time between the operator moving the lever and the panel lamp coming on to confirm the lever input is less that 1/10 of a second. The most important thing that has been done is to give every part of the system a sensible name so that the code is easy to read and understand.

If you would like assistance, further information or a live demonstration of my system, please contact me on kelly.loyd at internode.on.net

Previous - Design