// "Five Minute Signal" // traffic signal based presentation timer for GO-Tech // (c) 2009 Mark Smith // The contents of this file may be freely used by anyone for any legal purpose. // File created: 10-January-2009 // Last updated: 12-January-2009 // // Arduino pins used: // Pin 6: normally open pushbutton switch (input) // Pin 4: Red light -- positive side of 5V relay coil (output) // Pin 7: Yellow light (output) // Pin 8: Green light (output) // // Using the timer: // After the startup sequence completes, the timer will enter the idle state (no lights). // In the idle state, press the button once to begin the countdown. // Press the button during the countdown to cancel the timer. // After the timer expires, the red light will remain on until the button is pressed. // // To set the countdown time: // 1) "Double press" the button (press and release twice quickly) while in the idle state (when no lights are lit). // The yellow light will illuminate. // 2) Press and release the button once for each minute of desired countdown time. // 3) "Double press" to finish and re-enter the idle state (no lights on). #include "WProgram.h" // #define MCS_DEBUG 1 // Uncomment this to enable serial trace. // Constants: #define kPinButton 6 // Pull high to +V via pull up resistor. #define kPinRed 4 // Connect pins 4, 7, and 8 to positive side of 5V relay coils. #define kPinYellow 7 #define kPinGreen 8 #define kButtonDebounceTimeMS 175 #define kButtonDoublePressTimeMS 350 #define kStartupFlashCount 2 #define kStartupFlashDelayMS 500 #define kShowDelayOnMS 300 #define kShowDelayOffMS 200 #define kStateStartUp 0 // Display red/yellow/green sequence (kStartupFlashCount times). #define kStateIdle 1 // Waiting to begin countdown (all lights off). #define kStateCountdown1 2 // First portion of timer countdown; asts until one minute to go (green light on). #define kStateCountdown2 3 // Final minute of countdown (yellow light is on) #define kStateStop 4 // Countdown has completed (red light is on). #define kStateSetDelay 5 // "Set timer" mode (yellow light is on). #define kStateShowDelay 6 // Show the current delay setting (flashes green light once for each second of delay). #define kMSPerMinute 60000 #define kDefaultTimeDelayMS 300000 // 5 * 60000 ms/minute = 5 minutes. // Function Prototypes: void enterIdleState(void); void turnLightOn(unsigned char aPin); void turnLightOff(); unsigned long calcYellowDelay(unsigned long aDelayMS); #ifdef MCS_DEBUG void debugShowState(char *aStateDesc, unsigned long aCurTimeMS, unsigned long aTargetTimeMS); #endif // Global Variables: unsigned char gState = kStateStartUp; // Initial state (one of the kState... constants). unsigned char gLightThatIsOn = 0; // Which light is lit (kPinRed, kPinYellow, kPinGreen or 0 for none). unsigned char gButtonPrevState = HIGH; unsigned long gButtonLastPushTime = 0; unsigned long gTargetTimeMS = 0; // The next significant time when we are in timer mode. unsigned long gTimeDelayMS = kDefaultTimeDelayMS; unsigned long gYellowTimeMS = 0; unsigned char gStartupFlashCount = 0; unsigned long gShowDelayRemainingMS = 0; void setup(void) { #ifdef MCS_DEBUG Serial.begin(19200); #endif pinMode(kPinButton, INPUT); pinMode(kPinRed, OUTPUT); pinMode(kPinYellow, OUTPUT); pinMode(kPinGreen, OUTPUT); digitalWrite(kPinRed, LOW); digitalWrite(kPinYellow, LOW); digitalWrite(kPinGreen, LOW); gState = kStateStartUp; turnLightOn(kPinRed); gStartupFlashCount = 0; gTargetTimeMS = millis() + kStartupFlashDelayMS; } // setup() void loop(void) { unsigned long curTimeMS = millis(); // Check for pushbutton press. unsigned char gotButtonPress = false; unsigned char gotDoublePress = false; int buttonVal = digitalRead(kPinButton); if ((buttonVal == LOW) && (gButtonPrevState == HIGH) && ((curTimeMS - gButtonLastPushTime) > kButtonDebounceTimeMS)) { gotButtonPress = true; gotDoublePress = ((curTimeMS - gButtonLastPushTime) <= kButtonDoublePressTimeMS); gButtonLastPushTime = curTimeMS; } gButtonPrevState = buttonVal; // State machine. switch (gState) { case kStateStartUp: if (curTimeMS >= gTargetTimeMS) { gTargetTimeMS = curTimeMS + kStartupFlashDelayMS; if (gLightThatIsOn == kPinRed) turnLightOn(kPinYellow); else if (gLightThatIsOn == kPinYellow) turnLightOn(kPinGreen); else { ++gStartupFlashCount; if (gStartupFlashCount < kStartupFlashCount) { turnLightOn(kPinRed); } else { turnLightOff(); gState = kStateIdle; #ifdef MCS_DEBUG debugShowState("Idle", curTimeMS, gTargetTimeMS); #endif } } } break; case kStateIdle: // No lights on; waiting for button press. if (gotButtonPress) { gYellowTimeMS = calcYellowDelay(gTimeDelayMS); gTargetTimeMS = curTimeMS + (gTimeDelayMS - gYellowTimeMS); turnLightOn(kPinGreen); gState = kStateCountdown1; #ifdef MCS_DEBUG debugShowState("Countdown1", curTimeMS, gTargetTimeMS); #endif } break; case kStateCountdown1: // Green portion of countdown. if (gotDoublePress) { turnLightOn(kPinYellow); gState = kStateSetDelay; gTimeDelayMS = 0; #ifdef MCS_DEBUG debugShowState("SetDelay", curTimeMS, gTargetTimeMS); #endif } else if (gotButtonPress) enterIdleState(); // Cancel timer. else { if (curTimeMS >= gTargetTimeMS) { turnLightOn(kPinYellow); gTargetTimeMS += gYellowTimeMS; gState = kStateCountdown2; #ifdef MCS_DEBUG debugShowState("Countdown2", curTimeMS, gTargetTimeMS); #endif } } break; case kStateCountdown2: // Yellow portion of countdown. if (gotButtonPress) enterIdleState(); // Cancel timer. else { if (curTimeMS >= gTargetTimeMS) { turnLightOn(kPinRed); gTargetTimeMS = 0; gState = kStateStop; #ifdef MCS_DEBUG debugShowState("Stop", curTimeMS, gTargetTimeMS); #endif } } break; case kStateStop: // Time is up. The red light is on. if (gotButtonPress) enterIdleState(); break; case kStateSetDelay: // "Set timer" mode. if (gotDoublePress) { gTimeDelayMS -= kMSPerMinute; // Do not add time for first press of double press. if (gTimeDelayMS < kMSPerMinute) gTimeDelayMS = kMSPerMinute; // One minute is the minimum delay. Serial.println("double press"); #ifdef MCS_DEBUG Serial.print("New delay (ms): "); Serial.println(gTimeDelayMS); #endif turnLightOn(kPinGreen); gShowDelayRemainingMS = gTimeDelayMS; gTargetTimeMS = curTimeMS + kShowDelayOnMS; gState = kStateShowDelay; #ifdef MCS_DEBUG debugShowState("ShowDelay", curTimeMS, gTargetTimeMS); #endif } else if (gotButtonPress) gTimeDelayMS += kMSPerMinute; // Add one minute to the delay. break; case kStateShowDelay: // Show the current delay setting (by flashing the green light once for each second of delay). if (gotButtonPress) enterIdleState(); else if (curTimeMS >= gTargetTimeMS) { if (0 == gLightThatIsOn) { turnLightOn(kPinGreen); gTargetTimeMS += kShowDelayOnMS; } else { turnLightOff(); gTargetTimeMS += kShowDelayOffMS; gShowDelayRemainingMS -= kMSPerMinute; if (gShowDelayRemainingMS <= 0) enterIdleState(); } } break; } } // loop() void enterIdleState(void) { turnLightOff(); gTargetTimeMS = 0; gState = kStateIdle; #ifdef MCS_DEBUG debugShowState("Idle", millis(), gTargetTimeMS); #endif } void turnLightOn(unsigned char aPin) { turnLightOff(); digitalWrite(aPin, HIGH); gLightThatIsOn = aPin; } void turnLightOff(void) { if (gLightThatIsOn != 0) { digitalWrite(gLightThatIsOn, LOW); gLightThatIsOn = 0; } } unsigned long calcYellowDelay(unsigned long aDelayMS) { unsigned long yellowDelayMS = kMSPerMinute; // Use one minute in most cases. if (aDelayMS < (15 * 1000)) yellowDelayMS = 0; else if (aDelayMS <= kMSPerMinute) yellowDelayMS = (15 * 1000); // If one minute or less, use 15 seconds. else if (aDelayMS <= (2 * kMSPerMinute)) yellowDelayMS = (30 * 1000); // If between one and two minutes, use 30 seconds. #ifdef MCS_DEBUG Serial.print(" total delay: "); Serial.print(aDelayMS); Serial.print(", yellow delay: "); Serial.println(yellowDelayMS); #endif return yellowDelayMS; } #ifdef MCS_DEBUG void debugShowState(char *aStateDesc, unsigned long aCurTimeMS, unsigned long aTargetTimeMS) { Serial.print("-> "); Serial.print(aStateDesc); Serial.print(": curTime: "); Serial.print(aCurTimeMS); Serial.print(", targetTime: "); Serial.println(aTargetTimeMS); } #endif