' {$STAMP BS1}
'------------------------------------------------------
' "Trainsaver" Digital Train Controller
' By Vern Graner Jul 18, 2005
' Last Update    Oct 24, 2006 (V1.0u)
' Any questions to vern@txis.com
'------------------------------------------------------
' Designed for the Prop-1/BS1 to control an electric
' train set and operate train sound effects
'------------------------------------------------------
  ' Hardware setup:
  ' P0 - Train sound 1 K3- Red (Crossing Bell)
  ' P1 - Train Sound 2 K4 -Black (Clickety-Clack)
  ' P2 - Train Sound 3 K2- Yellow (Train Whistle)
  ' P3 - IR Detector Motion detector (LOW=MOTION)
  ' P4 - LED Indicator (w/P4 LOW for lit)
  ' P5 - Train Motor (via ULN2803 bridged)
  ' P6 - Trigger Button (with jumper for pull-down)
  ' P7 - LapSense (IR Phototransistor with jumper for pull-down)

'------------------------------------------------------
' Create "human readble" names
'------------------------------------------------------
   SYMBOL Bell         = 0
   SYMBOL Click        = 1
   SYMBOL Whistle      = 2
   SYMBOL RoomEmpty    = PIN3
   SYMBOL LED          = 4
   SYMBOL TRAIN        = 5
   SYMBOL Btn          = 6
   SYMBOL Btn1         = PIN6
   SYMBOL TrainPresent = PIN7
   SYMBOL True         = 1
   SYMBOL False        = 0

'------------------------------------------------------
' Define Variables
'------------------------------------------------------
   SYMBOL IRSense         = BIT0 ' Flag for IR motion sense
   SYMBOL TrainRunning    = BIT1 ' Flag for train run state
   SYMBOL Rest            = BIT2 ' Flag for "Sleep" when no motion detected
'  SYMBOL Empty           = BIT3 ' Value can be 0 or 1
   SYMBOL I               = B1   ' value can be 0 to 254 used for loop couter
   SYMBOL btnWrk          = B2   ' value can be 0 to 254 used by BUTTON command
   SYMBOL Laps            = B3   ' Value can be 0 to 254
   SYMBOL TrainIdleTime   = B4   ' Value can be 0 to 254
   SYMBOL RoomIdleTime    = B5   ' Value can be 0 to 254
'  SYMBOL empty           = B6   ' Value can be 0 to 254
'  SYMBOL empty           = B7   ' Value can be 0 to 254
   SYMBOL LoopTicks       = W4   ' Uses B8/B9 value from 0 to 65535

'------------------------------------------------------
' Initialize Train System Variables
'------------------------------------------------------
   HIGH LED                      ' Turn off the LED in the button
   SYMBOL TrainIdleTarget = 10   ' Minutes of idle before a lap is added
                                 '  for the train to run (values 0-254)
   SYMBOL RoomIdleTarget  = 15   ' Minutes without motion before the train
                                 '  is put to sleep (values 0-254)
   SYMBOL MaskTime        = 1000 ' How many MS to wait b4 we check for the
                                 '  end of the train after beam-break.
   SYMBOL Credit          = 5    ' Number of laps added by a button press
                                 '  (or coin drop)
   SYMBOL LapLimit        = 9    ' Set the maximum number of laps that may
                                 '  be added
   SYMBOL TicksPerMinute  = 4000 ' 4000 ~number of ticks that pass in a minute
                                 '  Note that if you alter the loop length you
                                 '  will have to recalibrate.
   SYMBOL TunnelDelay     = 6000 ' Delay from end of train till stop execution
                                 '  (allow train to traverse tunnel)

'--------------------
' Calibration/Setup |
'-------------------------------------------------------------------------
' IR Beam Calibration and Walktest for PIR sensor Hold button down on
' powerup, then release the button within 3 seconds after powerup to
' invoke the "calibrate/Test" mode. LED will idicate motion detected.
' Press the button again to align the IR beam. LED will indicate beam
' presence. Note: All this code may be commented and/or moved to a stand
' alone program if on-site calibration is not needed or if additional
' code space is required for updtes/improvements.
'-------------------------------------------------------------------------
IF BTN1 = False THEN NoTest      ' No button, so no testing invoked
PAUSE 2000                       ' Wait a bit for the person to let go!

WalkTest:                        ' Light the LED when motion detected.
  IF RoomEmpty = True THEN NoLED1
    LOW LED
  NoLED1:
  HIGH LED
  IF BTN1 = True THEN DoneWalk   ' Exit the test on button press
  GOTO WalkTest
DoneWalk:

PAUSE 2000                       ' Wait for the person to let go!

IRTest:                          ' Light the LED when IR beam is aligned
 IF TrainPresent = True THEN NoLED2
    LOW LED
  NoLED2:
  HIGH LED
  IF BTN1 = True THEN DoneIR     ' Exit the test on button press
  GOTO IRTest
DoneIR:

PAUSE 2000                       ' Wait for the person to let go!
                                 ' (so we don't add laps)
NoTest:

'------------------------------------------------------
' Deal with "deadlock" IR beam issue
'------------------------------------------------------
Laps=1                           'Make sure the train starts on startup
                                 'to avoid the a dead-lock if the train
                                 'is turned off blocking the sensor

'------------------------------------------------------
' Main Program Begins Here
'------------------------------------------------------
   MAIN:
     GOSUB CheckButton    'See if the button has been pressed
     GOSUB CheckMotion    'See if there has been motion detected
     GOSUB CheckLaps      'See if the train should stop/start
     GOSUB CheckIR        'See if the train is blocking the beam
     GOSUB Counters       'Increment all activity counters
   GOTO MAIN

'------------------------------------------------------
'Subroutines-------------------------------------------
'------------------------------------------------------

'--------------------
'Check for Button   |
'------------------------------------------------------
' Routine to see if the button has been pressed. If so
' add the # of laps indicated by the "credit" value.
'------------------------------------------------------
CheckButton:
  'BUTTON Pin, DownState, Delay, Rate, Workspace, TargetState, Address
   BUTTON Btn,         1,   254,  150,    btnWrk,           0, NoPress

AddLaps:
'    Code here is executed ONCE for EACH button press
'     DEBUG "Button pressed",CR
     LOW LED
     Laps = Laps + Credit 'Number of laps added by button press/credit
     PAUSE 50
     HIGH LED
  NoPress:                'Comes here if button is NOT pressed
  RETURN

'--------------------
' Check PIR motion  |
'------------------------------------------------------
' Routine to see if motion has been detected in the room
' If no motion for the amount of minutes specified by
' RoomIdleTarget value, the set the "rest" bit to TRUE
' So the train will cease "self-adding" laps
'------------------------------------------------------
CheckMotion:

  IF RoomIdleTime < RoomIdleTarget THEN NoRest ' Has room been empty long enough
                                               ' to stop the show?
     Rest = TRUE          ' YES -Put the system "to sleep" till we see motion again.
     TrainIdleTime = 0    '      Hold the idle time to zero so no laps will auto-add
  NoRest:                 ' NO - Don't put the system to sleep

  IF RoomEmpty = TRUE THEN DontWakeUp 'Is motion detected?
    Rest = FALSE          ' YES- tell the train system to wake up!
    RoomIdleTime = 0      '      Reset the room idle time to zero
  DontWakeUp:             ' NO- Don't reset the counter

  RETURN

'--------------------
' Check the IR Beam |
'-------------------------------------------------------------------------
' Routine to see if the train is present (has blocked the IR LED). If so,
' decrement the lap counter. This section also plays sounds to indicate
' function. If the train will be passing through, the "click" sound is
' played. The Mask time variable is used To keep the space between rail
' cars from decrementing multiple laps. This value should reflect the
' amount of time it takes for the all the cars to pass the sensor.
' Note: If the sensor is placed so that the couplers between cars continue
' to block the beam, the mask is not necessary.
'-------------------------------------------------------------------------
CheckIR:

  IF TrainPresent = True THEN IRBlocked  ' Is the IR beam blocked?
    RETURN                               ' NO- Train not present
  IRBlocked:                             ' YES- The IR is blocked

  FOR I =1 TO 100                        ' Crock pot detector :)
   IF TrainPresent = FALSE THEN CheckIR: ' IR sensor must stay in state for
                                         ' for 100 iterations in order to prove "valid"
  NEXT I

  PAUSE MaskTime                        ' Ignore the sensor while other train cars pass
                                        ' Note: May not be necessary if sensor is located
                                        ' at the level of the train car couplers
  CheckAgain:
  IF TrainPresent = False THEN IRUnBlocked 'Wait for the train to pass the sensor
    GOTO CheckAgain
  IRUnBlocked:

'  DEBUG "IR Unblocked",CR
'  DEBUG "Blink # of laps remaining (",#laps,")",CR

  IF LAPS = 1 THEN NoBLink  ' No laps remain if we're stopping the train, so no blink.

    HIGH CLICK              ' Play click/clack sound when just passing through
    PAUSE 100
    LOW CLICK

    FOR I = 2 TO LAPS       ' Blink the LED with the remaining number of laps
      LOW LED
      PAUSE 75
      HIGH LED
      PAUSE 250
    NEXT

  NoBLink:
  IF LAPS=0 THEN NoDecrement 'Don't decrement if we are already at zero
    Laps=Laps-1              'Decremet one lap from the counter
  NoDecrement:
  RETURN

'--------------------
'Check Laps Routine |
'-------------------------------------------------------------------------
' Routine to see if its time to stop/start the train
' If the Train Idle time exceeds the Train Idle Target time, a lap will
' be added to the lap counter unless the "rest" switch has been thrown
' due to no activity in the viewing area. The start and stop of the train
' engine is "eased" by using PWM values here. These values may need to
' to be "tweaked" to allow smooth acceleration/deceleration of your
' particular engine. This section will also play a sound effect if the
' train is starting (whistle) or if the train is stopping (crossing bell
' sound).
'-------------------------------------------------------------------------
CheckLaps:

   IF TrainIdleTime < TrainIdleTarget OR REST=1 THEN NoLap1
      LAPS=LAPS+1       ' Add laps if we have been without a lap for TrainIdleTarget time
      TrainIdleTime = 0 ' Once a lap is added, reset the idle timer
    NoLap1:

  IF LAPS = 0 THEN StopTrain

  StartTrain:
    IF TrainRunning = True THEN AlreadyStarted
'        DEBUG "PWM Start (Whistle)",CR

         HIGH Whistle        ' Play the Whistle
         PAUSE 100           ' Have to pause long enough for the module to reliably
         LOW Whistle         ' get a "play sound" signal

       ' PWM   Pin, Duty, Duration

        FOR I = 3 TO 10      ' Ease the motor on using PWM
          PWM Train, I, 100
        NEXT

        HIGH Train           ' Train full on here
        PAUSE 5500           ' wait for train to reach bridge
                             ' added for PokeJos train only!!
                             ' NOTE: May need to be removed as button ignored all this time!

        HIGH CLICK           ' play click/clack sound
        PAUSE 100
        LOW CLICK

        TrainRunning=TRUE ' Set the flag to indicate the train is running
    AlreadyStarted:
    RETURN

  StopTrain:
    IF TrainRunning = FALSE THEN AlreadyStopped
'        DEBUG "PWM Stop (Bell)",CR
        HIGH Bell            ' Play the bell
        PAUSE 100            ' Have to pause long enough for the module to reliably
        LOW Bell             '  get a "play sound" signal

        PAUSE TunnelDelay    ' Allow the train to get to the start of the tunnel b4 stopping

        FOR I = 10 TO 3 STEP -1 ' Ease the motor on using PWM
          PWM Train, I, 100
        NEXT

        LOW Train          ' Train Full Stop here
        TrainIdleTime = 0  ' Reset the idle counter to zero
        TrainRunning=FALSE ' Set the flag to indicate the train is stopped
    AlreadyStopped:        ' Gets here if we have been circling in this loop.
    RETURN

'-------------------------
'Check/Incremet Counters |
'-------------------------------------------------------------------------
'Routine to increment the counters for idle and motion. This section
' increments "ticks" until it reachs the number that indicate approximately
' one minute of elapsed time. Once a minute has been reached the RoomIdleTime
' and TrainIdle variables are incremented. Note: The number of ticks per
' minute is estimated and may have to be altered if the code is changed
' dramaticly.
'-------------------------------------------------------------------------
Counters:
      LoopTicks=LoopTicks + 1
      IF LoopTicks < TicksPerMinute THEN NoCascade1 ' don't add one minute to the counter
'        DEBUG "Train Idle=",#TrainIdle," Room Idle=",#RoomIdle," Laps=",#laps," Rest=",#rest,CR
        LoopTicks = 0                        ' Resent the Loopstick so we can start counting again
        TrainIdleTime = TrainIdleTime + 1    ' add to the train idle time counter
        RoomIdleTime  = RoomIdleTime  + 1    ' add to the motion sensor idle time
        RoomIdleTime  = RoomIdleTime MAX 250 ' when room is idle, make sure we don't "rollover" the counter
      NoCascade1:
     Laps = Laps MAX LapLimit                'Limit the Maximum laps that may be added
RETURN