' {$STAMP BS1} '---------------------------------------------------------- ' "Trainsaver" Digital Train Controller ' By Vern Graner Jul 18, 2005 ' Last Update Feb 20, 2005 (V1.0q) ' 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 NoMotion = PIN3 SYMBOL LED = 4 SYMBOL TRAIN = 5 SYMBOL Btn = 6 SYMBOL Btn1 = PIN6 SYMBOL IRLED = PIN7 SYMBOL True = 1 SYMBOL False = 0 '---------------------------------------------------------- ' Define Variables '---------------------------------------------------------- SYMBOL IRSense = BIT0 ' Flag for IR motion sense SYMBOL StartTrainDone = BIT1 ' Flag for last stop state SYMBOL StopTrainDone = BIT2 ' Flag for last stop state SYMBOL Rest = BIT3 ' Flag for "Sleep" when no motion detected SYMBOL I = B1 ' value can be 0 to 254 SYMBOL btnWrk = B2 ' value can be 0 to 254 SYMBOL Laps = B3 ' Value can be 0 to 254 SYMBOL TrainIdle = B4 ' Value can be 0 to 254 SYMBOL TrainIdleTarget = B5 ' Value can be 0 to 254 SYMBOL RoomIdle = B6 ' Value can be 0 to 254 SYMBOL RoomIdleTarget = 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 StopTrainDone = TRUE ' Train is already stopped TrainIdleTarget = 10 ' Minutes of idle before a lap is added ' for the train to run (values 0-254) RoomIdleTarget = 15 ' Minutes without motion before the train ' is put to sleep (values 0-254) SYMBOL MaskTime = 500 ' 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 TunnelDelay = 8000 ' How many seconds to wait after sensing the ' train before initiating the STOP sequence 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. '-------------------- ' 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 NoMotion = 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 IRLED = 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: '------------------------------------------------------ ' Main Program Begins Here '------------------------------------------------------ MAIN: GOSUB CheckButton 'See if the button has been pressed GOSUB CheckMotion 'See if there has been motion detected GOSUB CheckIR 'See if the train is blocking the beam GOSUB CheckLaps 'See if the train should stop/start 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 is detected for the amount of minutes ' specified by RoomIdleTarget value, the set the "rest" ' bit to TRUE so the controller will cease "self-adding" laps '------------------------------------------------------ CheckMotion: IF RoomIdle < 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. TrainIdle=FALSE ' Hold the idle time to zero so no laps will auto-add NoRest: ' NO - Don't put the system to sleep IF NoMotion = TRUE THEN DontResetSleep 'Is motion detected? Rest=FALSE ' YES- tell the train system to wake up! RoomIdle = 0 ' Reset the room idle time to zero DontResetSleep: ' 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 IRLED = True THEN IRBlocked ' Is the IR beam blocked? RETURN ' NO- Train not present IRBlocked: ' YES- The IR is blocked ' DEBUG "IR Blocked!",CR IF Laps = 1 THEN NoClick ' If the train not stopping HIGH CLICK ' play click/clack sound PAUSE 100 LOW CLICK NoClick: ' DEBUG "Waiting for mask time to expire..." PAUSE MaskTime ' ignore the sensor while other train cars pass ' before we test the sensor again. ' DEBUG "Done!",CR CheckAgain: IF IRLED = 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. 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 TrainIdle < TrainIdleTarget OR REST=1 THEN NoLap1 LAPS=LAPS+1 ' Add laps if we have been without a lap for TrainIdleTarget time TrainIdle = 0 ' Once a lap is added, reset the idle timer NoLap1: IF LAPS = 0 THEN StopTrain StartTrain: IF StartTrainDone = True THEN AlreadyStarted ' DEBUG "PWM Start (Whistle)",CR StopTrainDone=FALSE 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 Train, 3, 150 ' Ease the motor on using PWM PWM Train, 4, 250 ' up.. PWM Train, 5, 250 ' up.... PWM Train, 6, 150 ' and AWAY! HIGH Train ' Train full on StartTrainDone=TRUE ' Set the flag to indicate the train is running AlreadyStarted: ' Gets here if we have been circling in this loop. RETURN StopTrain: IF StopTrainDone = TRUE THEN AlreadyStopped ' DEBUG "PWM Stop (Bell)",CR StartTrainDone=FALSE 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 PWM Train, 4, 100 ' Ease the motor down using PWM PWM Train, 3, 100 ' Whoa Silver! PWM Train, 2, 100 ' Slow... LOW Train ' Train Full Stop TrainIdle = 0 ' Reset the idle counter to zero StopTrainDone=TRUE ' 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 RoomIdle ' 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 ' Reset the LoopTtick so we can start counting again TrainIdle = TrainIdle + 1 ' add to the train idle time counter RoomIdle = RoomIdle + 1 ' add to the motion sensor idle time RoomIdle = RoomIdle 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