// ESP32 code for a 4-digit Neopixel countdown timer (MM:SS format) + adjustable time + 24/14 second indicator with 2 strips
// Main timer: 4 strips for MM:SS, starts at 08:00, counts down when button pressed
// Adjustment buttons: Add/subtract minutes (Button 3 as shifter for subtract)
// 24/14 indicator: Strips 5-6 display countdown when running, mode when not; strips 7-8 always show ball possession
// Buzzer: Plays for 5 seconds when main timer reaches 00:00, 3 seconds when shotclock reaches 00, or manually while BUTTON_ADD2_PIN is pressed
// All LEDs in red color
// Non-blocking using millis()
#include <Adafruit_NeoPixel.h>
// Pin definitions (updated as per user request)
#define STRIP1_PIN 10 // Main: Minutes Tens
#define STRIP2_PIN 11 // Main: Minutes Units
#define STRIP3_PIN 2 // Main: Seconds Tens
#define STRIP4_PIN 3 // Main: Seconds Units
#define STRIP5_PIN 1 // 24/14: Tens (e.g., "2")
#define STRIP6_PIN 8 // 24/14: Units (e.g., "4")
#define STRIP7_PIN 0 // Ball possession strip 1 (6 LEDs)
#define STRIP8_PIN 7 // Ball possession strip 2 (6 LEDs)
#define BUZZER_PIN 15
#define BUTTON_PIN 13 // Main timer start/pause/resume button
#define BUTTON2_PIN 9 // Switch between 24 and 14 seconds
#define BUTTON_ADD1_PIN 18 // Add/subtract 1 minute
#define BUTTON_ADD2_PIN 19 // Manual buzzer control (on while pressed)
#define BUTTON_SHIFTER_PIN 20 // Shifter for subtract mode
#define BUTTON3_PIN 21 // Toggle ball possession strip and reset timer
// Number of LEDs per strip (original 14, new 6)
#define LEDS_PER_STRIP_MAIN 14
#define LEDS_PER_STRIP_INDICATOR 6
// Create NeoPixel objects for each strip
Adafruit_NeoPixel strip1(LEDS_PER_STRIP_MAIN, STRIP1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2(LEDS_PER_STRIP_MAIN, STRIP2_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip3(LEDS_PER_STRIP_MAIN, STRIP3_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip4(LEDS_PER_STRIP_MAIN, STRIP4_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip5(LEDS_PER_STRIP_MAIN, STRIP5_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip6(LEDS_PER_STRIP_MAIN, STRIP6_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip7(LEDS_PER_STRIP_INDICATOR, STRIP7_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip8(LEDS_PER_STRIP_INDICATOR, STRIP8_PIN, NEO_GRB + NEO_KHZ800);
// LED patterns for digits 0-9 (14 LEDs: 1=on, 0=off)
bool digitPatterns[10][14] = {
{1,1,1,1,1,1,0,0,1,1,1,1,1,1}, // 0
{1,1,0,0,0,0,0,0,1,1,0,0,0,0}, // 1
{0,0,1,1,1,1,1,1,1,1,1,1,0,0}, // 2
{1,1,1,1,0,0,1,1,1,1,1,1,0,0}, // 3
{1,1,0,0,0,0,1,1,1,1,0,0,1,1}, // 4
{1,1,1,1,0,0,1,1,0,0,1,1,1,1}, // 5
{1,1,1,1,1,1,1,1,0,0,1,1,1,1}, // 6
{1,1,0,0,0,0,0,0,1,1,1,1,0,0}, // 7
{1,1,1,1,1,1,1,1,1,1,1,1,1,1}, // 8
{1,1,1,1,0,0,1,1,1,1,1,1,1,1} // 9
};
// Main timer variables
unsigned long mainPreviousMillis = 0;
const unsigned long interval = 1000; // 1 second countdown
bool mainTimerRunning = false;
int totalMinutes = 8; // Start with 8 minutes
int seconds = 0; // Start with 0 seconds
// 24/14 timer variables
unsigned long indicatorPreviousMillis = 0;
bool indicatorRunning = false;
int indicatorDuration = 24; // Default 24 seconds, switchable to 14
int indicatorSeconds = 24; // Current countdown
bool useStrip7 = true; // Toggle between strip7 and strip8
// Buzzer variables
bool buzzerActive = false;
unsigned long buzzerEndTime = 0;
// Button debouncing variables
unsigned long lastButtonPress = 0;
unsigned long lastButton2Press = 0;
unsigned long lastAdd1Press = 0;
unsigned long lastButton3Press = 0;
const unsigned long debounceDelay = 200; // 200ms debounce
// Manual buzzer button state tracking
bool previousButtonAdd2State = HIGH; // Assuming INPUT_PULLUP, HIGH when not pressed
// Colors (all red)
uint32_t onColor = strip1.Color(255, 0, 0); // Red for on
uint32_t offColor = strip1.Color(0, 0, 0); // Off
void setup() {
Serial.begin(115200);
// Initialize all strips
strip1.begin(); strip1.show();
strip2.begin(); strip2.show();
strip3.begin(); strip3.show();
strip4.begin(); strip4.show();
strip5.begin(); strip5.show();
strip6.begin(); strip6.show();
strip7.begin(); strip7.show();
strip8.begin(); strip8.show();
// Buzzer and buttons
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUTTON2_PIN, INPUT_PULLUP);
pinMode(BUTTON_ADD1_PIN, INPUT_PULLUP);
pinMode(BUTTON_ADD2_PIN, INPUT_PULLUP);
pinMode(BUTTON_SHIFTER_PIN, INPUT_PULLUP);
pinMode(BUTTON3_PIN, INPUT_PULLUP);
// Initial displays
displayMainTime(totalMinutes, seconds);
updateIndicatorDisplay();
Serial.println("Timers ready. Press button to start main countdown from 08:00. Use add1 button to adjust main time. BUTTON2 switches 24/14. BUTTON3 toggles ball possession and resets indicator timer. BUTTON_ADD2_PIN controls buzzer manually (on while pressed). Hold shifter and press main button to reset everything.");
}
void loop() {
unsigned long currentMillis = millis();
// Buzzer control
if (buzzerActive && currentMillis >= buzzerEndTime) {
digitalWrite(BUZZER_PIN, LOW);
buzzerActive = false;
}
// Manual buzzer control with serial prints
bool currentButtonAdd2State = digitalRead(BUTTON_ADD2_PIN);
if (currentButtonAdd2State == LOW && previousButtonAdd2State == HIGH) {
Serial.println("Buzzer is on");
}
if (currentButtonAdd2State == HIGH && previousButtonAdd2State == LOW) {
Serial.println("Buzzer is off");
}
previousButtonAdd2State = currentButtonAdd2State;
if (digitalRead(BUTTON_ADD2_PIN) == LOW) {
digitalWrite(BUZZER_PIN, HIGH);
} else if (!buzzerActive) {
digitalWrite(BUZZER_PIN, LOW);
}
// Main timer start/pause/resume/reset button
if (digitalRead(BUTTON_PIN) == LOW && (currentMillis - lastButtonPress) > debounceDelay) {
if (digitalRead(BUTTON_SHIFTER_PIN) == LOW) {
// Reset everything
mainTimerRunning = false;
indicatorRunning = false;
totalMinutes = 8;
seconds = 0;
indicatorSeconds = indicatorDuration;
buzzerActive = false;
digitalWrite(BUZZER_PIN, LOW);
displayMainTime(totalMinutes, seconds);
updateIndicatorDisplay();
Serial.println("Everything reset!");
} else {
// Normal toggle
mainTimerRunning = !mainTimerRunning;
if (mainTimerRunning) {
indicatorRunning = true;
indicatorSeconds = indicatorDuration;
indicatorPreviousMillis = currentMillis;
Serial.println("Running");
} else {
indicatorRunning = false;
Serial.println("Paused");
}
}
lastButtonPress = currentMillis;
}
// BUTTON2: Switch between 24 and 14 seconds
if (digitalRead(BUTTON2_PIN) == LOW && (currentMillis - lastButton2Press) > debounceDelay) {
indicatorDuration = (indicatorDuration == 24) ? 14 : 24;
indicatorSeconds = indicatorDuration; // Reset to new duration
updateIndicatorDisplay();
Serial.printf("Switched to %d seconds mode.\n", indicatorDuration);
lastButton2Press = currentMillis;
}
// Time adjustment buttons
bool shifterHeld = (digitalRead(BUTTON_SHIFTER_PIN) == LOW);
// Add1 button: +1 min if shifter not held, -1 min if held
if (digitalRead(BUTTON_ADD1_PIN) == LOW && (currentMillis - lastAdd1Press) > debounceDelay) {
int adjustment = shifterHeld ? -1 : 1;
totalMinutes = max(0, totalMinutes + adjustment);
lastAdd1Press = currentMillis;
displayMainTime(totalMinutes, seconds);
Serial.printf("Main time adjusted by %d minute(s): %02d:%02d\n", adjustment, totalMinutes, seconds);
}
// BUTTON3: Toggle ball possession strip and reset indicator timer
if (digitalRead(BUTTON3_PIN) == LOW && (currentMillis - lastButton3Press) > debounceDelay) {
useStrip7 = !useStrip7; // Alternate strip
indicatorSeconds = indicatorDuration; // Reset to current duration
if (mainTimerRunning) {
indicatorRunning = true;
indicatorPreviousMillis = currentMillis;
}
updateIndicatorDisplay();
Serial.printf("Ball possession toggled and indicator timer reset to %d seconds on strip %d.\n", indicatorDuration, useStrip7 ? 7 : 8);
lastButton3Press = currentMillis;
}
// Main countdown logic
if (mainTimerRunning) {
if (currentMillis - mainPreviousMillis >= interval) {
mainPreviousMillis = currentMillis;
if (seconds == 0) {
if (totalMinutes == 0) {
mainTimerRunning = false; // Stop timer
indicatorRunning = false; // Stop indicator
// Start buzzer for 5 seconds
buzzerActive = true;
buzzerEndTime = currentMillis + 5000;
digitalWrite(BUZZER_PIN, HIGH);
Serial.println("Main timer finished! Buzzer on for 5 seconds.");
} else {
totalMinutes--;
seconds = 59;
}
} else {
seconds--;
}
Serial.printf("Main Time: %02d:%02d\n", totalMinutes, seconds);
displayMainTime(totalMinutes, seconds);
}
}
// Indicator countdown logic
if (indicatorRunning && mainTimerRunning) {
if (currentMillis - indicatorPreviousMillis >= interval) {
indicatorPreviousMillis = currentMillis;
if (indicatorSeconds > 0) {
indicatorSeconds--;
Serial.printf("Indicator Time: %02d\n", indicatorSeconds);
updateIndicatorDisplay();
} else {
indicatorRunning = false;
// Start buzzer for 3 seconds
buzzerActive = true;
buzzerEndTime = currentMillis + 3000;
digitalWrite(BUZZER_PIN, HIGH);
updateIndicatorDisplay();
Serial.println("Indicator timer finished. Buzzer on for 3 seconds.");
}
}
}
}
// Display digit on a strip
void displayDigit(Adafruit_NeoPixel &strip, int digit) {
for (int i = 0; i < LEDS_PER_STRIP_MAIN; i++) {
strip.setPixelColor(i, digitPatterns[digit][i] ? onColor : offColor);
}
strip.show();
}
// Display main time on strips 1-4 (MM:SS)
void displayMainTime(int min, int sec) {
int digits[4] = {min / 10, min % 10, sec / 10, sec % 10};
displayDigit(strip1, digits[0]);
displayDigit(strip2, digits[1]);
displayDigit(strip3, digits[2]);
displayDigit(strip4, digits[3]);
}
// Update indicator display: display countdown on strips 5-6 if running, else mode; always light active ball possession strip
void updateIndicatorDisplay() {
// Display on strips 5-6: countdown if running, else mode
int displayValue = indicatorRunning ? indicatorSeconds : indicatorDuration;
int tens = displayValue / 10;
int units = displayValue % 10;
displayDigit(strip5, tens);
displayDigit(strip6, units);
// Always light the active ball possession strip
if (useStrip7) {
for (int i = 0; i < LEDS_PER_STRIP_INDICATOR; i++) {
strip7.setPixelColor(i, onColor);
strip8.setPixelColor(i, offColor);
}
strip7.show();
strip8.show();
} else {
for (int i = 0; i < LEDS_PER_STRIP_INDICATOR; i++) {
strip7.setPixelColor(i, offColor);
strip8.setPixelColor(i, onColor);
}
strip7.show();
strip8.show();
}
}Loading
esp32-c6-devkitc-1
esp32-c6-devkitc-1
main timer
24/14 toggle
+/- 1 minute
horn
shifter
ball possession