/*
Forum: https://forum.arduino.cc/t/countdown-timer/1440331
Wokwi: https://wokwi.com/projects/461542314286743553
Based on
https://github.com/untr0py/SevSeg/tree/master/examples/SevSeg_Counter
ec2021
2026/04/17
Starts to count down from 2 minutes (120 seconds) when green button pressed
During countdown mode the red button can be pressed at any time to reset the counter
The counter value is converted into minutes and seconds for display
The decimal point is (mis-)used to indicate where minutes are displayed to the right and seconds to the left
A minimum countdown value of - 10 minutes = -600 sec has been defined to take care of the displayable numbers
If this minimum values has been reached or at any lower number the display will show "--.--"
*/
#include "SevSeg.h"
class BtnClass {
private:
byte pin;
byte state;
byte lastState;
unsigned long lastChange;
public:
init(byte p) {
pin = p;
pinMode(pin, INPUT_PULLUP);
};
boolean pressed() {
byte actState = digitalRead(pin);
if (actState != lastState) {
lastChange = millis();
lastState = actState;
};
if (actState != state && millis() - lastChange > 30) { // Fixed debounce time 30 ms
state = actState;
return !state;
}
return false;
};
};
constexpr byte numOfDigits = 4;
constexpr byte digitPins[] = {2, 3, 4, 5};
constexpr byte segmentPins[] = {6, 7, 8, 9, 10, 11, 12, 13};
constexpr bool resistorsOnSegments = false; // 'false' means resistors are on digit pins
constexpr byte hardwareConfig = COMMON_ANODE; // See README.md at GitHub (link see above)for options
constexpr bool updateWithDelays = false; // Default 'false' is Recommended
constexpr bool leadingZeros = true; // Use 'true' if you'd like to keep the leading zeros
constexpr bool disableDecPoint = false; // Use 'true' if your decimal point doesn't exist or isn't connected
constexpr byte startPin {A1};
constexpr byte resetPin {A0};
constexpr int startCount {120}; // 2 minutes in seconds = 120
constexpr int minCount {- 600}; // in seconds = - (10 minutes ) = - 600
constexpr unsigned long countDownInterval {1000}; // in ms, can be changed for test purposes (e.g. to 500 ms or 200 ms ...)
enum class MODE {IDLE, COUNTDOWN, RESET};
MODE mode = MODE::IDLE;
SevSeg sevseg;
BtnClass startBtn;
BtnClass resetBtn;
unsigned long lastChange = 0;
int combinedTime = 200;
int counter;
void setup() {
sevseg.begin(hardwareConfig, numOfDigits, digitPins, segmentPins, resistorsOnSegments,
updateWithDelays, leadingZeros, disableDecPoint);
sevseg.setBrightness(90);
startBtn.init(startPin);
resetBtn.init(resetPin);
resetCounter();
}
void loop() {
countDownMachine();
sevseg.refreshDisplay(); // Must run repeatedly
}
void countDownMachine() {
switch (mode) {
case MODE::IDLE:
if (startBtn.pressed()) {
mode = MODE::COUNTDOWN;
lastChange = millis(); // the first second starts with the
}
break;
case MODE::COUNTDOWN:
countDown();
// The btnClass makes sure that you have to release a button before a next
// pressed() event can become true
// Therefore you can use the startBtn here also to reset the counter
// To do this uncomment the following line
//if (startBtn.pressed()) {
// and comment this line:
if (resetBtn.pressed()) {
mode = MODE::RESET;
}
break;
case MODE::RESET:
resetCounter();
mode = MODE::IDLE;
break;
}
}
void resetCounter() {
counter = startCount;
setTimer();
mode = MODE::IDLE;
}
void countDown() {
if (millis() - lastChange >= countDownInterval && counter > minCount) {
lastChange = millis();
counter--;
}
setTimer();
}
void setTimer() {
int minutes = counter / 60;
int seconds = counter - minutes * 60;
int combinedTime = minutes * 100 + seconds;
sevseg.setNumber(combinedTime, 2);
}