Project tutorial

More than an Hour Timer © LGPL

A one button interface for a "Hour" countdown timer that uses a 3 digit binary number to select times other than an hour.

  • 443 views
  • 0 comments
  • 1 respect

Components and supplies

Apps and online services

About this project

Introduction

I was in need of an Hour timer that could be started and stopped with a press of a button. When I got to building and coding it I thought that I could do more than a simple hour timer. I cobbled together a timer that has a range from 1 minute to an hour and a half and still only uses 1 button to control it.

I didn’t want to use an Arduino for the final product but still prototyped it on the Arduino. I used an ATTINY85 for the final product.

Parts Required

  • 3 x 220 OHM resistors
  • 1 x 10k Resistor
  • 1 x NPN Transistor
  • 3 x LED's
  • A Piezo Buzzer
  • A Button
  • Some Jumper Wires
  • A breadboard and
  • An Arduino

The Code

I've used a lot defines to make the program more readable. The “defines’ don't use any memory other than the actual value itself.

I have also used preprocessor directives so I can use the same sketch on an Arduino and the ATTiny85.

// LOTS OF DEFINES TO KEEP THINGS EASY TO READ AND SAVE SPACE 
#define DEVICE ARDUINO      // ARDUINO or ATTINY85 
#define COMMON_PIN_ANODE 1  // 1 = Common Anode; 0 = Common Cathode 
#define ATTINY85 1 
#define ARDUINO 0 
#define SWITCH_OFF_LENGTH 3 
#if DEVICE 
//ATTINY85 INCLUDES & DEFINES 
#include <avr/sleep.h> 
#include <avr/interrupt.h> 
 #define LED1   0 
 #define LED2   1 
 #define LED3   2 
 #define BUTTON 3 
 #define BUZZER 4 
#else 
 #define LED1   11 
 #define LED2   12 
 #define LED3   13 
 #define BUTTON 2 
 #define BUZZER 6 
#endif 
// PROGRAM STATES 
#define CHOOSETIMER 1 
#define COUNTDOWN   2 
#define BUZZ        3 
// BUTTON STATES 
#define NO_PRESS 0 
#define SHORT_PRESS 1 
#define LONG_PRESS 2 
#define OFF_PRESS 3 

The times array holds the seconds that can be selected for the countdown. Feel free to change these to the times you would use most often. Mine are set to 1 min, 5 min, 15 min, 30 min, 45min, 1 hour and 1 hour 30 mins.

 // VARIABLES WOOP! 
unsigned long timer = 0; 
byte MODE = 2; 
byte cdMenu = 1; 
int countDown = 45; 
int times[] = {0,   60,   300,    900,    1800,   2700,   3600,   5400}; 

The function Sleep will:

  • Beep 3 times when switching off,
  • Twice when switching on.
  • It will put the ATTINY85 into low power mode or
  • Emulate the way the ATTINY will sleep when prototyping on the Arduino.

There are 2 sleep functions, one for the arduino and one for the AtTiny85

#if DEVICE 
void sleep () {                         // with compliments of https://bigdanzblog.wordpress.com/2014/08/10/attiny85-wake-from-sleep-on-pin-state-change-code-example/ 
 delay(500);                           // BUTTON DEBOUNCE (LONG) 
 beep(); delay(200); 
 beep(); delay(200); 
 beep(); 
 countDown = times[cdMenu];            // RESET TIMER READY FOR WHEN YOU COME BACK FROM SLEEPING 
 MODE = COUNTDOWN;                     // GETTING READY FOR WHEN YOU COME BACK FROM SLEEPING 
 GIMSK |= _BV(PCIE);                   // Enable Pin Change Interrupts 
 PCMSK |= _BV(BUTTON);                 // Use PB3 as interrupt pin 
 ADCSRA &= ~_BV(ADEN);                 // ADC off 
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // replaces above statement 
 sleep_enable();                       // Sets the Sleep Enable bit in the MCUCR Register (SE BIT) 
 sei();                                // Enable interrupts 
 sleep_cpu();                          // sleep 
 cli();                                // Disable interrupts 
 PCMSK &= ~_BV(BUTTON);                // Turn off PB3 as interrupt pin 
 sleep_disable();                      // Clear SE bit 
 ADCSRA |= _BV(ADEN);                  // ADC on 
 sei();                                // Enable interrupts 
 beep(); delay(200); 
 beep(); 
} 
ISR(PCINT0_vect) {} 
#else 
void sleep () { 
 delay(500); // BUTTON DEBOUNCE (LONG) 
 beep(); delay(200); 
 beep(); delay(200); 
 beep(); 
 countDown = times[cdMenu];  // RESET TIMER READY FOR WHEN YOU COME BACK FROM SLEEPING 
 MODE = COUNTDOWN;           // GETTING READY FOR WHEN YOU COME BACK FROM SLEEPING 
 // EMULATES THE SLEEPING WHEN USING THE ARDUIN (IT SAVES NO POWER) 
 while (digitalRead(BUTTON)); 
 beep(); delay(200); 
 beep(); 
} 
#endif 

The beep function beeps.

// SELF EXPLANATORY 
void beep() { 
 digitalWrite(BUZZER, HIGH); 
 delay(30); 
 digitalWrite(BUZZER, LOW); 
} 

The “clearDisplay”, “displayTime” and “displayCountDownTime” functions control the 3 LED's on or off states.

I've also included preprocessor directives in these functions to easily choose between whether I’m using common anode or common cathode for the LED's.

// SETS ALL LED's PINS TO LOW OR HIGH DEPENDING ON THE LED's COMMON PIN CONNECTION 
void clearDisplay() { 
 byte leds = 0; 
#if COMMON_PIN_ANODE 
 digitalWrite(LED1, !bitRead(leds, 0)); 
 digitalWrite(LED2, !bitRead(leds, 1)); 
 digitalWrite(LED3, !bitRead(leds, 2)); 
#else 
 digitalWrite(LED1, bitRead(leds, 0)); 
 digitalWrite(LED2, bitRead(leds, 1)); 
 digitalWrite(LED3, bitRead(leds, 2)); 
#endif 
} 
// SHOW THE TIME SELECTED 
void displayTime() { 
 int leds = 0; 
 // GET THE INDEX IN THE ARRAY FROM THE countDown VALUE 
 for (int t = 0; t < 8; t++) { 
   if (countDown >= times[t]) { 
     leds = t; 
   } 
 } 
#if COMMON_PIN_ANODE 
 digitalWrite(LED1, !bitRead(leds, 0)); 
 digitalWrite(LED2, !bitRead(leds, 1)); 
 digitalWrite(LED3, !bitRead(leds, 2)); 
#else 
 digitalWrite(LED1, bitRead(leds, 0)); 
 digitalWrite(LED2, bitRead(leds, 1)); 
 digitalWrite(LED3, bitRead(leds, 2)); 
#endif 
} 
// SHOW THE CURRENT COUNT DOWN (ROUNDED UP) 
void displayCountDownTime() { 
 int leds = 0; 
 for (int t = 0; t < 8; t++) { 
   if (countDown >= times[t]) { 
     leds = t + 1; 
   } 
 } 
#if COMMON_PIN_ANODE 
 digitalWrite(LED1, !bitRead(leds, 0)); 
 digitalWrite(LED2, !bitRead(leds, 1)); 
 digitalWrite(LED3, !bitRead(leds, 2)); 
#else 
 digitalWrite(LED1, bitRead(leds, 0)); 
 digitalWrite(LED2, bitRead(leds, 1)); 
 digitalWrite(LED3, bitRead(leds, 2)); 
#endif 
} 

The “buttonCheck” function is called on every loop of the Loop function:

  • Checks if the button is pressed,
  • how long it is pressed and
  • returns whether it was pressed quickly, long or extremely long.

This function is the interface between the code and the real world. It plays a huge function in the program and it took a long time to make it feel just right.

 // FINDS OUT THE BUTTON STATES 
byte buttonCheck() { 
 clearDisplay(); 
 int butLength = NO_PRESS; 
 int mybut = digitalRead(BUTTON); 
 if (!mybut) { 
   // START TIMING 
   unsigned long timerButton = millis() / 10; 
   beep(); 
   // LOOP HERE WHILE THE BUTTON IS DOWN CONTINUING TO TIME 
   while (!mybut) { 
     mybut = digitalRead(BUTTON); 
     // BLINK RANDOMLY WHEN THE TIMER VALUE IS LONGER THAN THE SET VALUE FOR THE SWITCH OFF 
     if ((millis() / 10 - timerButton) > (SWITCH_OFF_LENGTH * 100)) { 
       digitalWrite(LED1, bitRead(millis() / 100, 0)); 
       digitalWrite(LED2, bitRead(millis() / 100, 1)); 
       digitalWrite(LED3, bitRead(millis() / 100, 2)); 
     } 
   } 
   clearDisplay(); 
   // DECIDE WHAT TO RETURN DEPENDING ON HOW LONG THE BUTTON WAS HELD IN 
   if ((millis() / 10 - timerButton) > 75) { 
     butLength = LONG_PRESS; 
   } else { 
     butLength = SHORT_PRESS; 
   } 
   if ((millis() / 10 - timerButton) > (SWITCH_OFF_LENGTH * 100)) { 
     butLength = OFF_PRESS; 
   } 
   delay(50);                    // SORT OF DEBOUNCING THE BUTTON PRESS 
 } 
 return butLength; 
} 

I set up the pins in the SETUP function

void setup() { 
 pinMode(LED1, OUTPUT); 
 pinMode(LED2, OUTPUT); 
 pinMode(LED3, OUTPUT); 
 pinMode(BUZZER, OUTPUT); 
 pinMode(BUTTON, INPUT_PULLUP); 
 timer = (millis() / 100); 
 clearDisplay(); 
} 

In the Loop function

I check the button state then decide what mode the program should go into. Either choosing the timer value, counting down or switching off. It’s a bit all over the place but it works. When the program has counted down to 0 it buzzes.

void loop() { 
 int button = buttonCheck(); 
 switch (button) { 
   case LONG_PRESS: 
     // CYCLE THROUGH THE MODES 
     MODE++; 
     if (MODE >= BUZZ) { 
       MODE = CHOOSETIMER; 
     } 
     break; 
   case OFF_PRESS: 
     sleep(); 
     button = NO_PRESS;                 // FORCE THE BUTTON TO NO_PRESS (I CAN'T REMEMBER WHY THOUGH) 
     break; 
 } 
 switch (MODE) { 
   case CHOOSETIMER: 
     countDown = times[cdMenu];          // SET THE COUNT DOWN TIME FROM THE ARRAY OF TIMES 
     if (button == SHORT_PRESS) {        // CYCLE TIMES 
       cdMenu++; 
       if (cdMenu == 8) { 
         cdMenu = 1; // skip 0 value in the array 
       } 
       countDown = times[cdMenu]; 
     } 
     displayTime(); 
     break; 
   case COUNTDOWN: 
     //EVERY SECCOND FLASH YOUR REMAINING TIME (ROUNDING UP) AND COUNT DOWN 
     if (((millis() / 100) - timer) >= 10) { 
       timer = (millis() / 100); 
       displayCountDownTime(); 
       delay(50); 
       clearDisplay(); 
       countDown--; 
     } 
     // WHEN THE COUNTDOWN IS UP BUZZ!!!!! 
     if (countDown <= 0) { 
       MODE = BUZZ; 
     } 
     break; 
   case BUZZ: 
     // BUZZ! 
     digitalWrite(BUZZER, HIGH); 
     // FLASH! 
     digitalWrite(LED1, bitRead(millis() / 100, 0)); 
     digitalWrite(LED2, bitRead(millis() / 100, 1)); 
     digitalWrite(LED3, bitRead(millis() / 100, 2)); 
     // SWITCH OFF WHEN THE BUTON IS PRESSED 
     if (button == SHORT_PRESS) { 
       digitalWrite(BUZZER, LOW); 
       clearDisplay(); 
       sleep(); 
       button = NO_PRESS;                 // FORCE THE BUTTON TO NO_PRESS (I CAN'T REMEMBER WHY THOUGH) 
     } 
     break; 
 } 
} 
 

Building the Circuit

  • Connect the 5v to the Positive rail and the ground to the ground rail
  • Grab the 3 LED's and connect the anodes to a positive rail and the cathodes to 3 separate terminal strips
  • Place resistors between the led's and another 3 terminal strips
  • Connect the LED RESISTOR pair to pins 2, 3 and 4
  • Place the transistor onto the breadboard
  • Place the buzzer between the collector and the positive rail using a jumper wire
  • Connect the resistor to the base of the transistor and another terminal strip then to pin 5 of the arduino
  • Connect the emitter of the transistor to the ground rail

I'm using a transistor on the buzzer because I have no idea what my buzzer's power consumption is and I don't want to draw too much power from one pin of the arduino.

  • Place the button between 2 terminal strips,
  • One end of the button goes to pin 6 of the Arduino and
  • The other end goes to the ground rail.

Usage

When I first power it up the led flashes, this indicates that the timer it is counting down.

  • If I hold the button in for more than 75 milliseconds, at least one of the LED's will be on. This means that I can choose the time I need.
  • If I give the button a short press he LED's change. The LED's are a 3 digit binary number (1 through to 7 and they correspond to the times array).
  • I can select the time I want to countdown by giving the button a long press. This will make the timer start counting down.
  • Once the countdown is complete the buzzer will ring, until you press the button. It will then switch off.
  • If I tap the button when in "sleep mode" it starts counting down from the start of last selected time.
  • If I want to turn it off at any time, I hold the button down for an extremely long time and once the led's flash and I release the button, it will then turn off.

Making it Easier to Read

Unless you can read binary the colors make using the timer 100 times easier.

When you use a red, green and blue led covered by a diffuser, you suddenly have 7 colours corresponding to the times in the times array.

Here I also changed the circuit to a common cathode and changed the code accordingly.

Custom parts and enclosures

Easy Timer Arduino Cathode
easy_timer_arduino_cathode_eKNuY8B6zu.fzz
Easy Timer Arduino
easy_timer_arduino_qSwCt8GJwi.fzz
Easy Timer AtTiny85
easy_timer_attiny_rBdUwVca6Y.fzz

Schematics

Arduino Anode Schematic
Easy timer arduino schem bjxouy5mp8
ATTINY Anode Schematic
Easy timer attiny schem cyxphiaeaa
Arduino Anode Breadboard
Easy timer arduino bb p8avovvuzu

Code

One button timerArduino
Arduino Code
/*
  Overly Complicated one Hour Timer
  A countdown timer controlled by one button that has a range of 0h01 to 1h30 in 7 intervals
  By Ian Cumming https://www.hackster.io/ianabcumming/


  H/W: 3 x LED's, 4 x 220OHM resistors, Piezo Buzzer, Arduino UNO, NPN Transistor, Button.
*/

// LOTS OF DEFINES TO KEEP THINGS EASY TO READ AND SAVE SPACE
#define DEVICE ARDUINO      // ARDUINO or ATTINY85
#define COMMON_PIN_ANODE 1  // 1 = Common Anode; 0 = Common Cathode

#define ATTINY85 1
#define ARDUINO 0

#define SWITCH_OFF_LENGTH 3

#if DEVICE
//ATTINY85 INCLUDES & DEFINES
#include <avr/sleep.h>
#include <avr/interrupt.h>
  #define LED1   0
  #define LED2   1
  #define LED3   2
  #define BUTTON 3
  #define BUZZER 4
#else
  #define LED1   11
  #define LED2   12
  #define LED3   13
  #define BUTTON 2
  #define BUZZER 6
#endif

// PROGRAM STATES
#define CHOOSETIMER 1
#define COUNTDOWN   2
#define BUZZ        3

// BUTTON STATES
#define NO_PRESS 0
#define SHORT_PRESS 1
#define LONG_PRESS 2
#define OFF_PRESS 3

// VARIABLES WOOP!
unsigned long timer = 0;
byte MODE = 2;
byte cdMenu = 1;
int countDown = 45;
int times[] = {0,   60,   300,    900,    1800,   2700,   3600,   5400};

#if DEVICE
void sleep () {                         // with compliments of https://bigdanzblog.wordpress.com/2014/08/10/attiny85-wake-from-sleep-on-pin-state-change-code-example/
  delay(500);                           // BUTTON DEBOUNCE (LONG)

  beep(); delay(200);
  beep(); delay(200);
  beep();

  countDown = times[cdMenu];            // RESET TIMER READY FOR WHEN YOU COME BACK FROM SLEEPING
  MODE = COUNTDOWN;                     // GETTING READY FOR WHEN YOU COME BACK FROM SLEEPING

  GIMSK |= _BV(PCIE);                   // Enable Pin Change Interrupts
  PCMSK |= _BV(BUTTON);                 // Use PB3 as interrupt pin
  ADCSRA &= ~_BV(ADEN);                 // ADC off
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // replaces above statement

  sleep_enable();                       // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
  sei();                                // Enable interrupts
  sleep_cpu();                          // sleep

  cli();                                // Disable interrupts
  PCMSK &= ~_BV(BUTTON);                // Turn off PB3 as interrupt pin
  sleep_disable();                      // Clear SE bit
  ADCSRA |= _BV(ADEN);                  // ADC on

  sei();                                // Enable interrupts


  beep(); delay(200);
  beep();
}
ISR(PCINT0_vect) {}
#else
void sleep () {

  delay(500); // BUTTON DEBOUNCE (LONG)

  beep(); delay(200);
  beep(); delay(200);
  beep();

  countDown = times[cdMenu];  // RESET TIMER READY FOR WHEN YOU COME BACK FROM SLEEPING
  MODE = COUNTDOWN;           // GETTING READY FOR WHEN YOU COME BACK FROM SLEEPING

  // EMULATES THE SLEEPING WHEN USING THE ARDUIN (IT SAVES NO POWER)
  while (digitalRead(BUTTON));

  beep(); delay(200);
  beep();
}
#endif

// SELF EXPLANITORY
void beep() {
  digitalWrite(BUZZER, HIGH);
  delay(30);
  digitalWrite(BUZZER, LOW);
}

// SETS ALL LED's PINS TO LOW OR HIGH DEPENDING ON THE LED's COMMON PIN CONNECTION
void clearDisplay() {
  byte leds = 0;
#if COMMON_PIN_ANODE
  digitalWrite(LED1, !bitRead(leds, 0));
  digitalWrite(LED2, !bitRead(leds, 1));
  digitalWrite(LED3, !bitRead(leds, 2));
#else
  digitalWrite(LED1, bitRead(leds, 0));
  digitalWrite(LED2, bitRead(leds, 1));
  digitalWrite(LED3, bitRead(leds, 2));
#endif
}
// SHOW THE TIME SELECTED
void displayTime() {
  int leds = 0;
  // GET THE INDEX IN THE ARRAY FROM THE countDown VALUE
  for (int t = 0; t < 8; t++) {
    if (countDown >= times[t]) {
      leds = t;
    }
  }
#if COMMON_PIN_ANODE
  digitalWrite(LED1, !bitRead(leds, 0));
  digitalWrite(LED2, !bitRead(leds, 1));
  digitalWrite(LED3, !bitRead(leds, 2));
#else
  digitalWrite(LED1, bitRead(leds, 0));
  digitalWrite(LED2, bitRead(leds, 1));
  digitalWrite(LED3, bitRead(leds, 2));
#endif
}

// SHOW THE CURRENT COUNT DOWN (ROUNDED UP)
void displayCountDownTime() {
  int leds = 0;
  for (int t = 0; t < 8; t++) {
    if (countDown >= times[t]) {
      leds = t + 1;
    }
  }
#if COMMON_PIN_ANODE
  digitalWrite(LED1, !bitRead(leds, 0));
  digitalWrite(LED2, !bitRead(leds, 1));
  digitalWrite(LED3, !bitRead(leds, 2));
#else
  digitalWrite(LED1, bitRead(leds, 0));
  digitalWrite(LED2, bitRead(leds, 1));
  digitalWrite(LED3, bitRead(leds, 2));
#endif
}

// FINDS OUT THE BUTTON STATES
byte buttonCheck() {
  clearDisplay();

  int butLength = NO_PRESS;
  int mybut = digitalRead(BUTTON);
  if (!mybut) {

    // START TIMING
    unsigned long timerButton = millis() / 10;
    beep();

    // LOOP HERE WHILE THE BUTTON IS DOWN CONTINUING TO TIME
    while (!mybut) {
      mybut = digitalRead(BUTTON);

      // BLINK RANDOMLY WHEN THE TIMER VALUE IS LONGER THAN THE SET VALUE FOR THE SWITCH OFF
      if ((millis() / 10 - timerButton) > (SWITCH_OFF_LENGTH * 100)) {
        digitalWrite(LED1, bitRead(millis() / 100, 0));
        digitalWrite(LED2, bitRead(millis() / 100, 1));
        digitalWrite(LED3, bitRead(millis() / 100, 2));
      }
    }

    clearDisplay();

    // DECIDE WHAT TO RETURN DEPENDING ON HOW LONG THE BUTTON WAS HELD IN
    if ((millis() / 10 - timerButton) > 75) {
      butLength = LONG_PRESS;
    } else {
      butLength = SHORT_PRESS;
    }
    if ((millis() / 10 - timerButton) > (SWITCH_OFF_LENGTH * 100)) {
      butLength = OFF_PRESS;
    }
    delay(50);                    // SORT OF DEBOUNCING THE BUTTON PRESS
  }

  return butLength;
}


void setup() {
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
  timer = (millis() / 100);
  clearDisplay();
}

void loop() {
  int button = buttonCheck();

  switch (button) {
    case LONG_PRESS:
      // CYCLE THROUGH THE MODES
      MODE++;
      if (MODE >= BUZZ) {
        MODE = CHOOSETIMER;
      }
      break;
    case OFF_PRESS:
      sleep();
      button = NO_PRESS;                 // FORCE THE BUTTON TO NO_PRESS (I CAN'T REMEMBER WHY THOUGH)
      break;
  }

  switch (MODE) {
    case CHOOSETIMER:
      countDown = times[cdMenu];          // SET THE COUNT DOWN TIME FROM THE ARRAY OF TIMES
      if (button == SHORT_PRESS) {        // CYCLE TIMES
        cdMenu++;
        if (cdMenu == 8) {
          cdMenu = 1; // skip 0 value in the array
        }
        countDown = times[cdMenu];
      }
      displayTime();
      break;
    case COUNTDOWN:
      //EVERY SECCOND FLASH YOUR REMAINING TIME (ROUNDING UP) AND COUNT DOWN
      if (((millis() / 100) - timer) >= 10) {
        timer = (millis() / 100);
        displayCountDownTime();
        delay(50);
        clearDisplay();
        countDown--;
      }

      // WHEN THE COUNTDOWN IS UP BUZZ!!!!!
      if (countDown <= 0) {
        MODE = BUZZ;
      }

      break;
    case BUZZ:
      // BUZZ!
      digitalWrite(BUZZER, HIGH);

      // FLASH!
      digitalWrite(LED1, bitRead(millis() / 100, 0));
      digitalWrite(LED2, bitRead(millis() / 100, 1));
      digitalWrite(LED3, bitRead(millis() / 100, 2));

      // SWITCH OFF WHEN THE BUTON IS PRESSED
      if (button == SHORT_PRESS) {
        digitalWrite(BUZZER, LOW);
        clearDisplay();
        sleep();
        button = NO_PRESS;                 // FORCE THE BUTTON TO NO_PRESS (I CAN'T REMEMBER WHY THOUGH)
      }
      break;
  }
}

Comments

Similar projects you might like

Magic Lamp

by Nekhil ravi

  • 992 views
  • 3 comments
  • 12 respects

PuzzleBox

Project tutorial by Arduino

  • 205 views
  • 0 comments
  • 1 respect

Arduino MKR GSM 1400 and DTMF

by Arduino_Genuino

  • 3,910 views
  • 0 comments
  • 9 respects

Love You Pillow

Project tutorial by Arduino

  • 2,447 views
  • 0 comments
  • 5 respects

Infrared Replicator

Project tutorial by Gustavo Gonnet

  • 3,836 views
  • 6 comments
  • 26 respects

Arduino Yun Controller

Project showcase by TATCO Inc

  • 134 views
  • 0 comments
  • 3 respects
Add projectSign up / Login