Saturday, November 5, 2011

Arduino based Big Countdown Timer for New Year

The finished project at 11:39, before turning itself into a countdown timer. This countdown timer has been in production for three years now, and getting ready for 2015.



October, 2011.
Inspired in Sparkfun's GPS clock, I decided to build my own version of a big countdown timer for New Year.

1) The concept
Dual function: A clock that starts at 11:00PM and simply shows the time for the entire hour. The idea behind it is to drag people's attention and let them know where the clock is.

I didn't  need a 24h clock. Something that tells the time from 11:00p to 11:58p and turns into a countdown timer for the last minute (the hour digits turns off and the countdown starts from second 59 to 00).

This approach saved me 6 segments and some space as well.

2) The clock itself:
Go big, readable. I used super-bright LEDs (not ultra-bright) and some transistors. I like Ben Krasnow's way of dealing with transistors.

This is what the first idea looks like:



3) Controlling the clock:
I used an Arduino and shift registers to control the segments, you can also use two Arduinos via I2C.

Two shift registers (74HC595) command the segments. 
An Arduino controls the shift registers, cycling about 150 times per second.

4) Time accuracy:
I wanted this thing to be accurate. The Atmega chip keeps track of time but they are not very accurate, reason why I added a RealTime Clock Module.
Now, in order to have the RTC set at the right time, I added NTP Atomic Clock Synchronization capabilities through an Ethernet Shield.

After the first year into production, I decided to simplify the design by adding LCD shield. This allows to manually adjust the clock which means I can remove both the RTC clock and the Ethernet Shield.

In order to keep accuracy I use an NTP app in my iPhone and manually adjust the Arduino until it's synchronized by using the buttons available on the LCD shield. This works very well and it keeps the implementation simple.

5) The Design:

Before the shift registers, I used a two-arduino approach with served me well while shift registers were not available at the local store.

One GREAT source to  understand how the shift registers work is here at BLDR.ORG:


Check out bldr.org
Wiring of the 74HC595 I used. Check out bldr.org!


Back to the design board:

- I wanted to balance the amount of LEDs with functionality, and I ended up with 3 LEDs per segment.
- I could have gone with larger digits but I found that the size I went with was good enough.
- I wanted this to be both inexpensive and battery operated so I chose super-bright leds instead of ultra-bright.








6) The building:

This design calls for portable and lightweight, so let's use these convenient cardboard box lid.









I had these boxes in my storage room. I borrowed 3 lids.



Design of the digits. I drew them proportional to a small 7 segment display, under the assumption that it will increase readability.

Adding the LEDs to each segment:


8 segments in place. Each segment consists of 3 LEDs

Time to solder some wire, add current limiting resistors, some transistors and more resistors for their base just like the original design in paper.



Wiring at the back. The deadbug chip at the center is a resistor array.


First test:


First digit is complete. number 3 shows up.


And then another instance of the same digit plus a third that only does number 11, to get the project complete.

Front of the completed project, the three digits can be easily separated and piled up for easy transportation.


Back of the unit

Back of the unit


Portable mode!

Test scenario: balcony. View from ground, several floors below:

Photo Enhanced ASA800 (Canon S3IS)

Normal Photo (ASA 100)

7) Power:

When all of the LEDs are on, consumption of the whole circuit is reasonably low.
This allows me to power the entire system with a 2000[mAh] 11.1[V] Lithium Polymer battery, making the clock 100% autonomous for the whole hour it is used.


8) Code for Arduino 1.0.3

/* New Year Giant Timer 
By RC
Rel 2011_10_30  First libraries for NTP and RTC
Rel 2011_10_31  Giant segment logic
Rel 2011_11_06  First untested final version
Rel 2011_12_22  First fully functional release.
Rel 2012_11_28  Shift registers version, no I2C for second arduino.
*/

#include <Time.h>  
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Constants Definition

const int SEGMENT_A = 0;  // Seconds digit
const int SEGMENT_B = 1;
const int SEGMENT_C = 2;
const int SEGMENT_D = 3;
const int SEGMENT_E = 4;
const int SEGMENT_F = 5;
const int SEGMENT_G = 6;

const int SEGMENT_H = 8;  // Tens digit
const int SEGMENT_I = 15;
const int SEGMENT_J = 14;
const int SEGMENT_K = 13;
const int SEGMENT_L = 12;
const int SEGMENT_M = 11;
const int SEGMENT_N = 10;

const int SEGMENT_P = 7;    // eleven segments
const int SEGMENT_COL = 9;    // Colon

/***********************************************************************************************************/

// SHIFT REGISTERS SECTION
int SER_Pin = 13;   //pin 14 on the 75HC595
int RCLK_Pin = 11;  //pin 12 on the 75HC595
int SRCLK_Pin = 12; //pin 11 on the 75HC595

//How many of the shift registers - change this
#define number_of_74hc595s 2 

//do not touch
#define numOfRegisterPins number_of_74hc595s * 8

boolean registers[numOfRegisterPins];

// Global variables
time_t t;
int previous_second = 0;  // Variable for detecting changes in seconds.
long elapsed_millis = 0;
int digit_segments[7];
int adjustment_threshold = 1; // Variable to set the increase and decrease rate of the clock
long loop_frequency = 0;  // Stores the frecuency of the loop. Only for measuring purposes on refresh rate of the registers
long milestone = 0;  // For frequency counting. Not critical for the clock itself.

void setup()  
{
  lcd.clear();
  lcd.begin(16,2);
  pinMode(SER_Pin, OUTPUT);  // Shift Register pin
  pinMode(RCLK_Pin, OUTPUT); // Shift Register pin
  pinMode(SRCLK_Pin, OUTPUT); // Shift Register pin
  
  Serial.begin(9600);
  
   //reset all register pins
  clearRegisters();
  writeRegisters();
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Shift registers");
  lcd.setCursor(0, 1);
  lcd.print("reset OK");
  setTime(22,59,45,31,12,2012); // Defaults to right before new year
  hourFormat12();
  delay(500);
  lcd.clear();
}

void loop()
{
   // serialClockDisplay();  // prints to serial port  
   bigClockDisplay();  // Displays on the big clock
   writeRegisters();
   lcdClockDisplay();  // Displays the time and other variables on the LCD
   //display_frequency();  Displays on LCD how fast the loop function is running.
   check_keypad();   // Checks for kepresses on the LCD shield
}

void serialClockDisplay()
{
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
}

void lcdClockDisplay()
{
  lcd.setCursor(0, 0);
  lcd.print("Time: ");
  if (hour()<10) lcd.print("0");
  lcd.print(hour());
  lcd.print(":");
  if (minute()<10) lcd.print("0");
  lcd.print(minute());
  lcd.print("'");
  if (second()<10) lcd.print("0");
  lcd.print(second());
}

void printDigits(int digits)
{
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}


// Checks the LCD shield keypad and performs actions
void check_keypad()
{
  lcd.setCursor(0,1);
  int keypressed = analogRead(A0);
  //Serial.println(keypressed);
  if ((keypressed <= 200) && (keypressed >= 100))   // Up button
   {
     delay(100);
     adjustTime(adjustment_threshold); // adjust system time by adding the adjustment value
     delay(50);
   }
   
  else if ((keypressed < 400) && (keypressed > 300))   // Down button
   {
     delay(100);
     adjustTime(-adjustment_threshold); // adjust system time by adding the adjustment value
     delay(50);
   }
   
   else if (keypressed < 50)   // Right button
   {
     delay(100);
     lcd.setCursor(0,1);
     lcd.print("Test");
     big_clock_segments_test(); // adjust system time by adding the adjustment value
     delay(5000);
   }
   
  else if ((keypressed < 800) && (keypressed > 700))   // Select button
   {
     delay(100);
     if (adjustment_threshold == 1)
      {
        lcd.setCursor(0,1);
        lcd.print("Set Minutes");
        delay(300);
        adjustment_threshold=60;
      }
     
     else if (adjustment_threshold == 60)
      {
        lcd.setCursor(0,1);
        lcd.print("Set Hours  ");
        delay(300);
        adjustment_threshold=3600;
      }
      
      else if (adjustment_threshold == 3600)
      {
        lcd.setCursor(0,1);
        lcd.print("Set Seconds");
        delay(300);
        adjustment_threshold=1;
      }
     
   }
}


long display_frequency()
{
  if ((millis() - milestone) >= 1000)
  {
    lcd.setCursor(0,1);
    lcd.print(loop_frequency);
    loop_frequency = 0;
    milestone = millis();
  }
  else 
    loop_frequency++;
}


void bigClockDisplay()
{
  t = now();  
  if ((hour(t) == 23 ) && (minute(t) == 59)) // last minute is a countdown second by second
    {
      draw_eleven(LOW);
      draw_countdown();
    }
  if ((hour(t) == 23 ) && (minute(t) <=58))  // from 11:00 to 11:58 acts as a regular digital clock
    {
      draw_eleven(HIGH);
      draw_minutes();
    }
  //if ((hour(t) == 23 ) && (minute(t) == 00)) 
  if ((hour(t) == 00 ) && (minute(t) == 00)) // When countdown finishes at 12:00 it turns off so everyone can look at the fireworks
  {
    draw_fireworks();
  }
  draw_colon();
}

/***********************************************************************************************************/

void draw_eleven(int eleven_status)
{
  setsegment(SEGMENT_P,eleven_status);
}

/***********************************************************************************************************/
void setsegment(int segment_code,int segment_status)
{
    setRegisterPin(segment_code, segment_status);
}
/***********************************************************************************************************/
void draw_colon()  //Blinks the colon separating hours from minutes
{
  //if ((second() % 2)== 0) // Will flash only for odd seconds
  lcd.setCursor(13,1);
  int this_second = second();  
  if ((this_second != previous_second))  // means a new second has started and blinks for half a second
    {
      lcd.print(":");
      previous_second = this_second;
      elapsed_millis = millis();
      setsegment(SEGMENT_COL, HIGH);
    }
  else
    {
      if ((millis()-elapsed_millis) >= 500)
       {
          lcd.print(" ");
          setsegment(SEGMENT_COL, LOW);
       }
    }
   
}
/***********************************************************************************************************/
void big_clock_segments_test()
{
  setsegment(SEGMENT_A, HIGH);
  setsegment(SEGMENT_B, HIGH);
  setsegment(SEGMENT_C, HIGH);
  setsegment(SEGMENT_D, HIGH);
  setsegment(SEGMENT_E, HIGH);
  setsegment(SEGMENT_F, HIGH);
  setsegment(SEGMENT_G, HIGH);
  setsegment(SEGMENT_H, HIGH);
  setsegment(SEGMENT_I, HIGH);
  setsegment(SEGMENT_J, HIGH);
  setsegment(SEGMENT_K, HIGH);
  setsegment(SEGMENT_L, HIGH);
  setsegment(SEGMENT_M, HIGH);
  setsegment(SEGMENT_N, HIGH);
  setsegment(SEGMENT_P, HIGH);
  draw_colon();
  writeRegisters();
}
/***********************************************************************************************************/
void draw_minutes()

    int number = minute(t);
    int tens = (int)number / 10;   // Obtains the digit to the left
    int units = number % 10;  // Obtains the digit to the right 
    decimal_to_bars(tens);
    draw_tens_digit(digit_segments);
    decimal_to_bars(units);
    draw_units_digit(digit_segments);
}
/***********************************************************************************************************/
void draw_countdown()

    int number = 60 - second(t);
    lcd.setCursor(14,1);
    if (number<10) lcd.print("0");
    lcd.print(number);
    int tens = (int)number / 10;   // Obtains the digit to the left
    int units = number % 10;  // Obtains the digit to the right 
    decimal_to_bars(tens);
    draw_tens_digit(digit_segments);
    decimal_to_bars(units);
    draw_units_digit(digit_segments);
}
/***********************************************************************************************************/
void draw_units_digit(int private_segment[])
{  
      //Serial.print(units);
      setsegment(SEGMENT_A,private_segment[0]);
      setsegment(SEGMENT_B,private_segment[1]);
      setsegment(SEGMENT_C,private_segment[2]);
      setsegment(SEGMENT_D,private_segment[3]);
      setsegment(SEGMENT_E,private_segment[4]);
      setsegment(SEGMENT_F,private_segment[5]);
      setsegment(SEGMENT_G,private_segment[6]);
}
/***********************************************************************************************************/
void draw_tens_digit(int private_segment[])
{
      setsegment(SEGMENT_H,private_segment[0]);
      setsegment(SEGMENT_I,private_segment[1]);
      setsegment(SEGMENT_J,private_segment[2]);
      setsegment(SEGMENT_K,private_segment[3]);
      setsegment(SEGMENT_L,private_segment[4]);
      setsegment(SEGMENT_M,private_segment[5]);
      setsegment(SEGMENT_N,private_segment[6]);
}

/***********************************************************************************************************/

void draw_fireworks()   //Blinks segments indicating New Year has arrived.
{
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("   Happy");
  lcd.setCursor(0,1);
  lcd.print("  NEW YEAR !!!");
  setsegment(SEGMENT_G,LOW);
  setsegment(SEGMENT_COL,HIGH);
  setsegment(SEGMENT_N,LOW);
  setsegment(SEGMENT_P,LOW);
  setsegment(SEGMENT_A,LOW);
  setsegment(SEGMENT_B,LOW);
  setsegment(SEGMENT_C,LOW);
  setsegment(SEGMENT_D,LOW);
  setsegment(SEGMENT_E,LOW);
  setsegment(SEGMENT_F,LOW);
    
  setsegment(SEGMENT_H,LOW);
  setsegment(SEGMENT_I,LOW);
  setsegment(SEGMENT_J,LOW);
  setsegment(SEGMENT_K,LOW);
  setsegment(SEGMENT_L,LOW);
  setsegment(SEGMENT_M,LOW);
  writeRegisters();
  
  for (int new_year = 1; new_year<30; new_year++)  // blinks the ":00"
  {
    setsegment(SEGMENT_A,HIGH);
    setsegment(SEGMENT_B,HIGH);
    setsegment(SEGMENT_C,HIGH);
    setsegment(SEGMENT_D,HIGH);
    setsegment(SEGMENT_E,HIGH);
    setsegment(SEGMENT_F,HIGH);
     
    setsegment(SEGMENT_H,HIGH);
    setsegment(SEGMENT_I,HIGH);
    setsegment(SEGMENT_J,HIGH);
    setsegment(SEGMENT_K,HIGH);
    setsegment(SEGMENT_L,HIGH);
    setsegment(SEGMENT_M,HIGH);
     writeRegisters();
  delay(400);
    setsegment(SEGMENT_A,LOW);
    setsegment(SEGMENT_B,LOW);
    setsegment(SEGMENT_C,LOW);
    setsegment(SEGMENT_D,LOW);
    setsegment(SEGMENT_E,LOW);
    setsegment(SEGMENT_F,LOW);
    
    setsegment(SEGMENT_H,LOW);
    setsegment(SEGMENT_I,LOW);
    setsegment(SEGMENT_J,LOW);
    setsegment(SEGMENT_K,LOW);
    setsegment(SEGMENT_L,LOW);
    setsegment(SEGMENT_M,LOW);
    writeRegisters();
  delay(400);
  }
  //shuts it all down
  setsegment(SEGMENT_COL,LOW);
  writeRegisters();
  while(1)
  {
    lcd.clear();
    lcd.print("Standby");
    delay(300);
    //byebye
  }
}

/***********************************************************************************************************/

void decimal_to_bars(int private_decimal)
{
  
  switch (private_decimal)
      {
        case 0:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 1;
            digit_segments[2] = 1;
            digit_segments[3] = 1;
            digit_segments[4] = 1;
            digit_segments[5] = 1;
            digit_segments[6] = 0;
            break;
          }
        case 1:
          {
            digit_segments[0] = 0;
            digit_segments[1] = 1;
            digit_segments[2] = 1;
            digit_segments[3] = 0;
            digit_segments[4] = 0;
            digit_segments[5] = 0;
            digit_segments[6] = 0;
            break;
          }
        case 2:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 1;
            digit_segments[2] = 0;
            digit_segments[3] = 1;
            digit_segments[4] = 1;
            digit_segments[5] = 0;
            digit_segments[6] = 1;
            break;
          }
        case 3:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 1;
            digit_segments[2] = 1;
            digit_segments[3] = 1;
            digit_segments[4] = 0;
            digit_segments[5] = 0;
            digit_segments[6] = 1;
            break;
          }
        case 4:
          {
            digit_segments[0] = 0;
            digit_segments[1] = 1;
            digit_segments[2] = 1;
            digit_segments[3] = 0;
            digit_segments[4] = 0;
            digit_segments[5] = 1;
            digit_segments[6] = 1;
            break;
          }
        case 5:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 0;
            digit_segments[2] = 1;
            digit_segments[3] = 1;
            digit_segments[4] = 0;
            digit_segments[5] = 1;
            digit_segments[6] = 1;
            break;
          }
        case 6:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 0;
            digit_segments[2] = 1;
            digit_segments[3] = 1;
            digit_segments[4] = 1;
            digit_segments[5] = 1;
            digit_segments[6] = 1;
            break;
          }     
        case 7:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 1;
            digit_segments[2] = 1;
            digit_segments[3] = 0;
            digit_segments[4] = 0;
            digit_segments[5] = 0;
            digit_segments[6] = 0;
            break;
          }  
        case 8:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 1;
            digit_segments[2] = 1;
            digit_segments[3] = 1;
            digit_segments[4] = 1;
            digit_segments[5] = 1;
            digit_segments[6] = 1;
            break;
          }
        case 9:
          {
            digit_segments[0] = 1;
            digit_segments[1] = 1;
            digit_segments[2] = 1;
            digit_segments[3] = 1;
            digit_segments[4] = 0;
            digit_segments[5] = 1;
            digit_segments[6] = 1;
            break;
          }  
        default:
        {}//nada
      } // End switch
}


//set all register pins to LOW
  void clearRegisters()
  {
  for(int i = numOfRegisterPins - 1; i >=  0; i--)
    {
       registers[i] = LOW;
    }
  }
  
//Set and display registers
//Only call AFTER all values are set how you would like (slow otherwise)
void writeRegisters()
{
  digitalWrite(RCLK_Pin, LOW);
  for(int i = numOfRegisterPins - 1; i >=  0; i--)
    {
      digitalWrite(SRCLK_Pin, LOW);
      int val = registers[i];
      digitalWrite(SER_Pin, val);
      digitalWrite(SRCLK_Pin, HIGH);
    }
  digitalWrite(RCLK_Pin, HIGH);
}

//set an individual pin HIGH or LOW
void setRegisterPin(int index, int value)
  {
    registers[index] = value;
  }




Happy new year!   - ViƱa del Mar, Chile.

4 comments:

  1. Nice project! However, I would have used a TLC5916 LED driver instead of all those transistors

    ReplyDelete
  2. Agree. I just happen to love transistors!

    ReplyDelete
  3. que bien te felicito es un gran proyecto

    ReplyDelete