Smart Bell V3.0: Industrial STM32 Controller featuring Exam Mode, Auto-Scheduling, and Punjabi Audio
The STM32 Smart School Bell V3.0 is an industrial-grade automation system developed in the Arduino IDE. It features an enclosure precision-engineered in Solid Edge 2025 for 3D printing and high-fidelity Punjabi voice alerts synthesized via ElevenLabs AI. With automated timetable generation and a robust hardware self-test diagnostic suite, it offers a professional, localized solution for modern schools and industrial facilities.
Devices & Components
DFPlayer Mini mp3 Player
STM32F103C8T6 *optional
push buttons
LCD 20x4 with I2C module
8GB Micro SD Class 10
AT24C256 Serial EEPROM I2C Interface EEPROM Data Storage Module For Arduino
5v relay module
Capacitor 22pF
Active buzzer
CR2025 Coin Battery Socket Holder
MAX7219 8x8 Dot Matrix Display Module
Hardware & Tools
3d printer
nuts for screws
Soldering Iron Kit
Jumper wires (Male to Female)
Crystal Oscillator 32.768khz
Digital Multimeter
Adjustable power supply
Software & Tools
Arduino IDE
Solid Edge 2025
ChatGPT
Google Gemini
Project description
Code
Automatic School Bell V3
c
Automatic School Bell V3.0
1#include <Wire.h> 2#include <LiquidCrystal_I2C.h> 3#include <LedControl.h> 4#include <string.h> 5#include <STM32RTC.h> 6#include <DFRobotDFPlayerMini.h> 7 8/* ================= BUTTON STRUCT ================= */ 9struct ButtonState { 10 bool last; 11 unsigned long lastTime; 12}; 13 14ButtonState btnSet = {true, 0}; 15ButtonState btnUp = {true, 0}; 16ButtonState btnDown = {true, 0}; 17 18/* ================= HARDWARE ================= */ 19#define BTN_SET PB14 20#define BTN_UP PB13 21#define BTN_DOWN PB12 22 23#define EEPROM_ADDR 0x50 24#define MAX_BELLS 20 25#define TT_NAME_LEN 12 26#define EEPROM_BASE 0 27 28 29#define EEPROM_TTCOUNT_L 0 30#define EEPROM_TTCOUNT_H 1 31#define EEPROM_DEFAULT_TT 2 32#define EEPROM_TT_START 3 33 34 35/* ---------- 8x8 MAX7219 ---------- */ 36#define MATRIX_DIN PA7 37#define MATRIX_CLK PA5 38#define MATRIX_CS PA4 39 40#define BUZZER_PIN PA2 41#define RELAY_PIN PA1 42 43 44 45/* -------- DFPLAYER -------- */ 46#define MP3_BUSY_PIN PB1 47HardwareSerial mp3Serial(PA10, PA9); // RX, TX 48 49/////////////////////// Start Exam timer ////////////// 50bool examRunning = false; 51bool examScheduled = false; 52uint32_t examStartSec = 0; 53uint32_t examDurationSec = 0; 54 55 56//examScheduled = true; 57 58 59uint8_t examStartH = 0; 60uint8_t examStartM = 0; 61 62uint8_t examDurH = 3; // default 3 hours 63uint8_t examDurM = 0; 64uint16_t selectedTimetableIndex = 0; 65 66//////////////////////End Exam Timer 67/* ================= UI ================= */ 68enum UiState { 69 UI_DASHBOARD, 70 UI_MENU, 71 UI_SET_DATETIME, 72 UI_SET_BELL, 73 UI_MANAGE_Timetable, 74 UI_CLEAR_EEPROM, 75 UI_TRIGGER_BELL, 76 UI_EXAM_TIMER, 77 UI_AUTO_BELLS, 78 UI_SELFTEST 79}; 80 81UiState uiState = UI_DASHBOARD; 82bool screenChanged = true; 83 84 85//HardwareSerial &mp3Serial = Serial1; 86DFRobotDFPlayerMini mp3; 87 88/* ================= BELL TYPE ================= */ 89enum BellType : uint8_t 90{ 91 BELL_PERIOD = 0, 92 BELL_PRAYER = 1, 93 BELL_RECESS = 2, 94 BELL_END = 3 95}; 96 97LedControl matrix = LedControl(MATRIX_DIN, MATRIX_CLK, MATRIX_CS, 1); 98LiquidCrystal_I2C lcd(0x27, 20, 4); 99 100STM32RTC& rtc = STM32RTC::getInstance(); 101 102/* ================= DATA ================= */ 103uint8_t bellCount = 0; 104uint8_t bellHour[MAX_BELLS]; 105uint8_t bellMin[MAX_BELLS]; 106uint8_t bellType[MAX_BELLS]; 107 108 109bool ttInfoEmbedded = false; 110bool bellActive = false; 111 112/* ---- mp3 runtime ---- */ 113bool mp3Playing = false; 114int activeBellIndex = -1; 115 116/* ================= Time Table ================= */ 117void readTimetableName(uint16_t index, char *out) 118{ 119 if (index >= getTimetableCount()) 120 { 121 out[0] = 0; 122 return; 123 } 124 125 uint16_t addr = getTimetableAddress(index); 126 127 for (uint8_t i = 0; i < TT_NAME_LEN; i++) 128 out[i] = eepromReadByte(addr + i); 129 130 out[TT_NAME_LEN] = 0; 131} 132 133 134 135 136 137 138 139/* ================= Time Table ================= */ 140bool deleteTimetable(uint16_t index) 141{ 142 uint16_t total = getTimetableCount(); 143 if (total == 0 || index >= total) return false; 144 145 uint16_t addrDel = getTimetableAddress(index); 146 uint16_t addrNext = getTimetableAddress(index + 1); 147 uint16_t addrEnd = getTimetableAddress(total); 148 149 // Shift EEPROM data to close the gap 150 for (uint16_t a = addrNext; a < addrEnd; a++) 151 { 152 uint8_t v = eepromReadByte(a); 153 eepromWriteByte(addrDel++, v); 154 } 155 156 setTimetableCount(total - 1); 157 158 uint8_t def = getDefaultTimetable(); 159 if (def == index) 160 { 161 setDefaultTimetable(0); // Reset to first if current was deleted 162 } 163 else if (def > index) 164 { 165 setDefaultTimetable(def - 1); // Shift index down 166 } 167 168 // Reload the current default into RAM so the bells update immediately 169 loadTimetable(getDefaultTimetable()); 170 171 return true; 172} 173 174/* ================= Time Table ================= */ 175uint8_t getTimetableBellCount(uint16_t index) 176{ 177 uint16_t addr = getTimetableAddress(index); 178 addr += TT_NAME_LEN; 179 return eepromReadByte(addr); 180} 181/* ================= Time Table ================= */ 182void readTimetableBell(uint16_t ttIndex, 183 uint8_t bellIndex, 184 uint8_t &h, 185 uint8_t &m, 186 uint8_t &t) 187{ 188 uint16_t addr = getTimetableAddress(ttIndex); 189 190 addr += TT_NAME_LEN; // skip name 191 uint8_t cnt = eepromReadByte(addr++); 192 if (bellIndex >= cnt) return; 193 194 addr += bellIndex * 3; 195 196 h = eepromReadByte(addr++); 197 m = eepromReadByte(addr++); 198 t = eepromReadByte(addr++); 199} 200/* ================= Time Table ================= */ 201void screenTimetableInfo(uint16_t ttIndex) 202{ 203 static uint8_t top = 0; 204 205 if (screenChanged) 206 { 207 top = 0; 208 lcd.clear(); 209 lcd.noBlink(); 210 screenChanged = false; 211 } 212 213 uint8_t total = getTimetableBellCount(ttIndex); 214 215 char name[TT_NAME_LEN+1]; 216 readTimetableName(ttIndex, name); 217 218 char line[21]; 219 220 // row 0 – name 221 snprintf(line, sizeof(line), "TT: %s", name); 222 lcdPrintRow(0, line); 223 224 // rows 1 & 2 – two bells per page 225 for (uint8_t r = 0; r < 2; r++) 226 { 227 uint8_t i = top + r; 228 229 if (i < total) 230 { 231 uint8_t h,m,t; 232 readTimetableBell(ttIndex, i, h, m, t); 233 234 char c; 235 if (t == BELL_PERIOD) c = 'C'; 236 else if (t == BELL_PRAYER) c = 'P'; 237 else if (t == BELL_RECESS) c = 'R'; 238 else if (t == BELL_END) c = 'E'; 239 else c = '?'; 240 241 snprintf(line, sizeof(line), 242 "B%02d %02d:%02d %c", 243 i+1, h, m, c); 244 245 lcdPrintRow(1+r, line); 246 } 247 else 248 lcdPrintRow(1+r, ""); 249 } 250 251 lcdPrintRow(3, "UP/DN SCROLL SET"); 252 253 if (readButton(BTN_DOWN, btnDown)) 254 { 255 if (top + 2 < total) top++; 256 } 257 258 if (readButton(BTN_UP, btnUp)) 259 { 260 if (top > 0) top--; 261 } 262 263 if (readButton(BTN_SET, btnSet)) 264 { 265 uiState = UI_MENU; // or back to list (see note below) 266 screenChanged = true; 267 } 268} 269/* ================= Time Table ================= */ 270/* ================= Time Table ================= */ 271/* ================= Time Table ================= */ 272/* ================= Time Table ================= */ 273/* ================= Time Table ================= */ 274/* ================= Time Table ================= */ 275/* ================= Time Table ================= */ 276/* ================= Time Table ================= */ 277 278void eepromWriteBlock(uint16_t addr, const uint8_t *buf, uint16_t len) 279{ 280 for (uint16_t i = 0; i < len; i++) 281 eepromWriteByte(addr + i, buf[i]); 282} 283 284void eepromReadBlock(uint16_t addr, uint8_t *buf, uint16_t len) 285{ 286 for (uint16_t i = 0; i < len; i++) 287 buf[i] = eepromReadByte(addr + i); 288} 289 290uint16_t getTimetableCount() 291{ 292 uint16_t v = eepromReadByte(EEPROM_TTCOUNT_L); 293 v |= (uint16_t)eepromReadByte(EEPROM_TTCOUNT_H) << 8; 294 return v; 295} 296 297void setTimetableCount(uint16_t c) 298{ 299 eepromWriteByte(EEPROM_TTCOUNT_L, c & 0xFF); 300 eepromWriteByte(EEPROM_TTCOUNT_H, (c >> 8) & 0xFF); 301} 302 303uint8_t getDefaultTimetable() 304{ 305 return eepromReadByte(EEPROM_DEFAULT_TT); 306} 307 308void setDefaultTimetable(uint8_t index) 309{ 310 eepromWriteByte(EEPROM_DEFAULT_TT, index); 311} 312 313 314 315uint16_t getTimetableAddress(uint16_t index) 316{ 317 uint16_t addr = EEPROM_TT_START; 318 319 uint16_t total = getTimetableCount(); 320 if (index >= total) index = total; 321 322 for (uint16_t i = 0; i < index; i++) 323 { 324 addr += TT_NAME_LEN; 325 326 uint8_t cnt = eepromReadByte(addr); 327 328 // ---------- HARD SAFETY ---------- 329 if (cnt > MAX_BELLS) 330 cnt = 0; 331 // -------------------------------- 332 333 addr += 1; 334 addr += cnt * 3; 335 } 336 337 return addr; 338} 339 340 341const char *monthName(uint8_t m) 342{ 343 static const char *names[] = 344 {"JAN","FEB","MAR","APR","MAY","JUN", 345 "JUL","AUG","SEP","OCT","NOV","DEC"}; 346 347 if (m < 1 || m > 12) return "UNK"; 348 return names[m-1]; 349} 350 351uint8_t countMonthTimetables(const char *base) 352{ 353 uint16_t n = getTimetableCount(); 354 uint8_t cnt = 0; 355 356 for(uint16_t i=0;i<n;i++) 357 { 358 char name[TT_NAME_LEN+1]; 359 360 uint16_t addr = getTimetableAddress(i); 361 362 for(uint8_t j=0;j<TT_NAME_LEN;j++) 363 name[j] = eepromReadByte(addr+j); 364 365 name[TT_NAME_LEN] = 0; 366 367 if (strncmp(name, base, 3) == 0) 368 cnt++; 369 } 370 return cnt; 371} 372void makeAutoTimetableName(char *out) 373{ 374 uint16_t cnt = getTimetableCount(); 375 376 // first ever timetable 377 if (cnt == 0) 378 { 379 strcpy(out, "DEFAULT"); 380 return; 381 } 382 383 uint8_t m = rtc.getMonth(); 384 const char *base = monthName(m); 385 386 uint8_t already = countMonthTimetables(base); 387 388 if (already == 0) 389 { 390 strcpy(out, base); 391 } 392 else 393 { 394 snprintf(out, TT_NAME_LEN+1, "%s_%d", base, already+1); 395 } 396} 397 398bool saveCurrentTimetable(const char *name) 399{ 400 uint16_t ttCount = getTimetableCount(); 401 402 uint16_t addr = getTimetableAddress(ttCount); 403 404 // name 405 for (uint8_t i = 0; i < TT_NAME_LEN; i++) 406 { 407 uint8_t c = 0; 408 if (i < strlen(name)) c = name[i]; 409 eepromWriteByte(addr++, c); 410 } 411 412 // bell count 413 eepromWriteByte(addr++, bellCount); 414 415 // bells 416 for (uint8_t i = 0; i < bellCount; i++) 417 { 418 eepromWriteByte(addr++, bellHour[i]); 419 eepromWriteByte(addr++, bellMin[i]); 420 eepromWriteByte(addr++, bellType[i]); 421 } 422 423 setTimetableCount(ttCount + 1); 424 425 // only first timetable becomes default 426 if (ttCount == 0) 427 { 428 setDefaultTimetable(0); 429 } 430 431 return true; 432} 433 434 435 436 437 438 439bool loadTimetable(uint16_t index) 440{ 441 if (index >= getTimetableCount()) 442 return false; 443 444 uint16_t addr = getTimetableAddress(index); 445 446 // skip name 447 addr += TT_NAME_LEN; 448 449 uint8_t cnt = eepromReadByte(addr++); 450 451 if (cnt > MAX_BELLS) return false; 452 453 bellCount = cnt; 454 455 for (uint8_t i = 0; i < bellCount; i++) 456 { 457 bellHour[i] = eepromReadByte(addr++); 458 bellMin[i] = eepromReadByte(addr++); 459 bellType[i] = eepromReadByte(addr++); 460 } 461 462 return true; 463} 464 465 466/* ================= Time Table ================= */ 467 468 469/* ================= FONT ================= */ 470// index mapping 471// 0..9 -> digits 0..9 472// 10 -> C 473// 11 -> R 474// 12 -> P 475// 13 -> E 476 477const byte font8x8[][8] = 478{ 479/* 0 */ 480{0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, 481/* 1 */ 482{0x18,0x38,0x18,0x18,0x18,0x18,0x3C,0x00}, 483/* 2 */ 484{0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00}, 485/* 3 */ 486{0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, 487/* 4 */ 488{0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00}, 489/* 5 */ 490{0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, 491/* 6 */ 492{0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00}, 493/* 7 */ 494{0x7E,0x66,0x06,0x0C,0x18,0x18,0x18,0x00}, 495/* 8 */ 496{0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, 497/* 9 */ 498{0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00}, 499 500/* 10 = C */ 501{0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00}, 502 503/* 11 = R */ 504{0x7C,0x66,0x66,0x7C,0x6C,0x66,0x66,0x00}, 505 506/* 12 = P */ 507{0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00}, 508 509/* 13 = E */ 510{0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00} 511}; 512 513 514/* ================= MATRIX ================= */ 515void drawChar8x8(byte index) 516{ 517 matrix.clearDisplay(0); 518 519 for (int row = 0; row < 8; row++) 520 { 521 // vertical flip 522 byte line = font8x8[index][7 - row]; 523 524 for (int col = 0; col < 8; col++) 525 { 526 // horizontal flip 527 bool on = line & (1 << col); 528 529 matrix.setLed(0, row, col, on); 530 } 531 } 532} 533 534 535 536/* ================= MATRIX ================= */ 537/* ================= MATRIX ================= */ 538void showCountdownOnMatrix(long sec) 539{ 540 if (sec < 0) { 541 matrix.clearDisplay(0); 542 return; 543 } 544 545 if (sec > 60) sec = 60; 546 547 int cols = map(sec, 0, 60, 0, 8); 548 549 matrix.clearDisplay(0); 550 551 for (int c = 0; c < cols; c++) { 552 for (int r = 0; r < 8; r++) { 553 matrix.setLed(0, r, 7 - c, true); 554 } 555 } 556} 557 558/* ================= INDICATOR ================= */ 559void updateCountdownIndicators(long countdown) 560{ 561 static unsigned long t = 0; 562 static bool blink = false; 563 564 if (bellCount == 0 || countdown < 0) { 565 matrix.clearDisplay(0); 566 return; 567 } 568 569 if (countdown <= 10 && countdown >= 1) 570 { 571 if (millis() - t > 200) { 572 t = millis(); 573 blink = !blink; 574 } 575 576 if (blink) showCountdownOnMatrix(countdown); 577 else matrix.clearDisplay(0); 578 579 return; 580 } 581 582 if (countdown <= 60) { 583 showCountdownOnMatrix(countdown); 584 return; 585 } 586 587 matrix.clearDisplay(0); 588} 589 590 591/* ================= Time Table ================= */ 592/* ================= Time Table ================= */ 593 594void screenTimetableList() { 595 static enum { L_LIST, L_INFO, L_CONFIRM } layer = L_LIST; 596 static uint16_t sel = 0; 597 static uint16_t top = 0; 598 static uint8_t infoTop = 0; 599 static uint8_t operation = 0; // 1=Delete, 2=Default 600 static bool waitRelease = false; 601 602 uint16_t total = getTimetableCount(); 603 604 // 1. Safety: Wait for button release 605 if (waitRelease) { 606 if (digitalRead(BTN_SET) == HIGH && digitalRead(BTN_UP) == HIGH && digitalRead(BTN_DOWN) == HIGH) { 607 waitRelease = false; 608 } 609 return; 610 } 611 612 // 2. Screen Reset 613 if (screenChanged) { 614 layer = L_LIST; sel = 0; top = 0; 615 lcd.clear(); lcd.noBlink(); 616 screenChanged = false; 617 } 618 619 // 3. If EEPROM is empty 620 if (total == 0) { 621 lcdPrintRow(0, "NO TIMETABLES"); 622 lcdPrintRow(3, "SET = BACK"); 623 if (readButton(BTN_SET, btnSet)) { uiState = UI_MENU; screenChanged = true; } 624 return; 625 } 626 627 char name[TT_NAME_LEN + 1]; 628 readTimetableName(sel, name); 629 630 /* ================= LAYER: CONFIRMATION (YES/NO) ================= */ 631 if (layer == L_CONFIRM) { 632 lcdPrintRow(0, (operation == 1) ? "DELETE TABLE?" : "SET AS DEFAULT?"); 633 lcdPrintRow(1, name); 634 lcdPrintRow(3, "SET=YES UP=NO"); 635 636 if (readButton(BTN_UP, btnUp)) { // User chose NO 637 layer = L_INFO; 638 lcd.clear(); 639 } 640 if (readButton(BTN_SET, btnSet)) { // User chose YES 641 if (operation == 1) { 642 deleteTimetable(sel); 643 total = getTimetableCount(); 644 if (sel >= total && total > 0) sel = total - 1; 645 layer = L_LIST; 646 } else { 647 setDefaultTimetable(sel); 648 loadTimetable(sel); 649 layer = L_INFO; 650 } 651 waitRelease = true; 652 lcd.clear(); 653 } 654 return; 655 } 656 657 /* ================= LAYER: DETAIL INFO (Bell List) ================= */ 658 if (layer == L_INFO) { 659 uint8_t bellTotal = getTimetableBellCount(sel); 660 lcdPrintRow(0, name); 661 662 for (uint8_t r = 0; r < 2; r++) { 663 uint8_t i = infoTop + r; 664 if (i < bellTotal) { 665 uint8_t h, m, t; readTimetableBell(sel, i, h, m, t); 666 667 // Convert Type Number to Text 668 const char* typeText = "PERIOD"; 669 if (t == 1) typeText = "PRAYER"; 670 else if (t == 2) typeText = "RECESS"; 671 else if (t == 3) typeText = "END"; 672 673 char line[21]; 674 snprintf(line, sizeof(line), "%02d:%02d %s", h, m, typeText); 675 lcdPrintRow(r + 1, line); 676 } else lcdPrintRow(r + 1, ""); 677 } 678 lcdPrintRow(3, "UP/DN=SCR SET=BACK"); 679 680 if (readButton(BTN_DOWN, btnDown) && infoTop + 1 < bellTotal) infoTop++; 681 if (readButton(BTN_UP, btnUp) && infoTop > 0) infoTop--; 682 683 // COMBOS for Delete/Default 684 if (digitalRead(BTN_SET) == LOW) { 685 if (digitalRead(BTN_UP) == LOW) { operation = 2; layer = L_CONFIRM; waitRelease = true; lcd.clear(); return; } 686 if (digitalRead(BTN_DOWN) == LOW) { operation = 1; layer = L_CONFIRM; waitRelease = true; lcd.clear(); return; } 687 } 688 689 if (readButton(BTN_SET, btnSet)) { layer = L_LIST; infoTop = 0; lcd.clear(); } 690 return; 691 } 692 693 /* ================= LAYER: LIST VIEW (Main Selection) ================= */ 694 if (readButton(BTN_DOWN, btnDown) && sel + 1 < total) sel++; 695 if (readButton(BTN_UP, btnUp)) { 696 if (sel > 0) sel--; 697 else { // If user presses UP at the top of the list, go back to Menu 698 uiState = UI_MENU; 699 screenChanged = true; 700 return; 701 } 702 } 703 704 if (sel < top) top = sel; 705 if (sel >= top + 2) top = sel - 1; 706 707 lcdPrintRow(0, " SELECT TABLE"); 708 uint8_t defIdx = getDefaultTimetable(); 709 710 for (uint8_t r = 0; r < 2; r++) { 711 uint16_t idx = top + r; 712 if (idx < total) { 713 char tName[TT_NAME_LEN + 1]; readTimetableName(idx, tName); 714 char line[21]; 715 snprintf(line, sizeof(line), "%c %-12s %c", (idx == sel) ? '>' : ' ', tName, (idx == defIdx) ? '*' : ' '); 716 lcdPrintRow(r + 1, line); 717 } else lcdPrintRow(r + 1, ""); 718 } 719 lcdPrintRow(3, "SET=OPEN UP=EXIT"); 720 721 if (readButton(BTN_SET, btnSet)) { layer = L_INFO; lcd.clear(); } 722} 723 724 725 726/* ================= Time Table ================= */ 727/* ================= Time Table ================= */ 728 729 730 731 732 733 734 735 736 737 738/////////////////////// 739//////////////////////// 740///////////////////////// 741 742 743 744/* ================= Time Table ================= */ 745/* ================= EEPROM ================= */ 746void eepromWriteByte(uint16_t addr, uint8_t data) { 747 Wire.beginTransmission(EEPROM_ADDR); 748 Wire.write((uint8_t)(addr >> 8)); 749 Wire.write((uint8_t)(addr & 0xFF)); 750 Wire.write(data); 751 Wire.endTransmission(); 752 delay(10); 753} 754 755uint8_t eepromReadByte(uint16_t addr) { 756 Wire.beginTransmission(EEPROM_ADDR); 757 Wire.write(addr >> 8); 758 Wire.write(addr & 0xFF); 759 Wire.endTransmission(); 760 Wire.requestFrom(EEPROM_ADDR, (uint8_t)1); 761 return Wire.available() ? Wire.read() : 0; 762} 763 764/* ================= BUTTON ================= */ 765bool readButton(uint8_t pin, ButtonState &b) 766{ 767 bool now = digitalRead(pin); 768 769 if (now != b.last) { 770 b.lastTime = millis(); 771 b.last = now; 772 } 773 774 if ((millis() - b.lastTime) > 40) { 775 if (now == LOW) { 776 static unsigned long lock[3] = {0,0,0}; 777 778 unsigned long *lk; 779 if (pin == BTN_SET) lk = &lock[0]; 780 else if (pin == BTN_UP) lk = &lock[1]; 781 else lk = &lock[2]; 782 783 if (millis() - *lk > 300) { 784 *lk = millis(); 785 return true; 786 } 787 } 788 } 789 return false; 790} 791 792/* ================= LCD ================= */ 793void lcdPrintRow(uint8_t row, const char *txt) { 794 lcd.setCursor(0, row); 795 lcd.print(txt); 796 for (int i = strlen(txt); i < 20; i++) lcd.print(' '); 797} 798 799void showError(const char* line1, const char* line2) { 800 lcd.clear(); 801 lcdPrintRow(1, line1); 802 lcdPrintRow(2, line2); 803 delay(1500); 804} 805 806/* ================= TIME / BELL ================= */ 807int timeToMinutes(uint8_t h, uint8_t m) { 808 return h * 60 + m; 809} 810 811void getBellStatus(int ¤tBell, int &nextBell, long &countdownSec) { 812 813 currentBell = -1; 814 nextBell = -1; 815 countdownSec = -1; 816 817 if (bellCount == 0) return; 818 819 int nowMin = timeToMinutes(rtc.getHours(), rtc.getMinutes()); 820 int nowSec = rtc.getSeconds(); 821 822 for (int i = 0; i < bellCount; i++) { 823 824 int bellTimeMin = timeToMinutes(bellHour[i], bellMin[i]); 825 826 if (nowMin > bellTimeMin || (nowMin == bellTimeMin && nowSec > 0)) { 827 currentBell = i; 828 } 829 else { 830 nextBell = i; 831 int deltaMin = bellTimeMin - nowMin; 832 countdownSec = deltaMin * 60 - nowSec; 833 return; 834 } 835 } 836 837 currentBell = -1; 838 nextBell = -1; 839 countdownSec = -1; 840} 841 842/* ================= BELL HANDLER ================= */ 843void handleBell() 844{ 845 static unsigned long bellStart = 0; 846 static bool timerStarted = false; 847 848 if (bellActive && !timerStarted) 849 { 850 bellStart = millis(); 851 timerStarted = true; 852 } 853 854 if (bellActive && timerStarted) 855 { 856 if (millis() - bellStart >= 3000) 857 { 858 digitalWrite(RELAY_PIN, HIGH); 859 digitalWrite(BUZZER_PIN, LOW); 860 861 bellActive = false; 862 timerStarted = false; 863 } 864 } 865} 866 867 868 869/* ================= I2C SCAN ================= */ 870void scanI2C() { 871 for (byte a = 1; a < 127; a++) { 872 Wire.beginTransmission(a); 873 if (Wire.endTransmission() == 0) { 874 lcd.clear(); 875 lcdPrintRow(0, "I2C found:"); 876 lcd.setCursor(0,1); 877 lcd.print(a, HEX); 878 delay(300); 879 } 880 } 881} 882 883/* ================= DASHBOARD ================= */ 884char getBellSymbolChar(int index) 885{ 886 if (index < 0 || index >= bellCount) return '-'; 887 888 if (bellType[index] == BELL_PRAYER) return 'P'; 889 if (bellType[index] == BELL_RECESS) return 'R'; 890 if (bellType[index] == BELL_END) return 'E'; 891 892 // PERIOD 893 return 'C'; // N = normal / period 894} 895 896 897 898/* ================= DASHBOARD ================= */ 899 900 901/* ================= DASHBOARD ================= */ 902void screenDashboard() { 903 904 if (screenChanged) { 905 lcd.clear(); 906 lcd.noBlink(); 907 screenChanged = false; 908 } 909 910 char line0[21]; 911 912 snprintf(line0, sizeof(line0), 913 "%02d/%02d/%04d %02d:%02d:%02d", 914 rtc.getDay(), 915 rtc.getMonth(), 916 2000 + rtc.getYear(), 917 rtc.getHours(), 918 rtc.getMinutes(), 919 rtc.getSeconds()); 920 921 lcdPrintRow(0, line0); 922 923 int curBell, nextBell; 924 long countdown; 925 926 getBellStatus(curBell, nextBell, countdown); 927 928if (curBell >= 0) 929// snprintf(line0, sizeof(line0),"Current Bell:%2d %c",curBell + 1,getBellSymbolChar(curBell)); 930{ 931 uint8_t t = bellType[curBell]; 932 int periodNumber = 0; 933 934 // Calculate period number exactly like the Matrix logic does 935 for (int i = 0; i <= curBell; i++) { 936 if (bellType[i] == BELL_PERIOD) { 937 periodNumber++; 938 } 939 } 940 941 if (t == BELL_PRAYER) { 942 snprintf(line0, sizeof(line0), "Current: PRAYER [P]"); 943 } else if (t == BELL_RECESS) { 944 snprintf(line0, sizeof(line0), "Current: RECESS [R]"); 945 } else if (t == BELL_END) { 946 snprintf(line0, sizeof(line0), "Current: SCHOOL OFF[E]"); 947 } else { 948 // This matches the 1, 2, 3... shown on your Matrix 949 snprintf(line0, sizeof(line0), "Current: PERIOD %d", periodNumber); 950 } 951} else { 952 snprintf(line0, sizeof(line0), "Current: --"); 953} 954lcdPrintRow(1, line0); 955//else 956 //snprintf(line0, sizeof(line0), "Current Bell: --"); 957 958//lcdPrintRow(1, line0); 959 960 961 962 963 964 965if (nextBell >= 0) 966 snprintf(line0, sizeof(line0), 967 "Next Bell: %02d:%02d %c", 968 bellHour[nextBell], 969 bellMin[nextBell], 970 getBellSymbolChar(nextBell)); 971else 972 snprintf(line0, sizeof(line0), "Next Bell: --:--"); 973 974lcdPrintRow(2, line0); 975 976 if (countdown >= 0) { 977 978 int hh = countdown / 3600; 979 int mm = (countdown % 3600) / 60; 980 int ss = countdown % 60; 981 982 snprintf(line0, sizeof(line0), 983 "In: %02d:%02d:%02d", hh, mm, ss); 984 } 985 else { 986 snprintf(line0, sizeof(line0), "In: --:--:--"); 987 } 988 989 lcdPrintRow(3, line0); 990 991 if (readButton(BTN_SET, btnSet)) { 992 uiState = UI_MENU; 993 screenChanged = true; 994 } 995} 996 997/* ================= MENU ================= */ 998/* ================= MENU ================= */ 999void screenMenu() 1000{ 1001 static int sel = 0; 1002 static int top = 0; 1003 static bool waitRelease = false; 1004 1005 const char* items[] = { 1006 "Set Date & Time", 1007 "Set Bell", 1008 "Manage Timetable", 1009 "Clear EEPROM", 1010 "Trigger Bell Now", 1011 "Start Exam Timer", 1012 "Auto Generate Bells", 1013 "Self Test", 1014 "Return" 1015 }; 1016 1017 const int ITEM_COUNT = 9; 1018 const int VISIBLE = 3; 1019 1020 /* -------- screen entry -------- */ 1021 if (screenChanged) 1022 { 1023 sel = 0; 1024 top = 0; 1025 waitRelease = true; 1026 1027 lcd.clear(); 1028 lcd.noBlink(); 1029 screenChanged = false; 1030 } 1031 1032 /* -------- wait for key release -------- */ 1033 if (waitRelease) 1034 { 1035 if (digitalRead(BTN_SET) == HIGH && 1036 digitalRead(BTN_UP) == HIGH && 1037 digitalRead(BTN_DOWN) == HIGH) 1038 { 1039 waitRelease = false; 1040 } 1041 return; 1042 } 1043 1044 /* -------- navigation -------- */ 1045 if (readButton(BTN_DOWN, btnDown) && sel < ITEM_COUNT - 1) sel++; 1046 if (readButton(BTN_UP, btnUp) && sel > 0) sel--; 1047 1048 if (sel < top) top = sel; 1049 if (sel >= top + VISIBLE) top = sel - VISIBLE + 1; 1050 1051 /* -------- render -------- */ 1052 lcdPrintRow(0, "MENU"); 1053 1054 for (int r = 0; r < VISIBLE; r++) 1055 { 1056 int idx = top + r; 1057 char line[21]; 1058 1059 if (idx < ITEM_COUNT) 1060 { 1061 snprintf(line, sizeof(line), 1062 "%c %s", 1063 (idx == sel) ? '>' : ' ', 1064 items[idx]); 1065 lcdPrintRow(r + 1, line); 1066 } 1067 else 1068 { 1069 lcdPrintRow(r + 1, ""); 1070 } 1071 } 1072 1073 /* -------- select -------- */ 1074 if (readButton(BTN_SET, btnSet)) 1075 { 1076 screenChanged = true; 1077 1078 switch (sel) 1079 { 1080 case 0: uiState = UI_SET_DATETIME; break; 1081 case 1: uiState = UI_SET_BELL; break; 1082 case 2: uiState = UI_MANAGE_Timetable; break; 1083 case 3: uiState = UI_CLEAR_EEPROM; break; 1084 case 4: uiState = UI_TRIGGER_BELL; break; 1085 case 5: uiState = UI_EXAM_TIMER; break; 1086 case 6: uiState = UI_AUTO_BELLS; break; 1087 case 7: uiState = UI_SELFTEST; break; 1088 case 8: uiState = UI_DASHBOARD; break; 1089 } 1090 return; 1091 } 1092} 1093 1094/* ================= PLACEHOLDER ================= */ 1095void placeholderScreen(const char* title) { 1096 1097 if (screenChanged) { 1098 lcd.clear(); 1099 lcd.noBlink(); 1100 screenChanged = false; 1101 } 1102 1103 lcdPrintRow(0, title); 1104 lcdPrintRow(1, "Feature Coming"); 1105 lcdPrintRow(2, "Soon..."); 1106 lcdPrintRow(3, "SET = BACK"); 1107 1108 if (readButton(BTN_SET, btnSet)) { 1109 uiState = UI_MENU; 1110 screenChanged = true; 1111 } 1112} 1113 1114/* ================= SET DATE/TIME ================= */ 1115void screenSetDateTime() 1116{ 1117 static int step, d, m, y, h, mi; 1118 1119 if (screenChanged) 1120 { 1121 d = rtc.getDay(); 1122 m = rtc.getMonth(); 1123 y = 2000 + rtc.getYear(); 1124 h = rtc.getHours(); 1125 mi = rtc.getMinutes(); 1126 1127 step = 0; 1128 lcd.clear(); 1129 lcd.blink(); 1130 screenChanged = false; 1131 } 1132 1133 char l1[21], l2[21]; 1134 1135 snprintf(l1, sizeof(l1), "%02d %02d %04d", d, m, y); 1136 snprintf(l2, sizeof(l2), "%02d %02d", h, mi); 1137 1138 lcdPrintRow(0, "SET DATE & TIME"); 1139 lcdPrintRow(1, l1); 1140 lcdPrintRow(2, l2); 1141 lcdPrintRow(3, "SET=SAVE"); 1142 1143 const uint8_t cx[] = {0,3,6,0,3}; 1144 const uint8_t cy[] = {1,1,1,2,2}; 1145 lcd.setCursor(cx[step], cy[step]); 1146 1147 if (readButton(BTN_UP, btnUp)) { 1148 if (step == 0 && d < 31) d++; 1149 else if (step == 1 && m < 12) m++; 1150 else if (step == 2) y++; 1151 else if (step == 3) h = (h + 1) % 24; 1152 else if (step == 4) mi = (mi + 1) % 60; 1153 } 1154 1155 if (readButton(BTN_DOWN, btnDown)) { 1156 if (step == 0 && d > 1) d--; 1157 else if (step == 1 && m > 1) m--; 1158 else if (step == 2 && y > 2000) y--; 1159 else if (step == 3) h = (h + 23) % 24; 1160 else if (step == 4) mi = (mi + 59) % 60; 1161 } 1162 1163 if (readButton(BTN_SET, btnSet)) { 1164 1165 step++; 1166 1167 if (step > 4) { 1168 1169 lcd.noBlink(); 1170 y = y % 100; 1171 rtc.setDate(d, m, y); 1172 rtc.setTime(h, mi, 0); 1173 1174 uiState = UI_MENU; 1175 screenChanged = true; 1176 } 1177 } 1178} 1179 1180/* ================= SET BELL ================= */ 1181void screenSetBell() 1182{ 1183 static int stage; // 0=count, 1=edit bells, 2=save screen 1184 static int idx; 1185 static int hh, mm; 1186 static int step; // 0=HH,1=MM,2=TYPE 1187 static uint8_t typeSel; 1188 1189 static char ttName[TT_NAME_LEN+1]; 1190 1191 if (screenChanged) 1192 { 1193 stage = 0; 1194 idx = 0; 1195 hh = 0; 1196 mm = 0; 1197 step = 0; 1198 typeSel = BELL_PERIOD; 1199 1200 lcd.clear(); 1201 lcd.noBlink(); 1202 screenChanged = false; 1203 } 1204 1205 /* ================= STAGE 0 : number of bells ================= */ 1206 if (stage == 0) 1207 { 1208 lcdPrintRow(0, "SET BELL"); 1209 lcdPrintRow(1, "No of Bells"); 1210 1211 char cntLine[21]; 1212 snprintf(cntLine, sizeof(cntLine), "%02d", bellCount); 1213 lcdPrintRow(2, cntLine); 1214 1215 lcdPrintRow(3, "UP/DN SET=OK"); 1216 1217 if (readButton(BTN_UP, btnUp) && bellCount < MAX_BELLS) bellCount++; 1218 if (readButton(BTN_DOWN, btnDown) && bellCount > 0) bellCount--; 1219 1220 if (readButton(BTN_SET, btnSet)) 1221 { 1222 if (bellCount == 0) 1223 { 1224 showError("INVALID", "Bell Count"); 1225 } 1226 else 1227 { 1228 stage = 1; 1229 idx = 0; 1230 hh = 0; 1231 mm = 0; 1232 step = 0; 1233 typeSel = BELL_PERIOD; 1234 1235 lcd.clear(); 1236 lcd.blink(); 1237 } 1238 } 1239 return; 1240 } 1241 1242 /* ================= STAGE 2 : save screen ================= */ 1243 if (stage == 2) 1244 { 1245 lcdPrintRow(0, "SAVE TIMETABLE ?"); 1246 1247 char line[21]; 1248 snprintf(line, sizeof(line), "NAME: %s", ttName); 1249 lcdPrintRow(1, line); 1250 1251 lcdPrintRow(3, "SET=SAVE UP=NO"); 1252 1253 if (readButton(BTN_SET, btnSet)) 1254 { 1255 saveCurrentTimetable(ttName); 1256 1257 showError("TIMETABLE", "SAVED"); 1258 1259 uiState = UI_MENU; 1260 screenChanged = true; 1261 return; 1262 } 1263 1264 if (readButton(BTN_UP, btnUp)) 1265 { 1266 uiState = UI_MENU; 1267 screenChanged = true; 1268 return; 1269 } 1270 1271 return; 1272 } 1273 1274 /* ================= STAGE 1 : edit bells ================= */ 1275 1276 lcdPrintRow(0, "SET BELL"); 1277 1278 char title[21]; 1279 snprintf(title, sizeof(title), "Bell %d of %d", idx + 1, bellCount); 1280 lcdPrintRow(1, title); 1281 1282 // row 2 : time 1283 char line2[21]; 1284 snprintf(line2, sizeof(line2), "%02d:%02d", hh, mm); 1285 lcdPrintRow(2, line2); 1286 1287 // row 3 : type when step==2 1288 if (step == 2) 1289 { 1290 const char* tname[] = {"PERIOD","PRAYER","RECESS","END"}; 1291 1292 char line3[21]; 1293 snprintf(line3, sizeof(line3), ">%s", tname[typeSel]); 1294 lcdPrintRow(3, line3); 1295 1296 lcd.setCursor(11, 3); 1297 lcd.print("SET=NEXT"); 1298 1299 lcd.setCursor(0, 3); 1300 } 1301 else 1302 { 1303 lcdPrintRow(3, ""); 1304 lcd.setCursor(11, 3); 1305 lcd.print("SET=NEXT"); 1306 1307 if (step == 0) lcd.setCursor(0,2); 1308 else if (step == 1) lcd.setCursor(3,2); 1309 } 1310 1311 /* ---- edit values ---- */ 1312 1313 if (readButton(BTN_UP, btnUp)) 1314 { 1315 if (step == 0 && hh < 23) hh++; 1316 else if (step == 1 && mm < 59) mm++; 1317 else if (step == 2 && typeSel < BELL_END) typeSel++; 1318 } 1319 1320 if (readButton(BTN_DOWN, btnDown)) 1321 { 1322 if (step == 0 && hh > 0) hh--; 1323 else if (step == 1 && mm > 0) mm--; 1324 else if (step == 2 && typeSel > 0) typeSel--; 1325 } 1326 1327 /* ---- confirm current bell ---- */ 1328 1329 if (readButton(BTN_SET, btnSet)) 1330 { 1331 if (step < 2) 1332 { 1333 step++; 1334 return; 1335 } 1336 1337 // only one END 1338 if (typeSel == BELL_END) 1339 { 1340 for (int i = 0; i < idx; i++) 1341 if (bellType[i] == BELL_END) 1342 { 1343 showError("ONLY ONE","END BELL"); 1344 return; 1345 } 1346 } 1347 1348 // only one PRAYER 1349 if (typeSel == BELL_PRAYER) 1350 { 1351 for (int i = 0; i < idx; i++) 1352 if (bellType[i] == BELL_PRAYER) 1353 { 1354 showError("ONLY ONE","PRAYER"); 1355 return; 1356 } 1357 } 1358 1359 // time must be increasing 1360 if (idx > 0) 1361 { 1362 int prev = bellHour[idx - 1] * 60 + bellMin[idx - 1]; 1363 int curr = hh * 60 + mm; 1364 1365 if (curr <= prev) 1366 { 1367 char err[21]; 1368 snprintf(err, sizeof(err), 1369 "Must be > %02d:%02d", 1370 bellHour[idx - 1], bellMin[idx - 1]); 1371 1372 showError("INVALID TIME", err); 1373 step = 0; 1374 return; 1375 } 1376 } 1377 1378 // store this bell 1379 bellHour[idx] = hh; 1380 bellMin[idx] = mm; 1381 bellType[idx] = typeSel; 1382 1383 idx++; 1384 step = 0; 1385 hh = 0; 1386 mm = 0; 1387 typeSel = BELL_PERIOD; 1388 1389 /* ---- last bell reached ---- */ 1390 if (idx == bellCount) 1391 { 1392 lcd.noBlink(); 1393 1394 makeAutoTimetableName(ttName); 1395 1396 stage = 2; // go to save screen 1397 lcd.clear(); 1398 return; 1399 } 1400 1401 // prepare next bell entry 1402 lcd.clear(); 1403 lcd.blink(); 1404 } 1405} 1406 1407 1408 1409/* ================= SELF TEST ================= */ 1410void screenSelfTest() { 1411 static uint8_t step = 0; 1412 static uint8_t subStep = 1; 1413 static unsigned long lastUpdate = 0; 1414 1415 // --- INITIALIZATION --- 1416 if (screenChanged) { 1417 step = 0; 1418 subStep = 1; 1419 lastUpdate = millis(); 1420 lcd.clear(); 1421 matrix.clearDisplay(0); 1422 1423 // Safety: Reset outputs to Idle states 1424 digitalWrite(RELAY_PIN, HIGH); // OFF (Active Low) 1425 digitalWrite(BUZZER_PIN, LOW); // OFF (Active High) 1426 1427 mp3.stop(); 1428 screenChanged = false; 1429 } 1430 1431 // --- EXIT LOGIC --- 1432 // Press SET to exit only after the full test is complete 1433 if (step == 10 && readButton(BTN_SET, btnSet)) { 1434 uiState = UI_MENU; 1435 screenChanged = true; 1436 return; 1437 } 1438 1439 // Standard interval for automatic hardware steps 1440 if (step < 6 && (millis() - lastUpdate < 2000)) return; 1441 lastUpdate = millis(); 1442 1443 switch (step) { 1444 case 0: // 1. I2C & RTC 1445 lcdPrintRow(0, "1. I2C/RTC TEST"); 1446 Wire.beginTransmission(EEPROM_ADDR); 1447 lcdPrintRow(1, (Wire.endTransmission() == 0) ? "EEPROM: OK (0x50)" : "EEPROM: NOT FOUND"); 1448 lcdPrintRow(2, (rtc.getYear() > 0) ? "RTC: RUNNING" : "RTC: NOT INIT"); 1449 break; 1450 1451 case 1: // 2. Relay Test 1452 lcdPrintRow(0, "2. RELAY TEST"); 1453 lcdPrintRow(1, "RELAY (A1) ON..."); 1454 digitalWrite(RELAY_PIN, LOW); // ON 1455 break; 1456 1457 case 2: // Stop Relay 1458 digitalWrite(RELAY_PIN, HIGH); // OFF 1459 lcdPrintRow(1, "RELAY (A1) OFF"); 1460 break; 1461 1462 case 3: // 3. Buzzer Test 1463 lcdPrintRow(0, "3. BUZZER TEST"); 1464 lcdPrintRow(1, "BUZZER (A2) ON..."); 1465 digitalWrite(BUZZER_PIN, HIGH); // ON 1466 break; 1467 1468 case 4: // Stop Buzzer 1469 digitalWrite(BUZZER_PIN, LOW); // OFF 1470 lcdPrintRow(1, "BUZZER OFF"); 1471 break; 1472 1473 case 5: // 4. Matrix Test 1474 lcdPrintRow(0, "4. MATRIX TEST"); 1475 lcdPrintRow(1, "FILLING..."); 1476 1477 for (int i = 0; i < 8; i++) { 1478 for (int j = 0; j < 8; j++) { 1479 matrix.setLed(0, i, j, true); 1480 } 1481 } 1482 1483 delay(5000); // 5 seconds ON time 1484 1485 matrix.clearDisplay(0); 1486 1487 step = 6; 1488 return; 1489 1490 case 6: // 5. Audio Scan Setup 1491 matrix.clearDisplay(0); 1492 lcd.clear(); 1493 lcdPrintRow(0, "5. AUDIO SCAN"); 1494 lcdPrintRow(3, "SET = SKIP TRACK"); 1495 subStep = 1; 1496 step = 7; 1497 return; 1498 1499 case 7: // 5. Audio Execution (Tracks 1-14) 1500 1501 static bool trackStarted = false; 1502 static unsigned long trackStartTime = 0; 1503 1504 if (subStep <= 17) { 1505 1506 char buf[20]; 1507 snprintf(buf, sizeof(buf), "PLAYING: %02d.mp3", subStep); 1508 lcdPrintRow(1, buf); 1509 1510 // ---- Start Track Only Once ---- 1511 if (!trackStarted && digitalRead(MP3_BUSY_PIN) == HIGH) { 1512 1513 mp3.playMp3Folder(subStep); 1514 trackStarted = true; 1515 trackStartTime = millis(); 1516 delay(200); // small stability delay 1517 return; 1518 } 1519 1520 // ---- If Track Finished ---- 1521 if (trackStarted && digitalRead(MP3_BUSY_PIN) == HIGH) { 1522 trackStarted = false; 1523 subStep++; 1524 delay(300); 1525 return; 1526 } 1527 1528 // ---- Manual Skip ---- 1529 if (digitalRead(BTN_SET) == LOW) { 1530 mp3.stop(); 1531 delay(300); 1532 trackStarted = false; 1533 subStep++; 1534 return; 1535 } 1536 1537 return; 1538 } 1539 1540 step = 8; 1541 return; 1542 1543 case 8: // 6. Button Test Setup 1544 lcd.clear(); 1545 lcdPrintRow(0, "6. BUTTON TEST"); 1546 subStep = 1; 1547 step = 9; 1548 return; 1549 1550 case 9: // 6. Button Execution (Wait for Release) 1551 if (subStep == 1) { 1552 lcdPrintRow(1, "PRESS: SET (B14)"); 1553 if (digitalRead(BTN_SET) == LOW) { 1554 lcdPrintRow(2, "SET OK!"); 1555 while(digitalRead(BTN_SET) == LOW); // WAIT FOR RELEASE 1556 delay(500); 1557 subStep++; 1558 } 1559 } else if (subStep == 2) { 1560 lcdPrintRow(1, "PRESS: UP (B13)"); 1561 if (digitalRead(BTN_UP) == LOW) { 1562 lcdPrintRow(2, "UP OK!"); 1563 while(digitalRead(BTN_UP) == LOW); // WAIT FOR RELEASE 1564 delay(500); 1565 subStep++; 1566 } 1567 } else if (subStep == 3) { 1568 lcdPrintRow(1, "PRESS: DOWN(B12)"); 1569 if (digitalRead(BTN_DOWN) == LOW) { 1570 lcdPrintRow(2, "DOWN OK!"); 1571 while(digitalRead(BTN_DOWN) == LOW); // WAIT FOR RELEASE 1572 delay(500); 1573 subStep++; 1574 } 1575 } else { 1576 step = 10; 1577 } 1578 return; 1579 1580 case 10: // Done 1581 lcd.clear(); 1582 lcdPrintRow(1, "TEST COMPLETED"); 1583 lcdPrintRow(3, "PRESS SET TO EXIT"); 1584 return; 1585 } 1586 1587 if (step < 6) step++; 1588} 1589 1590/////////////////////////////////////////////////////// 1591//////////////////////////////////////////////////// 1592uint32_t rtcToSeconds() 1593{ 1594 return (uint32_t)rtc.getHours() * 3600UL 1595 + (uint32_t)rtc.getMinutes() * 60UL 1596 + (uint32_t)rtc.getSeconds(); 1597} 1598 1599 1600 1601///////////////////////////////////////////////// 1602bool bothPressed(uint8_t p1, uint8_t p2) 1603{ 1604 static unsigned long t = 0; 1605 1606 if (digitalRead(p1) == LOW && digitalRead(p2) == LOW) 1607 { 1608 if (millis() - t > 400) // hold both for 400ms 1609 return true; 1610 } 1611 else 1612 { 1613 t = millis(); 1614 } 1615 1616 return false; 1617} 1618 1619/////////////////////////////////////////////////// 1620/////////////////////////////////////////////////// 1621void updateExamMatrixPattern(uint32_t left, uint32_t total) { 1622 matrix.clearDisplay(0); 1623 1624 // ਸਮੇਂ ਦੇ ਹਿਸਾਬ ਨਾਲ ਉਚਾਈ (0 ਤੋਂ 8) ਕੱਢੋ 1625 int height = map(left, 0, total, 0, 8); 1626 1627 // 8 ਵਰਟੀਕਲ ਬਾਰਾਂ ਬਣਾਉਣ ਲਈ ਲੂਪ 1628 for (int col = 0; col < 8; col++) { 1629 for (int row = 0; row < height; row++) { 1630 // ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਬਾਰਾਂ ਭਰੋ 1631 matrix.setLed(0, 7 - row, col, true); 1632 } 1633 } 1634} 1635 1636void screenExamTimer() { 1637 static uint8_t step = 0; 1638 static uint8_t lastStep = 255; 1639 static unsigned long lastMatrixUpdate = 0; 1640 1641 // --- 1. LCD ਦੀ ਪਹਿਲੀ ਲਾਈਨ (ਹਮੇਸ਼ਾ ਚੱਲਦੀ ਘੜੀ) --- 1642 char clockLine[21]; 1643 snprintf(clockLine, sizeof(clockLine), "%02d/%02d %02d:%02d:%02d", 1644 rtc.getDay(), rtc.getMonth(), rtc.getHours(), rtc.getMinutes(), rtc.getSeconds()); 1645 lcd.setCursor(0, 0); 1646 lcd.print(clockLine); 1647 1648 if (screenChanged) { 1649 if (!examScheduled && !examRunning) { 1650 examStartH = rtc.getHours(); 1651 examStartM = rtc.getMinutes(); 1652 examDurH = 3; 1653 examDurM = 0; 1654 step = 0; 1655 } 1656 lastStep = 255; 1657 lcd.clear(); 1658 screenChanged = false; 1659 } 1660 1661 /* ================= WAITING (ਸਮਾਂ ਹੋਣ ਦੀ ਉਡੀਕ) ================= */ 1662 if (examScheduled && !examRunning) { 1663 uint32_t now = rtcToSeconds(); 1664 uint32_t startSec = (uint32_t)examStartH * 3600UL + (uint32_t)examStartM * 60UL; 1665 1666 lcdPrintRow(1, "STATUS: WAITING..."); 1667 char startLine[21]; 1668 snprintf(startLine, sizeof(startLine), "START AT: %02d:%02d", examStartH, examStartM); 1669 lcdPrintRow(2, startLine); 1670 lcdPrintRow(3, "UP+SET = CANCEL"); 1671 1672 // ਸਿਰਫ਼ ਉਸੇ ਸੈਕਿੰਡ 'ਤੇ ਸਟਾਰਟ ਹੋਵੇਗਾ ਜਦੋਂ ਸਮਾਂ ਮੈਚ ਕਰੇਗਾ 1673 if (now == startSec && rtc.getSeconds() == 0) { 1674 examScheduled = false; 1675 examRunning = true; 1676 examStartSec = now; 1677 1678 digitalWrite(RELAY_PIN, LOW); // ਘੰਟੀ ਚਾਲੂ 1679 digitalWrite(BUZZER_PIN, HIGH); 1680 mp3.playMp3Folder(15); // Exam Start Punjabi Audio 1681 delay(2000); // ਸਿਰਫ਼ ਸਟਾਰਟ ਅਲਰਟ ਲਈ ਛੋਟਾ ਡਿਲੇਅ 1682 digitalWrite(RELAY_PIN, HIGH); // ਘੰਟੀ ਬੰਦ 1683 digitalWrite(BUZZER_PIN, LOW); 1684 lcd.clear(); 1685 } 1686 1687 if (bothPressed(BTN_UP, BTN_SET)) { 1688 examScheduled = false; 1689 uiState = UI_MENU; 1690 screenChanged = true; 1691 } 1692 return; 1693 } 1694 1695 /* ================= RUNNING (ਪ੍ਰੀਖਿਆ ਚੱਲ ਰਹੀ ਹੈ) ================= */ 1696 if (examRunning) { 1697 uint32_t now = rtcToSeconds(); 1698 uint32_t elapsed = now - examStartSec; 1699 1700 if (elapsed >= examDurationSec) { 1701 examRunning = false; 1702 digitalWrite(RELAY_PIN, LOW); 1703 digitalWrite(BUZZER_PIN, HIGH); 1704 mp3.playMp3Folder(16); // Exam Over Punjabi Audio 1705 delay(3000); 1706 digitalWrite(RELAY_PIN, HIGH); 1707 digitalWrite(BUZZER_PIN, LOW); 1708 lcd.clear(); 1709 showError("EXAM FINISHED", "TIME UP"); 1710 uiState = UI_DASHBOARD; 1711 screenChanged = true; 1712 return; 1713 } 1714 1715 // 8x8 ਮੈਟ੍ਰਿਕਸ ਪੈਟਰਨ ਅੱਪਡੇਟ (ਹਰ 1 ਸੈਕਿੰਡ ਬਾਅਦ) 1716 if (millis() - lastMatrixUpdate > 1000) { 1717 updateExamMatrixPattern(examDurationSec - elapsed, examDurationSec); 1718 lastMatrixUpdate = millis(); 1719 } 1720 1721 uint32_t left = examDurationSec - elapsed; 1722 char leftLine[21]; 1723 lcdPrintRow(1, "EXAM IN PROGRESS"); 1724 snprintf(leftLine, sizeof(leftLine), "TIME LEFT: %02lu:%02lu:%02lu", left/3600, (left%3600)/60, left%60); 1725 lcdPrintRow(2, leftLine); 1726 lcdPrintRow(3, "UP+SET = STOP"); 1727 1728 if (bothPressed(BTN_UP, BTN_SET)) { 1729 examRunning = false; 1730 matrix.clearDisplay(0); 1731 uiState = UI_MENU; 1732 screenChanged = true; 1733 } 1734 return; 1735 } 1736 1737 /* ================= EDIT / SETUP (ਸੈਟਿੰਗ ਮੋਡ) ================= */ 1738 if (step != lastStep) { 1739 if (step < 4) lcdPrintRow(3, "SET=NEXT"); 1740 else lcdPrintRow(3, "SET=START UP=BACK"); 1741 lastStep = step; 1742 } 1743 1744 char sLine[21], dLine[21]; 1745 snprintf(sLine, sizeof(sLine), "SET START: %02d:%02d", examStartH, examStartM); 1746 lcdPrintRow(1, sLine); 1747 snprintf(dLine, sizeof(dLine), "SET DUR : %02d:%02d", examDurH, examDurM); 1748 lcdPrintRow(2, dLine); 1749 1750 lcd.blink(); 1751 if (step == 0) lcd.setCursor(11, 1); 1752 else if (step == 1) lcd.setCursor(14, 1); 1753 else if (step == 2) lcd.setCursor(11, 2); 1754 else if (step == 3) lcd.setCursor(14, 2); 1755 else lcd.noBlink(); 1756 1757 if (readButton(BTN_UP, btnUp)) { 1758 if (step == 0) examStartH = (examStartH + 1) % 24; 1759 else if (step == 1) examStartM = (examStartM + 1) % 60; 1760 else if (step == 2) examDurH = (examDurH + 1) % 24; 1761 else if (step == 3) examDurM = (examDurM + 1) % 60; 1762 } 1763 if (readButton(BTN_DOWN, btnDown)) { 1764 if (step == 0) examStartH = (examStartH + 23) % 24; 1765 else if (step == 1) examStartM = (examStartM + 59) % 60; 1766 else if (step == 2) examDurH = (examDurH + 23) % 24; 1767 else if (step == 3) examDurM = (examDurM + 59) % 60; 1768 } 1769 1770 if (readButton(BTN_SET, btnSet)) { 1771 if (step < 4) { step++; return; } 1772 examDurationSec = (uint32_t)examDurH * 3600UL + (uint32_t)examDurM * 60UL; 1773 examScheduled = true; 1774 step = 0; 1775 lcd.clear(); 1776 lcd.noBlink(); 1777 } 1778} 1779/////////////////////////////////////////////////// 1780/////////////////////////////////////////////////// 1781 1782////////////////////////////////////////////////// 1783 1784 1785 1786/////////////////////////////////////////////////// 1787// 1788void drawTwoDigitScroll(uint8_t d1, uint8_t d2) 1789{ 1790 static uint8_t offset = 0; 1791 static unsigned long t = 0; 1792 1793 if (millis() - t > 120) // scroll speed 1794 { 1795 t = millis(); 1796 offset++; 1797 if (offset > 8) offset = 0; 1798 } 1799 1800 matrix.clearDisplay(0); 1801 1802 for (int row = 0; row < 8; row++) 1803 { 1804 // your drawChar8x8 uses flipped access 1805 byte a = font8x8[d1][7 - row]; 1806 byte b = font8x8[d2][7 - row]; 1807 1808 // combine two chars side by side (16 bit wide) 1809 uint16_t line16 = ((uint16_t)a << 8) | b; 1810 1811 // take sliding 8-bit window 1812 byte out = (line16 >> (8 - offset)) & 0xFF; 1813 1814 for (int col = 0; col < 8; col++) 1815 { 1816 bool on = out & (1 << col); // keep your horizontal flip 1817 matrix.setLed(0, row, col, on); 1818 } 1819 } 1820} 1821 1822////////////////////////////////////////////////////// 1823bool generateAutoTimetable( 1824 uint8_t startH, 1825 uint8_t startM, 1826 1827 uint8_t prayerMinutes, // FIRST activity 1828 uint8_t periodMinutes, 1829 uint8_t totalPeriods, 1830 1831 uint8_t recessMinutes, 1832 uint8_t recessAfterPeriod // 1..totalPeriods , 0 = no recess 1833) 1834{ 1835 bellCount = 0; 1836 1837 int cur = startH * 60 + startM; 1838 1839 /* ---------- FIRST : PRAYER ---------- */ 1840 if (prayerMinutes > 0) 1841 { 1842 if (bellCount >= MAX_BELLS) return false; 1843 1844 bellHour[bellCount] = cur / 60; 1845 bellMin [bellCount] = cur % 60; 1846 bellType[bellCount] = BELL_PRAYER; 1847 bellCount++; 1848 1849 cur += prayerMinutes; 1850 } 1851 1852 /* ---------- PERIODS ---------- */ 1853 for (uint8_t p = 1; p <= totalPeriods; p++) 1854 { 1855 if (bellCount >= MAX_BELLS) return false; 1856 1857 // period start bell 1858 bellHour[bellCount] = cur / 60; 1859 bellMin [bellCount] = cur % 60; 1860 bellType[bellCount] = BELL_PERIOD; 1861 bellCount++; 1862 1863 cur += periodMinutes; 1864 1865 // recess after this period 1866 if (recessAfterPeriod == p && recessMinutes > 0) 1867 { 1868 if (bellCount >= MAX_BELLS) return false; 1869 1870 bellHour[bellCount] = cur / 60; 1871 bellMin [bellCount] = cur % 60; 1872 bellType[bellCount] = BELL_RECESS; 1873 bellCount++; 1874 1875 cur += recessMinutes; 1876 } 1877 } 1878 1879 /* ---------- END ---------- */ 1880 if (bellCount >= MAX_BELLS) return false; 1881 1882 bellHour[bellCount] = cur / 60; 1883 bellMin [bellCount] = cur % 60; 1884 bellType[bellCount] = BELL_END; 1885 bellCount++; 1886 1887 return true; 1888} 1889///////////////////////////////////////// 1890void screenAutoGenerateBells() 1891{ 1892 static uint8_t step = 0; 1893 static uint8_t mode = 0; // 0 = input, 1 = preview, 2 = confirm 1894 1895 static uint8_t startH, startM; 1896 static uint8_t prayerMin; 1897 static uint8_t periodMin; 1898 static uint8_t totalPeriods; 1899 static uint8_t recessMin; 1900 static uint8_t recessAfter; 1901 1902 static uint8_t top = 0; // for preview scroll 1903 1904 if (screenChanged) 1905 { 1906 startH = 8; 1907 startM = 0; 1908 1909 prayerMin = 10; 1910 periodMin = 40; 1911 totalPeriods = 6; 1912 recessMin = 20; 1913 recessAfter = 3; 1914 1915 step = 0; 1916 mode = 0; 1917 top = 0; 1918 1919 lcd.clear(); 1920 lcd.blink(); 1921 screenChanged = false; 1922 } 1923 1924 /* ===================================================== 1925 MODE 1 : PREVIEW GENERATED BELLS (RAM ONLY) 1926 ===================================================== */ 1927 if (mode == 1) 1928 { 1929 lcd.noBlink(); 1930 1931 lcdPrintRow(0, "AUTO PREVIEW"); 1932 1933 for (uint8_t r = 0; r < 2; r++) 1934 { 1935 uint8_t i = top + r; 1936 1937 if (i < bellCount) 1938 { 1939 char line[21]; 1940 char c; 1941 1942 if (bellType[i] == BELL_PERIOD) c = 'C'; 1943 else if (bellType[i] == BELL_PRAYER) c = 'P'; 1944 else if (bellType[i] == BELL_RECESS) c = 'R'; 1945 else if (bellType[i] == BELL_END) c = 'E'; 1946 else c = '?'; 1947 1948 snprintf(line, sizeof(line), 1949 "B%02d %02d:%02d %c", 1950 i + 1, 1951 bellHour[i], 1952 bellMin[i], 1953 c); 1954 1955 lcdPrintRow(r + 1, line); 1956 } 1957 else 1958 lcdPrintRow(r + 1, ""); 1959 } 1960 1961 lcdPrintRow(3, "UP/DN SCROLL SET"); 1962 1963 if (readButton(BTN_DOWN, btnDown)) 1964 if (top + 2 < bellCount) top++; 1965 1966 if (readButton(BTN_UP, btnUp)) 1967 if (top > 0) top--; 1968 1969 if (readButton(BTN_SET, btnSet)) 1970 { 1971 mode = 2; // go to save confirm 1972 lcd.clear(); 1973 } 1974 1975 return; 1976 } 1977 1978 /* ===================================================== 1979 MODE 2 : SAVE CONFIRM 1980 ===================================================== */ 1981 if (mode == 2) 1982 { 1983 lcd.noBlink(); 1984 1985 lcdPrintRow(0, "SAVE TIMETABLE?"); 1986 lcdPrintRow(1, "SET=YES UP=NO"); 1987 lcdPrintRow(2, ""); 1988 lcdPrintRow(3, ""); 1989 1990 if (readButton(BTN_SET, btnSet)) 1991 { 1992 char name[TT_NAME_LEN + 1]; 1993 makeAutoTimetableName(name); 1994 1995 saveCurrentTimetable(name); 1996 1997 showError("AUTO TIME", "SAVED"); 1998 1999 uiState = UI_MENU; 2000 screenChanged = true; 2001 return; 2002 } 2003 2004 if (readButton(BTN_UP, btnUp)) 2005 { 2006 mode = 1; // back to preview 2007 lcd.clear(); 2008 return; 2009 } 2010 2011 return; 2012 } 2013 2014 2015 2016 /* ===================================================== 2017 MODE 0 : INPUT SCREEN 2018 ===================================================== */ 2019 2020/* ===================================================== 2021 MODE 0 : INPUT SCREEN 2022 ===================================================== */ 2023 2024 lcdPrintRow(0, "AUTO GENERATE"); 2025 2026 char line[21]; 2027 2028 switch (step) 2029 { 2030 case 0: 2031 snprintf(line, sizeof(line), "START %02d:%02d", startH, startM); 2032 lcdPrintRow(1, line); 2033 lcd.setCursor(6,1); 2034 break; 2035 2036 case 1: 2037 snprintf(line, sizeof(line), "START %02d:%02d", startH, startM); 2038 lcdPrintRow(1, line); 2039 lcd.setCursor(9,1); 2040 break; 2041 2042 case 2: 2043 snprintf(line, sizeof(line), "PRAYER %02d MIN", prayerMin); 2044 lcdPrintRow(1, line); 2045 lcd.setCursor(7,1); 2046 break; 2047 2048 case 3: 2049 snprintf(line, sizeof(line), "PERIOD %02d MIN", periodMin); 2050 lcdPrintRow(1, line); 2051 lcd.setCursor(7,1); 2052 break; 2053 2054 case 4: 2055 snprintf(line, sizeof(line), "TOTAL P %02d", totalPeriods); 2056 lcdPrintRow(1, line); 2057 lcd.setCursor(8,1); 2058 break; 2059 2060 case 5: 2061 snprintf(line, sizeof(line), "RECESS %02d MIN", recessMin); 2062 lcdPrintRow(1, line); 2063 lcd.setCursor(7,1); 2064 break; 2065 2066 case 6: 2067 snprintf(line, sizeof(line), "RECESS AFT %02d", recessAfter); 2068 lcdPrintRow(1, line); 2069 lcd.setCursor(11,1); 2070 break; 2071 } 2072 2073 lcdPrintRow(2, ""); 2074 lcdPrintRow(3, "UP/DN SET=NEXT"); 2075 lcd.blink(); 2076 2077 /* -------- edit values -------- */ 2078 2079 if (readButton(BTN_UP, btnUp)) 2080 { 2081 if (step == 0 && startH < 23) startH++; 2082 else if (step == 1 && startM < 59) startM++; 2083 else if (step == 2 && prayerMin < 60) prayerMin++; 2084 else if (step == 3 && periodMin < 90) periodMin++; 2085 else if (step == 4 && totalPeriods < 17) totalPeriods++; 2086 else if (step == 5 && recessMin < 60) recessMin++; 2087 else if (step == 6 && recessAfter < totalPeriods) recessAfter++; 2088 } 2089 2090 if (readButton(BTN_DOWN, btnDown)) 2091 { 2092 if (step == 0 && startH > 0) startH--; 2093 else if (step == 1 && startM > 0) startM--; 2094 else if (step == 2 && prayerMin > 0) prayerMin--; 2095 else if (step == 3 && periodMin > 1) periodMin--; 2096 else if (step == 4 && totalPeriods > 1) totalPeriods--; 2097 else if (step == 5 && recessMin > 0) recessMin--; 2098 else if (step == 6 && recessAfter > 0) recessAfter--; 2099 } 2100 if (recessAfter > totalPeriods) 2101 recessAfter = totalPeriods; 2102 /* -------- SET -------- */ 2103 2104 if (readButton(BTN_SET, btnSet)) 2105 { 2106 if (step < 6) 2107 { 2108 step++; 2109 return; 2110 } 2111 2112 // last field confirmed → generate now 2113 2114 if (!generateAutoTimetable( 2115 startH, startM, 2116 prayerMin, 2117 periodMin, 2118 totalPeriods, 2119 recessMin, 2120 recessAfter)) 2121 { 2122 showError("FAILED", "TOO MANY"); 2123 screenChanged = true; 2124 return; 2125 } 2126 2127 // go to preview 2128 top = 0; 2129 mode = 1; 2130 lcd.clear(); 2131 return; 2132 } 2133} 2134 2135 2136/* ================= SETUP ================= */ 2137/* ================= SETUP ================= */ 2138/* ================= SETUP ================= */ 2139/* ================= SETUP ================= */ 2140void screenTriggerBell() { 2141 static bool triggering = false; 2142 static unsigned long lastFlash = 0; 2143 static bool flashState = false; 2144 2145 if (screenChanged) { 2146 lcd.clear(); 2147 triggering = false; 2148 screenChanged = false; 2149 } 2150 2151 lcdPrintRow(0, "MANUAL TRIGGER"); 2152 2153 if (!triggering) { 2154 lcdPrintRow(1, "READY TO RING"); 2155 lcdPrintRow(3, "SET=START UP=BACK"); 2156 2157 if (readButton(BTN_SET, btnSet)) { 2158 mp3.stop(); 2159 delay(50); 2160 mp3.playMp3Folder(17); // Play Emergency Punjabi Script 2161 delay(200); // Wait for DFPlayer to pull Busy Pin LOW 2162 2163 digitalWrite(RELAY_PIN, LOW); // Industrial Bell ON 2164 digitalWrite(BUZZER_PIN, HIGH); // Internal Buzzer ON 2165 2166 triggering = true; 2167 lcd.clear(); 2168 } 2169 } 2170 else { 2171 lcdPrintRow(1, "!! EMERGENCY !!"); 2172 lcdPrintRow(2, " ANNOUNCING "); 2173 2174 // --- 8x8 Matrix Flashing Logic --- 2175 if (millis() - lastFlash > 300) { // Flash every 300ms 2176 lastFlash = millis(); 2177 flashState = !flashState; 2178 if (flashState) drawChar8x8(10); // Show 'C' 2179 else matrix.clearDisplay(0); // Turn off 2180 } 2181 2182 // --- Check if MP3 has finished --- 2183 // digitalRead(MP3_BUSY_PIN) == HIGH means the sound has stopped 2184 if (digitalRead(MP3_BUSY_PIN) == HIGH) { 2185 digitalWrite(RELAY_PIN, HIGH); // Bell OFF 2186 digitalWrite(BUZZER_PIN, LOW); // Buzzer OFF 2187 2188 triggering = false; 2189 matrix.clearDisplay(0); 2190 showError("ALERT FINISHED", ""); 2191 uiState = UI_MENU; 2192 screenChanged = true; 2193 } 2194 } 2195} 2196/* ================= SETUP ================= */ 2197void screenTriggerBell1() { 2198 static bool triggering = false; 2199 static unsigned long startTime = 0; 2200 2201 if (screenChanged) { 2202 lcd.clear(); 2203 lcd.noBlink(); 2204 triggering = false; 2205 screenChanged = false; 2206 } 2207 2208 lcdPrintRow(0, "MANUAL TRIGGER"); 2209 2210 if (!triggering) { 2211 lcdPrintRow(1, "READY TO RING"); 2212 lcdPrintRow(3, "SET=START UP=BACK"); 2213 2214 if (readButton(BTN_UP, btnUp)) { 2215 uiState = UI_MENU; 2216 screenChanged = true; 2217 } 2218 2219 if (readButton(BTN_SET, btnSet)) { 2220 // Start the physical bell 2221 digitalWrite(RELAY_PIN, LOW); 2222 digitalWrite(BUZZER_PIN, HIGH); 2223 2224 // Start the MP3 (Track 1 is usually the standard bell) 2225 //mp3.play(1); 2226 mp3.play(17); 2227 //mp3.playMp3Folder(17); 2228 delay(100); // Small pause for serial to finish 2229 // Step 2: Show the Matrix icon 2230 drawChar8x8(10); 2231 delay(100); 2232 startTime = millis(); 2233 triggering = true; 2234 lcd.clear(); 2235 } 2236 } 2237 else { 2238 // While the bell is ringing 2239 lcdPrintRow(1, "RINGING..."); 2240 lcdPrintRow(2, "PLEASE WAIT"); 2241 2242 // Show an animation on the Matrix to indicate action 2243 drawChar8x8(10); // Show 'C' or a custom bell icon 2244 2245 // Stop after 3 seconds 2246 if (millis() - startTime >= 3000) { 2247 digitalWrite(RELAY_PIN, HIGH); 2248 digitalWrite(BUZZER_PIN, LOW); 2249 2250 triggering = false; 2251 matrix.clearDisplay(0); 2252 showError("BELL FINISHED", ""); 2253 uiState = UI_MENU; 2254 screenChanged = true; 2255 } 2256 } 2257} 2258/* ================= SETUP ================= */ 2259void screenClearEEPROM() { 2260 static bool confirm = false; 2261 2262 if (screenChanged) { 2263 confirm = false; 2264 lcd.clear(); 2265 lcd.noBlink(); 2266 screenChanged = false; 2267 } 2268 2269 if (!confirm) { 2270 lcdPrintRow(0, "!!! WARNING !!!"); 2271 lcdPrintRow(1, "WIPE ALL DATA?"); 2272 lcdPrintRow(3, "SET=YES UP=BACK"); 2273 2274 if (readButton(BTN_UP, btnUp)) { 2275 uiState = UI_MENU; 2276 screenChanged = true; 2277 } 2278 2279 if (readButton(BTN_SET, btnSet)) { 2280 confirm = true; // Move to the actual clearing phase 2281 lcd.clear(); 2282 } 2283 } 2284 else { 2285 lcdPrintRow(1, "CLEARING..."); 2286 2287 // 1. Reset the Timetable Count (Bytes 0 & 1) 2288 setTimetableCount(0); 2289 2290 // 2. Reset Default Index (Byte 2) 2291 setDefaultTimetable(0); 2292 2293 // 3. Clear the first 32 bytes (Management Area + First Name) 2294 // This ensures even if the count was wrong, the system sees 0s 2295 for (uint16_t i = 0; i < 32; i++) { 2296 eepromWriteByte(i, 0); 2297 2298 // Visual feedback on the Matrix while it's working 2299 if (i % 4 == 0) matrix.setLed(0, 0, i/4, true); 2300 } 2301 2302 // Reset RAM variables 2303 bellCount = 0; 2304 2305 showError("EEPROM WIPED", "SYSTEM RESET"); 2306 matrix.clearDisplay(0); 2307 uiState = UI_DASHBOARD; 2308 screenChanged = true; 2309 } 2310} 2311/* ================= SETUP ================= */ 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348/* ================= SETUP ================= */ 2349/* ================= SETUP ================= */ 2350 2351/* ================= SETUP ================= */ 2352void setup() { 2353 2354 pinMode(BTN_SET, INPUT_PULLUP); 2355 pinMode(BTN_UP, INPUT_PULLUP); 2356 pinMode(BTN_DOWN, INPUT_PULLUP); 2357 2358 pinMode(BUZZER_PIN, OUTPUT); 2359 pinMode(RELAY_PIN, OUTPUT); 2360 2361 pinMode(MP3_BUSY_PIN, INPUT_PULLUP); 2362 2363 digitalWrite(BUZZER_PIN, LOW); 2364 digitalWrite(RELAY_PIN, HIGH); 2365 2366 Wire.begin(); 2367 Wire.setClock(100000); 2368 Wire.setTimeout(20); 2369 2370uint16_t cnt = getTimetableCount(); 2371if (cnt > 50) 2372{ 2373 setTimetableCount(0); 2374 setDefaultTimetable(0); 2375} 2376 2377 2378 lcd.init(); 2379 lcd.backlight(); 2380 2381 // scanI2C(); 2382 2383 rtc.setClockSource(STM32RTC::LSE_CLOCK); 2384 rtc.begin(); 2385 2386// loadBellsFromEEPROM(); 2387uint16_t n = getTimetableCount(); 2388 2389if (n > 0) 2390{ 2391 uint8_t d = getDefaultTimetable(); 2392 if (d >= n) d = 0; 2393 2394 loadTimetable(d); 2395} 2396 // default 2397else 2398 bellCount = 0; 2399 2400 2401 2402 2403 pinMode(MATRIX_CS, OUTPUT); 2404 pinMode(MATRIX_CLK, OUTPUT); 2405 pinMode(MATRIX_DIN, OUTPUT); 2406 2407 matrix.shutdown(0, false); 2408 matrix.setIntensity(0, 5); 2409 matrix.clearDisplay(0); 2410 2411 /* ---- DFPLAYER ---- */ 2412 mp3Serial.begin(9600); 2413 mp3.begin(mp3Serial); 2414 mp3.volume(25); 2415 2416 lcd.clear(); 2417 uiState = UI_DASHBOARD; 2418 screenChanged = true; 2419} 2420 2421/* ================= LOOP ================= */ 2422void loop() 2423{ 2424 int cb, nb; 2425 long cd; 2426 2427 getBellStatus(cb, nb, cd); 2428 2429 /* ===================================================== 2430 1️⃣ EXACT TIME TRIGGER (ONLY ONCE PER MINUTE) 2431 ===================================================== */ 2432 2433 static int lastBellMinute = -1; 2434 2435 int nowH = rtc.getHours(); 2436 int nowM = rtc.getMinutes(); 2437 int nowS = rtc.getSeconds(); 2438 2439 int currentMinute = nowH * 60 + nowM; 2440 2441 if (!examRunning) 2442 { 2443 // Trigger only at second 0 and only once per minute 2444 if (nowS == 0 && currentMinute != lastBellMinute) 2445 { 2446 for (int i = 0; i < bellCount; i++) 2447 { 2448 if (bellHour[i] == nowH && 2449 bellMin[i] == nowM) 2450 { 2451 activeBellIndex = i; 2452 bellActive = true; 2453 mp3Playing = true; 2454 2455 digitalWrite(BUZZER_PIN, HIGH); 2456 digitalWrite(RELAY_PIN, LOW); 2457 2458 break; 2459 } 2460 } 2461 2462 lastBellMinute = currentMinute; 2463 } 2464 } 2465 2466 /* ===================================================== 2467 2️⃣ MATRIX DISPLAY 2468 ===================================================== */ 2469/* -------- MATRIX DISPLAY -------- */ 2470 2471if (!examRunning) 2472{ 2473 // Show current bell if valid 2474 if (cb >= 0 && !(cb == bellCount - 1 && nb == -1 && !bellActive)) 2475 { 2476 uint8_t t = bellType[cb]; 2477 2478 // 🔹 PRAYER 2479 if (t == BELL_PRAYER) 2480 { 2481 drawChar8x8(12); // P 2482 } 2483 2484 // 🔹 RECESS 2485 else if (t == BELL_RECESS) 2486 { 2487 drawChar8x8(11); // R 2488 } 2489 2490 // 🔹 END 2491 else if (t == BELL_END) 2492 { 2493 // Show only while ringing 2494 if (bellActive) 2495 drawChar8x8(13); // E 2496 else 2497 matrix.clearDisplay(0); 2498 } 2499 2500 // 🔹 PERIOD 2501 else if (t == BELL_PERIOD) 2502 { 2503 int periodNumber = 0; 2504 2505 for (int i = 0; i <= cb; i++) 2506 if (bellType[i] == BELL_PERIOD) 2507 periodNumber++; 2508 2509 if (periodNumber < 10) 2510 drawChar8x8(periodNumber); 2511 else 2512 drawTwoDigitScroll(periodNumber / 10, 2513 periodNumber % 10); 2514 } 2515 } 2516 else 2517 { 2518 // Show countdown when no active bell 2519 updateCountdownIndicators(cd); 2520 } 2521} 2522else 2523{ 2524 // -------- EXAM MODE MATRIX -------- 2525 uint32_t now = rtcToSeconds(); 2526 2527 if (now >= examStartSec) 2528 { 2529 uint32_t elapsed = now - examStartSec; 2530 2531 if (elapsed < examDurationSec) 2532 showCountdownOnMatrix(examDurationSec - elapsed); 2533 else 2534 matrix.clearDisplay(0); 2535 } 2536 else 2537 { 2538 matrix.clearDisplay(0); 2539 } 2540} 2541 2542 2543 2544 2545 2546 /* ===================================================== 2547 3️⃣ BELL DURATION HANDLER 2548 ===================================================== */ 2549 2550 if (!examRunning) 2551 handleBell(); 2552 2553 /* ===================================================== 2554 4️⃣ MP3 CONTROL 2555 ===================================================== */ 2556 2557/* -------- MP3 CONTROL -------- */ 2558 2559if (mp3Playing) 2560{ 2561 if (activeBellIndex >= 0) 2562 { 2563 uint8_t t = bellType[activeBellIndex]; 2564 uint16_t track = 0; 2565 2566 // 🔹 PRAYER 2567 if (t == BELL_PRAYER) 2568 { 2569 track = 11; // 0011.mp3 2570 } 2571 2572 // 🔹 RECESS 2573 else if (t == BELL_RECESS) 2574 { 2575 track = 12; // 0012.mp3 2576 } 2577 2578 // 🔹 END 2579 else if (t == BELL_END) 2580 { 2581 track = 13; // 0013.mp3 2582 } 2583 2584 // 🔹 PERIOD 2585 else if (t == BELL_PERIOD) 2586 { 2587 int periodNumber = 0; 2588 2589 // Count only period bells up to this index 2590 for (int i = 0; i <= activeBellIndex; i++) 2591 { 2592 if (bellType[i] == BELL_PERIOD) 2593 periodNumber++; 2594 } 2595 2596 // Safety limit (1 to 10) 2597 if (periodNumber >= 1 && periodNumber <= 10) 2598 track = periodNumber; 2599 } 2600 2601 // 🔥 Play only if track valid 2602 if (track > 0) 2603 { 2604 mp3.play(track); 2605 2606 } 2607 } 2608 2609 mp3Playing = false; 2610} 2611 2612 2613 /* ===================================================== 2614 5️⃣ UI HANDLER 2615 ===================================================== */ 2616 2617 switch (uiState) 2618 { 2619 case UI_DASHBOARD: screenDashboard(); break; 2620 case UI_MENU: screenMenu(); break; 2621 case UI_SET_DATETIME: screenSetDateTime(); break; 2622 case UI_SET_BELL: screenSetBell(); break; 2623 case UI_MANAGE_Timetable: screenTimetableList(); break; 2624 case UI_CLEAR_EEPROM: screenClearEEPROM(); break; 2625 case UI_TRIGGER_BELL: screenTriggerBell(); break; 2626 case UI_EXAM_TIMER: screenExamTimer(); break; 2627 case UI_AUTO_BELLS: screenAutoGenerateBells(); break; 2628 case UI_SELFTEST: screenSelfTest(); break; 2629 } 2630 2631 delay(80); 2632} 2633 2634/* ================= LOOP ================= */
Downloadable files
3D Encloser Files and MP3 Files in Punjabi Language
Solid edge 2025 Community Edition
3D Printer Encloser Design_MP3_files.zip
Industrial-Grade STM32 Smart School Bell V3 0
Manual
Smart School Bell V3 0 Manual.pdf
Comments
Only logged in users can leave comments