' {$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