Welcome to Blogs @ Andrew Qu
Blog Index
All blogs
Search results

Learning Arduino - Making a Clock

Summary

In this exercise, I am going to make a clock, displaying hours and minutes: HH:MM. The clock will also have set time functionality using a switch.

Material List
  • 1 x 3 digits, 7 segments LED display (F5461AH)
  • 4 x 220 resistors
  • 1 x switch
  • Wires

4 Dgits 7 Segments LED display
Fig. 1

The clock display has 4 digits. Each digit has 7 segments named from a to g, plus a dot named using letter "p". The LED has 12 legs, numbered from 1 through 12 in the counter-clock wise direction.

The negative ends of all the segments in a digit are connected together. For digit 1, they are connected to leg 12. For digit 2, they are connected to leg 9, etc. The positive ends of the same segments of each digit are connected together. For example, segments "a" from all 4 digits are connected to leg 11.

How does it work? If all positive legs (11, 7, 4, 2, 1, 10, 5, 3) are applied 5V (HIGH), and all negative legs (12, 9, 8, 6) are applied 0V(LOW), then all segments on. If all negative legs applied HIGH, then all segments off.

Therefore, it looks impossible to display different numbers on 4 digits at the same time. This is because, turning on a segment will turn on the same segment on all 4 digits. For example, assume we want to display "1" for digit 1, "7" for digit 2, to display "1" we need to turn on segment "b" and "c", the other segments off. However, doing so will display "1" on all digit, unless the whole digit of 2,3,4 are turned off. It is impossible to show "1" for digit 1 and "7" for digit 2 at the same time. The design of the LED display relies on a clever trick that utilises our visual memory for a very short period. So the trick is as follows (say, to display 4321):

  1. Turn on digit 1, all other digit off.
  2. Show number "4" (on digit 1).
  3. Turn on digit 2, all others off.
  4. Show number "3" (on digit 2).
  5. Turn on digit 3, all others off.
  6. Show number "2" (on digit 3).
  7. Turn on digit 4, all others off.
  8. Show number "1" (on digit 4).

In slow motion, the display will be "4" on digit 1, then "3" on digit 2, then "2" on digit 3, then "1" on digit 4. The numbers are not shown at the same time. At any time, only one digit is visible. The magic is, if we run those steps continuously and quickly enough, the display will appear to be showing the different numbers at the same time. This works all because of our eye's visual memory.

To keep the clock time accurately, we need to use a timer and interrupt. Once setup, a timer calls an Interrupt Service Routine (ISR) at the specified interval. For example, if we setup a timer at 2hz, then the MCU will call our ISR every 1/2 seconds. This should be quite accurate (depending on the accuracy of the crystal inside the MCU).

An interrupt is a system event triggered by a H/W, S/W trigger. In our case, it is triggered by our timer. Examples of other trigger could be a signal from an analogue pin. When an interrupt occurs, the CPU stops the normal execution (of our loop() function), keeps the execution point address, then calls our service routine. Once the ISR is done, normal execution resumes.

The ISR should execute the min. amount of code and return as quickly as possible. Any important data should be kept in buffers and processed in the normal execution cycle. Otherwise, it could lead to data loss due to interrupt overflow.

Setting up a timer requires the knowledge of the MCU's register architecture. I have simply copied some sample code from internet for this case. The most important thing is the calculation of Compare Match register value for a given hz value. The max. value of the register is 65534. Therefore, the min. hz value is about 1 for the 16bit timer. Ideally, since we are only displaying hour and minutes, we may wish to have a timer that triggers at 1 minute interval. But that is impossible.

To reset time, a button switch is used. The switch is connected to analog pin 0 with a 10K resistor. The reset button operates as follows:

  1. For every HIGH reading of the analog pin 0, the minute is incremented by 1.
  2. If the button is pressed down continuously for more than 60 HIGH readings, it changes to increment hours, instead of minutes.
  3. The continus press down count is reset to 0 when never the button is released.
Connection Diagrams
 


Program Code

The sketch for this exercise is shown below:

byte pinSegments[8] = {1, 2, 3, 4, 5, 6, 7, 8};
byte pinBlocks[4] = {12, 11, 10, 9};
byte bits_onoff[10] = {63, 6, 91, 79, 102, 109, 125, 7, 127, 111};

byte dot_time = 0;  // <75 dot on, >=75 dot off
byte hhmm[4] = {1, 2, 0, 0};
byte num_switch_count=0;  // Number of continuous switch on

volatile short seconds_count = 0;

#define CPU_HZ 16000000
#define timer_hz 5
#define switchPin 0

void setup() {
  for(int i=0; i<8; i++){
    pinMode(pinSegments[i], OUTPUT);
    if(i<4) pinMode(pinBlocks[i], OUTPUT);
  }
  setInterruptTimer();
//  Serial.begin(9600);
}

void loop() {
  for(int i=0; i<4; i++) {
    if( i==0 && hhmm[0] == 0) { delay(2); continue; }
    blockOn(i);
    showNumber( hhmm[i], i==1 && dot_time < 80);
    delay(2);
  }
  dot_time++;
  if( dot_time > 160) dot_time = 0;

  // Read swtich
  if( dot_time % 15 == 0) {
    int val = analogRead(switchPin);
    if( val < 50 ) // Not pressed, end of continuous press
       num_switch_count = 0;
    else {
      if( num_switch_count < 60) {
         addMinute(1);
      }
      else if(num_switch_count % 4 == 0) {
         addHour();
      }
      if( num_switch_count < 250)
         num_switch_count++;
      else
         num_switch_count = 60;
    }
  }
}

// Clears a block by writing the block pin HIGH
void blockOn(int blkn){
  for(int i=0; i<4; i++) digitalWrite(pinBlocks[i], HIGH);
  digitalWrite(pinBlocks[blkn], LOW);
}

// Show a single number: 0~9
void showNumber(int num, bool show_dot){
  byte power2 = 1;
  for(int i=0; i<8; i++)
  {
    digitalWrite(pinSegments[i], (bits_onoff[num] & power2) > 0  ? HIGH : LOW);
    power2 = power2 << 1;
  }
  digitalWrite(pinSegments[7], show_dot ? HIGH : LOW);  
}

// Setup timer1 so that interrupts hz times per second
void setInterruptTimer(){
  noInterrupts();

  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = CPU_HZ / 1024 / timer_hz - 1; // must be <65536
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS12 and CS10 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  interrupts();
}

// interrupt service routine called automatically every timer tick.
ISR(TIMER1_COMPA_vect){  //timer1 interrupt 1Hz 
  seconds_count++;
  if(seconds_count == 60 * timer_hz) {
    seconds_count = 0;
    addMinute(1);
  }
}

void addMinute(int m) {
   hhmm[3] += m;
   if(hhmm[3] > 9) { // 10m
     hhmm[3] = 0;
     hhmm[2]++;
     if(hhmm[2] == 6) {
       hhmm[2] = 0;
       addHour();
     }
   }
}
void addHour(){
   hhmm[1]++;
   if(hhmm[1] > 9) { // 10h
      hhmm[1] = 0;
      hhmm[0]++;
   }
   else if(hhmm[1] == 4 && hhmm[0] == 2){  // 24h
      hhmm[0] = hhmm[1] = 0;
   }
}

bits_onoff[] array is the bitwise values of displaying numbers 0 to 9. The bit value for each segment is shown in Fig.1 alongside the segment a,b,c,...g. To display the required segments, we simply add the numbers marked on side of the segment together. For example, to display number 4, we should turn on segments a,g,b,c. Therefore, add the values together, we get 32+64+2+4 = 102. This is bits_onoff[4].

END
Prev Next



Ads from Googlee
Dr Li Anchor Profi
www.anchorprofi.de
Engineering anchorage plate design system
©Andrew Qu, 2014. All rights reserved. Code snippets may be used "AS IS" without any kind of warranty. DIY tips may be followed at your own risk.