Over the weekend I made a 4 digit 7 segment display countdown timer.

The display driver is the SevSeg library, which I just grabbed for ease of use, on the Raspberry Pi I’ve previously just written the driver myself by mapping letters/numbers to an array of segments with HIGH/LOW states (Youtube link) but that was only a single digit display.

I don’t call the delay() function at all in the code, instead it uses timers and millis() so the button presses are instantly detected and the digits redraw quicker so the countdown happens in realtime – tested with a stopwatch.

The button has multiple functions – if you press it once it resets the timer to zero. If you hold it down it increments the timer and when you release it the timer starts counting down again.

Firstly it started off on an Arduino Uno, then I ported it to a bare ATMega328P on breadboard. It uses all 12 digital pins, and the button uses an analog pin setup as a digital input with internal pullup resistor.

Finally I moved to a Sparkfun Pro Micro soldered on perfboard. The Pro Micro has more digital pins and also meant I didn’t need to use a regulator, reset resistor, crystal, capacitors etc; and could also power/reprogram over USB. The only changes I made to the code were pin mappings, although even they could have stayed the same had I soldered them that way.

I was pretty impressed that 3 variants in hardware and the software was written from scratch in under 2 days. I also found a use for the hot knife tip on my new soldering station – cutting strips of pin headers takes a couple of seconds instead of minutes with a hacksaw. The hotter soldering iron seemed much better too, and made perfboard quite usable, even though I generally prefer stripboard (Vero).

The code is:

#include "SevSeg.h"

// create an instance of the object
SevSeg myDisplay;

// create global variables
unsigned long timer = millis();
int deciSecond = 0;
int previous = LOW;
unsigned long last_time = 0;
const int button = A0;

void setup()
{
    // set button to input with built-in pull-up
    pinMode(button, INPUT_PULLUP);
    
    // set display to to command cathode/anode
    int displayType = COMMON_CATHODE;

    int digit1 =  9; // pin 12 on my 4 digit display
    int digit2 =  3; // pin 9
    int digit3 =  2; // pin 8
    int digit4 =  4; // pin 6
    int segA   = 10; // pin 11
    int segB   =  6; // pin 7
    int segC   = 14; // pin 4
    int segD   =  7; // pin 2
    int segE   =  8; // pin 1
    int segF   =  5; // pin 10
    int segG   = 15; // pin 5
    int segDP  = 16; // pin 3
    
     // 1, 2 or 4 digit display
    int numberOfDigits = 4;

    // constructor
    myDisplay.Begin(displayType, numberOfDigits, digit1, digit2, digit3, digit4,
                    segA, segB, segC, segD, segE, segF, segG, segDP);

    // set the display to 100% brightness level
    myDisplay.SetBrightness(100);
}

void loop()
{
    // create a char array
    char tempString[10];

    // take a reading
    int reading = digitalRead(button);

    // changed state - debounce=250ms
    if (reading == LOW && previous == HIGH && (millis() - last_time > 250))
    {
        // reset counter
        deciSecond = 0;

        // store the time the button was pressed
        last_time = millis();
    }

    // being held down for >2secs
    if (reading == LOW && previous == LOW && (millis() - last_time > 2000))
    {
        deciSecond++;
    }

    // released for over 10ms
    if (reading == HIGH && deciSecond > 0 && millis() - timer > 100)
    {
        timer = millis();
        deciSecond--;
    }

    // store previous reading
    previous = reading;

    // update display
    sprintf(tempString, "%04d", deciSecond); // leading zeros
    myDisplay.DisplayString(tempString, 4); // (number, decimal point)
}

The Makefile is:

#BOARD_TAG    = uno
#ISP_PROG     = usbasp
BOARD_TAG    = leonardo
MONITOR_PORT = /dev/ttyACM0
ARDUINO_LIBS = SevSeg

include /usr/share/arduino/Arduino.mk