Simple Binary Clock

Learn how to build a Binary Clock. Practice your coding skills and basic electronic concepts by building every geek’s dream: a clock that gives the time using binary numbers.

Introduction

This will be our first project here in Paltonico, a Binary Clock aimed to teach the very basics of designing an electronic device from scratch.

Prerequisites

  • Some basic knowledge of C programming and binary numbers is preferable for the analysis of the embedded code on the micro-controller.
  • We strongly recommend reading our previous article on LED Driving using fewer GPIO.

The Objective

We want to build a cheap binary clock using the smallest MCU that does the work; which means using as few GPIO to control our array of LEDs as possible.

Our first constraint will be how we display the time using binary numbers. One usual solution is to display the numbers one at the time dividing by tens:

This solution is easy to read even for the uninitiated in the world of binary numbers. However, it requires up to 4 bits (or LEDs in this case) per number, with the exception of the hours which only get to 2; this gives us a total of 13 LEDs. I think we can do better.

If we divide the time into just two groups: hours and minutes, we can reduce that number to just 11 LEDs. It doesn’t even matter if we decide to do a 24 or 12 hours clock, the number is still the same.

Our second constraint, and the one that will define the type of Microcontrollers we use, is the lack of external oscillator. We want to keep the circuit as simple as possible just to show the basic components of an embedded device; this means that whichever MCU we choose must have an internal oscillator to keep the time and operate peripherals like PWM and Timers.

It’s important to notice that internal oscillators are not the best for time tracking, this given that their accuracy is between 2% which means that the actual time we are counting can be slower or faster than predicted. And to make matters worst, this changes with temperature and from device to device. This is why it is important to know that this project’s objective is not to create an accurate clock, but one that serves the purpose of learning the basics of circuit design.

The Circuit

I’m not gonna extend too much on the workings of the circuit itself. We have a very good article that explains how to get several LEDs running using as few GPIOs as possible. Following the instruction given we get that to be able to run at least 15 LEDs we need 4 outputs; plus one extra for the control button.

The Micro-controller we are using this time is the smallest possible available from Microchip. Just because they are easy to get my hands on and they are quick to program, but It can be any 8-bit micro with at least 8 feet and an internal oscillator.

The Code

First, one important note, all the code showed in this section can be found on GitHub.

As said before, for this project we are using a 8-bit Microcontrollers from Microchip; the exact code of the device can vary but one great thing about microchip is that as a general rule, GPIOs are located on the same positions by ports.

Let’s start by defining our ports:

#include <xc.h>         // PIC16 Developer Kit
#include <stdint.h>    // Used for uint8_t definition

/* Project GPIO Definitions */
#define PWM_GPIO0 RA0PPS    // This constants are device dependant
#define PWM_GPIO1 RA2PPS    // In this case we are using PORT A
#define PWM_GPIO2 RA1PPS    // And Outputs 0,1,2,4 and 5
#define PWM_GPIO3 RA5PPS    // PPS: Peripheral Pin Select
#define GPINPUT RA4_ON

#define PWM_CCP1 0x09        // This constant is defined by the datasheet
                            // It defines the activation bit of the Compare/Capture/PWM module

A quick word on PWM on micro-controllers. They usually work by defining the frequency to a timer and assigning the timer to a GPIO. You can also set the duty cycle, but this will be linked to the counter itself, which means that whenever we assign the counter to an output, in this case by using the constant PWM_CCP1, it will use this same duty cycle.

The next step is to initialise every module:

PIN_MANAGER_Initialize();
PIN_MANAGER_SetInterruptHandler(buttonHandler);
PWM1_Initialize();
TMR0_Initialize();  // Used to trigger PrintMatrix
TMR1_Initialize();  // Used to control the clock timing
TMR2_Initialize();  // Used to control PWM
TMR1_SetInterruptHandler(timeHandler);

buttonHandler and timeHandler are defined by:

void buttonHandler(){
    button = 1;
}

void timeHandler(){
    static uint16_t secCount = 0;
    static uint8_t correct = 0;

    INTERRUPT_GlobalInterruptDisable();
    INTERRUPT_PeripheralInterruptDisable();

    if(++secCount == 2){
        secCount=0;

        if(PORTAbits.RA4 == 0){
            t_button++;
        } else{
            t_button = 0;
            if(aftercount < 3) aftercount++;
        }

        if(++sec >= 60){
            sec = sec - 60;

            if(++t_minute >= 60) {
                t_minute = t_minute - 60;
                if(++t_hour == 24) t_hour = 0;
            }
        }
    }

    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();
}

buttonHandler simply triggers a flag that is later check to perform button actions. The reason behind this is that I’d like to perform several checks on the button to avoid physical bouncing and it is easy to perform on timed events.

timeHandler is a bit more complex. Let’s analyse it piece by piece. The first step is to disable interrupts

INTERRUPT_GlobalInterruptDisable();
INTERRUPT_PeripheralInterruptDisable();

This is done to avoid other interruptions to interrupt the time keeping algorithm; this can be done because no other interruption is more important than this one. On more complex systems, a hierarchy of interruptions can be use; but here is not really necessary. Next, we count for how long we have been pressing the button:

if(PORTAbits.RA4 == 0){
     t_button++;
} else{
     t_button = 0;
     if(aftercount < 3) aftercount++;
}

`

And the rest of the code is just simple time keeping. In this case the timer interrupt is set to be triggered every 500ms. This means that every two interruptions we increase the seconds counter by 1 and we check if we already reached 60 to increase a minute.

Next step is to print our numbers using the printMatrix interruption; where we also have the debouncing algorithm for the tactile button:

/* Handles button interruption */
    if(button == 1){

    // Anti-bounce code
    // Doesn't executes button code until its value has been proved 20 times
        if((reading = PORTAbits.RA4) == last_reading)      bounce_count++;
        if(reading != last_reading && bounce_count > 0) bounce_count--;

        if(bounce_count >= 20){    
            if(reading == LOW){
                if(aftercount < 3){
                    aftercount = 0;
                    if(++t_hour == 24) t_hour = 0;
                } else{
                    sec = 0;
                    if(++t_minute >= 60) t_minute = 0;
                }
            }

            bounce_count = 0;
            last_reading = PORTAbits.RA4;
            button = 0;
        }

        last_reading = reading;
    }

    /* Here I modify the time if I'm holding the button down
 *First 5 seconds (after 2 seconds of wait) it modifies minutes and
 *then hours until released. Increments of 1 every 100 cycles */
    if(PORTAbits.RA4 == LOW && ++print_cycles_buttonheld >= HELD_TIME_INCREASE){
        print_cycles_buttonheld = 0;

        if(t_button >= 7) {
            if(++t_hour == 24) t_hour = 0;
            aftercount = 0;

        } elseif(t_button >= 2) {
            sec = 0;
            if(++t_minute >= 60) {
                t_minute = 0;
            }
        }
    } elseif (PORTAbits.RA4 == HIGH) t_button = 0;

The rest of the function just checks the values of minute and hour and turns on the corresponding LEDs.

void printMatrix(){
    static uint8_t current_LED = 1;
    static uint8_t bounce_count = 0;
    static uint8_t reading;
    static uint8_t last_reading = 1;
    static uint16_t print_cycles_buttonheld = 0; // Counts the amount of print cycles that have been called

    /* Prints LED matrix 1 LED per interruption */
    if (++current_LED > 11) current_LED = 1;
    if (current_LED < 7) {
        // current 1 to 6
        (t_minute>>(current_LED-1) & 1) ? setLED_PWM(current_LED) : setLED_PWM(0);
    } else{
        // current 7 to 12
        (t_hour>>(current_LED-7) & 1) ? setLED_PWM(current_LED) : setLED_PWM(0);
    } 
}

It is important to be sure that we are spending the same time on disabled LEDs as we do on enabled LEDs. This will ensures that all of them will have the same brightness regardless of the amount of them that are actually working. It also helps reducing power consumption and brightness.

void setLED_PWM (short ledN){
  if (ledN<0 || ledN>11) **return**;

  PWM_GPIO0 = 0x00;
  PWM_GPIO1 = 0x00;
  PWM_GPIO2 = 0x00;
  PWM_GPIO3 = 0x00;

  LATA = 0x00;

  switch (ledN){
      case 0:
          TRISA = GPINPUT;
          break;

      case 1:
          TRISA = GPIO2|GPIO3|GPINPUT;  *// Every GPIO is out except these ones*
          PWM_GPIO1 = PWM_CCP1;
          break;

      case 2:
          TRISA = GPIO2|GPIO3|GPINPUT;
          PWM_GPIO0 = PWM_CCP1;
          break;

      case 3:
          TRISA = GPIO0|GPIO3|GPINPUT;
          PWM_GPIO1 = PWM_CCP1;
          break;

      case 4:
          TRISA = GPIO0|GPIO3|GPINPUT;
          PWM_GPIO2 = PWM_CCP1;
          break;

      case 5:
          TRISA = GPIO1|GPIO3|GPINPUT;
          PWM_GPIO0 = PWM_CCP1;
          break;

      case 6:
          TRISA = GPIO1|GPIO3|GPINPUT;
          PWM_GPIO2 = PWM_CCP1;
          break;

      case 7:
          TRISA = GPIO1|GPIO2|GPINPUT;
          PWM_GPIO3 = PWM_CCP1;
          break;

      case 8:
          TRISA = GPIO1|GPIO2|GPINPUT;
          PWM_GPIO0 = PWM_CCP1;
          break;

      case 9:
          TRISA = GPIO0|GPIO2|GPINPUT;
          PWM_GPIO3 = PWM_CCP1;
          break;

      case 10:
          TRISA = GPIO0|GPIO2|GPINPUT;
          PWM_GPIO1 = PWM_CCP1;
          break;

      case 11:
          TRISA = GPIO0|GPIO1|GPINPUT;
          PWM_GPIO3 = PWM_CCP1;
          break;

      case 12:
          TRISA = GPIO0|GPIO1|GPINPUT;
          PWM_GPIO2 = PWM_CCP1;
          break;

      default:
          break;
  }
}

Final Thoughts

Even if not an exact clock for tracking time, this projects is easy to put together and fun to play with. The type of MCU can be changed in order to provide additional functionalities or change the way time is presented, the color of the LEDs, their intensity; which makes it perfect for learning and experimentation purposes.

It can be improved by including a MCU with a real time clock or a crystal oscillator to take care of accurate time tracking. It could also be made portable by adding a battery socket, just don’t forget to take into account battery life vs LEDs intensity.

That’s it for now and don’t forget to have fun!

0 Shares

Leave a Reply

Your email address will not be published. Required fields are marked *

0 Shares
Tweet
Share