Programmers clock

Have you ever wanted a clock that speaks the language of programmers? Imagine a clock where the time is not in the typical digits but is represented in the hexadecimal format familiar to those who delve into the world of coding. In this post, I’ll share my journey of creating just that – a Programmer’s Clock.

The Inspiration

As a programming enthusiast, I’ve always been fascinated by hexadecimal notation. It’s the base-16 numbering system that programmers frequently encounter in their coding adventures. So, the idea of a clock displaying time in hexadecimal was born.

The Components

To bring this idea to life, I gathered the following components:

  • Arduino (I used an Arduino Leonardo clone)
  • 4-digit 7-segment display
  • 2 buttons for setting hours and minutes
  • 10 resistors
  • 74HC595 shift register

The Schematic

The schematic below shows how the components are connected to the Arduino. The 7-segment display is controlled by the shift register, which is connected to the Arduino via the SPI interface. The buttons are connected to the Arduino’s digital pins, and the resistors are used to limit the current flowing through the 7-segment display.

Schematic

The Code

#include "SevSegShift.h"

#define SHIFT_PIN_DS 8   /* Data input PIN */
#define SHIFT_PIN_STCP 7 /* Shift Register Storage PIN */
#define SHIFT_PIN_SHCP 6 /* Shift Register Shift PIN */

#define BUTTON_LEFT_PIN 14
#define BUTTON_RIGHT_PIN 15

int timeOffset = 0;

struct time
{
  unsigned long long milli;
  unsigned long long seconds;
  unsigned int minutes;
  unsigned int hours;

  void getTime()
  {
    unsigned long long currentTime = millis();
    long long adjustedMillis = currentTime % 86400000 + timeOffset * 60000;

    // Ensure that adjustedMillis is positive
    if (adjustedMillis < 0)
    {
      adjustedMillis += 86400000;
    }

    milli = adjustedMillis;
    seconds = milli / 1000;
    minutes = seconds / 60;
    hours = minutes / 60;
  }
};

time clock;

unsigned long buttonPressStartTime = 0;
const unsigned long holdThreshold = 100;        // Time in milliseconds to consider a button press as "held"
const unsigned long initialIncrementDelay = 50; // Initial delay between increments in milliseconds

enum ButtonState
{
  IDLE,
  PRESSED,
  HELD
};

ButtonState leftButtonState = IDLE;
ButtonState rightButtonState = IDLE;

// Instantiate a seven segment controller object (with Shift Register functionality)
SevSegShift sevseg(
    SHIFT_PIN_DS,
    SHIFT_PIN_SHCP,
    SHIFT_PIN_STCP,
    1,   /* number of shift registers there is only 1 Shiftregister
            used for all Segments (digits are on Controller)
            default value = 2 (see SevSegShift example)
            */
    true /* Digits are connected to Arduino directly
            default value = false (see SevSegShift example)
          */
);

void setup()
{
  byte numDigits = 4;
  byte digitPins[] = {5, 4, 3, 2};               // These are the PINS of the ** Arduino **
  byte segmentPins[] = {0, 1, 2, 3, 4, 5, 6, 7}; // these are the PINs of the ** Shift register **
  bool resistorsOnSegments = false;              // 'false' means resistors are on digit pins
  byte hardwareConfig = COMMON_ANODE;            // See README.md for options
  bool updateWithDelays = false;                 // Default 'false' is Recommended
  bool leadingZeros = true;                      // Use 'true' if you'd like to keep the leading zeros
  bool disableDecPoint = false;                  // Use 'true' if your decimal point doesn't exist or isn't connected. Then, you only need to specify 7 segmentPins[]

  sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments,
               updateWithDelays, leadingZeros, disableDecPoint);
  sevseg.setBrightness(100);

  Serial.begin(9600);
}

void checkButton()
{
  int leftButtonStateNow = digitalRead(BUTTON_LEFT_PIN);
  int rightButtonStateNow = digitalRead(BUTTON_RIGHT_PIN);

  handleButton(BUTTON_LEFT_PIN, leftButtonStateNow, leftButtonState);
  handleButton(BUTTON_RIGHT_PIN, rightButtonStateNow, rightButtonState);
}

void handleButton(int buttonPin, int buttonStateNow, ButtonState &buttonState)
{
  if (buttonStateNow == HIGH)
  {
    if (buttonState == IDLE)
    {
      // Button pressed for the first time
      buttonState = PRESSED;
      buttonPressStartTime = millis();
      timeOffset += (buttonPin == BUTTON_LEFT_PIN) ? -1 : 1; // Adjust timeOffset
    }
    else if (buttonState == PRESSED)
    {
      // Button held down
      unsigned long elapsedTime = millis() - buttonPressStartTime;
      if (elapsedTime >= holdThreshold)
      {
        buttonState = HELD;
        buttonPressStartTime = millis();
      }
    }
    else if (buttonState == HELD)
    {
      // Increment timeOffset and adjust delay
      unsigned long elapsedTime = millis() - buttonPressStartTime;
      if (elapsedTime >= initialIncrementDelay)
      {
        timeOffset += (buttonPin == BUTTON_LEFT_PIN) ? -1 : 1;
        buttonPressStartTime = millis();
      }
    }
  }
  else
  {
    // Button released or not pressed
    if (buttonState == PRESSED)
    {
      // Reset button press start time when button is released after a short press
      buttonState = IDLE;
      buttonPressStartTime = 0;
    }
    else if (buttonState == HELD)
    {
      // Reset button state when button is released after being held
      buttonState = IDLE;
      buttonPressStartTime = 0;
      // Additional actions can be added here for the end of held state
    }
  }
}

void loop()
{
  checkButton();

  clock.getTime();

  int displayTime = ((clock.hours % 24) << 8) | (clock.minutes % 60);
  Serial.println(displayTime);
  sevseg.setNumber(displayTime, -1, true);
  sevseg.refreshDisplay();
}

Writing the code for the clock involved converting the current time into hexadecimal format and updating the display accordingly. The buttons allowed for easy adjustment of the hours and minutes. The 74HC595 shift register efficiently controlled the 7-segment display, making the code both elegant and functional.

Testing and Debugging

No project is complete without its fair share of testing and debugging. I meticulously went through each function, ensuring that the clock displayed the correct time in hexadecimal format and that the buttons worked seamlessly for adjusting the time. Debugging is a crucial part of any programming project, and it’s where the real learning happens.

The Final Product

After overcoming challenges and refining the code, I was able to witness the successful creation of my Programmer’s Clock. The 4-digit 7-segment display now showcased the current time in hexadecimal, providing a unique and visually appealing twist to the conventional clock. The user can change the time using 2 buttons. Pressing the button will adjust the time by 1 and holding it will quickly increase or decrease the time.

Conclusion

Creating a Programmer’s Clock was not just about displaying time differently; it was a journey of combining programming skills with electronics. The integration of the Arduino, 7-segment display, buttons, resistors, and the shift register resulted in a tangible, functional piece that speaks directly to the hearts of programmers.

Whether you’re a seasoned coder or a hobbyist, experimenting with Arduino and electronics can be a rewarding experience. It opens up a realm of possibilities for creative projects that blend coding and hardware. So, if you’re feeling inspired, gather your components, write some code, and embark on your own journey of crafting something uniquely yours.

References