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

// #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;
}
}