A Zero Component, Super Simple Minimalist Arduino Digital Clock

File:Arduino Logo.svg - Wikimedia Commons

Having moved house recently, I found myself re-organising a lot of stuff. I also had to carry a lot of boxes containing all sort of electronic components, including a lot of Arduino related stuff. At one point I also found myself wanting a good old 7 segment LED digital clock for my bedroom. This was the perfect opportunity to put some of the old “junk” to good use.

Since I do not yet have a fully functional workshop, I do not have the luxury of manufacturing pcb’s, so the idea was to build a clock with the least possible number of components and minimal effort.

Normally, the way to go about building a clock would be to use something like a DS-1302, which automatically takes care of keeping the time for us, and just use the Arduino as an interface between the 1302 and the display.

But … what if we could use the Arduino to take care of both the display and also the timekeeping part without adding any external components?

Less Hardware, More Software

To keep hardware complexity to a minimum, a multiplexed 7-Segment display unit was used. These displays require just 12 connections to drive the four digits required to display time in the HH:MM format. The downside to this type of display is that in order to keep the numbers visible, without any flickering, the display needs to be constantly refreshed.

I needed to find a way to accurately keep track of time, while ensuring the display is always up to date. I knew full well that the refresh cycle would depend on the individual digits being displayed, and was never going to be the same. One option was to use something like the 50hz from a mains supply as a reference clock, but this would add circuit complexity and the aim of the project was to offload “complexity” to the code part of the project, keeping the hardware part as simple as possible.

This is where Interrupts come to save the day.

Timekeeping and Interrupts

By definition an Interrupt is “a signal emitted by hardware or software when a process or an event needs immediate attention”. It alerts the processor to a high priority process requiring interruption of the current working process.

The concept of cpu interrupts can be traced back to the old Univac 1103 machines. An interrupt overload made history during the first moon landing with the infamous 1202, and 1203 alarms. You can read more about this here.

The Atmega 328 processor, supports both internal and external interrupts. It also has various timers we can set, which will interrupt the cpu every time a counter overflows, so theoretically, we could set a timer to interrupt our cpu once every second.

During an interrupt, the cpu, would briefly exit the main loop, which in our case is constantly updating the display, and handle the interrupt via our ISR (interrupt service routine) defined in setup.h.

The Interrupt Service Routine (ISR)

The ISR will do the following :

  • Toggle the “1 Second” LED
  • Increment ss (our seconds counter)
  • If ss > 59, it will set ss to 0, and increment mm (our minutes counter)
  • Check if mm > 59, if it is, set mm to 0, and increment hh (our hours counter)
  • Check if hh > 23, if it is, set hh to 0

The ISR code for the above steps, can be found below.

// Interrupt service routine. 
// This gets trigerred exactly every one second
ISR(TIMER1_COMPA_vect){//timer1 interrupt 1Hz
  
  // Toggle Second marker LED
  if (toggle){
    digitalWrite(SEC_LED, H);
    toggle = 0;
  }
  else{
    digitalWrite(SEC_LED, L);
    toggle = 1;
  }
  // Increment seconds, if seconds = 60, 
  // set seconds to 0, and increment 
  // minutes
  ss++;
  if (ss > 59){
    ss=0;
    mm ++;
  }
  // If minutes = 59, set minutes = 0, and increment hours
  if (mm > 59){
    mm = 0;
    hh++;
  }
  
  // if hours = 24, set hours to 0  
  if (hh > 23){
    hh = 0;    
  }
}

With the time keeping part out of the way, the only remaining part is to drive the 7-Segment display. Normally, this would require a 220 to 470 ohm resistor is series with every segment, however, once again we would rather write an extra few lines of code, other than solder 7 extra resistors. Keeping the led pulses as short as possible will ensure the leds are not damaged, even if they are driven directly from the Aruino.

The final thing to take care of is, a couple of buttons to set the time. One to set the Hours, and one to set the Minutes. These are the only 2 external components, apart from the 7 segment display, and the Arduino itself.

The Main Loop

The main loop will basically “listen” for the “SET_H” and “SET_M” buttons defined in “definitions.h”. If any of them is pressed, the Hours or the Minutes are incremented accordingly.

While either of the buttons are pressed, the display is cleared. Remember we need to keep refreshing the display in order to show the time, so in order to avoid unnecessary extra code complexity, the easiest way to deal with this is to actually blank the display while setting the buttons are held down.

A while loop in the code, will also ensure that the relevant variable (hours or minutes) is only incremented once per button pulse.

void loop() {
  
  while (1){
    //check if the SET_H button is pressed, and if it is, clear display, and increment hours
    if (!digitalRead (SET_H)){
      //Increment hours by 1, if the SET_H Button is pressed
      hh++;
      if (hh > 23){
        hh = 0;
      }
      while (!digitalRead (SET_H)){
        //Clear display while button is held down
        digitalWrite (SEG_A, L);
        digitalWrite (SEG_B, L); 
        digitalWrite (SEG_C, L);
        digitalWrite (SEG_D, L);
        digitalWrite (SEG_E, L);
        digitalWrite (SEG_F, L);
        digitalWrite (SEG_G, L);
      }
    }
    //check if the SET_M button is pressed, and if it is, clear display, and increment minutes
    if (!digitalRead (SET_M)){
      //Increment minutes by 1, if the SET_M Button is pressed
      mm++;
      if (mm > 59){
        mm = 0;
      }
      while (!digitalRead (SET_M)){
        //Clear display while button is held down
        digitalWrite (SEG_A, L);
        digitalWrite (SEG_B, L); 
        digitalWrite (SEG_C, L);
        digitalWrite (SEG_D, L);
        digitalWrite (SEG_E, L);
        digitalWrite (SEG_F, L);
        digitalWrite (SEG_G, L);
      }
    }
    //call the display time subroutine
    displayTime (hh,mm);
  }
}

Once the button is released, the loop will resume, and the time is displayed again.

Pin Definitions

All the I/O pins are defined inside the definitions.h file. These definitions can be changed to best suite your needs.

Display Subroutines

The display_subroutines.h file contains all the functions relating to updating the display.

The displayTime() function is called from within the main loop, its purpose is to split the hours and the minutes into 4 separate digits, and stored in 4 variables called h1, h2, m1 and m2. The “tens” are obtained literally by dividing the hour or minute by 10, if the hour or minute is less than 10, then h1 or m1 will contain a “0”. If not, h1 and m1 will contain the number of tens.

h2, m2 will contain the units. The units are obtained by multiplying the result obtained above by 10, and subtracting it from hh or mm accordingly.

Once h1, h2, m1, m2 have been calculated, the function displayNumber() is summoned.

displayNumber()

displayNumber() requires two parameters, the digit (1-4) and the value. So displayNumber (1,1) would display the number 1 on the leftmost digit. displayNumber (2,1) would display the number 1 on the second digit and so on.

The 4 digits are refreshed constantly by pulling the corresponding DIGIT_n pin low, and presenting the correct sequence on SEG_A to SEG_G pins. A 4 channel oscilloscope on the DIGIT_n pins would look like this

As you can see, each digit is kept on for approximately 5ms, and refreshed every 20ms (5ms X 4 digits).

This is the what the 1 Second pulse looks like on a real DSO. The display is 2V/Div (Vertical) and 800ms/Div (Horiz).

The Schematic

The schematic consists only of an arduino nano, a 4 digit common cathode display, and two button switches.

If you wish to use common anode displays, all you have to do is reverse the logic in the showNumber() and setDigit() subroutines. Different 7-Segment pin configurations can also be changed in the definitions.h file.

The prototype

The whole project took about a day to design and code. The prototype was built on a piece of bread board and left running for a couple of days. The accuracy is very decent, and it did not gain or loose any minutes in the first 48 hours of its life.

The Source Code

The source code for the project along with the schematic can be found at https://gitlab.com/jafenech/simple-arduino-digital-clock.

The finished Clock

The clock was constructed on small piece of verboard, roughly 60 x 40, and a 3D printed box was printed in about two and a half hours.

The unit can be powered from any usb port or charger, even the cheapest ones around. D.C. smoothing is taken care of by the nano board itself.

Conclusion

The whole project took less than a weekend to complete, from concept to a fully working boxed model. Sure, I could have easily ordered a digital clock of any online shop, but it would not have been as much fun as building one off my junk box.

Hope this project helps or inspires someone getting started with microcontrollers. As always, feedback and comments are always welcome.

Leave a reply:

Your email address will not be published.