Defusable Clock - initial display, speed up countdown, pause countdown

Hello All,

I have recently purchased a Nootropic Design Defusable Clock for an upcoming paintball game. Their source code is posted here. They are using a custom build board using UNO board programming style, board design is listed here. All buttons are momentary and what I am referring to as switches are the 4 cutable wires that I have wired to a 2 position flip switch.

I have done some surgery to get everything the way I would like but have a few things I am not able to figure out on my own.
Asks

  1. On power-up, the display shows the DEFAULT_COUNTDOWN_DURATION value, in the case listed below it is 55:55. I would like this to display the value stored in the EEPROM, which is the last value used as the countdown duration.
  2. Currently there is a Detonation Switch and a Defuse Switch. I would like to add functions to the other two to double the speed of the countdown/beep cycle and another to temporarily pause the countdown for a few seconds.

Current state is

  • Countdown Timer only, no clock or alarm/snooze
  • Switches are randomly assigned a task (defuse, detonate, nothing, nothing)
  • After detonation/defuse HOUR or MIN BUTTON must be pressed, followed by DET BUTTON to cycle back to a new countdown (I believe by making the adjustment 1 listed above the det button would not have to be pressed to complete a cycle, just the hour or min button)
  • Once a countdown is initiated there must be a defuse or a detonation to adjust any values
  • On Detonate the LED DET stays lit, the display shows 00:00, and a trigger is set off for 3 seconds
  • On Defuse the LED TOP stays lit and the display pauses on the time the defuse occurred

I have posted my current code modifications in the first reply due to character limitations.

Key areas of change are in the functions:

  • ISR(TIMER2_OVF_vect), where the variables displayHour and displayMinute have been changed to match the values is displayCountdown were true. I believe this has something to do with the bootup display of DEFAULT_COUNTDOWN_DURATION being displayed, but when I remove it I get the dreaded flashing 12:00.
  • countdown() I have added the variables speedUpPin and pausePin (I was also playing with a superSpeedUpPin that may make it go even faster, but decided to work on the pause as it would be easier). I am also unsure of the random assignment of these pins. I tried to mimic what was there, but I am not sure if it will assign to a nonused Pin.

Any help on this would be greatly appreciated. I know enough coding to get me in trouble, but not to do anything useful, one of those, "I took a class in high school/college" kinda things.

My current code modifications:

void setup() {

  pinMode(CLOCK, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(COLON, OUTPUT);
  digitalWrite(COLON, LOW);
  pinMode(LED_PM, OUTPUT);
  pinMode(LED_ALARM, OUTPUT);
  pinMode(LED_TOP, OUTPUT);
  pinMode(LED_DET, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(TRIGGER, OUTPUT);
  pinMode(HOUR_BUTTON_PIN, INPUT);
  pinMode(MIN_BUTTON_PIN, INPUT);
  pinMode(ALARM_BUTTON_PIN, INPUT);
  pinMode(DET_BUTTON_PIN, INPUT);
  pinMode(WIRE_1, INPUT);
  pinMode(WIRE_2, INPUT);
  pinMode(WIRE_3, INPUT);
  pinMode(WIRE_4, INPUT);
  digitalWrite(HOUR_BUTTON_PIN, HIGH);
  digitalWrite(MIN_BUTTON_PIN, HIGH);
  digitalWrite(ALARM_BUTTON_PIN, HIGH);
  digitalWrite(DET_BUTTON_PIN, HIGH);
  digitalWrite(WIRE_1, HIGH);
  digitalWrite(WIRE_2, HIGH);
  digitalWrite(WIRE_3, HIGH);
  digitalWrite(WIRE_4, HIGH);

  if (EEPROMValid() && (!((buttonPressed(HOUR_BUTTON)) && (buttonPressed(MIN_BUTTON))))) {
    defaultCountdownSeconds = EEPROM.read(10);
    defaultCountdownSeconds = defaultCountdownSeconds << 8;
    defaultCountdownSeconds |= EEPROM.read(11);
    if (defaultCountdownSeconds > 5999) {
      defaultCountdownSeconds = DEFAULT_COUNTDOWN_DURATION;
    }
  } else {
    defaultCountdownSeconds = DEFAULT_COUNTDOWN_DURATION;
  }

  TIMSK2 &= ~(1 << TOIE2);
  TCCR2A &= ~((1 << WGM21) | (1 << WGM20));
  TCCR2B &= ~(1 << WGM22);
  ASSR &= ~(1 << AS2);
  TIMSK2 &= ~(1 << OCIE2A);
  TCCR2B |= (1 << CS22);
  TCCR2B &= ~(1 << CS21);
  TCCR2B |= (1 << CS20);
  TCNT2 = 0;
  TIMSK2 |= (1 << TOIE2);
  TIMSK1 &= ~(1 << TOIE1);
  TCCR1A = 0;
  TCCR1B = (1 << CS12) | (1 << CS10);
  TIMSK1 |= (1 << TOIE1);
  TCNT1 = TIMER1_SECOND_START;

  if (buttonPressed(DET_BUTTON)) {
    beep(3500, 50);
    silent = false;
    digitalWrite(LED_TOP, LOW);
    digitalWrite(LED_DET, LOW);
    while (buttonPressed(DET_BUTTON));
  }

}

void loop() {

  delay(10);

  if (buttonPressedNew(DET_BUTTON)) {
    if ((displayZeros) || (isDefused)) {
      isDefused = false;
      displayZeros = false;
      displayCountdown = false;
      return;
    }
    detPressed = true;
    digitalWrite(LED_TOP, LOW);
    digitalWrite(LED_DET, LOW);
    countdownSeconds = defaultCountdownSeconds;
    displayCountdown = true;
  }

  if (!buttonPressed(DET_BUTTON)) {
    if (detPressed) {
      detPressed = false;
      defaultCountdownSeconds = countdownSeconds;
      writeEEPROM();
      countdown();
    }
  }
}

void countdown() {
  int ledCounter = 0;
  int ledCounterThreshold = 100000;
  byte ledCurrentState = HIGH;
  byte defusePin;
  byte detPin;
  byte speedUpPin;
  byte pausePin;
  boolean defused = false;
  countdownRunning = true;
  int fractionalSecond;

  defusePin = random(WIRE_1, (WIRE_4 + 1));
  speedUpPin = random(WIRE_1, (WIRE_4 + 1));
  pausePin = random(WIRE_1, (WIRE_4 + 1));
  detPin = defusePin;
  while (detPin == defusePin) {
    detPin = random(WIRE_1, (WIRE_4 + 1));
  }

  digitalWrite(LED_PM, LOW); 

  // Keep track of how far we are into the current second so we can correct later.
  fractionalSecond = TCNT1 - TIMER1_SECOND_START;

  // Reset back to the last second boundary so we can start the countdown immediately and so that the first second isn't truncated
  TCNT1 = TIMER1_SECOND_START;

  beep(3800, 30);
  digitalWrite(LED_DET, ledCurrentState);
  while ((countdownSeconds > 0) && (!defused)) {
    for (int i = 0; i < 10000; i++) {
      if (digitalRead(detPin) == HIGH) {
        countdownSeconds = 0;
        break;
      }
      if (digitalRead(defusePin) == HIGH) {
        defused = true;
        break;
      }
      if (digitalRead(speedUpPin) == HIGH) {
        ledCounterThreshold == 400000;
        break;
      }
      if (digitalRead(pausePin) == HIGH) {
        delay(100);
      }
    }
    delay(20);
    if (ledCounter++ > ledCounterThreshold) {
      ledCounter = 0;
      if (ledCurrentState == HIGH) {
        ledCurrentState = LOW;
      } else {
        ledCurrentState = HIGH;
      }
      digitalWrite(LED_DET, ledCurrentState);
    }
  }
  digitalWrite(LED_DET, LOW);
  countdownRunning = false;
  if (!defused) {
    detonate();
  } else {
    beep(4500, 80);
    isDefused = true;
    digitalWrite(LED_TOP, HIGH);
    while (!((buttonPressed(HOUR_BUTTON)) || (buttonPressed(MIN_BUTTON))))
  }

  // Now to keep the time accurate, add back in the fractional second that we took off when we started the countdown sequence.
  // Wait until we can add it back to TCNT1 without overflowing.
  while (TCNT1 >= (65535 - fractionalSecond));
  TCNT1 += fractionalSecond;
}

void detonate() {
  for (int i = 0; i < 8; i++) {
    digitalWrite(LED_DET, HIGH);
    beep(5000, 50, false);
    delay(25);
    digitalWrite(LED_DET, LOW);
    delay(25);
  }

  unsigned long triggerStart = millis();
  unsigned long triggerStop = triggerStart + TRIGGER_DURATION_MS;
  digitalWrite(TRIGGER, HIGH);

  for (int i = 0; i < 50; i++) {
    if (millis() >= triggerStop) {
      digitalWrite(TRIGGER, LOW);
    }
    digitalWrite(random(LED_PM, LED_DET + 1), HIGH);
    digitalWrite(random(LED_PM, LED_DET + 1), HIGH);
    for (int j = 0; j < 5; j++) {
      beep(random(100, 300), 10);
    }
    for (int led = LED_PM; led <= LED_DET; led++) {
      digitalWrite(led, LOW);
    }
  }

  displayCountdown = false;
  blank = false;
  digitalWrite(LED_DET, HIGH);

  while (millis() < triggerStop) {
    if (buttonPressedNew(DET_BUTTON)) {
      displayZeros = true;
      break;
    }
  }

  digitalWrite(TRIGGER, LOW);
  while (!((buttonPressed(HOUR_BUTTON)) || (buttonPressed(MIN_BUTTON)))) {  }
}

boolean buttonPressed(byte button) {
  if (digitalRead(buttonPins[button]) == LOW) {
    if (buttonState[button] == HIGH) {
      buttonChange[button] = millis();
      buttonState[button] = LOW;
    }
    return true;
  } else {
    if (buttonState[button] == LOW) {
      buttonChange[button] = millis();
      buttonState[button] = HIGH;
    }
    return false;
  }
}

boolean buttonPressedNew(byte button) {
  if (digitalRead(buttonPins[button]) == LOW) {
    if (buttonState[button] == HIGH) {
      buttonChange[button] = millis();
      buttonState[button] = LOW;
      return true;
    }
    return false;
  } else {
    if (buttonState[button] == LOW) {
      buttonChange[button] = millis();
      buttonState[button] = HIGH;
    }
    return false;
  }
}

void beep(int frequency, int duration) {
  beep(frequency, duration, true);
}

void beep(int frequency, int duration, boolean disableDisplayInterrupt) {
  int us = 1000000 / frequency / 2;
  int loopCount = (duration * ((float)frequency / 1000.0));
  if (disableDisplayInterrupt) {
    TIMSK2 &= ~(1 << TOIE2);
  }
  for (int i = 0; i < loopCount; i++) {
    if (!silent) PORTB |= (1 << 3);
    delayMicroseconds(us);
    if (!silent) PORTB &= ~(1 << 3);
    delayMicroseconds(us);
  }
  TIMSK2 |= (1 << TOIE2);
}

// This is the display interrupt to implement multiplexing of the digits.
ISR(TIMER2_OVF_vect) {
  byte nDigits = 4;
  byte data;
  byte digitValue;
  byte displayHours, displayMinutes;

  TCNT2 = 0;

  displayHours = countdownSeconds / 60;
  displayMinutes = countdownSeconds % 60;
  if (displayCountdown) {
    displayHours = countdownSeconds / 60;
    displayMinutes = countdownSeconds % 60;
  }
  if (displayZeros) {
    displayHours = 0;
    displayMinutes = 0;
  }


  if (++currentDigit > (nDigits - 1)) {
    currentDigit = 0;
  }

  switch (currentDigit) {
    case 0:
      digitValue = displayMinutes % 10;
      break;
    case 1:
      digitValue = displayMinutes / 10;
      break;
    case 2:
      digitValue = displayHours % 10;
      break;
    case 3:
      digitValue = displayHours / 10;
      break;
  }

  data = (digitValue << 4);

  data |= 0x0F;

  if (!blank) {
    data &= ~(1 << currentDigit);
  }

  digitalWrite(LATCH, LOW);
  shiftOut(DATA, CLOCK, LSBFIRST, data);
  digitalWrite(LATCH, HIGH);
}


// Timer 1 interrupt.  This executes every second.
ISR(TIMER1_OVF_vect) {
  TCNT1 = TIMER1_SECOND_START;

  ticked = true;
  seconds++;
  if (seconds == 60) {
    seconds = 0;
    minutes++;
    if (minutes == 60) {
      minutes = 0;
      hours++;
    }
  }

  if ((countdownRunning) && (countdownSeconds > 0)) {
    beep(3800, 30);
    countdownSeconds--;
  }
}

Ask #1 - #include the eeprom library (found at IDE/sketch/include library/eeprom). Then, in setup() read a pre-designated eeprom location to retrieve your time value. Somewhere in loop() when the time is set you save the time value to the designated eeprom location so it's available on next startup.

Hello Dougp,

Thanks for the suggestion.

I do already have the EEPROM library in the define but had to eliminate some of my code due to character limit in the forum. Within Setup() I am setting the variable volatile unsigned int countdownSeconds = EEPROM.read(10); listed right after the pinMode and digitalWrites.

void setup() {

  pinMode(CLOCK, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(COLON, OUTPUT);
  digitalWrite(COLON, LOW);

  pinMode(LED_PM, OUTPUT);
  pinMode(LED_ALARM, OUTPUT);
  pinMode(LED_TOP, OUTPUT);
  pinMode(LED_DET, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(TRIGGER, OUTPUT);

  pinMode(HOUR_BUTTON_PIN, INPUT);
  pinMode(MIN_BUTTON_PIN, INPUT);
  pinMode(ALARM_BUTTON_PIN, INPUT);
  pinMode(DET_BUTTON_PIN, INPUT);
  pinMode(WIRE_1, INPUT);
  pinMode(WIRE_2, INPUT);
  pinMode(WIRE_3, INPUT);
  pinMode(WIRE_4, INPUT);

  digitalWrite(HOUR_BUTTON_PIN, HIGH);
  digitalWrite(MIN_BUTTON_PIN, HIGH);
  digitalWrite(ALARM_BUTTON_PIN, HIGH);
  digitalWrite(DET_BUTTON_PIN, HIGH);
  digitalWrite(WIRE_1, HIGH);
  digitalWrite(WIRE_2, HIGH);
  digitalWrite(WIRE_3, HIGH);
  digitalWrite(WIRE_4, HIGH);

if (EEPROMValid() && (!((buttonPressed(HOUR_BUTTON)) && (buttonPressed(MIN_BUTTON))))) {
    defaultCountdownSeconds = EEPROM.read(10);
    defaultCountdownSeconds = defaultCountdownSeconds << 8;
    defaultCountdownSeconds |= EEPROM.read(11);
    if (defaultCountdownSeconds > 5999) {
      defaultCountdownSeconds = DEFAULT_COUNTDOWN_DURATION;
    }
  } else {
    defaultCountdownSeconds = DEFAULT_COUNTDOWN_DURATION;
  }

  // Initialize timers.
  // Timer1 is used to keep the clock time
  // Timer2 is used for the display multiplexing

  // Disable the timer overflow interrupt
  TIMSK2 &= ~(1 << TOIE2);

  // Set timer2 to normal mode
  TCCR2A &= ~((1 << WGM21) | (1 << WGM20));
  TCCR2B &= ~(1 << WGM22);

  // Use internal I/O clock
  ASSR &= ~(1 << AS2);

  // Disable compare match interrupt
  TIMSK2 &= ~(1 << OCIE2A);

  // Prescalar is clock divided by 128
  TCCR2B |= (1 << CS22);
  TCCR2B &= ~(1 << CS21);
  TCCR2B |= (1 << CS20);

  // Start the counting at 0
  TCNT2 = 0;

  // Enable the timer2 overflow interrupt
  TIMSK2 |= (1 << TOIE2);


  // init timer1
  // set prescaler to 1024
  TIMSK1 &= ~(1 << TOIE1);
  TCCR1A = 0;
  TCCR1B = (1 << CS12) | (1 << CS10);
  TIMSK1 |= (1 << TOIE1);
  // With prescalar of 1024, TCNT1 increments 15,625 times per second
  // 65535 - 15625 = 49910
  TCNT1 = TIMER1_SECOND_START;

//  randomSeed(analogRead(0));

  if (buttonPressed(DET_BUTTON)) {
    // enable silent mode for testing
    beep(3500, 50);
    silent = false;
    digitalWrite(LED_TOP, LOW);
    digitalWrite(LED_DET, LOW);
    while (buttonPressed(DET_BUTTON)); // wait for release
  }

  while ((buttonPressed(HOUR_BUTTON)) || (buttonPressed(MIN_BUTTON))); // wait for release of factory reset procedure

}

For whatever reason, when I boot up I get the default value (that I set) of 55:55, but after I start the loop for the first time the EEPROM is read correctly and displays as it should. How would I get the EEPROM read correctly the first time it boots up?

What is the criteria for EEPROMValid()?

boolean EEPROMValid() {
  // determine if the EEPROM has ever been written by this firmware
  // so we can determine if the values can be trusted
  unsigned int magic = EEPROM.read(0);
  magic = magic << 8;
  magic |= EEPROM.read(1);
  return (magic == EEPROM_MAGIC_NUMBER);
}

void setEEPROMValid() {
  EEPROM.write(0, EEPROM_MAGIC_NUMBER >> 8);
  EEPROM.write(1, (EEPROM_MAGIC_NUMBER & 0xFF));
}

Seems complicated. I use this simple check and a function to restore values on power-up. It just requires that the first of several values has been previously written - from elsewhere in the program. Have you verified EEPROMValid() returns the correct value?

void eepromRestore() {
  //Serial.println("restore from eeprom");
  byte i;
  if (EEPROM.read(eepromLctn) != 255) { // address has been used so load parameter[]/counter data from there
    for (i = 0; i < menuItem_qty; i++) {
      textAndParms[i].parmVal = EEPROM.read(i + eepromLctn);
      parmValChecksum += textAndParms[i].parmVal; // compute a simple checksum on the parmval data
    }
    EEPROM.get(i + eepromLctn, sessionCount); // session count stored immediately after parameters
    i += sizeof(sessionCount); // skip to total count address
    EEPROM.get(i + eepromLctn, totalCount); // total count stored immediately after session count
  }
}

It's all Greek to me. This is not my code. This is the default code from Michael, the developer at Nootropic Design.

I know it is working because the values are being restored after a loop. It's just on power up that it defaults to the default value. Once I begin the loop the eeprom values are displayed correctly.

At this point I am more focused on part 2 of my ask, the speedup and pause functionality. Do you happen to know how this could be accomplished? I have been working on the pause function because I thought it would be easiest. I have added a delay, but the countdown still continues. Thoughts?

Just a stab but, if you can find a way to manipulate

countdownRunning

that would stop the seconds decrementing.

785   if ((countdownRunning) && (countdownSeconds > 0)) { 
786     beep(3800, 30); 
787     countdownSeconds--; 
788   }
I am setting the variable volatile unsigned int countdownSeconds = EEPROM.read(10);

EEPROM.read() returns a byte not an int. Could that be a problem ?

Dougp, thank you. I dont know how I missed that, but that was exactly what I needed. I updated the code in 2 places:

  1. Moved defusePin, detPin, speedUpPin, and pausePin to global variables and added pauseTime, also as global variable.
  2. Added a new if else to the lines you pointed out, shown below
if ((countdownRunning) && (countdownSeconds > 0)) {
    if (digitalRead(pausePin) == HIGH) {
        countdownRunning = false;
        delay(pauseTime);
        digitalWrite(pausePin, LOW);
        countdownRunning = true;
    } else {
      beep(3800, 30);
      countdownSeconds--;
    }
  }

Now to play with speeding up the timer.

Progress is being made. Very good!

I'm not sure why, but regardless of the pasueTime value I set the loop will only pause for 3 or less seconds.

Also, I have been reading up on timers to speed up the countdown; I liked this article. I have tried to reinitialize timer1, which I believe is the solution. I'm still struggling with the value, anything I use causes a single second to take 4 seconds. The below code causes each second to take 4 seconds from the beginning regardless of the state of speedUpPin.

// Timer 1 interrupt.  This executes every second.
ISR(TIMER1_OVF_vect) {
//  TCNT1 = TIMER1_SECOND_START;
  if(digitalRead(speedUpPin == HIGH)) {
    TCNT1 = SPEED_UP_TIME;
    digitalWrite(speedUpPin, HIGH);
  } else {
    TCNT1 = TIMER1_SECOND_START;
    digitalWrite(speedUpPin, LOW);
  }

if ((countdownRunning) && (countdownSeconds > 0)) {
    if (digitalRead(pausePin) == HIGH) {
        countdownRunning = false;
        delay(PAUSE_TIME);
        digitalWrite(pausePin, LOW);
        countdownRunning = true;
    } else {
      beep(3800, 30);
      countdownSeconds--;
    }
  }
}

I have played similar code within setup() with similar results, either the timer always ticks at 1 second per second or 1 second per 4 seconds (4x slow).

Thoughts?

Taggart:
I have played similar code within setup() with similar results, either the timer always ticks at 1 second per second or 1 second per 4 seconds (4x slow).

Thoughts?

Not from me. I haven't progressed to manipulating the internal timers yet.

Thank you very much for helping where you could.

UKHeliBob:
EEPROM.read() returns a byte not an int. Could that be a problem ?

Hello UKHeliBob,

Are you suggesting to define defaultCountdownSeconds as a byte rather than an int?

If so, I just gave that a shot. If I use an unsigned byte I get an error:

expected initializer before 'defaultCountdownSeconds'

So, I tried with just byte defaulCountdownSeconds. This did not produce an error, but did not read the eeprom value that is stored either.

I also commented out some of the setup() code to remove setting defaultCountdownSeconds to DEFAULT_COUNTDOWN_SEONDS, as follows:

//#define DEFAULT_COUNTDOWN_DURATION 3355

//volatile unsigned int countdownDuration = DEFAULT_COUNTDOWN_DURATION;
volatile unsigned int countdownDuration = EEPROM.read(10);
//volatile unsigned int countdownSeconds = DEFAULT_COUNTDOWN_DURATION;
volatile unsigned int countdownSeconds = EEPROM.read(10);

setup()
[...]
 if (EEPROMValid() && (!((buttonPressed(HOUR_BUTTON)) && (buttonPressed(MIN_BUTTON))))) {
    defaultCountdownSeconds = EEPROM.read(10);
    defaultCountdownSeconds = defaultCountdownSeconds << 8;
    defaultCountdownSeconds |= EEPROM.read(11);
//    if (defaultCountdownSeconds > 5999) {
//      // guard against bad data
//      defaultCountdownSeconds = DEFAULT_COUNTDOWN_DURATION;
//    }
//  } else {
//    defaultCountdownSeconds = DEFAULT_COUNTDOWN_DURATION;
//    //    writeEEPROM();
[...]

After running the loop once, defaultCountdownSeconds should be written to eeprom (10), which it appears to do prior to this as the second loop will display the last countdown correctly. However, even with DEFAULT_COUNTDOWN_SECONDS never being set to a variable I am still getting its value on startup.

Hello UKHeliBob,

Are you suggesting to define defaultCountdownSeconds as a byte rather than an int?

What I am suggesting is that you declare defaultCountdownSeconds as the appropriate type to hold the value that you require and that having done that you save/load it to/from the EEPROM with the appropriate function. If you declare it as an int then you can't save it using EEPROM.save() or load it back with EEPROM.load() because they save/load only a byte. EEPROM.put() and EEPROM.get() will save and load any type of data if used properly.

If I use an unsigned byte I get an error:

A byte has no sign. It is always positive with a value between 0 and 255. There is no such thing as an unsigned byte in a variable declaration.