balli2000in
Published © GPL3+

Industrial-Grade STM32 Smart School Bell V3.0

Industrial STM32 Smart Bell V3.0: Automated scheduling, Punjabi audio alerts, and a visual Exam Timer with RTC backup for school

AdvancedProtip2 hours46
Industrial-Grade STM32 Smart School Bell V3.0

Things used in this project

Hardware components

STM32F103C8T6
×1
LCD 20x4 (I2C)
×1
MAX7219 8x8 LED Matrix
×1
DS3231 or Internal RTC (32.768kHz crystal , two 22pF ceramic load capacitors , CR2032 3V Lithium Coin Cell with holding socket )
×1
AT24C32/256
×1
DFPlayer - A Mini MP3 Player
DFRobot DFPlayer - A Mini MP3 Player
×1
Relay Module (5V)
×1
Active Buzzer
×1
Push to on Buttons
×3
Micro SD Card 8GB
×1
on off switch
×1

Software apps and online services

Arduino IDE
Arduino IDE
solid edge 2025

Hand tools and fabrication machines

Wire Stripper & Cutter, 32-20 AWG / 0.05-0.5mm² Solid & Stranded Wires
Wire Stripper & Cutter, 32-20 AWG / 0.05-0.5mm² Solid & Stranded Wires
Soldering Iron Kit, Weller XNT/THM Tips
Soldering Iron Kit, Weller XNT/THM Tips
Extraction Tool, 6 Piece Screw Extractor & Screwdriver Set
Extraction Tool, 6 Piece Screw Extractor & Screwdriver Set
Solder Wire, Lead Free
Solder Wire, Lead Free
Wire Stripper & Cutter, 26-14 AWG Solid & Stranded Wires
Wire Stripper & Cutter, 26-14 AWG Solid & Stranded Wires

Story

Read more

Custom parts and enclosures

3D Encloser Files

Designed in Solid Edge 2025 Community Edition

Schematics

Manual and Other Information

Hardware Connection with Operating Manual

Code

STM32 Smart Bell: A Programmable Multi-Timetable System with MP3 & Exam Mode

C/C++
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <LedControl.h>
#include <string.h>
#include <STM32RTC.h>
#include <DFRobotDFPlayerMini.h>

/* ================= BUTTON STRUCT ================= */
struct ButtonState {
  bool last;
  unsigned long lastTime;
};

ButtonState btnSet  = {true, 0};
ButtonState btnUp   = {true, 0};
ButtonState btnDown = {true, 0};

/* ================= HARDWARE ================= */
#define BTN_SET   PB14
#define BTN_UP    PB13
#define BTN_DOWN  PB12

#define EEPROM_ADDR 0x50
#define MAX_BELLS 20
#define TT_NAME_LEN 12
#define EEPROM_BASE 0


#define EEPROM_TTCOUNT_L   0
#define EEPROM_TTCOUNT_H   1
#define EEPROM_DEFAULT_TT  2
#define EEPROM_TT_START    3


/* ---------- 8x8 MAX7219 ---------- */
#define MATRIX_DIN PA7
#define MATRIX_CLK PA5
#define MATRIX_CS  PA4

#define BUZZER_PIN PA2
#define RELAY_PIN  PA1



/* -------- DFPLAYER -------- */
#define MP3_BUSY_PIN PB1
HardwareSerial mp3Serial(PA10, PA9);   // RX, TX

/////////////////////// Start Exam timer //////////////
bool examRunning = false;
bool examScheduled = false;
uint32_t examStartSec = 0;
uint32_t examDurationSec = 0;


//examScheduled = true;


uint8_t examStartH = 0;
uint8_t examStartM = 0;

uint8_t examDurH = 3;   // default 3 hours
uint8_t examDurM = 0;
uint16_t selectedTimetableIndex = 0;

//////////////////////End Exam Timer 
/* ================= UI ================= */
enum UiState {
  UI_DASHBOARD,
  UI_MENU,
  UI_SET_DATETIME,
  UI_SET_BELL,
  UI_MANAGE_Timetable,
  UI_CLEAR_EEPROM,
  UI_TRIGGER_BELL,
  UI_EXAM_TIMER,
  UI_AUTO_BELLS,
  UI_SELFTEST
};

UiState uiState = UI_DASHBOARD;
bool screenChanged = true;


//HardwareSerial &mp3Serial = Serial1;
DFRobotDFPlayerMini mp3;

/* ================= BELL TYPE ================= */
enum BellType : uint8_t
{
  BELL_PERIOD = 0,
  BELL_PRAYER = 1,
  BELL_RECESS = 2,
  BELL_END    = 3
};

LedControl matrix = LedControl(MATRIX_DIN, MATRIX_CLK, MATRIX_CS, 1);
LiquidCrystal_I2C lcd(0x27, 20, 4);

STM32RTC& rtc = STM32RTC::getInstance();

/* ================= DATA ================= */
uint8_t bellCount = 0;
uint8_t bellHour[MAX_BELLS];
uint8_t bellMin[MAX_BELLS];
uint8_t bellType[MAX_BELLS];


bool ttInfoEmbedded = false;
bool bellActive = false;

/* ---- mp3 runtime ---- */
bool mp3Playing = false;
int  activeBellIndex = -1;

/* ================= Time Table  ================= */
void readTimetableName(uint16_t index, char *out)
{
  if (index >= getTimetableCount())
  {
    out[0] = 0;
    return;
  }

  uint16_t addr = getTimetableAddress(index);

  for (uint8_t i = 0; i < TT_NAME_LEN; i++)
    out[i] = eepromReadByte(addr + i);

  out[TT_NAME_LEN] = 0;
}







/* ================= Time Table  ================= */
bool deleteTimetable(uint16_t index)
{
  uint16_t total = getTimetableCount();
  if (total == 0 || index >= total) return false;

  uint16_t addrDel  = getTimetableAddress(index);
  uint16_t addrNext = getTimetableAddress(index + 1);
  uint16_t addrEnd  = getTimetableAddress(total);

  // Shift EEPROM data to close the gap
  for (uint16_t a = addrNext; a < addrEnd; a++)
  {
    uint8_t v = eepromReadByte(a);
    eepromWriteByte(addrDel++, v);
  }

  setTimetableCount(total - 1);

  uint8_t def = getDefaultTimetable();
  if (def == index) 
  {
      setDefaultTimetable(0); // Reset to first if current was deleted
  } 
  else if (def > index) 
  {
      setDefaultTimetable(def - 1); // Shift index down
  }

  // Reload the current default into RAM so the bells update immediately
  loadTimetable(getDefaultTimetable()); 
  
  return true;
}

/* ================= Time Table  ================= */
uint8_t getTimetableBellCount(uint16_t index)
{
  uint16_t addr = getTimetableAddress(index);
  addr += TT_NAME_LEN;
  return eepromReadByte(addr);
}
/* ================= Time Table  ================= */
void readTimetableBell(uint16_t ttIndex,
                        uint8_t bellIndex,
                        uint8_t &h,
                        uint8_t &m,
                        uint8_t &t)
{
  uint16_t addr = getTimetableAddress(ttIndex);

  addr += TT_NAME_LEN;           // skip name
  uint8_t cnt = eepromReadByte(addr++);
  if (bellIndex >= cnt) return;

  addr += bellIndex * 3;

  h = eepromReadByte(addr++);
  m = eepromReadByte(addr++);
  t = eepromReadByte(addr++);
}
/* ================= Time Table  ================= */
void screenTimetableInfo(uint16_t ttIndex)
{
  static uint8_t top = 0;

  if (screenChanged)
  {
    top = 0;
    lcd.clear();
    lcd.noBlink();
    screenChanged = false;
  }

  uint8_t total = getTimetableBellCount(ttIndex);

  char name[TT_NAME_LEN+1];
  readTimetableName(ttIndex, name);

  char line[21];

  // row 0  name
  snprintf(line, sizeof(line), "TT: %s", name);
  lcdPrintRow(0, line);

  // rows 1 & 2  two bells per page
  for (uint8_t r = 0; r < 2; r++)
  {
    uint8_t i = top + r;

    if (i < total)
    {
      uint8_t h,m,t;
      readTimetableBell(ttIndex, i, h, m, t);

      char c;
      if      (t == BELL_PERIOD) c = 'C';
      else if (t == BELL_PRAYER) c = 'P';
      else if (t == BELL_RECESS) c = 'R';
      else if (t == BELL_END)    c = 'E';
      else                       c = '?';

      snprintf(line, sizeof(line),
               "B%02d %02d:%02d %c",
               i+1, h, m, c);

      lcdPrintRow(1+r, line);
    }
    else
      lcdPrintRow(1+r, "");
  }

  lcdPrintRow(3, "UP/DN SCROLL SET");

  if (readButton(BTN_DOWN, btnDown))
  {
    if (top + 2 < total) top++;
  }

  if (readButton(BTN_UP, btnUp))
  {
    if (top > 0) top--;
  }

  if (readButton(BTN_SET, btnSet))
  {
    uiState = UI_MENU;   // or back to list (see note below)
    screenChanged = true;
  }
}
/* ================= Time Table  ================= */
/* ================= Time Table  ================= */
/* ================= Time Table  ================= */
/* ================= Time Table  ================= */
/* ================= Time Table  ================= */
/* ================= Time Table  ================= */
/* ================= Time Table  ================= */
/* ================= Time Table  ================= */

void eepromWriteBlock(uint16_t addr, const uint8_t *buf, uint16_t len)
{
  for (uint16_t i = 0; i < len; i++)
    eepromWriteByte(addr + i, buf[i]);
}

void eepromReadBlock(uint16_t addr, uint8_t *buf, uint16_t len)
{
  for (uint16_t i = 0; i < len; i++)
    buf[i] = eepromReadByte(addr + i);
}

uint16_t getTimetableCount()
{
  uint16_t v  = eepromReadByte(EEPROM_TTCOUNT_L);
  v |= (uint16_t)eepromReadByte(EEPROM_TTCOUNT_H) << 8;
  return v;
}

void setTimetableCount(uint16_t c)
{
  eepromWriteByte(EEPROM_TTCOUNT_L, c & 0xFF);
  eepromWriteByte(EEPROM_TTCOUNT_H, (c >> 8) & 0xFF);
}

uint8_t getDefaultTimetable()
{
  return eepromReadByte(EEPROM_DEFAULT_TT);
}

void setDefaultTimetable(uint8_t index)
{
  eepromWriteByte(EEPROM_DEFAULT_TT, index);
}



uint16_t getTimetableAddress(uint16_t index)
{
  uint16_t addr = EEPROM_TT_START;

  uint16_t total = getTimetableCount();
  if (index >= total) index = total;

  for (uint16_t i = 0; i < index; i++)
  {
    addr += TT_NAME_LEN;

    uint8_t cnt = eepromReadByte(addr);

    // ---------- HARD SAFETY ----------
    if (cnt > MAX_BELLS)
      cnt = 0;
    // --------------------------------

    addr += 1;
    addr += cnt * 3;
  }

  return addr;
}


const char *monthName(uint8_t m)
{
  static const char *names[] =
  {"JAN","FEB","MAR","APR","MAY","JUN",
   "JUL","AUG","SEP","OCT","NOV","DEC"};

  if (m < 1 || m > 12) return "UNK";
  return names[m-1];
}

uint8_t countMonthTimetables(const char *base)
{
  uint16_t n = getTimetableCount();
  uint8_t cnt = 0;

  for(uint16_t i=0;i<n;i++)
  {
    char name[TT_NAME_LEN+1];

    uint16_t addr = getTimetableAddress(i);

    for(uint8_t j=0;j<TT_NAME_LEN;j++)
      name[j] = eepromReadByte(addr+j);

    name[TT_NAME_LEN] = 0;

    if (strncmp(name, base, 3) == 0)
      cnt++;
  }
  return cnt;
}
void makeAutoTimetableName(char *out)
{
  uint16_t cnt = getTimetableCount();

  // first ever timetable
  if (cnt == 0)
  {
    strcpy(out, "DEFAULT");
    return;
  }

  uint8_t m = rtc.getMonth();
  const char *base = monthName(m);

  uint8_t already = countMonthTimetables(base);

  if (already == 0)
  {
    strcpy(out, base);
  }
  else
  {
    snprintf(out, TT_NAME_LEN+1, "%s_%d", base, already+1);
  }
}

bool saveCurrentTimetable(const char *name)
{
  uint16_t ttCount = getTimetableCount();

  uint16_t addr = getTimetableAddress(ttCount);

  // name
  for (uint8_t i = 0; i < TT_NAME_LEN; i++)
  {
    uint8_t c = 0;
    if (i < strlen(name)) c = name[i];
    eepromWriteByte(addr++, c);
  }

  // bell count
  eepromWriteByte(addr++, bellCount);

  // bells
  for (uint8_t i = 0; i < bellCount; i++)
  {
    eepromWriteByte(addr++, bellHour[i]);
    eepromWriteByte(addr++, bellMin[i]);
    eepromWriteByte(addr++, bellType[i]);
  }

  setTimetableCount(ttCount + 1);

  // only first timetable becomes default
  if (ttCount == 0)
  {
    setDefaultTimetable(0);
  }

  return true;
}






bool loadTimetable(uint16_t index)
{
  if (index >= getTimetableCount())
    return false;

  uint16_t addr = getTimetableAddress(index);

  // skip name
  addr += TT_NAME_LEN;

  uint8_t cnt = eepromReadByte(addr++);

  if (cnt > MAX_BELLS) return false;

  bellCount = cnt;

  for (uint8_t i = 0; i < bellCount; i++)
  {
    bellHour[i] = eepromReadByte(addr++);
    bellMin[i]  = eepromReadByte(addr++);
    bellType[i] = eepromReadByte(addr++);
  }

  return true;
}


/* ================= Time Table  ================= */


/* ================= FONT ================= */
// index mapping
// 0..9  -> digits 0..9
// 10    -> C
// 11    -> R
// 12    -> P
// 13    -> E

const byte font8x8[][8] =
{
/* 0 */
{0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00},
/* 1 */
{0x18,0x38,0x18,0x18,0x18,0x18,0x3C,0x00},
/* 2 */
{0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00},
/* 3 */
{0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00},
/* 4 */
{0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00},
/* 5 */
{0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00},
/* 6 */
{0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00},
/* 7 */
{0x7E,0x66,0x06,0x0C,0x18,0x18,0x18,0x00},
/* 8 */
{0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00},
/* 9 */
{0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00},

/* 10 = C */
{0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00},

/* 11 = R */
{0x7C,0x66,0x66,0x7C,0x6C,0x66,0x66,0x00},

/* 12 = P */
{0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00},

/* 13 = E */
{0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00}
};


/* ================= MATRIX ================= */
void drawChar8x8(byte index)
{
  matrix.clearDisplay(0);

  for (int row = 0; row < 8; row++)
  {
    // vertical flip
    byte line = font8x8[index][7 - row];

    for (int col = 0; col < 8; col++)
    {
      // horizontal flip
      bool on = line & (1 << col);

      matrix.setLed(0, row, col, on);
    }
  }
}



/* ================= MATRIX ================= */
/* ================= MATRIX ================= */
void showCountdownOnMatrix(long sec)
{
  if (sec < 0) {
    matrix.clearDisplay(0);
    return;
  }

  if (sec > 60) sec = 60;

  int cols = map(sec, 0, 60, 0, 8);

  matrix.clearDisplay(0);

  for (int c = 0; c < cols; c++) {
    for (int r = 0; r < 8; r++) {
      matrix.setLed(0, r, 7 - c, true);
    }
  }
}

/* ================= INDICATOR ================= */
void updateCountdownIndicators(long countdown)
{
  static unsigned long t = 0;
  static bool blink = false;

  if (bellCount == 0 || countdown < 0) {
    matrix.clearDisplay(0);
    return;
  }

  if (countdown <= 10 && countdown >= 1)
  {
    if (millis() - t > 200) {
      t = millis();
      blink = !blink;
    }

    if (blink) showCountdownOnMatrix(countdown);
    else matrix.clearDisplay(0);

    return;
  }

  if (countdown <= 60) {
    showCountdownOnMatrix(countdown);
    return;
  }

  matrix.clearDisplay(0);
}


/* ================= Time Table  ================= */
/* ================= Time Table  ================= */

void screenTimetableList() {
  static enum { L_LIST, L_INFO, L_CONFIRM } layer = L_LIST;
  static uint16_t sel = 0;
  static uint16_t top = 0;
  static uint8_t infoTop = 0;
  static uint8_t operation = 0; // 1=Delete, 2=Default
  static bool waitRelease = false;

  uint16_t total = getTimetableCount();

  // 1. Safety: Wait for button release
  if (waitRelease) {
    if (digitalRead(BTN_SET) == HIGH && digitalRead(BTN_UP) == HIGH && digitalRead(BTN_DOWN) == HIGH) {
      waitRelease = false;
    }
    return; 
  }

  // 2. Screen Reset
  if (screenChanged) {
    layer = L_LIST; sel = 0; top = 0;
    lcd.clear(); lcd.noBlink();
    screenChanged = false;
  }

  // 3. If EEPROM is empty
  if (total == 0) {
    lcdPrintRow(0, "NO TIMETABLES");
    lcdPrintRow(3, "SET = BACK");
    if (readButton(BTN_SET, btnSet)) { uiState = UI_MENU; screenChanged = true; }
    return;
  }

  char name[TT_NAME_LEN + 1];
  readTimetableName(sel, name);

  /* ================= LAYER: CONFIRMATION (YES/NO) ================= */
  if (layer == L_CONFIRM) {
    lcdPrintRow(0, (operation == 1) ? "DELETE TABLE?" : "SET AS DEFAULT?");
    lcdPrintRow(1, name);
    lcdPrintRow(3, "SET=YES   UP=NO");

    if (readButton(BTN_UP, btnUp)) { // User chose NO
      layer = L_INFO; 
      lcd.clear();
    }
    if (readButton(BTN_SET, btnSet)) { // User chose YES
      if (operation == 1) {
        deleteTimetable(sel);
        total = getTimetableCount();
        if (sel >= total && total > 0) sel = total - 1;
        layer = L_LIST;
      } else {
        setDefaultTimetable(sel);
        loadTimetable(sel);
        layer = L_INFO;
      }
      waitRelease = true; 
      lcd.clear();
    }
    return;
  }

  /* ================= LAYER: DETAIL INFO (Bell List) ================= */
  if (layer == L_INFO) {
    uint8_t bellTotal = getTimetableBellCount(sel);
    lcdPrintRow(0, name);

    for (uint8_t r = 0; r < 2; r++) {
      uint8_t i = infoTop + r;
      if (i < bellTotal) {
        uint8_t h, m, t; readTimetableBell(sel, i, h, m, t);
        
        // Convert Type Number to Text
        const char* typeText = "PERIOD";
        if (t == 1) typeText = "PRAYER";
        else if (t == 2) typeText = "RECESS";
        else if (t == 3) typeText = "END";

        char line[21]; 
        snprintf(line, sizeof(line), "%02d:%02d %s", h, m, typeText);
        lcdPrintRow(r + 1, line);
      } else lcdPrintRow(r + 1, "");
    }
    lcdPrintRow(3, "UP/DN=SCR SET=BACK");

    if (readButton(BTN_DOWN, btnDown) && infoTop + 1 < bellTotal) infoTop++;
    if (readButton(BTN_UP, btnUp) && infoTop > 0) infoTop--;

    // COMBOS for Delete/Default
    if (digitalRead(BTN_SET) == LOW) {
      if (digitalRead(BTN_UP) == LOW)   { operation = 2; layer = L_CONFIRM; waitRelease = true; lcd.clear(); return; }
      if (digitalRead(BTN_DOWN) == LOW) { operation = 1; layer = L_CONFIRM; waitRelease = true; lcd.clear(); return; }
    }

    if (readButton(BTN_SET, btnSet)) { layer = L_LIST; infoTop = 0; lcd.clear(); }
    return;
  }

  /* ================= LAYER: LIST VIEW (Main Selection) ================= */
  if (readButton(BTN_DOWN, btnDown) && sel + 1 < total) sel++;
  if (readButton(BTN_UP, btnUp)) {
    if (sel > 0) sel--;
    else { // If user presses UP at the top of the list, go back to Menu
       uiState = UI_MENU;
       screenChanged = true;
       return;
    }
  }

  if (sel < top) top = sel;
  if (sel >= top + 2) top = sel - 1;

  lcdPrintRow(0, "   SELECT TABLE");
  uint8_t defIdx = getDefaultTimetable();

  for (uint8_t r = 0; r < 2; r++) {
    uint16_t idx = top + r;
    if (idx < total) {
      char tName[TT_NAME_LEN + 1]; readTimetableName(idx, tName);
      char line[21];
      snprintf(line, sizeof(line), "%c %-12s %c", (idx == sel) ? '>' : ' ', tName, (idx == defIdx) ? '*' : ' ');
      lcdPrintRow(r + 1, line);
    } else lcdPrintRow(r + 1, "");
  }
  lcdPrintRow(3, "SET=OPEN  UP=EXIT");

  if (readButton(BTN_SET, btnSet)) { layer = L_INFO; lcd.clear(); }
}



/* ================= Time Table  ================= */
/* ================= Time Table  ================= */










///////////////////////
////////////////////////
/////////////////////////



/* ================= Time Table  ================= */
/* ================= EEPROM ================= */
void eepromWriteByte(uint16_t addr, uint8_t data) {
  Wire.beginTransmission(EEPROM_ADDR);
  Wire.write((uint8_t)(addr >> 8));
  Wire.write((uint8_t)(addr & 0xFF));
  Wire.write(data);
  Wire.endTransmission();
  delay(10);
}

uint8_t eepromReadByte(uint16_t addr) {
  Wire.beginTransmission(EEPROM_ADDR);
  Wire.write(addr >> 8);
  Wire.write(addr & 0xFF);
  Wire.endTransmission();
  Wire.requestFrom(EEPROM_ADDR, (uint8_t)1);
  return Wire.available() ? Wire.read() : 0;
}

/* ================= BUTTON ================= */
bool readButton(uint8_t pin, ButtonState &b)
{
  bool now = digitalRead(pin);

  if (now != b.last) {
    b.lastTime = millis();
    b.last = now;
  }

  if ((millis() - b.lastTime) > 40) {
    if (now == LOW) {
      static unsigned long lock[3] = {0,0,0};

      unsigned long *lk;
      if (pin == BTN_SET)      lk = &lock[0];
      else if (pin == BTN_UP)  lk = &lock[1];
      else                     lk = &lock[2];

      if (millis() - *lk > 300) {
        *lk = millis();
        return true;
      }
    }
  }
  return false;
}

/* ================= LCD ================= */
void lcdPrintRow(uint8_t row, const char *txt) {
  lcd.setCursor(0, row);
  lcd.print(txt);
  for (int i = strlen(txt); i < 20; i++) lcd.print(' ');
}

void showError(const char* line1, const char* line2) {
  lcd.clear();
  lcdPrintRow(1, line1);
  lcdPrintRow(2, line2);
  delay(1500);
}

/* ================= TIME / BELL ================= */
int timeToMinutes(uint8_t h, uint8_t m) {
  return h * 60 + m;
}

void getBellStatus(int &currentBell, int &nextBell, long &countdownSec) {

  currentBell = -1;
  nextBell = -1;
  countdownSec = -1;

  if (bellCount == 0) return;

  int nowMin = timeToMinutes(rtc.getHours(), rtc.getMinutes());
  int nowSec = rtc.getSeconds();

  for (int i = 0; i < bellCount; i++) {

    int bellTimeMin = timeToMinutes(bellHour[i], bellMin[i]);

    if (nowMin > bellTimeMin || (nowMin == bellTimeMin && nowSec > 0)) {
      currentBell = i;
    }
    else {
      nextBell = i;
      int deltaMin = bellTimeMin - nowMin;
      countdownSec = deltaMin * 60 - nowSec;
      return;
    }
  }

  currentBell = -1;
  nextBell    = -1;
  countdownSec = -1;
}

/* ================= BELL HANDLER ================= */
void handleBell()
{
  static unsigned long bellStart = 0;
  static bool timerStarted = false;

  if (bellActive && !timerStarted)
  {
      bellStart = millis();
      timerStarted = true;
  }

  if (bellActive && timerStarted)
  {
      if (millis() - bellStart >= 3000)
      {
          digitalWrite(RELAY_PIN, HIGH);
          digitalWrite(BUZZER_PIN, LOW);

          bellActive = false;
          timerStarted = false;
      }
  }
}



/* ================= I2C SCAN ================= */
void scanI2C() {
  for (byte a = 1; a < 127; a++) {
    Wire.beginTransmission(a);
    if (Wire.endTransmission() == 0) {
      lcd.clear();
      lcdPrintRow(0, "I2C found:");
      lcd.setCursor(0,1);
      lcd.print(a, HEX);
      delay(300);
    }
  }
}

/* ================= DASHBOARD ================= */
char getBellSymbolChar(int index)
{
  if (index < 0 || index >= bellCount) return '-';

  if (bellType[index] == BELL_PRAYER) return 'P';
  if (bellType[index] == BELL_RECESS) return 'R';
  if (bellType[index] == BELL_END)    return 'E';

  // PERIOD
  return 'C';   // N = normal / period
}



/* ================= DASHBOARD ================= */


/* ================= DASHBOARD ================= */
void screenDashboard() {

  if (screenChanged) {
    lcd.clear();
    lcd.noBlink();
    screenChanged = false;
  }

  char line0[21];

  snprintf(line0, sizeof(line0),
         "%02d/%02d/%04d %02d:%02d:%02d",
         rtc.getDay(),
         rtc.getMonth(),
         2000 + rtc.getYear(),
         rtc.getHours(),
         rtc.getMinutes(),
         rtc.getSeconds());

  lcdPrintRow(0, line0);

  int curBell, nextBell;
  long countdown;

  getBellStatus(curBell, nextBell, countdown);

if (curBell >= 0)
//  snprintf(line0, sizeof(line0),"Current Bell:%2d %c",curBell + 1,getBellSymbolChar(curBell));
{
    uint8_t t = bellType[curBell];
    int periodNumber = 0;

    // Calculate period number exactly like the Matrix logic does
    for (int i = 0; i <= curBell; i++) {
        if (bellType[i] == BELL_PERIOD) {
            periodNumber++;
        }
    }

    if (t == BELL_PRAYER) {
        snprintf(line0, sizeof(line0), "Current: PRAYER [P]");
    } else if (t == BELL_RECESS) {
        snprintf(line0, sizeof(line0), "Current: RECESS [R]");
    } else if (t == BELL_END) {
        snprintf(line0, sizeof(line0), "Current: SCHOOL OFF[E]");
    } else {
        // This matches the 1, 2, 3... shown on your Matrix
        snprintf(line0, sizeof(line0), "Current: PERIOD %d", periodNumber);
    }
} else {
    snprintf(line0, sizeof(line0), "Current: --");
}
lcdPrintRow(1, line0);
//else
  //snprintf(line0, sizeof(line0), "Current Bell: --");

//lcdPrintRow(1, line0);






if (nextBell >= 0)
  snprintf(line0, sizeof(line0),
           "Next Bell: %02d:%02d %c",
           bellHour[nextBell],
           bellMin[nextBell],
           getBellSymbolChar(nextBell));
else
  snprintf(line0, sizeof(line0), "Next Bell:   --:--");

lcdPrintRow(2, line0);

  if (countdown >= 0) {

    int hh = countdown / 3600;
    int mm = (countdown % 3600) / 60;
    int ss = countdown % 60;

    snprintf(line0, sizeof(line0),
             "In: %02d:%02d:%02d", hh, mm, ss);
  }
  else {
    snprintf(line0, sizeof(line0), "In: --:--:--");
  }

  lcdPrintRow(3, line0);

  if (readButton(BTN_SET, btnSet)) {
    uiState = UI_MENU;
    screenChanged = true;
  }
}

/* ================= MENU ================= */
/* ================= MENU ================= */
void screenMenu()
{
...

This file has been truncated, please download it to see its full contents.

Credits

balli2000in
1 project • 0 followers

Comments