#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 ¤tBell, 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.
Comments