/*
Four digit Countdown Timer
Written for common cathode
In a real circuit the digit pins should have transistor drivers.
Google AI wrote the CounterButton class
*/
#include <mechButton.h>
#include "buttons.h"
// Global Configuration
const int START_TIME = 60; // default to 1 minute countdown
const int MIN_VAL = 0;
const int MAX_VAL = 5999; // 99m 59s max
const int NUM_DIGITS = 4;
const int BUZZ_PIN = A4;
const int COLON_PIN = A0;
const int DIG_PINS[] = {2, 3, 4, 5};
const int SEG_PINS[] = {11, 13, 7, 9, 10, 12, 6, 8};
const bool DIGITS[][8] = {
// A B C D E F G DP
{1, 1, 1, 1, 1, 1, 0, 0}, // 0
{0, 1, 1, 0, 0, 0, 0, 0}, // 1
{1, 1, 0, 1, 1, 0, 1, 0}, // 2
{1, 1, 1, 1, 0, 0, 1, 0}, // 3
{0, 1, 1, 0, 0, 1, 1, 0}, // 4
{1, 0, 1, 1, 0, 1, 1, 0}, // 5
{1, 0, 1, 1, 1, 1, 1, 0}, // 6
{1, 1, 1, 0, 0, 0, 0, 0}, // 7
{1, 1, 1, 1, 1, 1, 1, 0}, // 8
{1, 1, 1, 1, 0, 1, 1, 0}, // 9
{0, 0, 0, 0, 0, 0, 0, 0} // blank
};
// System State
bool isRunning = false;
bool alarmActive = false; // Tracks if the buzzer alarm is currently ringing
int runCounter = START_TIME; // default seconds to countdown from
int setCounter = START_TIME;
unsigned long lastTickTime = 0;
unsigned long alarmStartTime = 0;
// Instantiate buttons
CounterButton btnUp(A2);
CounterButton btnDown(A1);
mechButton btnStartPause(A3);
void decrementCounter() {
if (runCounter > MIN_VAL) {
runCounter--;
setCounter = runCounter;
printCounter();
}
}
void incrementCounter() {
if (runCounter < MAX_VAL) {
runCounter++;
setCounter = runCounter;
printCounter();
}
}
void onTimerFinished() {
isRunning = false;
alarmActive = true;
alarmStartTime = millis(); // Set time anchor for the buzzer routine
Serial.println("***********************");
Serial.println(" ALARM: TIME'S UP! ");
Serial.println("***********************");
}
void printCounter() {
Serial.print("Time Remaining: ");
Serial.print(runCounter);
Serial.println("s");
}
void toggleStartPause() {
if (!btnStartPause.trueFalse()) {
// If the alarm is currently ringing, clicking this button instantly silences it
if (alarmActive) {
noTone(BUZZ_PIN);
alarmActive = false;
runCounter = setCounter; ///////////////////////////////////
Serial.println(">>> ALARM SILENCED BY USER <<<");
return;
}
isRunning = !isRunning;
if (isRunning) {
Serial.println(">>> TIMER STARTED <<<");
lastTickTime = millis();
tone(BUZZ_PIN, 1500, 100); // Friendly start tone
} else {
Serial.println(">>> TIMER PAUSED <<<");
tone(BUZZ_PIN, 1200, 100); // Friendly pause tone
}
}
}
void writeDigit(int digit, int value) {
for (int seg = 0; seg < 8; seg++) {
digitalWrite(SEG_PINS[seg], DIGITS[value][seg]); // !DIGITS for common anode
}
digitalWrite(DIG_PINS[digit], LOW); // invert for common anode
digitalWrite(DIG_PINS[digit], HIGH); // invert for common anode
}
void updateDisplay(int value) {
int minutes = value / 60;
int seconds = value % 60;
writeDigit(0, (minutes / 10) ? (minutes / 10) : 10);
writeDigit(1, minutes % 10);
writeDigit(2, seconds / 10);
writeDigit(3, seconds % 10);
}
void setup() {
Serial.begin(115200);
pinMode(BUZZ_PIN, OUTPUT);
pinMode(COLON_PIN, OUTPUT);
for (int digit = 0; digit < NUM_DIGITS; digit++) {
pinMode(DIG_PINS[digit], OUTPUT);
}
for (int segment = 0; segment < 8; segment++) {
pinMode(SEG_PINS[segment], OUTPUT);
}
digitalWrite(COLON_PIN, HIGH);
btnUp.setActionCallback(incrementCounter);
btnDown.setActionCallback(decrementCounter);
btnStartPause.setCallback(toggleStartPause);
Serial.println("--- Countdown Timer Ready ---");
printCounter();
Serial.println("Status: PAUSED");
}
void loop() {
idle(); // Background debouncer for all 3 buttons
// 1. If paused and alarm isn't ringing, allow user to adjust the timer setting
if (!isRunning && !alarmActive) {
btnUp.update();
btnDown.update();
}
// 2. If running, process the countdown clock
else if (isRunning) {
unsigned long currentMillis = millis();
if (currentMillis - lastTickTime >= 1000) {
lastTickTime = currentMillis;
if (runCounter > MIN_VAL) {
runCounter--;
printCounter();
// Play a short click tone on the buzzer every second for a "ticking" effect
if (runCounter > 0) {
tone(BUZZ_PIN, 2000, 20);
}
if (runCounter == MIN_VAL) {
onTimerFinished();
}
}
}
}
// 3. Non-blocking buzzer alarm control
if (alarmActive) {
unsigned long elapsedAlarm = millis() - alarmStartTime;
// Ring the alarm for a total of 3 seconds
if (elapsedAlarm < 3000) {
// Create a pulsing beep pattern (on for 250ms, off for 250ms)
if ((elapsedAlarm / 250) % 2 == 0) {
tone(BUZZ_PIN, 1000); // Ring at a clear 1000 Hz tone
} else {
noTone(BUZZ_PIN); // Pause sound
}
} else {
// 3 seconds are up, silence the alarm
noTone(BUZZ_PIN);
alarmActive = false;
runCounter = setCounter; /////////////////////////////////
Serial.println("Alarm stopped automatically.");
}
}
// refresh the display frequently
updateDisplay(runCounter);
}
Start /
Pause
+
-