Yard Program Code




This page contains the source code portion of the Basic Stamp 2 program that controls the yard and industrial spur turnouts. The complete code and documentation can be downloaded using this link:  YardRoute.zip

' ======================================================================
' YardRoute.bs2                                               9-02-2003

'{$STAMP BS2}

DefaultDir  CON  %1111101011110000

' Basic Stamp pin definitions
'
' Pin 0 = address 0
' Pin 1 = address 1
' Pin 2 = address 4
' Pin 3 = address 8
' Pin 4 = Heartbeat Led
' Pin 5 = KeyPad 1 first entry Led
' Pin 6 = KeyPad 2 first entry Led
' Pin 7 = Sound pin
' Pin 8 = KeyPad 1 data available
' Pin 9 = KeyPad 1 chip enable
' Pin 10 = KeyPad 2 data available
' Pin 11 = KeyPad 2 chip enable
' Pin 12 = Turnouts 1-8 chip enable
' Pin 13 = Turnouts 9-16 chip enable
' Pin 14 = Available
' Pin 15 = Available

' Turnout bit definitions. Upper nibble used for 74154 chip enable.
' Lower nibble used for 74154 address input.
Y1Close         CON  $E0
Y1Open          CON  $E1
Y2Close         CON  $E2
Y2Open          CON  $E3
Y3Close         CON  $E4
Y3Open          CON  $E5
Y4Close         CON  $E6
Y4Open          CON  $E7
Y5Close         CON  $E8  ' Also causes Y6 to close
Y5Open          CON  $E9  ' Also causes Y6 to open
Y6Close         CON  $E8  ' Also causes Y5 to close
Y6Open          CON  $E9  ' Also causes Y5 to open
Y7Close         CON  $EA  ' Also causes Y8 to close
Y7Open          CON  $EB  ' Also causes Y8 to open
Y8Close         CON  $EA  ' Also causes Y7 to close
Y8Open          CON  $EB  ' Also causes Y7 to open
Y9Close         CON  $EC
Y9Open          CON  $ED
Y10Close        CON  $EE
Y10Open         CON  $EF
Y11Close        CON  $D0
Y11Open         CON  $D1
Y12Close        CON  $D2
Y12Open         CON  $D3
Y13Close        CON  $D4
Y13Open         CON  $D5
Y14Close        CON  $D6
Y14Open         CON  $D7
Y15Close        CON  $D8
Y15Open         CON  $D9
Y16Close        CON  $DA
Y16Open         CON  $DB
Y17Close        CON  $DC
Y17Open         CON  $DD
Y18Close        CON  $DE
Y18Open         CON  $DF

Reset           CON  $FA        ' Chip enable bits

' Define working variables
Tracks          VAR  BYTE        ' User entered route
FromTrack       VAR  Tracks.HIGHNIB
ToTrack         VAR  Tracks.LOWNIB
LastTracks      VAR  BYTE        ' Previous user entry

RouteIndex      VAR  BYTE  ' Index of validated route number
Route           VAR  WORD  ' Location of route to use
EntryCount      VAR  BYTE  ' Number of bytes in route entry (excluding count byte)
Index           VAR  BYTE  ' Variable used to read turnout addresses from route

TurnoutAddr     VAR  BYTE    ' Turnout address
AddrHigh        VAR  TurnoutAddr.HIGHNIB
AddrLow         VAR  TurnoutAddr.LOWNIB
InactCount1     VAR  BYTE    ' Keypad 1 inactivity counter
InactCount2     VAR  BYTE    ' Keypad 2 inactivity counter
LedCount        VAR  BYTE    ' Countdown time between Led state toggle
KeyPad1Ent1     VAR  NIB     ' Keypad 1 first number
KeyPad2Ent1     VAR  NIB     ' Keypad 2 first number
KeyFlags        VAR  NIB     ' Program control flags
KeyPressed1     VAR  KeyFlags.BIT0
KeyPressed2     VAR  KeyFlags.BIT1
MemArray        VAR  NIB(8)  ' Turnout position array. See Turnout routine.
Value           VAR  NIB
Addr            VAR  NIB
Mask            VAR  NIB

'--------------------------------------------------------------------------
ProgramStart:
   DIRS = DefaultDir         ' Set default I/O direction bits
   OUTH = Reset              ' Reset chip enable bits
   OUTB = 1111               ' Reset Led's
   KeyPressed1 = 0           ' Reset control bit
   KeyPressed2 = 0           ' Reset control bit
   InactCount1 = 0           ' Reset inactivity timer
   InactCount2 = 0           ' Reset inactivity timer

'--------------------------------------------------------------------------
' Begin main program loop. Each iteration decrements the LedCount variable.
' Calls are made to subroutines for processing of keypad input and route setting.

MainLoop:
   LedCount = LedCount - 1              ' Decrement heartbeat indicator timer
   IF LedCount <> 0 THEN MainLoop2      ' Jump if no Led change yet
   TOGGLE 4                             ' Toggle Led state.
   IF InactCount1 = 0 OR KeyPressed1 = 1 THEN MainLoop1  ' Jump if no timeout or
                                                         ' if key still down
   InactCount1 = InactCount1 - 1        ' Decrement inactivity counter
   IF InactCount1 <> 0 THEN MainLoop1   ' Jump if not zero
   OUT5 = 1                             ' Turn off 1st entry Led
MainLoop1:
   IF InactCount2 = 0 OR KeyPressed2 = 1 THEN MainLoop2  ' Jump if no timeout or
                                                         ' if key still down
   InactCount2 = InactCount2 - 1        ' Decrement inactivity counter
   IF InactCount2 <> 0 THEN MainLoop2   ' Jump if not zero
   OUT6 = 1                             ' Turn off 1st entry Led
MainLoop2:
   IF IN8 = 0 THEN MainLoop3            ' Jump if no keypad 1 input
   IF KeyPressed1 = 1 THEN MainLoop4    ' Jump if key is still down
   GOSUB KeyPad1In                      ' Read key number and save
   KeyPressed1 = 1                      ' Set key pressed flag
   GOTO MainLoop4
MainLoop3:
   KeyPressed1 = 0                      ' Reset key pressed flag
MainLoop4:
   IF IN10 = 0 THEN MainLoop5           ' Jump if no keypad 2 input
   IF KeyPressed2 = 1 THEN MainLoop6    ' Jump if key is still down
   GOSUB KeyPad2In                      ' Read key number and save
   KeyPressed2 = 1                      ' Set key pressed flag
   GOTO MainLoop6
MainLoop5:
   KeyPressed2 = 0                      ' Reset key pressed flag
MainLoop6:
   GOTO MainLoop

'--------------------------------------------------------------------------
' This code is run to process a KeyPad 1 input. The first entry ("from" track
' number) is stored in a KeyPad 1 specific variable. The "first entry" Led
' doubles as a program control flag. The second entry ("to" track number)
' is combined with the first entry and stored in the Tracks variable. A call
' is then made to the SetRoute routine.

KeyPad1In:
   OUT9 = 0                             ' Enable keypad 1 data read
   IF IN5 = 0 THEN KeyPad1In1           ' Jump if 2nd number
   KeyPad1Ent1 = INA                    ' Read keypad value (1st entry)
   OUT9 = 1                             ' Disable keypad 1 data read
   OUT5 = 0                             ' Turn on 1st entry Led
   InactCount1 = 10                     ' Set inactivity timer
   RETURN

KeyPad1In1:
   ToTrack = INA                        ' Read keypad value (2nd entry)
   OUT9 = 1                             ' Disable keypad 1 data read
   OUT5 = 1                             ' Turn off 1st entry Led
   InactCount1 = 1                      ' Reset inactivity timer
   FromTrack = KeyPad1Ent1
   GOSUB SetRoute                       ' Process KeyPad 1 input
   RETURN

'--------------------------------------------------------------------------
' This code is run to process a KeyPad 2 input. The first entry ("from" track
' number) is stored in a KeyPad 2 specific variable. The "first entry" Led
' doubles as a program control flag. The second entry ("to" track number)
' is combined with the first entry and stored in the Tracks variable. A call
' is then made to the SetRoute routine.

KeyPad2In:
   OUT11 = 0                            ' Enable keypad 2 data read
   IF IN6 = 0 THEN KeyPad2In1           ' Jump if 2nd number
   KeyPad2Ent1 = INA                    ' Read keypad value (1st entry)
   OUT11 = 1                            ' Disable keypad 2 data read
   OUT6 = 0                             ' Turn on 1st entry Led
   InactCount2 = 10                     ' Set inactivity timer
   RETURN

KeyPad2In1:
   ToTrack = INA                        ' Read keypad value (2nd entry)
   OUT11 = 1                            ' Disable keypad 2 data read
   OUT6 = 1                             ' Turn off 1st entry Led
   InactCount2 = 1                      ' Reset inactivity timer
   FromTrack = KeyPad2Ent1
   GOSUB SetRoute                       ' Process KeyPad 2 input
   RETURN

'--------------------------------------------------------------------------
' Set the turnouts corresponding to the specified From and To track numbers.
'
' Find the route to be used for the entered combination; made up from a "From"
' track number and a "To" track number (Tracks variable). Invalid combinations
' are not included and will result in the initialized value being unchanged.
' Though the user enters track numbers 1 - 16, the corresponding values are
' 0 - 15 in the Tracks variable.
'
' The LOOKDOWN statement matches the contents of the Tracks variable with one
' of the entries in the list of valid combinations. If a match is found, the
' LOOKDOWN statement will set the RouteIndex variable to the offset position
' of the matching entry. For example, if the user entered track 1 to track 3
' ($02 in the Tracks variable), RouteIndex will be set to $00 (offset from
' start of list in LOOKDOWN statement. If track 1 to track 13 was entered
' ($0C in the Tracks variable), RouteIndex would retain its initialized
' value ($FF) and a warning tone would be output.
'
' We have to handle a couple of special cases here. For a track 4 to 5 or
' track 5 to 4 request, the turnouts used are train direction dependent. Since
' the train direction is not known, the code initially sets turnouts 5 and 6
' for track 4 to 5 and turnouts 7 and 8 for track 5 to 4. If the same track
' command is consecutively entered, the code inverts turnouts 5/6 and 7/8.
' Entries (3E and 3F) have been added to the lookup lists for these cases.

SetRoute:
   RouteIndex = $FF                          ' Initialize to invalid route.
   IF Tracks <> $34 AND Tracks <> $43 THEN SetRoute2
   IF LastTracks <> Tracks THEN SetRoute2
   IF Tracks <> $34 THEN SetRoute1
   Tracks = $3E
   GOTO SetRoute2
SetRoute1:
   Tracks = $3F
SetRoute2:
   LastTracks = Tracks
   LOOKDOWN Tracks, [$02,$03,$04,$05,$06,$07,$08,$09,$0A,$12,$13,$14,$20,
                     $21,$22,$30,$31,$33,$34,$3E,$3F,$40,$41,$43,$44,$45,
                     $46,$47,$48,$49,$4A,$50,$54,$55,$5B,$5C,$5D,$60,$64,
                     $66,$70,$74,$77,$80,$84,$88,$90,$94,$99,$A0,$A4,$AA,
                     $B5,$BB,$BE,$C5,$CC,$CE,$D5,$DD,$DE,$EB,$EC,$ED,$EE,
                     $FF], RouteIndex
   IF RouteIndex <> $FF THEN SetRoute3
   FREQOUT 7, 100, 660
   PAUSE 100
   FREQOUT 7, 100, 660
   PAUSE 500
   RETURN

SetRoute3:
' Now use the validated RouteIndex to access the list of turnout addresses
' to be used to set the route. It is important that the entries in the LOOKUP
' statement correspond to the entries in the LOOKDOWN statement. Otherwise,
' the wrong route list entry will be selected.
'
' The LOOKUP statement uses the contents of the RouteIndex variable to select
' the starting address from the list of route entries. Each route is made up of
' a count entry (specifying the number of bytes in the route) and a list of
' turnout control addresses.
'
' The first location in the route is read and stored in the EntryCount variable.
' The EntryCount variable tells the code when to exit the FOR loop below. Each
' iteration of the FOR loop reads two bytes from the route and stores them in
' the TurnoutAddr variable. A call is then made to the Turnout routine which
' causes the addressed turnout to be positioned.

   LOOKUP RouteIndex, [R02,R03,R04,R05,R06,R07,R08,R09,R0A,R12,R13,R14,R20,
                       R21,R22,R30,R31,R33,R34,R3E,R3F,R40,R41,R43,R44,R45,
                       R46,R47,R48,R49,R4A,R50,R54,R55,R5B,R5C,R5D,R60,R64,
                       R66,R70,R74,R77,R80,R84,R88,R90,R94,R99,RA0,RA4,RAA,
                       RB5,RBB,RBE,RC5,RCC,RCE,RD5,RDD,RDE,REB,REC,RED,REE,
                       RFF], Route
   READ Route, EntryCount

' Now step through the route and set the turnouts.

   FOR Index = 1 TO EntryCount
      READ Route+Index, TurnoutAddr
      GOSUB Turnout
   NEXT
   RETURN

'--------------------------------------------------------------------------
' Check the current position of the turnout and exit if already in position.
' This serves to minimize the number of commands sent to the turnouts by
' remembering their commanded positions. This will improve program response
' time and reduce switch machine coil heating.
'
' MemArray contains two bits for each turnout based upon its address. Bit
' meanings are: 00 = Unknown, 01 = Closed, 10 = Open. The bits are updated
' after each turnout position change. At power up, all turnout positions are
' unknown.
'
' If a position change is required, this routine will set the turnout who's
' address is specified by TurnoutAddr. The routine must also preserve the
' heartbeat and data entry led states. The low turnout address byte is output
' first so bits 3-0 will be stable prior to the chip select bits 12 and 13.
'
' The PAUSE statement value should be adjusted for a balance between reliable
' turnout operation and minimum switching noise. Longer than required time
' will also generate unnecessary heat and wear on the switch machine. 200
' milliseconds seems to work well when using 12 volts for the switch machine
' coil circuit. For higher voltages, this value can be lowered which will
' improve overall program response time.

Turnout:
   Value = %0001 << (AddrLow // 4)   ' Value = 1000, 0100, 0010, or 0001
   Addr = AddrLow / 4                ' Potential array location
   IF AddrHigh = $E THEN Turnout1
   Addr = Addr + 4                   ' Adjust array location
Turnout1:
   IF MemArray(Addr) & Value <> 0 THEN Turnout3  ' Jump if in position
   DIRA = %1111                      ' Set bits 3 - 0 to output mode
   OUTA = AddrLow                    ' Select the turnout coil
   OUTD = AddrHigh                   ' Energize it
   PAUSE 200
   OUTH = Reset                      ' De-energize the turnout coil
   DIRA = %0000                      ' Set bits 3 - 0 back to input mode

   Mask = %0011                      ' Set mask for bits 2 and 3
   IF Value > 2 THEN Turnout2
   Mask = Mask << 2                  ' Set mask for bits 0 and 1
Turnout2:
   MemArray(Addr) = MemArray(Addr) & Mask   ' Reset current contents
   MemArray(Addr) = MemArray(Addr) | Value  ' Save new position data
Turnout3:
   RETURN

'--------------------------------------------------------------------------
' The following data statements define the turnout positions to set for all
' valid routes. Valid routes were identified using the track diagram. Each
' route is made up of a count entry (specifying the number of bytes in the
' route) and a list of turnout control addresses (16 bit values). The DATA
' statement information is stored in the Basic Stamp EEPROM by the Basic
' Stamp editor. The label associated with each route (R02, R03, etc) is used
' to store the staring address of each route in the LOOKUP statement list.

R02  DATA  2, Y1Close,  Y2Open
R03  DATA  4, Y1Close,  Y2Close,  Y6Close, Y7Close
R04  DATA  5, Y1Open,   Y5Close,  Y9Close, Y10Close, Y8Close
R05  DATA  5, Y1Open,   Y5Close,  Y9Open,  Y12Open,  Y16Close
R06  DATA  5, Y1Open,   Y5Close,  Y9Open,  Y11Close, Y12Open
R07  DATA  5, Y1Open,   Y5Close,  Y9Open,  Y11Close, Y12Close
R08  DATA  6, Y1Open,   Y5Close,  Y9Close, Y10Open,  Y13Open, Y14Open
R09  DATA  6, Y1Open,   Y5Close,  Y9Close, Y10Open,  Y13Open, Y14Close
R0A  DATA  5, Y1Open,   Y5Close,  Y9Close, Y10Open,  Y13Close
R12  DATA  1, Y4Close
R13  DATA  4, Y4Open,   Y3Close,  Y7Close, Y6Close
R14  DATA  6, Y4Open,   Y3Open,   Y8Close, Y10Close, Y9Close, Y5Close
R20  DATA  2, Y2Open,   Y1Close
R21  DATA  1, Y4Close
R22  DATA  3, Y1Close,  Y2Open,   Y4Close
R30  DATA  4, Y7Close,  Y6Close,  Y2Close, Y1Close
R31  DATA  4, Y3Close,  Y4Open,   Y7Close, Y6Close
R33  DATA  6, Y1Close,  Y2Close,  Y6Close, Y7Close,  Y3Close, Y4Open
R34  DATA  1, Y5Open
R3E  DATA  2, Y5Close,  Y7Open    ' Special entry. See SetRoute routine.
R3F  DATA  2, Y7Close,  Y5Open    ' Special entry. See SetRoute routine.
R40  DATA  5, Y8Close,  Y10Close, Y9Close, Y5Close,  Y1Open
R41  DATA  6, Y8Close,  Y10Close, Y9Close, Y5Close,  Y3Open,  Y4Open
R43  DATA  1, Y7Open
R44  DATA  4, Y5Close,  Y9Close,  Y10Close, Y8Close
R45  DATA  4, Y9Open,   Y12Open,  Y11Open,  Y16Close
R46  DATA  3, Y9Open,   Y12Open,  Y11Close
R47  DATA  2, Y9Open,   Y12Close
R48  DATA  5, Y5Close,  Y9Close,  Y10Open,  Y13Open, Y14Open
R49  DATA  5, Y5Close,  Y9Close,  Y10Open,  Y13Open, Y14Close
R4A  DATA  4, Y5Close,  Y9Close,  Y10Open,  Y13Close
R50  DATA  5, Y16Close, Y12Open,  Y9Open,   Y5Close, Y1Open
R54  DATA  5, Y16Close, Y11Open,  Y12Open,  Y9Open,  Y5Close
R55  DATA  3, Y16Close, Y11Open,  Y12Open
R5B  DATA  3, Y16Open,  Y15Close, Y17Close
R5C  DATA  4, Y16Open,  Y15Close, Y17Open,  Y18Close
R5D  DATA  4, Y16Open,  Y15Close, Y17Open,  Y18Open
R60  DATA  5, Y12Open,  Y11Close, Y9Open,   Y5Close, Y1Open
R64  DATA  5, Y16Close, Y11Open,  Y12Open,  Y9Open,  Y5Close
R66  DATA  2, Y12Open,  Y11Close
R70  DATA  5, Y12Close, Y11Close, Y9Open,   Y5Close, Y1Open
R74  DATA  3, Y12Close, Y9Open,   Y5Close
R77  DATA  2, Y12Close, Y11Close
R80  DATA  6, Y14Open,  Y13Open,  Y10Open,  Y9Close, Y5Close, Y1Open
R84  DATA  5, Y14Open,  Y13Open,  Y10Open,  Y9Close, Y5Close
R88  DATA  2, Y14Open,  Y13Open
R90  DATA  6, Y14Close, Y13Open,  Y10Open,  Y9Close, Y5Close, Y1Open
R94  DATA  5, Y14Close, Y13Open,  Y10Open,  Y9Close, Y5Close
R99  DATA  2, Y14Close, Y13Open
RA0  DATA  5, Y13Close, Y10Open,  Y9Close,  Y5Close, Y1Open
RA4  DATA  4, Y13Close, Y10Open,  Y9Close,  Y5Close
RAA  DATA  1, Y13Close
RB5  DATA  3, Y17Close, Y15Close, Y16Open
RBB  DATA  2, Y17Close, Y15Close
RBE  DATA  2, Y17Close, Y15Open
RC5  DATA  4, Y18Close, Y17Open,  Y15Close,  Y16Open
RCC  DATA  3, Y18Close, Y17Open,  Y15Close
RCE  DATA  3, Y18Close, Y17Open,  Y15Open
RD5  DATA  4, Y18Open,  Y17Open,  Y15Close,  Y16Open
RDD  DATA  3, Y18Open,  Y17Open,  Y15Close
RDE  DATA  3, Y18Open,  Y17Open,  Y15Open
REB  DATA  2, Y15Open,  Y17Close
REC  DATA  3, Y15Open,  Y17Open,  Y18Close
RED  DATA  3, Y15Open,  Y17Open,  Y18Open
REE  DATA  1, Y15Open
RFF  DATA  2, Y5Open,   Y8Open
      

Color Bar

Navigation:   Yard   D&B Home   Buczynski.com Index

Copyright © 2006 Don Buczynski
San Diego, California