Arduino based digital clock.
https://zzncx.top/posts/digital-clock-part-6/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
737 lines
25 KiB
737 lines
25 KiB
// #define DEBUG /* Uncomment this line to enable Serial debug output. */ |
|
|
|
#define USE_STANDARD_LCD |
|
#include <LcdMenu.h> |
|
#include <ThreeWire.h> |
|
#include <RtcDS1302.h> |
|
#include <DHT.h> |
|
#include <toneAC.h> /* This means only pin 9 and 10 can be used by the buzzer. */ |
|
#include <LowPower.h> |
|
#include "DebugUtil.h" |
|
|
|
/* Command key for each button. This signifies which button is currently pressed. */ |
|
#define CMD_IGNORE 0 /* This means no button is pressed. */ |
|
#define CMD_ENTER 1 /* Enter button is pressed. */ |
|
#define CMD_BACK 2 /* Back button is pressed. */ |
|
#define CMD_UP 3 /* Up button is pressed. */ |
|
#define CMD_DOWN 4 /* Down button is pressed. */ |
|
#define CMD_LEFT 5 /* Left button is pressed. */ |
|
#define CMD_RIGHT 6 /* Right button is pressed. */ |
|
|
|
#define SCREEN_CLOCK 0 /* Currently showing clock. */ |
|
#define SCREEN_MENU 1 /* Currently in the menu system. */ |
|
#define SCREEN_SET_TIME 2 /* Currently executing menu item "Set Time". */ |
|
#define SCREEN_SET_ALARM_TIME 3 /* Currently executing menu item "Set Alarm Time". */ |
|
|
|
/* Edit positions. Used in SCREEN_SET_TIME and SCREEN_SET_ALARM_TIME. */ |
|
#define EDIT_YEAR 0 /* Editing year. */ |
|
#define EDIT_MONTH 1 /* Editing month. */ |
|
#define EDIT_DAY 2 /* Editing day of month.*/ |
|
#define EDIT_HOUR 3 /* Editing hour. */ |
|
#define EDIT_MINUTE 4 /* Editing minute. */ |
|
#define EDIT_SECOND 5 /* Editing second. */ |
|
|
|
void isrBtn(); |
|
void checkButtons(); |
|
|
|
void cbMenuSetTime(); |
|
void cbMenuToggleAlarm(uint8_t); |
|
void cbMenuSetAlarm(); |
|
void cbMenuSetAlarmDuration(uint8_t); |
|
|
|
void screenClock(); |
|
void screenMenu(); |
|
void screenSetTime(); |
|
void screenSetAlarmTime(); |
|
void powerDown(period_t); |
|
void screenTransition(); |
|
void alarm(); |
|
void resetAlarmRun(); |
|
void sleep(); |
|
|
|
void showClock(const RtcDateTime&, uint8_t, uint8_t, uint8_t, uint8_t); |
|
void showDHT(); |
|
void showSetAlarmTime(uint8_t, uint8_t); |
|
|
|
#define MAD_ALARM_ENABLE (uint8_t) 0 /* RTC memory address for "Alarm Enable/Disable" flag. */ |
|
#define MAD_ALARM_HOUR (uint8_t) 1 /* RTC memory address for "Alarm Time (Hour)". */ |
|
#define MAD_ALARM_MINUTE (uint8_t) 2 /* RTC memory address for "Alarm Time (Minute)". */ |
|
#define MAD_ALARM_DURATION (uint8_t) 3 /* RTC memory address for "Alarm Duration" (in minute). */ |
|
|
|
void restoreAlarm(); |
|
|
|
uint8_t rollOver(uint8_t, uint8_t, uint8_t, boolean); |
|
|
|
String alarmRunStr[] = { "1m", "5m", "10m", "15m" }; /* Array of allowable alarm runs (displayed in menu). */ |
|
uint8_t alarmRunMin[] = { 1, 5, 10, 15 }; /* Array of allowable alarm runs (actual minutes). */ |
|
|
|
MenuItem mainMenu[] = { |
|
ItemHeader(), |
|
ItemCommand("Date/Time", cbMenuSetTime), |
|
ItemToggle("Alarm", "Yes", "No", cbMenuToggleAlarm), |
|
ItemCommand("Alarm Time", cbMenuSetAlarm), |
|
ItemList("Alarm Run", alarmRunStr, 4, cbMenuSetAlarmDuration), |
|
ItemFooter() |
|
}; |
|
|
|
#define LCD_ROWS 2 |
|
#define LCD_COLS 16 |
|
|
|
extern volatile unsigned long timer0_millis; /* DHT11 depends on millis(). */ |
|
volatile boolean btnPressed = false; |
|
LcdMenu menu(LCD_ROWS, LCD_COLS); |
|
ThreeWire rtcWire(7, 8, 6); // DAT, CLK, RST |
|
RtcDS1302<ThreeWire> rtc(rtcWire); |
|
RtcDateTime clockTime; /* The currently displayed clock time. */ |
|
uint8_t prevSec = 61; /* Pick a value that's not 0 to 60. */ |
|
uint8_t cmdKey = CMD_IGNORE; /* Which button is currently pressed. */ |
|
uint8_t currScreen = SCREEN_CLOCK; /* Which screen is currenly active. */ |
|
boolean screenInTransit = false; /* Flag that the screen transition is in progress, so do not sleep. */ |
|
boolean editTimeInit = false; /* Is editTime already initialized? */ |
|
RtcDateTime editTime; /* The time shown when in SCREEN_SET_TIME and SCREEN_SET_ALARM_TIME mode. */ |
|
boolean alarmActive = false; /* Whether the alarm is currently sounding or not. */ |
|
unsigned long alarmActiveStart = 0; /* Record the millis() start of alarmActive. */ |
|
boolean alarmEnabled = false; /* Trigger the alarm when the alarmHour and alarmMinute is reached. */ |
|
uint8_t alarmHour = 0; /* Alarm time hour. */ |
|
uint8_t alarmMinute = 0; /* Alarm time minute. */ |
|
uint8_t alarmRunIdx = 0; /* Index of alarmRunMin. */ |
|
uint8_t editPos = EDIT_YEAR; /* Which field is being edited. */ |
|
boolean editChanged = false; /* If time has been edited, this is the flag to allow repainting. */ |
|
LiquidCrystal* lcd = NULL; /* Hold reference to menu.lcd .*/ |
|
|
|
#define DHT11_INIT 255 /* Value outside DHT11 range (and also not NaN) to signify "please read the sensor value". */ |
|
|
|
DHT dht(A1, DHT11); |
|
float temperature = DHT11_INIT; |
|
float humidity = DHT11_INIT; |
|
|
|
void setup() { |
|
DEBUG_SETUP(); |
|
|
|
menu.setupLcdWithMenu(5, 4, 11, 12, A2, A3, mainMenu); |
|
menu.hide(); |
|
lcd = menu.lcd; |
|
|
|
rtc.Begin(); |
|
if (rtc.GetIsWriteProtected()) { |
|
rtc.SetIsWriteProtected(false); |
|
} |
|
if (!rtc.GetIsRunning()) { |
|
rtc.SetIsRunning(true); |
|
} |
|
|
|
restoreAlarm(); |
|
|
|
dht.begin(); |
|
|
|
pinMode(2, INPUT); |
|
attachInterrupt(digitalPinToInterrupt(2), isrBtn, RISING); |
|
|
|
pinMode(13, OUTPUT); |
|
digitalWrite(13, LOW); |
|
|
|
pinMode(A4, OUTPUT); |
|
digitalWrite(A4, HIGH); |
|
|
|
pinMode(A5, OUTPUT); |
|
digitalWrite(A5, HIGH); |
|
|
|
pinMode(A6, OUTPUT); |
|
digitalWrite(A6, HIGH); |
|
|
|
pinMode(A7, OUTPUT); |
|
digitalWrite(A7, HIGH); |
|
} |
|
|
|
void loop() { |
|
sleep(); |
|
checkButtons(); |
|
clockTime = rtc.GetDateTime(); /* Used by the clock display and the alarm trigger. */ |
|
if (currScreen == SCREEN_CLOCK) { |
|
screenClock(); |
|
} else if (currScreen == SCREEN_MENU) { |
|
screenMenu(); |
|
} else if (currScreen == SCREEN_SET_TIME) { |
|
screenSetTime(); |
|
} else if (currScreen == SCREEN_SET_ALARM_TIME) { |
|
screenSetAlarmTime(); |
|
} |
|
alarm(); |
|
cmdKey = CMD_IGNORE; /* Must be reset at the end of each loop to prevent button press "lock". */ |
|
} |
|
|
|
void isrBtn() { |
|
btnPressed = true; |
|
digitalWrite(LED_BUILTIN, HIGH); |
|
} |
|
|
|
void checkButtons() { |
|
if (btnPressed) { |
|
const int val = analogRead(A0); |
|
if (val >= 1000 && val <= 1023) { |
|
cmdKey = CMD_ENTER; |
|
DEBUG_PRINTLN(F("ENTER")); |
|
} else if (val >= 910 && val <= 930) { |
|
cmdKey = CMD_LEFT; |
|
DEBUG_PRINTLN(F("LEFT")); |
|
} else if (val >= 810 && val <= 830) { |
|
cmdKey = CMD_UP; |
|
DEBUG_PRINTLN(F("UP")); |
|
} else if (val >= 710 && val <= 730) { |
|
cmdKey = CMD_DOWN; |
|
DEBUG_PRINTLN(F("DOWN")); |
|
} else if (val >= 620 && val <= 640) { |
|
cmdKey = CMD_RIGHT; |
|
DEBUG_PRINTLN(F("RIGHT")); |
|
} else if (val >= 530 && val <= 555) { |
|
cmdKey = CMD_BACK; |
|
DEBUG_PRINTLN(F("BACK")); |
|
} else { |
|
cmdKey = CMD_IGNORE; |
|
DEBUG_PRINT(F("Button value = ")); |
|
DEBUG_PRINT(val); |
|
DEBUG_PRINTLN(); |
|
} |
|
btnPressed = false; |
|
digitalWrite(LED_BUILTIN, LOW); |
|
} else { |
|
cmdKey = CMD_IGNORE; |
|
} |
|
screenInTransit = false; |
|
} |
|
|
|
/* Callback when the user activates the "Set Time" menu item. */ |
|
void cbMenuSetTime() { |
|
menu.hide(); |
|
currScreen = SCREEN_SET_TIME; |
|
screenTransition(); |
|
} |
|
|
|
/* Callback when the user toggles the "Alarm" enable/disable menu item. */ |
|
void cbMenuToggleAlarm(const uint8_t yesNo) { |
|
alarmEnabled = yesNo == (uint8_t) 1; |
|
rtc.SetMemory(MAD_ALARM_ENABLE, alarmEnabled ? (uint8_t) 1 : (uint8_t) 0); |
|
/* If the alarm is currently sounding, disabling alarm will stop it. */ |
|
if (!alarmEnabled && alarmActive) { |
|
alarmActive = false; |
|
noToneAC(); |
|
DEBUG_PRINTLN(F("Alarm stopped")); |
|
} |
|
} |
|
|
|
/* Callback when the user activates the "Set Alarm Time" menu item. */ |
|
void cbMenuSetAlarm() { |
|
menu.hide(); |
|
currScreen = SCREEN_SET_ALARM_TIME; |
|
screenTransition(); |
|
} |
|
|
|
/* Callback when the user commits (pressed ENTER) the alarm duration in the menu system. */ |
|
void cbMenuSetAlarmDuration(const uint8_t pos) { |
|
alarmRunIdx = pos; |
|
rtc.SetMemory(MAD_ALARM_DURATION, alarmRunIdx); |
|
|
|
DEBUG_PRINT(F("Alarm duration pos = ")); |
|
DEBUG_PRINT(pos); |
|
DEBUG_PRINT(F(", str = ")); |
|
DEBUG_PRINTLN(alarmRunStr[pos]); |
|
} |
|
|
|
/* The clock screen. This displays the current date/time and DHT sensor readings. */ |
|
void screenClock() { |
|
if (cmdKey == CMD_ENTER) { |
|
currScreen = SCREEN_MENU; |
|
lcd->clear(); /* Only necessary to make the transition uniform. */ |
|
screenTransition(); |
|
menu.show(); |
|
return; |
|
} else if (cmdKey == CMD_BACK) { |
|
if (alarmActive) { |
|
alarmActive = false; |
|
noToneAC(); |
|
DEBUG_PRINTLN(F("Alarm stopped")); |
|
} |
|
return; |
|
} |
|
|
|
if (clockTime.IsValid()) { |
|
const uint8_t sec = clockTime.Second(); |
|
if (sec != prevSec) { /* Only need to redraw when something changed. */ |
|
prevSec = sec; |
|
showClock(clockTime, 0, 0, 0, 1); |
|
} |
|
} else { |
|
lcd->setCursor(0, 0); |
|
lcd->print(F("RTC err ")); |
|
lcd->setCursor(0, 1); |
|
lcd->print(F("Check batt ")); |
|
} |
|
showDHT(); |
|
} |
|
|
|
/* The menu screen. */ |
|
void screenMenu() { |
|
if (cmdKey == CMD_ENTER) { |
|
DEBUG_PRINTLN(F("Menu ENTER")); |
|
menu.enter(); |
|
} else if (cmdKey == CMD_BACK) { |
|
DEBUG_PRINTLN(F("Menu BACK")); |
|
resetAlarmRun(); |
|
menu.hide(); |
|
temperature = DHT11_INIT; /* If not reset, it will not be redrawn. */ |
|
humidity = DHT11_INIT; /* If not reset, it will not be redrawn. */ |
|
currScreen = SCREEN_CLOCK; |
|
screenTransition(); |
|
} else if (cmdKey == CMD_UP) { |
|
DEBUG_PRINTLN(F("Menu UP")); |
|
resetAlarmRun(); |
|
menu.up(); |
|
} else if (cmdKey == CMD_DOWN) { |
|
DEBUG_PRINTLN(F("Menu DOWN")); |
|
resetAlarmRun(); |
|
menu.down(); |
|
} else if (cmdKey == CMD_LEFT) { |
|
DEBUG_PRINTLN(F("Menu LEFT")); |
|
menu.left(); |
|
} else if (cmdKey == CMD_RIGHT) { |
|
DEBUG_PRINTLN(F("Menu RIGHT")); |
|
menu.right(); |
|
} |
|
} |
|
|
|
/* The "Set Time" screen. */ |
|
void screenSetTime() { |
|
if (cmdKey == CMD_ENTER) { /* Apply date/time adjustments. */ |
|
rtc.SetDateTime(editTime); |
|
} |
|
if (cmdKey == CMD_ENTER || cmdKey == CMD_BACK) { /* Exit this screen. */ |
|
lcd->noBlink(); |
|
editTimeInit = false; |
|
editChanged = false; |
|
currScreen = SCREEN_MENU; |
|
lcd->clear(); /* Only necessary to make the transition uniform. */ |
|
screenTransition(); |
|
menu.show(); |
|
return; |
|
} |
|
|
|
boolean posChanged = false; |
|
if (!editTimeInit) { |
|
editTimeInit = true; |
|
editTime = clockTime; |
|
lcd->blink(); |
|
editChanged = true; |
|
editPos = EDIT_YEAR; |
|
posChanged = true; |
|
} |
|
|
|
if (editPos == EDIT_YEAR) { |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
const uint8_t year = rollOver(editTime.Year() - c_OriginYear, 0, 99, cmdKey == CMD_UP); |
|
editTime = RtcDateTime(year, editTime.Month(), editTime.Day(), editTime.Hour(), editTime.Minute(), editTime.Second()); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT) { |
|
editPos = EDIT_SECOND; |
|
posChanged = true; |
|
} else if (cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_MONTH; |
|
posChanged = true; |
|
} |
|
} else if (editPos == EDIT_MONTH) { |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
const uint8_t month = rollOver(editTime.Month(), 1, 12, cmdKey == CMD_UP); |
|
editTime = RtcDateTime(editTime.Year(), month, editTime.Day(), editTime.Hour(), editTime.Minute(), editTime.Second()); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT) { |
|
editPos = EDIT_YEAR; |
|
posChanged = true; |
|
} else if (cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_DAY; |
|
posChanged = true; |
|
} |
|
} else if (editPos == EDIT_DAY) { |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
const uint8_t dayOfMonth = rollOver(editTime.Day(), 1, 31, cmdKey == CMD_UP); |
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), dayOfMonth, editTime.Hour(), editTime.Minute(), editTime.Second()); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT) { |
|
editPos = EDIT_MONTH; |
|
posChanged = true; |
|
} else if (cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_HOUR; |
|
posChanged = true; |
|
} |
|
} else if (editPos == EDIT_HOUR) { |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
const uint8_t hour = rollOver(editTime.Hour(), 0, 23, cmdKey == CMD_UP); |
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), editTime.Day(), hour, editTime.Minute(), editTime.Second()); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT) { |
|
editPos = EDIT_DAY; |
|
posChanged = true; |
|
} else if (cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_MINUTE; |
|
posChanged = true; |
|
} |
|
} else if (editPos == EDIT_MINUTE) { |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
const uint8_t minute = rollOver(editTime.Minute(), 0, 59, cmdKey == CMD_UP); |
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), editTime.Day(), editTime.Hour(), minute, editTime.Second()); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT) { |
|
editPos = EDIT_HOUR; |
|
posChanged = true; |
|
} else if (cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_SECOND; |
|
posChanged = true; |
|
} |
|
} else { // editPos == EDIT_SECOND |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
const uint8_t second = rollOver(editTime.Second(), 0, 59, cmdKey == CMD_UP); |
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), editTime.Day(), editTime.Hour(), editTime.Minute(), second); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT) { |
|
editPos = EDIT_MINUTE; |
|
posChanged = true; |
|
} else if (cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_YEAR; |
|
posChanged = true; |
|
} |
|
} |
|
|
|
if (editChanged) { /* In the "Set Time" screen, the LCD only need to be updated when something has been changed. */ |
|
uint8_t dayMax = 31; |
|
while (!editTime.IsValid()) { /* Needed to prevent edge case such as 2020-02-31. */ |
|
dayMax--; |
|
const boolean inc = editPos == EDIT_DAY ? cmdKey == CMD_UP : false; |
|
const uint8_t dayOfMonth = rollOver(editTime.Day(), 1, dayMax, inc); |
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), dayOfMonth, editTime.Hour(), editTime.Minute(), editTime.Second()); |
|
} |
|
showClock(editTime, 3, 0, 4, 1); /* This changes cursor position, so the posChanged need to be adjusted. */ |
|
editChanged = false; |
|
posChanged = true; |
|
} |
|
|
|
if (posChanged) { /* Change cursor coordinate to the new edit position. */ |
|
uint8_t lcdX, lcdY; |
|
if (editPos == EDIT_YEAR) { |
|
lcdX = 6; |
|
lcdY = 0; |
|
} else if (editPos == EDIT_MONTH) { |
|
lcdX = 9; |
|
lcdY = 0; |
|
} else if (editPos == EDIT_DAY) { |
|
lcdX = 12; |
|
lcdY = 0; |
|
} else if (editPos == EDIT_HOUR) { |
|
lcdX = 5; |
|
lcdY = 1; |
|
} else if (editPos == EDIT_MINUTE) { |
|
lcdX = 8; |
|
lcdY = 1; |
|
} else { // editPos == EDIT_SECOND |
|
lcdX = 11; |
|
lcdY = 1; |
|
} |
|
lcd->setCursor(lcdX, lcdY); |
|
} |
|
} |
|
|
|
/* The "Set Alarm Time" screen. */ |
|
void screenSetAlarmTime() { |
|
if (cmdKey == CMD_ENTER) { /* Write alarm time adjustments to RTC memory. */ |
|
rtc.SetMemory(MAD_ALARM_HOUR, alarmHour); |
|
rtc.SetMemory(MAD_ALARM_MINUTE, alarmMinute); |
|
} else if (cmdKey == CMD_BACK) { /* Reset alarm time to the one already set in RTC memory. */ |
|
alarmHour = rtc.GetMemory(MAD_ALARM_HOUR); |
|
alarmMinute = rtc.GetMemory(MAD_ALARM_MINUTE); |
|
if (alarmHour > 23 || alarmMinute > 59) { |
|
/* Garbage from RTC RAM, so we set default alarm: 7 o'clock. */ |
|
alarmHour = 7; |
|
alarmMinute = 0; |
|
} |
|
} |
|
if (cmdKey == CMD_ENTER || cmdKey == CMD_BACK) { /* Exit this screen. */ |
|
lcd->noBlink(); |
|
editTimeInit = false; |
|
editChanged = false; |
|
currScreen = SCREEN_MENU; |
|
lcd->clear(); /* Only necessary to make the transition uniform. */ |
|
screenTransition(); |
|
menu.show(); |
|
return; |
|
} |
|
|
|
boolean posChanged = false; |
|
if (!editTimeInit) { |
|
editTimeInit = true; |
|
lcd->blink(); |
|
editChanged = true; |
|
editPos = EDIT_HOUR; |
|
posChanged = true; |
|
} |
|
|
|
if (editPos == EDIT_HOUR) { |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
alarmHour = rollOver(alarmHour, 0, 23, cmdKey == CMD_UP); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT || cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_MINUTE; |
|
posChanged = true; |
|
} |
|
} else { // editPos == EDIT_MINUTE |
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) { |
|
alarmMinute = rollOver(alarmMinute, 0, 59, cmdKey == CMD_UP); |
|
editChanged = true; |
|
} else if (cmdKey == CMD_LEFT || cmdKey == CMD_RIGHT) { |
|
editPos = EDIT_HOUR; |
|
posChanged = true; |
|
} |
|
} |
|
|
|
if (editChanged) { /* In the "Set Alarm Time" screen, the LCD only need to be updated when something has been changed. */ |
|
showSetAlarmTime(alarmHour, alarmMinute); /* This changes cursor position, so the posChanged need to be adjusted. */ |
|
editChanged = false; |
|
posChanged = true; |
|
} |
|
|
|
if (posChanged) { /* Change cursor coordinate to the new edit position. */ |
|
uint8_t lcdX, lcdY; |
|
if (editPos == EDIT_HOUR) { |
|
lcdX = 7; |
|
lcdY = 0; |
|
} else { // editPos == EDIT_MINUTE |
|
lcdX = 10; |
|
lcdY = 0; |
|
} |
|
lcd->setCursor(lcdX, lcdY); |
|
} |
|
} |
|
|
|
void powerDown(period_t period) { |
|
unsigned long addMillis; |
|
if (period == SLEEP_15MS) { addMillis = 15; } |
|
else if (period == SLEEP_30MS) { addMillis = 30; } |
|
else if (period == SLEEP_60MS) { addMillis = 60; } |
|
else if (period == SLEEP_120MS) { addMillis = 120; } |
|
else if (period == SLEEP_250MS) { addMillis = 250; } |
|
else if (period == SLEEP_500MS) { addMillis = 500; } |
|
else if (period == SLEEP_1S) { addMillis = 1000; } |
|
else if (period == SLEEP_2S) { addMillis = 2000; } |
|
else if (period == SLEEP_4S) { addMillis = 4000; } |
|
else if (period == SLEEP_8S) { addMillis = 8000; } |
|
else if (period == SLEEP_FOREVER) { addMillis = 0; } |
|
if (addMillis > 0) { |
|
DEBUG_PRINT(F("Sleep for ")); |
|
DEBUG_PRINT(addMillis) |
|
DEBUG_PRINTLN(F(" ms")); |
|
} else { |
|
DEBUG_PRINTLN(F("Sleep forever")); |
|
} |
|
DEBUG_TEARDOWN(); |
|
LowPower.powerDown(period, ADC_OFF, BOD_OFF); |
|
const uint8_t oldSREG = SREG; |
|
cli(); |
|
timer0_millis += addMillis; /* Ensure DHT11 behaves. */ |
|
SREG = oldSREG; |
|
DEBUG_SETUP(); |
|
} |
|
|
|
void screenTransition() { |
|
powerDown(SLEEP_60MS); |
|
screenInTransit = true; |
|
} |
|
|
|
/* |
|
Start the alarm according to the alarmEnabled, alarmHour, and alarmMinute settings. |
|
The alarm tone is played continuously until the user stopped the alarm, or the alarm runtime is over. |
|
*/ |
|
void alarm() { |
|
boolean stopAlarm = false; |
|
const unsigned long now = millis(); |
|
if (alarmEnabled) { |
|
if (!alarmActive && clockTime.Hour() == alarmHour && clockTime.Minute() == alarmMinute && clockTime.Second() >= 00 && clockTime.Second() <= 1) { |
|
alarmActive = true; |
|
alarmActiveStart = now; |
|
DEBUG_PRINTLN(F("Alarm start")); |
|
} |
|
} else { |
|
if (alarmActive) { |
|
stopAlarm = true; |
|
} |
|
} |
|
|
|
if (alarmActive) { |
|
if (now - alarmActiveStart <= 1000UL * 60 * (unsigned long) alarmRunMin[alarmRunIdx]) { |
|
/* |
|
The alarm tone is alternating between 2400Hz and 2850Hz, each played for 250ms. |
|
The tone is one of the "General Purpose" from here: |
|
https://www.eaton.com/gb/en-gb/products/safety-security-emergency-communications/alarms-and-signaling-devices/fire-rated/fire-alarm-tones-and-sounds.html |
|
*/ |
|
toneAC((now / 250UL) % 2 == 0 ? 2400 : 2850, 10, 0, true); /* Play tone continuously in background. */ |
|
} else { |
|
stopAlarm = true; |
|
} |
|
} |
|
|
|
if (stopAlarm) { |
|
alarmActive = false; |
|
noToneAC(); |
|
DEBUG_PRINTLN(F("Alarm stopped")); |
|
} |
|
} |
|
|
|
void sleep() { |
|
if (alarmActive || screenInTransit) { |
|
return; |
|
} |
|
|
|
RtcDateTime alarmStart = RtcDateTime(clockTime.Year(), clockTime.Month(), clockTime.Day(), alarmHour, alarmMinute, 0); |
|
if (clockTime > alarmStart) { |
|
alarmStart = RtcDateTime(alarmStart.TotalSeconds() + (((uint32_t) 24) * ((uint32_t) 60) * ((uint32_t) 60))); |
|
} |
|
|
|
const uint32_t as = alarmStart.TotalSeconds(); |
|
const uint32_t c = clockTime.TotalSeconds(); |
|
period_t period; |
|
if (currScreen == SCREEN_CLOCK) { |
|
if (c + 2 <= as) { |
|
period = SLEEP_1S; |
|
} else { |
|
period = SLEEP_250MS; |
|
} |
|
} else { |
|
if (c + 9 <= as) { |
|
period = SLEEP_8S; |
|
} else if (c + 5 <= as) { |
|
period = SLEEP_4S; |
|
} else if (c + 3 <= as) { |
|
period = SLEEP_2S; |
|
} else if (c + 2 <= as) { |
|
period = SLEEP_1S; |
|
} else { |
|
period = SLEEP_250MS; |
|
} |
|
} |
|
powerDown(period); |
|
} |
|
|
|
/* |
|
Reset the menu if the selected alarm position is not committed yet. |
|
Resetting is necessary because the menu system has no indication for committed/uncommitted list position. |
|
This should be called when in the menu screen and the user pressed: BACK, UP, or RIGHT buttons. |
|
*/ |
|
void resetAlarmRun() { |
|
if (mainMenu[4].itemIndex != alarmRunIdx) { |
|
mainMenu[4].itemIndex = alarmRunIdx; |
|
} |
|
} |
|
|
|
/* Display the current RTC time to the LCD. */ |
|
void showClock(const RtcDateTime& dt, const uint8_t dateX, const uint8_t dateY, const uint8_t timeX, const uint8_t timeY) { |
|
const size_t len = 11; /* 10 chars max + 1 null terminator. */ |
|
char dateString[len]; |
|
snprintf_P(dateString, len, PSTR("%04u-%02u-%02u"), dt.Year(), dt.Month(), dt.Day()); |
|
lcd->setCursor(dateX, dateY); |
|
lcd->print(dateString); |
|
snprintf_P(dateString, len, PSTR("%02u:%02u:%02u"), dt.Hour(), dt.Minute(), dt.Second()); |
|
lcd->setCursor(timeX, timeY); |
|
lcd->print(dateString); |
|
} |
|
|
|
/* |
|
Read the DHT sensor and show the readings to the LCD. |
|
Unlike RTC, the DHT info is only needed on the main screen. |
|
So there is no point in sampling the sensor when not in the main screen. |
|
*/ |
|
void showDHT() { |
|
char str[6]; /* 5 char max + 1 null terminator */ |
|
float val = dht.readTemperature(); |
|
if (val != temperature) { /* Only need to refresh the LCD when the reading has changed. */ |
|
temperature = val; |
|
lcd->setCursor(11, 0); |
|
if (isnan(temperature)) { |
|
lcd->print(F("T err")); |
|
} else { |
|
dtostrf(temperature, 4, 1, str); |
|
lcd->print(str); |
|
lcd->print(F("C")); |
|
} |
|
} |
|
|
|
val = dht.readHumidity(); |
|
if (val != humidity) { /* Only need to refresh the LCD when the reading has changed. */ |
|
humidity = val; |
|
lcd->setCursor(11, 1); |
|
if (isnan(humidity)) { |
|
lcd->print(F("H err")); |
|
} else { |
|
dtostrf(humidity, 4, 1, str); |
|
lcd->print(str); |
|
lcd->print(F("%")); |
|
} |
|
} |
|
} |
|
|
|
/* Show the alarm time being edited to the LCD. */ |
|
void showSetAlarmTime(const uint8_t hour, const uint8_t minute) { |
|
const size_t len = 6; /* 5 chars max + 1 null terminator. */ |
|
char dateString[len]; |
|
snprintf_P(dateString, len, PSTR("%02u:%02u"), hour, minute); |
|
lcd->setCursor(6, 0); |
|
lcd->print(dateString); |
|
} |
|
|
|
/* Restore the alarm settings from the RTC RAM. */ |
|
void restoreAlarm() { |
|
const size_t count = 4; |
|
uint8_t rtcRam[count]; |
|
const uint8_t got = rtc.GetMemory(rtcRam, count); |
|
alarmEnabled = rtcRam[MAD_ALARM_ENABLE] == (uint8_t) 1; |
|
alarmHour = rtcRam[MAD_ALARM_HOUR]; |
|
alarmMinute = rtcRam[MAD_ALARM_MINUTE]; |
|
alarmRunIdx = rtcRam[MAD_ALARM_DURATION]; |
|
|
|
if (alarmHour > 23 || alarmMinute > 59) { |
|
/* Garbage from RTC RAM, so we set default alarm: 7 o'clock. */ |
|
alarmHour = 7; |
|
alarmMinute = 0; |
|
} |
|
if (alarmRunIdx >= sizeof(alarmRunMin) / sizeof(alarmRunMin[0])) { |
|
/* Garbage from RTC RAM, so we set default alarm duration: 1 minute. */ |
|
alarmRunIdx = 0; |
|
} |
|
|
|
mainMenu[2].isOn = alarmEnabled; |
|
mainMenu[4].itemIndex = alarmRunIdx; |
|
|
|
#ifdef DEBUG |
|
if (got != count) { |
|
DEBUG_PRINT(F("RTC RAM problem, count = ")); |
|
DEBUG_PRINT(count); |
|
DEBUG_PRINT(", got = "); |
|
DEBUG_PRINTLN(got); |
|
} |
|
DEBUG_PRINT(F("RTC RAM read (")); |
|
DEBUG_PRINT(got); |
|
DEBUG_PRINT(F(") = alarm ")); |
|
DEBUG_PRINT(alarmEnabled ? F("enabled") : F("disabled")); |
|
DEBUG_PRINT(F(", alarm time = ")); |
|
DEBUG_PRINT(alarmHour); |
|
DEBUG_PRINT(F(":")); |
|
DEBUG_PRINT(alarmMinute); |
|
DEBUG_PRINT(F(", alarm run = ")); |
|
DEBUG_PRINTLN(alarmRunStr[alarmRunIdx]); |
|
#else |
|
UNUSED(got); |
|
#endif |
|
} |
|
|
|
/* Roll over a numeric value when the user incremented it up or down. */ |
|
uint8_t rollOver(const uint8_t now, const uint8_t min, const uint8_t max, const boolean increment) { |
|
if (increment) { |
|
return now < max ? now + 1 : min; |
|
} else { |
|
return now > min ? now - 1 : max; |
|
} |
|
}
|
|
|