// Title: goldeneye_timed_mine.ino
// Author: DAD_Projects
// Date: July 16, 2024
// Description: timed mine project for Arduino Nano controls a
// 4 character 7-segment display for a timer countdown. The timer
// is set using a rotary quadrature encoder with built-in push switch.
// Four LEDs and a piezo buzzer indicate when the timer has terminated.

// Arduino pin definition
#define DIG1A_ANODE 1
#define DIG1B_ANODE 0
#define DIG2A_ANODE 2
#define DIG2B_ANODE 3
#define DIG3A_ANODE 4
#define DIG3B_ANODE 5
#define DIG4A_ANODE 6
#define DIG4B_ANODE 7
#define DIGEX_ANODE 8
#define A_G2_CAT 13
#define B_H_CAT 14
#define C_J_CAT 15
#define D_K_CAT 16
#define E_L_CAT 17
#define F_M_CAT 18
#define G1_N_CAT 19
#define SOUND 12
#define LEDS 9        // PWM capable
#define QUAD_A 10
#define QUAD_B 11
#define QUAD_S 20     // A ditial pin would be a better solution for the rotary encoder switch,
//                    but all other digital switches have been used

enum DIGIT{DIGIT1A, DIGIT1B, DIGIT2A, DIGIT2B, DIGIT3A, DIGIT3B, DIGIT4A, DIGIT4B, DIGITEX};
int switch_val, encoder_val, switch_state, encoder_state;

unsigned long current_time = 0;
unsigned long previous_time_display = 0;
unsigned long previous_time_seconds = 0;
unsigned long start_time = 0;
unsigned long seconds_elapsed = 0;
unsigned seconds = 0;
unsigned minutes = 0;
int active_digit = DIGIT1A; // one digit at a time can be active
byte set_minutes = false; // has minutes been set?
byte set_seconds = false; // has seconds been set?
byte digits_on = true; // should the digit big flashing during setup?

unsigned inc_count = 0;
unsigned dec_count = 0;

// Parameters for the strobing effect while the mine is counting down
#define strobe_period 4000 // milliseconds
unsigned int ramp_value=0;
unsigned int strobe_value=0;

// Parameters for beeping and flashing effect when timer is done
#define beep_on_time 25 // milliseconds
#define beep_period 50 // milliseconds
#define beep_frequency 4097 // Hz
#define flash_half_period 50 // milliseconds
unsigned long flash_time = 0;
unsigned long beep_time = 0;
byte LED_state=0;

void setup() {
  pinMode(DIG1A_ANODE, OUTPUT);
  pinMode(DIG1B_ANODE, OUTPUT);
  pinMode(DIG2A_ANODE, OUTPUT);
  pinMode(DIG2B_ANODE, OUTPUT);
  pinMode(DIG3A_ANODE, OUTPUT);
  pinMode(DIG3B_ANODE, OUTPUT);
  pinMode(DIG4A_ANODE, OUTPUT);
  pinMode(DIG4B_ANODE, OUTPUT);
  pinMode(DIGEX_ANODE, OUTPUT);
  pinMode(A_G2_CAT, OUTPUT);
  pinMode(B_H_CAT, OUTPUT);
  pinMode(C_J_CAT, OUTPUT);
  pinMode(D_K_CAT, OUTPUT);
  pinMode(E_L_CAT, OUTPUT);
  pinMode(F_M_CAT, OUTPUT);
  pinMode(G1_N_CAT, OUTPUT);
  pinMode(SOUND, OUTPUT);
  pinMode(LEDS, OUTPUT);
  pinMode(QUAD_A, INPUT);
  pinMode(QUAD_B, INPUT);

  digitalWrite(LEDS, HIGH);

  // Initialize the rotary encoder state
  encoder_state = (digitalRead(QUAD_B) << 1) | digitalRead(QUAD_A);
  set_timer();
  start_time = millis();
}

void set_timer() {
  unsigned long previous_time = millis();
  while (1) {
    // This section toggles a control bit so that the active digits being set flash
    // during the increment and display function call
    current_time = millis();
    if ((current_time - previous_time) > 500) {
      digits_on = !digits_on;
      previous_time = current_time;
    }

    // Test if rotary encoder switch has been pressed
    if (analogRead(QUAD_S) > 512) {
      if (!set_minutes) {
        set_minutes = true;
        while (analogRead(QUAD_S) > 512) {
          // wait for button to be depressed
          inc_and_display_digit();
        }
      }
      else {
        set_seconds = true;
        return; // minutes and seconds have both been entered
      }
    }

    // Read the rotary encoder value
    // Clockwise states go 0 -> 1 -> 3 -> 2 -> 0 ...
    encoder_val = (digitalRead(QUAD_B) << 1) | digitalRead(QUAD_A);
    
    // Has quadrature encoder rotated?
    // Each detent is 4 state value changes, so count to 4
    if (encoder_state != encoder_val) {
      // Check if encoder is increasing
      if ((encoder_state == 0 && encoder_val == 1) || (encoder_state == 1 && encoder_val == 3) ||
          (encoder_state == 3 && encoder_val == 2) || (encoder_state == 2 && encoder_val == 0)) {
            if (inc_count < 3) {
              inc_count++;
              if (dec_count > 0)
                dec_count--;
            }
            else {
              inc_count = 0;
              dec_count = 0;
              if (set_minutes && seconds < 59)
                seconds++;
              else if (!set_minutes && minutes < 99)
                minutes++;
            }
          }
      // Check if encoder is decreasing
      else if ((encoder_state == 0 && encoder_val == 2) || (encoder_state == 2 && encoder_val == 3) ||
          (encoder_state == 3 && encoder_val == 1) || (encoder_state == 1 && encoder_val == 0)) {
            if (dec_count < 3) {
              dec_count++;
              if (inc_count > 0)
                inc_count--;
            }
            else { 
              inc_count = 0;
              dec_count = 0;
              if (set_minutes && seconds > 0)
                seconds--;
              else if (!set_minutes && minutes > 0)
                minutes--;
            }
          }
      // If one of the previous conditions was not met then a state was skipped and can't determine if increasing or decreasing
      // Don't update seconds or minutes, but set the encoder state to the current reading
      encoder_state = encoder_val;
    }

    // Update the display continuously
    inc_and_display_digit();
  }
}

void loop() {
  current_time = millis();
  seconds_elapsed = (current_time - start_time)/1000;

  // Is it time to update the clock?
  if (previous_time_seconds != seconds_elapsed) {
    if (seconds > 0)
      seconds--;
    else if (minutes > 0) {
      seconds = 59;
      minutes--;
    }
    else // time is up
      countdown_complete();
    previous_time_seconds = seconds_elapsed;
  }
  

  // Slowly blink the 4 LEDs on the mine
  ramp_value = (current_time*512/strobe_period) % 512;
  if (ramp_value > 255)
    strobe_value = (511 - ramp_value); //Divide by 10 to limit brightness
  else
    strobe_value = ramp_value;
  analogWrite(LEDS, strobe_value);

  // Continuously update the display
  inc_and_display_digit();
}

void countdown_complete () {
  while (1) {
    current_time = millis();
    if (current_time-flash_time>flash_half_period) {
      LED_state=~LED_state;
      flash_time=current_time;
      digitalWrite(LEDS, LED_state);
    }

    if (current_time-beep_time >= beep_period) {
      tone(SOUND, beep_frequency, beep_on_time);
      beep_time = current_time;
    }

    inc_and_display_digit();
  }
}

// cycles through the four 7-segment digits and passes the correct value to display
void inc_and_display_digit() {
  switch(active_digit) {
    case DIGIT1A:
      active_digit = DIGIT1B;
      write_digit(DIGIT1B, minutes/10);
      break;
    case DIGIT1B:
      active_digit = DIGIT2A;
      write_digit(DIGIT2A, minutes%10);
      break;
    case DIGIT2A:
      active_digit = DIGIT2B;
      write_digit(DIGIT2B, minutes%10);
      break;
    case DIGIT2B:
      active_digit = DIGIT3A;
      write_digit(DIGIT3A, seconds/10);
      break;
    case DIGIT3A:
      active_digit = DIGIT3B;
      write_digit(DIGIT3B, seconds/10);
      break;
    case DIGIT3B:
      active_digit = DIGIT4A;
      write_digit(DIGIT4A, seconds%10);
      break;
    case DIGIT4A:
      active_digit = DIGIT4B;
      write_digit(DIGIT4B, seconds%10);
      break;
    case DIGIT4B:
      active_digit = DIGITEX;
      write_digit(DIGITEX, 0);
      break;
    case DIGITEX:
      active_digit = DIGIT1A;
      write_digit(DIGIT1A, minutes/10);
      break;
    default:
      active_digit = DIGIT1A;
      write_digit(DIGIT1A, minutes/10);
      break;
  }
}

// sets the appropriate cathodes and anodes
void write_digit(int digit_place, int digit_value) {
  // Turn off all digits initially--otherwise, unintended segments will light up briefly
  digitalWrite(A_G2_CAT, HIGH);
  digitalWrite(B_H_CAT, HIGH);
  digitalWrite(C_J_CAT, HIGH);
  digitalWrite(D_K_CAT, HIGH);
  digitalWrite(E_L_CAT, HIGH);
  digitalWrite(F_M_CAT, HIGH);
  digitalWrite(G1_N_CAT, HIGH);
  digitalWrite(DIG1A_ANODE, LOW);
  digitalWrite(DIG1B_ANODE, LOW);
  digitalWrite(DIG2A_ANODE, LOW);
  digitalWrite(DIG2B_ANODE, LOW);
  digitalWrite(DIG3A_ANODE, LOW);
  digitalWrite(DIG3B_ANODE, LOW);
  digitalWrite(DIG4A_ANODE, LOW);
  digitalWrite(DIG4B_ANODE, LOW);
  digitalWrite(DIGEX_ANODE, LOW);
  // Digits are active when anodes set high and cathodes are set low
  // The conditional statements act to toggle the digit on and off while
  // the initial countdown is being set
  switch (digit_place) {
    case DIGIT1A:
      digitalWrite(DIG1A_ANODE, (set_minutes | (digits_on && !set_minutes)));
      break;
    case DIGIT1B:
      digitalWrite(DIG1B_ANODE, (set_minutes | (digits_on && !set_minutes)));
      break;
    case DIGIT2A:
      digitalWrite(DIG2A_ANODE, (set_minutes | (digits_on && !set_minutes)));
      break;
    case DIGIT2B:
      digitalWrite(DIG2B_ANODE, (set_minutes | (digits_on && !set_minutes)));
      break;
    case DIGIT3A:
      digitalWrite(DIG3A_ANODE, (!set_minutes | set_seconds | (digits_on && set_minutes && !set_seconds)));
      break;
    case DIGIT3B:
      digitalWrite(DIG3B_ANODE, (!set_minutes | set_seconds | (digits_on && set_minutes && !set_seconds)));
      break;
    case DIGIT4A:
      digitalWrite(DIG4A_ANODE, (!set_minutes | set_seconds | (digits_on && set_minutes && !set_seconds)));
      break;
    case DIGIT4B:
      digitalWrite(DIG4B_ANODE, (!set_minutes | set_seconds | (digits_on && set_minutes && !set_seconds)));
      break;
    case DIGITEX:
      digitalWrite(DIGEX_ANODE, HIGH);
      break;
  }
  // This is the ":" character in the middle of the 4 digits
  if (digit_place == DIGITEX) {
        digitalWrite(A_G2_CAT, LOW);
  }
  // This is the first half of the digit (A, B, C, D, E, F, G1)
  else if (digit_place == DIGIT1A || digit_place == DIGIT2A ||
      digit_place == DIGIT3A || digit_place == DIGIT4A)
    switch (digit_value) {
      case 0:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        digitalWrite(D_K_CAT, LOW);
        digitalWrite(E_L_CAT, LOW);
        digitalWrite(F_M_CAT, LOW);
        break;
      case 1:
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        break;
      case 2:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(D_K_CAT, LOW);
        digitalWrite(E_L_CAT, LOW);
        digitalWrite(G1_N_CAT, LOW);
        break;
      case 3:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        digitalWrite(D_K_CAT, LOW);
        digitalWrite(G1_N_CAT, LOW);
        break;
      case 4:
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        digitalWrite(F_M_CAT, LOW);
        digitalWrite(G1_N_CAT, LOW);
        break;
      case 5:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        digitalWrite(D_K_CAT, LOW);
        digitalWrite(F_M_CAT, LOW);
        digitalWrite(G1_N_CAT, LOW);
        break;
      case 6:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        digitalWrite(D_K_CAT, LOW);
        digitalWrite(E_L_CAT, LOW);
        digitalWrite(F_M_CAT, LOW);
        digitalWrite(G1_N_CAT, LOW);
        break;
      case 7:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        break;
      case 8:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        digitalWrite(D_K_CAT, LOW);
        digitalWrite(E_L_CAT, LOW);
        digitalWrite(F_M_CAT, LOW);
        digitalWrite(G1_N_CAT, LOW);
        break;
      case 9:
        digitalWrite(A_G2_CAT, LOW);
        digitalWrite(B_H_CAT, LOW);
        digitalWrite(C_J_CAT, LOW);
        digitalWrite(F_M_CAT, LOW);
        digitalWrite(G1_N_CAT, LOW);
        break;
    }
  // This is the second half of the digit (G2, H, J, K, L, M, N)
  else
    switch (digit_value) {
      case 0:
        break;
      case 1:
        break;
      case 2:
        digitalWrite(A_G2_CAT, LOW);
        break;
      case 3:
        digitalWrite(A_G2_CAT, LOW);
        break;
      case 4:
        digitalWrite(A_G2_CAT, LOW);
        break;
      case 5:
        digitalWrite(A_G2_CAT, LOW);
        break;
      case 6:
        digitalWrite(A_G2_CAT, LOW);
        break;
      case 7:
        break;
      case 8:
        digitalWrite(A_G2_CAT, LOW);
        break;
      case 9:
        digitalWrite(A_G2_CAT, LOW);
        break;
    }
}