' {$STAMP BS1} '------------------------------------------------------ ' "Trainsaver" Digital Train Controller ' By Vern Graner Jul 18, 2005 ' Last Update Apr 25, 2006 (V1.0t) ' 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: '------------------------------------------------------ ' 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 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