Learning Arduino - Making a Clock
SummaryIn 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):
- Turn on digit 1, all other digit off.
- Show number "4" (on digit 1).
- Turn on digit 2, all others off.
- Show number "3" (on digit 2).
- Turn on digit 3, all others off.
- Show number "2" (on digit 3).
- Turn on digit 4, all others off.
- 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:
- For every HIGH reading of the analog pin 0, the minute is incremented by 1.
- If the button is pressed down continuously for more than 60 HIGH readings, it changes to increment hours, instead of minutes.
- The continus press down count is reset to 0 when never the button is released.


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 |