#include <FastLED.h>
#include <avr/pgmspace.h>
// ================= HARDWARE CONFIGURATION =================
#define LED_PIN 12 // Data pin for LED matrix
#define COLOR_ORDER GRB // Color order (try RGB if colors wrong)
#define CHIPSET WS2812 // LED chip type
#define BRIGHTNESS 255 // Brightness (0-255)
// Matrix dimensions
const uint8_t matrixWidth = 32;
const uint8_t matrixHeight = 16;
#define NUM_LEDS (matrixWidth * matrixHeight)
CRGB leds[NUM_LEDS];
// ================= LAYOUT CONFIGURATION =================
// Change this based on your physical wiring
#define SERPENTINE true // Set to false if rows are all same direction
#define ROW_MAJOR true // Set to true for row-major layout
// ================= 5x7 FONT DATA =================
const uint8_t font5x7[96][5] PROGMEM = {
{0,0,0,0,0}, // Space
{0,0,95,0,0}, // !
{0,7,0,7,0}, // "
{20,127,20,127,20}, // #
{36,42,127,42,18}, // $
{35,19,8,100,98}, // %
{54,73,86,32,80}, // &
{0,7,0,0,0}, // '
{0,28,34,65,0}, // (
{0,65,34,28,0}, // )
{20,8,62,8,20}, // *
{8,8,62,8,8}, // +
{0,160,96,0,0}, // ,
{8,8,8,8,8}, // -
{0,96,96,0,0}, // .
{32,16,8,4,2}, // /
{62,81,73,69,62}, // 0
{0,66,127,64,0}, // 1
{98,81,73,73,70}, // 2
{34,65,73,73,54}, // 3
{24,20,18,127,16}, // 4
{39,69,69,69,57}, // 5
{60,74,73,73,48}, // 6
{1,113,9,5,3}, // 7
{54,73,73,73,54}, // 8
{6,73,73,41,30}, // 9
{0,54,54,0,0}, // :
{0,182,102,0,0}, // ;
{8,20,34,65,0}, // <
{20,20,20,20,20}, // =
{0,65,34,20,8}, // >
{2,1,89,9,6}, // ?
{62,65,93,85,30}, // @
{126,17,17,17,126}, // A
{127,73,73,73,54}, // B
{62,65,65,65,34}, // C
{127,65,65,34,28}, // D
{127,73,73,73,65}, // E
{127,9,9,9,1}, // F
{62,65,73,73,122}, // G
{127,8,8,8,127}, // H
{0,65,127,65,0}, // I
{32,64,65,63,1}, // J
{127,8,20,34,65}, // K
{127,64,64,64,64}, // L
{127,2,12,2,127}, // M
{127,4,8,16,127}, // N
{62,65,65,65,62}, // O
{127,9,9,9,6}, // P
{62,65,97,33,94}, // Q
{127,9,25,41,70}, // R
{38,73,73,73,50}, // S
{1,1,127,1,1}, // T
{63,64,64,64,63}, // U
{31,32,64,32,31}, // V
{63,64,56,64,63}, // W
{99,20,8,20,99}, // X
{3,4,120,4,3}, // Y
{97,81,73,69,67} // Z
};
// ================= XY CONVERSION FUNCTION =================
uint16_t XY(uint8_t x, uint8_t y) {
if (x >= matrixWidth || y >= matrixHeight) return NUM_LEDS - 1;
uint16_t index;
if (SERPENTINE && (y & 1)) {
// Odd row: reverse direction
index = (y * matrixWidth) + (matrixWidth - 1 - x);
} else {
// Even row: normal direction
index = (y * matrixWidth) + x;
}
return index;
}
// ================= DRAW CHARACTER FUNCTION =================
void drawChar(int x, int y, char c, CRGB color) {
if (c < 32 || c > 127) return;
for (uint8_t col = 0; col < 5; col++) {
uint8_t line = pgm_read_byte(&font5x7[c - 32][col]);
for (uint8_t row = 0; row < 7; row++) {
if (line & (1 << row)) {
uint16_t ledIndex = XY(x + col, y + row);
if (ledIndex < NUM_LEDS) {
leds[ledIndex] = color;
}
}
}
}
}
// ================= DISPLAY NUMBER FUNCTION =================
void displayNumber(int num, CRGB color = CRGB::Red) {
FastLED.clear();
// Convert number to string
char buf[10];
sprintf(buf, "%d", num);
// Calculate width and position
int textWidth = strlen(buf) * 6; // 5 pixels + 1 space
int startX = (matrixWidth - textWidth) / 2;
int startY = 4; // Center vertically (16-7=9, 9/2≈4)
// Make sure startX is not negative
if (startX < 0) startX = 0;
// Draw each character
for (uint16_t i = 0; buf[i] != '\0'; i++) {
drawChar(startX + i * 6, startY, buf[i], color);
}
FastLED.show();
}
// ================= DISPLAY TEXT FUNCTION =================
void displayText(const char* text, CRGB color = CRGB::Red) {
FastLED.clear();
int textWidth = strlen(text) * 6;
int startX = (matrixWidth - textWidth) / 2;
int startY = 4;
if (startX < 0) startX = 0;
for (uint16_t i = 0; text[i] != '\0'; i++) {
drawChar(startX + i * 6, startY, text[i], color);
}
FastLED.show();
}
// ================= ANIMATION FUNCTIONS =================
void blinkNumber(int num, int times = 3) {
for (int i = 0; i < times; i++) {
displayNumber(num, CRGB::Red);
delay(300);
FastLED.clear();
FastLED.show();
delay(200);
}
displayNumber(num, CRGB::Red);
}
void countdownCompleteAnimation() {
// Flash "DONE" message
for (int i = 0; i < 5; i++) {
displayText("DONE", CRGB::Green);
delay(300);
FastLED.clear();
FastLED.show();
delay(200);
}
// Rainbow effect
for (int i = 0; i < 255; i+=10) {
fill_rainbow(leds, NUM_LEDS, i, 10);
FastLED.show();
delay(50);
}
FastLED.clear();
FastLED.show();
}
// ================= SERIAL VARIABLES =================
String serialBuffer = "";
int countdownValue = 0;
unsigned long lastUpdate = 0;
bool countdownActive = false;
bool countdownPaused = false;
unsigned long pauseTime = 0;
// ================= READ SERIAL COMMANDS =================
void processSerialCommand(String cmd) {
cmd.trim();
cmd.toUpperCase();
if (cmd == "HELP" || cmd == "?") {
Serial.println("\n=== Countdown Commands ===");
Serial.println("Enter number - Start countdown (e.g., 60)");
Serial.println("PAUSE - Pause countdown");
Serial.println("RESUME - Resume countdown");
Serial.println("STOP - Stop countdown");
Serial.println("RESET - Reset to 0");
Serial.println("TEST - Test display");
Serial.println("HELP - Show this menu");
Serial.println("========================\n");
}
else if (cmd == "PAUSE") {
if (countdownActive && !countdownPaused) {
countdownPaused = true;
pauseTime = millis() - lastUpdate;
displayText("PAUSE", CRGB::Yellow);
Serial.println("Countdown paused");
}
}
else if (cmd == "RESUME") {
if (countdownActive && countdownPaused) {
countdownPaused = false;
lastUpdate = millis() - pauseTime;
displayNumber(countdownValue, CRGB::Red);
Serial.println("Countdown resumed");
}
}
else if (cmd == "STOP") {
countdownActive = false;
countdownPaused = false;
FastLED.clear();
FastLED.show();
Serial.println("Countdown stopped");
}
else if (cmd == "RESET") {
countdownActive = false;
countdownPaused = false;
countdownValue = 0;
displayNumber(0, CRGB::White);
Serial.println("Reset to 0");
}
else if (cmd == "TEST") {
// Run display test
Serial.println("Running display test...");
displayText("TEST", CRGB::Red);
delay(1000);
for (int i = 0; i <= 9; i++) {
displayNumber(i, CRGB::Green);
delay(500);
}
displayText("DONE", CRGB::Blue);
delay(1000);
}
else if (cmd.length() > 0) {
// Check if it's a number
boolean isValid = true;
for (int i = 0; i < cmd.length(); i++) {
if (!isDigit(cmd[i])) {
isValid = false;
break;
}
}
if (isValid) {
countdownValue = cmd.toInt();
if (countdownValue > 99999) countdownValue = 99999;
countdownActive = true;
countdownPaused = false;
lastUpdate = millis();
displayNumber(countdownValue, CRGB::Red);
Serial.print("Countdown started: ");
Serial.println(countdownValue);
} else {
Serial.println("Unknown command. Type HELP for commands.");
}
}
}
// ================= SETUP =================
void setup() {
Serial.begin(9600);
Serial.println("\n=================================");
Serial.println("16x32 LED Matrix Countdown Timer");
Serial.println("=================================");
Serial.println("Type HELP for commands");
Serial.println("Enter seconds (1-99999) to start");
// Initialize LED matrix
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
// Clear display
FastLED.clear();
FastLED.show();
// Show ready message
displayText("READY", CRGB::Green);
delay(1500);
FastLED.clear();
FastLED.show();
Serial.println("Ready!");
}
// ================= MAIN LOOP =================
void loop() {
// Check for serial input
while (Serial.available()) {
char c = Serial.read();
if (c == '\n' || c == '\r') {
if (serialBuffer.length() > 0) {
processSerialCommand(serialBuffer);
serialBuffer = "";
}
} else {
serialBuffer += c;
}
}
// Update countdown
if (countdownActive && !countdownPaused) {
unsigned long now = millis();
if (now - lastUpdate >= 1000) {
countdownValue--;
lastUpdate = now;
if (countdownValue <= 0) {
// Countdown finished
countdownValue = 0;
countdownActive = false;
displayNumber(0, CRGB::Green);
Serial.println("COUNTDOWN FINISHED!");
countdownCompleteAnimation();
} else {
// Update display
displayNumber(countdownValue, CRGB::Red);
// Warning at last 10 seconds (blink)
if (countdownValue <= 10 && countdownValue > 0) {
if (countdownValue % 2 == 0) {
displayNumber(countdownValue, CRGB::Yellow);
}
}
Serial.println(countdownValue);
}
}
}
}