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.

Background: 

It's hard to synchronize a new-year countdown when you have hundreds of people with individual wristwatches, so what usually happens is that many of them count in groups and while a group is hugging after the new year and watching at the skies waiting for the fireworks, others are yelling "nine!... eight!... seven!... six!..."


Solution:

Inspired in Sparkfun's GPS clock, I decided to help all these folks to count in unison by building my own version of a big clock: hopefully simpler and designed to countdown for the new year.

1) The concept
So we don't need a 24h clock. A partial clock will suffice, say... something that tells the time from 11:00p to 11:58p and turns into a countdown timer for the last minute (11 turns off and the countdown starts from second 59 to 00).
The idea of starting at 11:00 is to let everyone know that the clock is actually there.

This design at saved me 6 segments and some space too.

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



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 command the segments. 
An Arduino controls the shift registers, cycling about 150 times per second.

4) Time accuracy:
I want 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.

Simplified version /less fancy: I added an LCD shield. This allows to manually adjust the clock which means I can get rid of both the RTC clock and the Ethernet Shield.

5) The Design:
I couldn't think of a simpler 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.









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.

Good, let's add some LEDs to this then:


8 segments in place. Each segment consists of 3 LEDs

Cool. Time to solder some wire, add current limiting resistors, some transistors and more resistors for their base. In other words, let's follow the original design!



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

How about testing the code from the Arduino?




First digit is complete and code sends number 3.

Voilá! We have the first digit.

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();  // Draws 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!

2 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