Timer
February 2022

 

Timer

I built this timer to aid with developing black and white film. In addition to the obvious timing function, there is an audible tick at one-second intervals and a 'beep' at one-minute intervals as a reminder to agitate the developer. Both options can be turned off if they're not needed. There's a longer alarm sound when the pre-set time is reached.

The Main Screen

With the timer paused, the required time is set using the left and right +/- buttons. Note that the set time will be remembered for next time once the Start/Pause button has started the timer running. The set time will not be remembered unless the timer is started.

The Setup Screen

Enter the Setup screen by holding down the Start/Pause button when turning on the power. The items in the menu are selected by using the left hand buttons to move the cursor (>) up and down. The value of an item is changed by using the right hand buttons.

The mSec item is used to adjust the accuracy of the timer. If the timer is running slow, increase the negative value.

The display contrast tends to fade as the battery runs low. The contrast can be changed in the setup menu or by using the top left button (Minutes+) when the timer is running.

The LED backlight can be switched on and off using the the Seconds+ button when the timer is running but the timer will always power up with the backlight OFF. To have the backlight ON at power up, set the setup menu item to ON.

Select Save/Exit and press either right-hand button to save the settings and exit the setup menu. Any changes to the setup menu will not be saved if the timer is powered off without saving the settings.

The Circuit

The Printed Circuit Board

Download PCB artwork in PDF format

 

Required Additional Arduino Libraries

Low-Power Library

Adafruit-GFX-Library

Adafruit-PCD8544-Nokia-5110-LCD-library

 

Arduino Sketch

#include <SPI.h>
#include <EEPROM.h>

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <Fonts/FreeSansBold12pt7b.h>

#include "LowPower.h"

#define LCD   A1  // Vcc
#define RST   A0   
#define CE    13   
#define DC    12  
#define DIN   11  
#define CLK   10 
#define LED    9

#define minUp       3
#define minDown     4
#define secUp       5
#define secDown     6
#define start_pause 7
#define buzz        A5 


Adafruit_PCD8544 lcd = Adafruit_PCD8544(CLK, DIN, DC, CE, RST);

#define oneSecond 1000000  // Microseconds


int secondsCount;
int minutesCount;

int InitMins, InitSecs;

long int adjustMillis;
long int adjustMicros;

int secondsTickDelay = 4;        // milliseonds
bool secondsTickActive;

int minutesTickDelay = 200;      // milliseconds
bool minutesTickActive;

int secondsVolume;
int minutesVolume;

bool ledState = true;

unsigned long int clockTick;

unsigned long int autoOffTime = 120000;       // Auto-off 2 minutes after timer ends or
unsigned long int offTimer;                   // no activity (ie button-pressing)

unsigned long int secUpTimer;
unsigned long int secDownTimer;
unsigned long int minUpTimer;
unsigned long int minDownTimer;

bool Running = false;
bool firstLoop = true;


int increment = 1;
byte contrast = 55;
float volts;

void setup() {

  pinMode(LCD, OUTPUT);
  pinMode(RST, OUTPUT);        
  pinMode(CE, OUTPUT);        
  pinMode(DC, OUTPUT);         
  pinMode(DIN, OUTPUT);       
  pinMode(CLK, OUTPUT); 
  pinMode(LED, OUTPUT);
  
  digitalWrite(LED, HIGH);        // HIGH = LEDs Off.
  digitalWrite(LCD, HIGH);        // HIGH = Power to display On
 
  pinMode(buzz, OUTPUT);
  digitalWrite(buzz, LOW);

  pinMode(start_pause, INPUT_PULLUP);
  
  pinMode(minUp, INPUT_PULLUP);
  pinMode(minDown, INPUT_PULLUP);
  pinMode(secUp, INPUT_PULLUP);
  pinMode(secDown, INPUT_PULLUP);

  volts = readVcc();
  if (volts < 3200) {
     PowerDown();
  }


  InitMins = constrain(EEPROM.read(0), 0, 59);  // Get Mins and Seconds from EEPROM.
  InitSecs = constrain(EEPROM.read(1), 0, 59);  // Will be random first time.


  
  byte test = EEPROM.read(10);
  byte test1 = EEPROM.read(11);
  if ((test == 0) && (test1 == 55)) {

     EEPROM.get(2, secondsTickActive);
     EEPROM.get(3, minutesTickActive);
    
     EEPROM.get(4, contrast);
     EEPROM.get(5, adjustMillis);
     EEPROM.get(12, ledState);
  }
  else {
    secondsTickActive = true;
    minutesTickActive = true;
    adjustMillis = -50;    
    contrast = 55;

    EEPROM.put(2, secondsTickActive);
    EEPROM.put(3, minutesTickActive);
   
    EEPROM.put(4, contrast);
    EEPROM.put(5, adjustMillis);
    EEPROM.put(10, 0);                 // test
    EEPROM.put(11, 55);                // test1
    EEPROM.put(12, true);              // LED on
  }

  digitalWrite(LED, ledState);

  adjustMillis = constrain(adjustMillis, -500, 500);

  contrast = constrain(contrast, 50, 65);
 
  minutesCount = InitMins;
  secondsCount = InitSecs;

  lcd.begin(contrast);            // set LCD contrast

  lcd.setTextSize(1); 
  lcd.setTextColor(BLACK, BLACK);

  int y = 0;
  char buff[5];

  bool menu = false;
   
  while ((digitalRead(start_pause) == LOW) || menu){
    menu = true;
  
    lcd.clearDisplay();
    lcd.setContrast(contrast);
    lcd.setCursor(10, 0);
    lcd.print("mSec ");
    lcd.setCursor(57, 0);
    sprintf(buff, "%4d", adjustMillis);   // Right justify text
    lcd.print(buff);
    lcd.setCursor(10, 10);
    lcd.print("Contrast ");
    lcd.setCursor(70, 10);
    lcd.print(contrast);
    lcd.setCursor(10, 20);
    lcd.print("LED");
    lcd.setCursor(65, 20);
    ledState ? lcd.print("OFF") : lcd.print(" ON");

    lcd.setCursor(10, 40);
    lcd.print("Save/Exit");
    lcd.setCursor(70, 40);
    lcd.print("OK");


    lcd.setCursor(0, y);
    lcd.print("> ");
    
    lcd.display();

    if (digitalRead(minDown) == LOW) {
      y = y+10;
      if (y > 40) y = 40;
      delay(250);
    }
    if (digitalRead(minUp) == LOW) {
      y = y - 10;
      if (y < 0) y = 0;
      delay(250);
    }
    

    if (digitalRead(secUp) == LOW) {
      if (y == 0) {
        adjustMillis++;
        delay(250);
      }
      if (y == 10) {
        contrast++;
        if (contrast > 65) contrast = 65;
        delay(250);
      }
      if (y == 20) {
        ledState = true;
        digitalWrite(LED, ledState);
        delay(250);
      }
      if (y == 30) {
        // spare
        delay(250);
      }
      if (y == 40) {
        menu = false;
        while (digitalRead(secUp) == LOW);
        delay(200);
      }
      
    }

    

    if (digitalRead(secDown) == LOW) {
      if (y == 0) {
        adjustMillis--;
        delay(250);
      }
      if (y == 10) {
        contrast--;
        if (contrast < 45) contrast = 45;
        delay(250);
      }
      if (y == 20) {
        ledState = false;
        digitalWrite(LED, ledState);
        delay(250);
      }
      if (y == 30) {
       // spare
        delay(250);
      }
      if (y == 40) {
        menu = false;
        while (digitalRead(secUp) == LOW);
        delay(200);
      }      
    }
  
  }

  EEPROM.put(2, secondsTickActive);
  EEPROM.put(3, minutesTickActive);  
  
  EEPROM.put(4, contrast);
  EEPROM.put(5, adjustMillis); 

  EEPROM.put(12, ledState);


  adjustMicros = adjustMillis * 1000;

  
    
  updateDisplay();
 
  clockTick = micros();
  offTimer = millis();
  
}

unsigned long int quickPress;
bool pressed = false;

void loop() {

  volts = readVcc();                            // Read the battery voltage.

  if (volts <= 3200) {                          // Get battery volts. If 3.2v or below,
    PowerDown();                                // Power down ATmega328 and LCD Display.
  }                                             
                                               
  

  // ==== Handle button presses.  =====


  if ((digitalRead(start_pause) == LOW) && !pressed){        // Detect accidental start_stop button press
    quickPress = millis();
    pressed = true;
  }
  
  if ((digitalRead(start_pause) == LOW) && (millis()- quickPress > 200)) {

    Running = !Running; 
    
    analogWrite(buzz, 140);
    delay(3);
    digitalWrite(buzz, LOW);

    if (Running && firstLoop)  {                   // 
      InitMins = minutesCount;
      InitSecs = secondsCount;
   
      EEPROM.put(0, InitMins);
      EEPROM.put(1, InitSecs);
      EEPROM.put(2, secondsTickActive);
      EEPROM.put(3, minutesTickActive);      
      
      EEPROM.put(4, contrast);
      EEPROM.put(5, adjustMillis); 

      EEPROM.put(12, ledState);
      
      firstLoop = false;

      clockTick = micros();
  
    }
   
    
    delay(1000);
  }

  if ((digitalRead(start_pause) == HIGH) && pressed){
    pressed = false;
  }  


//===============  If timer is not running, buttons adjust timer Minutes and Seconds ==============

  if (!Running) {                                   // If the timer isn't runnng, set the timer.

    if (digitalRead(minUp) == LOW) {
      firstLoop = true;
      minutesCount++;
      if (minutesCount > 59) minutesCount = 0;
      updateDisplay();
      delay(300);
    }

    if (digitalRead(minDown) == LOW) {
      firstLoop = true;
      minutesCount--;
      if (minutesCount < 0) minutesCount = 59;
      updateDisplay();
      delay(300);
    }
  
    if (digitalRead(secUp) == LOW) {
      firstLoop = true;
      secondsCount++ ;
      if (secondsCount > 59) secondsCount = 0;
      updateDisplay();
      delay(300);
    }

    if (digitalRead(secDown) == LOW) {
      firstLoop = true;
      secondsCount--;
      if (secondsCount < 0) secondsCount = 59;
      updateDisplay();
      delay(300);
    }

    
  }

  // === If timer is running, buttons set second tick, minute beep and backlight on or off ====


  if (Running) { 


     if ((digitalRead(secUp) == LOW) && (millis() - secUpTimer) > 1000) {      // Seconds Up button: Light on/off
      digitalWrite(LED, !digitalRead(LED));
      secUpTimer = millis();
     }


     if ((digitalRead(secDown) == LOW) && (millis() - secDownTimer) > 400) {
        secondsTickActive = !secondsTickActive;
        EEPROM.put(2, secondsTickActive); 
        updateDisplay();
        secDownTimer = millis();
     }
     
     
     if ((digitalRead(minUp) == LOW) && (millis() - minUpTimer) > 150) {
        
        contrast+= increment;
        if ((contrast > 65) || (contrast < 45)) {
          increment = -increment;
        }
        lcd.setContrast(contrast);
        updateDisplay();
        EEPROM.put(10, contrast);
        minUpTimer = millis();
     }

     if ((digitalRead(minDown) == LOW) && (millis() - minDownTimer) > 400) {
        minutesTickActive = !minutesTickActive;
        EEPROM.put(3, minutesTickActive); 
        updateDisplay();
        minDownTimer = millis();
     }
     
  }

  //==================== End of button-press handlers ========================================


    

  if (Running) {
    
    
     offTimer = millis();                                // Keep resetting the auto-off timer
     
    if (micros() - (clockTick + adjustMicros) >= oneSecond) {
     
        secondsCount--;
        
        if (secondsTickActive || (minutesTickActive && (secondsCount == 0))) {
          analogWrite(buzz, 140);
        }
        else
         analogWrite(buzz, 0);  
          
        if ((secondsCount != 0) || !minutesTickActive) {
           delay(secondsTickDelay);
         }
        else {
           delay(minutesTickDelay);
        }
         
         digitalWrite(buzz, LOW);
      
    
        if (secondsCount < 0) {                          // If the seconds count has reached 0, reset it
           secondsCount = 59;                            // to 59 and decrement the minute count.

           minutesCount--;
           
     
           if (minutesCount < 0) {                      // Stop the minutes count at 0
             minutesCount = 0;
          }
         }
        
        updateDisplay();

  
        if ((minutesCount == 0)  && (secondsCount == 0)) {             // Time's up!
           Running = false;
           digitalWrite(LED, HIGH);                                    // Backlight off.
           beep(2000, 800);                                            // Sound long beep

           updateDisplay(); 
           while(digitalRead(start_pause) == LOW);  
           delay(500);   
        }

       
        clockTick = micros();        // Reset clock timer for next second.
     }
  } 

  if ((millis() - offTimer) >  autoOffTime) {
     PowerDown();
  }  


}

void updateDisplay() {


  lcd.setFont();                                // Set standard font.
  lcd.clearDisplay();

  lcd.setCursor(0, 0);                          // Display battery volts
                                                
  lcd.print(volts/1000, 2);
  lcd.print("v");

  if (minutesTickActive) {
    lcd.setCursor(12, 39);
    
    lcd.write(174);
    lcd.setCursor(20, 39);
    lcd.write(14);
  }
   

  if (secondsTickActive) {
    lcd.setCursor(62, 39);
    lcd.write(14);
    lcd.setCursor(70, 39);
    lcd.write(175);
  }

  if (Running) {
    lcd.setCursor(50, 0);
    if (InitMins < 10) {
     lcd.print("0");
    }
    lcd.print(InitMins);
    lcd.print(":");

    if (InitSecs < 10) {
      lcd.print("0");
    }
    lcd.print(InitSecs);
    lcd.display();
  }
  
  lcd.setCursor(15, 24);
  lcd.setFont(&FreeSansBold12pt7b);              // Use larger font

  if (minutesCount < 10) {
    lcd.print("0");
  }
  lcd.print(minutesCount);
  lcd.print(":");

  if (secondsCount < 10) {
    lcd.print("0");
  }
  lcd.print(secondsCount);
  lcd.display();

  offTimer = millis();

}


void wakeUp() {           // Interrupt from Minutes Up button - jump to setup()
  offTimer = millis();
}

void PowerDown() {        // Poweer down due to low battery or Auto Off timer expired.
                          // Use Minutes Up button to restart.
  beep(10, 800);

  digitalWrite(LCD, LOW);
  digitalWrite(LED, HIGH);                   
  digitalWrite(buzz, LOW);
  attachInterrupt(digitalPinToInterrupt(minUp), wakeUp, FALLING);
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 
  detachInterrupt(1);
}


void beep(int d, int t) {              // Sound tone, d = duration, t = tone.

  for(int i = 0; i < d; i++) {
     digitalWrite(buzz, HIGH);
     delayMicroseconds(t);
    
     digitalWrite(buzz, LOW);
     delayMicroseconds(t);
  }
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1113000L / result; // Back-calculate AVcc in mV
  return result;
}

 

Back to Index

 


This site and its contents are © Copyright 2015 - All Rights Reserved.