From 047050f7d79f0cab7f0dbdf13d194ac7228246ec Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Wed, 3 May 2023 22:35:35 +0200 Subject: [PATCH 1/8] Adding Timer & Alarm for PCF8563 with examples --- .gitignore | 1 + examples/pcf8563_alarm/pcf8563_alarm.ino | 164 ++++++++++++ .../pcf8563_interrupt/pcf8563_interrupt.ino | 237 +++++++++++++----- keywords.txt | 10 +- src/RTC_PCF8563.cpp | 228 ++++++++++++++++- src/RTClib.h | 38 +++ 6 files changed, 602 insertions(+), 76 deletions(-) create mode 100644 examples/pcf8563_alarm/pcf8563_alarm.ino diff --git a/.gitignore b/.gitignore index bae33346..541e53b3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ html Doxyfile* doxygen_sqlite3.db +.vscode \ No newline at end of file diff --git a/examples/pcf8563_alarm/pcf8563_alarm.ino b/examples/pcf8563_alarm/pcf8563_alarm.ino new file mode 100644 index 00000000..a8133c6d --- /dev/null +++ b/examples/pcf8563_alarm/pcf8563_alarm.ino @@ -0,0 +1,164 @@ + +#include "RTClib.h" + +// using an ESP12-E module: +// use 2 Wire SCL: GPIO5 and SDA: GPIO4 to connect to the RTC +RTC_PCF8563 rtc; + +char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + +// Input pin with interrupt capability +// const int timerInterruptPin = 2; // Most Arduinos +// const int timerInterruptPin = 5; // Adafruit Feather M0/M4/nRF52840 +const int timerInterruptPin = 13; // ESP12F + +// Variables modified during an interrupt must be declared volatile +volatile bool alarmTriggered = false; + +DateTime alarm = DateTime(); +uint32_t timeSpan = 120; + +// Triggered by the PCF8563 Countdown Timer interrupt at the end of a countdown +// period. Meanwhile, the PCF8563 immediately starts the countdown again. +void IRAM_ATTR isrCheckAlarm() { + // Set a flag to run code in the loop(): + // Check if the interrupt was triggered by the alarm + // When using the alarm and the timer, the interrupt could be triggered by + // both. + if (rtc.alarmFired()) { + alarmTriggered = true; + rtc.clearAlarm(); + } +} + +void printDateTime(DateTime dt, String prefix = "") { + if (prefix != "") { + Serial.print(prefix); + } + Serial.print(dt.year(), DEC); + Serial.print('/'); + Serial.print(dt.month(), DEC); + Serial.print('/'); + Serial.print(dt.day(), DEC); + Serial.print(" ("); + Serial.print(daysOfTheWeek[dt.dayOfTheWeek()]); + Serial.print(") "); + Serial.print(dt.hour(), DEC); + Serial.print(':'); + Serial.print(dt.minute(), DEC); + Serial.print(':'); + Serial.print(dt.second(), DEC); + Serial.println(); +} + +void setup() { + + Serial.begin(57600); + Serial.println("\n"); + +#ifndef ESP8266 + while (!Serial) + ; // wait for serial port to connect. Needed for native USB +#endif + + // initialize RTC + if (!rtc.begin()) { + Serial.println("Couldn't find RTC"); + Serial.flush(); + while (1) + delay(10); + } + + if (rtc.lostPower()) { + Serial.println("Detected a 'power loss', RTC is NOT initialized!"); + } + + // Clear any pending alarms or timers + rtc.clearAlarm(); + rtc.clearTimer(); + rtc.disableAlarm(); + rtc.disableTimer(); + + // Use compile time to set the RTC + DateTime compileTime = DateTime(F(__DATE__), F(__TIME__)); + rtc.adjust(compileTime); + // This line sets the RTC with an explicit date & time, for example to set + // January 21, 2014 at 3am you would call: + // DateTime compileTime = DateTime(2014, 5, 21, 11, 22, 33); + // rtc.adjust(compileTime); + + // When the RTC was stopped and stays connected to the battery, it has + // to be restarted by clearing the STOP bit. Let's do this to ensure + // the RTC is running. + rtc.start(); + + // Enable the interrupt to check the alarm + attachInterrupt(digitalPinToInterrupt(timerInterruptPin), isrCheckAlarm, + FALLING); + + // Set the alarm to trigger in 2 Minutes using a DateTime object + // The alarm will be triggered on exact match, so when the minutes, + // hours, day of the month and day of the week the alarm time. + alarm = DateTime(compileTime + TimeSpan(timeSpan)); + + rtc.enableAlarm(alarm, /* minute_alarm */ true, + /* hour_alarm */ true, /* day_alarm */ true, + /* weekday_alarm*/ true); + + Serial.print(F("Current Time set to compile time: ")); + printDateTime(rtc.now()); + Serial.println(); + + Serial.print(F("Waiting for initial alarm interrupt at: ")); + printDateTime(alarm); + Serial.println(); + + Serial.println(F("=> The inital Interrupt will be triggered in ~2 Minutes! " + "After that the next alarm ")); + Serial.println(F(" will be set to twice the time span and so on!")); + Serial.println(F("=> Be aware that the PCF8573s alarm does not have a " + "seconds field, so the alarm will")); + Serial.println(F(" trigger at the minute value! Depending on the time the " + "alarm was set, this could be")); + Serial.println(F(" up to 59 seconds earlier! e.g. if a two minute alarm " + "was set at 12:00:59, the alarm")); + Serial.println(F(" will trigger 61 seconds later at at 12:02:00")); +} + +void loop() { + if (alarmTriggered) { + Serial.println(); + Serial.print("Alarm Triggered: "); + printDateTime(rtc.now()); + + timeSpan *= 2; + alarm = DateTime(rtc.now() + TimeSpan(timeSpan)); + rtc.enableAlarm(alarm, /* minute_alarm */ true, + /* hour_alarm */ true, /* day_alarm */ true, + /* weekday_alarm*/ true); + Serial.print(F("Set new Alarm to ")); + printDateTime(alarm); + Serial.println(); + alarmTriggered = false; + } else { + Serial.print("."); + } + + // Exit condition to double check the Alarm + // A 10 second buffer is added to the alarm to account + // for the time it takes to print the output. + if (rtc.now() > alarm + TimeSpan(10)) { + Serial.println(); + Serial.println("Alarm should already been triggered:"); + printDateTime(rtc.now(), "Now: "); + printDateTime(alarm, "Alarm: "); + Serial.println( + F("=> This should not happen, check the alarm settings and wiring!")); + while (1) { + delay(1000); + } + } + + delay(1000); // wait one second +} diff --git a/examples/pcf8563_interrupt/pcf8563_interrupt.ino b/examples/pcf8563_interrupt/pcf8563_interrupt.ino index 27cae411..049f0cc4 100644 --- a/examples/pcf8563_interrupt/pcf8563_interrupt.ino +++ b/examples/pcf8563_interrupt/pcf8563_interrupt.ino @@ -1,89 +1,188 @@ -// Date and time functions using a PCF8563 RTC connected via I2C and Wire lib +/**************************************************************************/ +/* + Countdown Timer using a PCF8563 RTC connected via I2C and Wire lib + with the INT pin wired to an interrupt-capable input. + + This sketch sets a countdown timer, and executes code when it reaches 0, + then blinks the built-in LED like BlinkWithoutDelay, but without millis()! + + NOTE: + You must connect the PCF8563's interrupt pin to your Arduino or other + microcontroller on an input pin that can handle interrupts, and that has a + pullup resistor, or add an external pull-up resistor. The pin will be briefly + pulled low each time the countdown reaches 0. This example will not work + without the interrupt pin connected! + + On Adafruit breakout boards, the interrupt pin is labeled 'INT' or 'SQW'. +*/ +/**************************************************************************/ + #include "RTClib.h" +// using an ESP12-E module: +// use 2 Wire SCL: GPIO5 and SDA: GPIO4 to connect to the RTC RTC_PCF8563 rtc; -char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; - -// use D2 for INT0; attach to CLKOUT pin on RTC -const uint8_t INT_PIN = 2; +// Input pin with interrupt capability +// const int timerInterruptPin = 2; // Most Arduinos +// const int timerInterruptPin = 5; // Adafruit Feather M0/M4/nRF52840 +const int timerInterruptPin = 13; // ESP12F + +// Variables modified during an interrupt must be declared volatile +volatile bool countdownInterruptTriggered = false; +volatile int numCountdownInterrupts = 0; + +// Triggered by the PCF8563 Countdown Timer interrupt at the end of a countdown +// period. Meanwhile, the PCF8563 immediately starts the countdown again. +void IRAM_ATTR countdownOver() { + // Set a flag to run code in the loop(): + Serial.println("ISR"); + countdownInterruptTriggered = true; + numCountdownInterrupts++; + rtc.clearTimer(); +} -// flag to update serial; set in interrupt callback -volatile uint8_t tick_tock = 1; +// Triggered by normal operation of timer INT pin +void IRAM_ATTR toggleLed() { + // Run certain types of fast executing code here: + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + // The clearTimer() function is called to reset the timer Flag + rtc.clearTimer(); +} -// INT0 interrupt callback; update tick_tock flag -void set_tick_tock(void) { - tick_tock = 1; +// Triggered by TI_TP mode of timer INT pin +void IRAM_ATTR pulsedToggleLed() { + // Run certain types of fast executing code here: + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + // As we are using the TI_TP mode, the clearTimer() function is not called + // to reset the timer Flag as it is done automatically by the PCF8563. } -void setup () { - Serial.begin(115200); +void setup() { + + Serial.begin(57600); + Serial.println("\n"); #ifndef ESP8266 - while (!Serial); // wait for serial port to connect. Needed for native USB + while (!Serial) + ; // wait for serial port to connect. Needed for native USB #endif - - pinMode(INT_PIN, INPUT); // set up interrupt pin - digitalWrite(INT_PIN, HIGH); // turn on pullup resistors - // attach interrupt to set_tick_tock callback on rising edge of INT0 - attachInterrupt(digitalPinToInterrupt(INT_PIN), set_tick_tock, RISING); - - if (! rtc.begin()) { + // initialize RTC + if (!rtc.begin()) { Serial.println("Couldn't find RTC"); Serial.flush(); - while (1) delay(10); - } - - if (rtc.lostPower()) { - Serial.println("RTC is NOT initialized, let's set the time!"); - // When time needs to be set on a new device, or after a power loss, the - // following line sets the RTC to the date & time this sketch was compiled - rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); - // This line sets the RTC with an explicit date & time, for example to set - // January 21, 2014 at 3am you would call: - // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); - // - // Note: allow 2 seconds after inserting battery or applying external power - // without battery before calling adjust(). This gives the PCF8523's - // crystal oscillator time to stabilize. If you call adjust() very quickly - // after the RTC is powered, lostPower() may still return true. + while (1) + delay(10); } - - - // When time needs to be re-set on a previously configured device, the - // following line sets the RTC to the date & time this sketch was compiled - // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); - // This line sets the RTC with an explicit date & time, for example to set - // January 21, 2014 at 3am you would call: - // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); - - // When the RTC was stopped and stays connected to the battery, it has - // to be restarted by clearing the STOP bit. Let's do this to ensure - // the RTC is running. - rtc.start(); - - // turn on 1Hz clock out, used as INT0 for serial update every second - rtc.writeSqwPinMode(PCF8563_SquareWave1Hz); + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + + Serial.println(F("\nStarting PCF8563 Countdown Timer example.")); + Serial.print( + F("Configured to expect PCF8563 INT/SQW pin connected to input pin: ")); + Serial.println(timerInterruptPin); + Serial.println( + F("This example will not work without the interrupt pin connected!\n\n")); + + // Timer configuration is not cleared on an RTC reset due to battery backup! + rtc.disableAlarm(); + rtc.disableTimer(); + + Serial.println( + F("First, use the PCF8563's 'Countdown Timer' with an interrupt.")); + Serial.println( + F("Set the countdown for 10 seconds and we'll let it run for 2 rounds.")); + Serial.println(F("Starting Countdown Timer now...")); + + // These are the PCF8563's built-in "Timer Source Clock Frequencies". + // They are predefined time periods you choose as your base unit of time, + // depending on the length of countdown timer you need. + // The minimum length of your countdown is 1 time period. + // The maximum length of your countdown is 255 time periods. + // + // PCF8563_TimerFrequencyMinute = 1 minute, max 4.25 hours + // PCF8563_TimerFrequencySecond = 1 second, max 4.25 minutes + // PCF8563_TimerFrequency64Hz = 1/64 of a second (15.625 milliseconds), + // max 3.984 seconds + // PCF8563_TimerFrequency4kHz = 1/4096 of a second (244 + // microseconds), max 62.256 milliseconds + // + // Uncomment an example below: + + // rtc.enableTimer(PCF8563_TimerFrequencyHour, 24); // 1 day + // rtc.enableTimer(PCF8563_TimerFrequencyMinute, 150); // 2.5 hours + rtc.enableTimer(PCF8563_TimerFrequencySecond, 10); // 10 seconds + // rtc.enableTimer(PCF8563_TimerFrequency64Hz, 32); // 1/2 second + // rtc.enableTimer(PCF8563_TimerFrequency64Hz, 16); // 1/4 second + // rtc.enableTimer(PCF8563_TimerFrequency4kHz, 205); // 50 + // milliseconds + + attachInterrupt(digitalPinToInterrupt(timerInterruptPin), countdownOver, + FALLING); + + // This message proves we're not blocked while counting down! + Serial.println(F(" While we're waiting, a word of caution:")); + Serial.println(F(" When starting a new countdown timer, the first time " + "period is not of fixed")); + Serial.println(F(" duration. The amount of inaccuracy for the first time " + "period is up to one full")); + Serial.println(F(" clock frequency. Example: just the first second of the " + "first round of a new")); + Serial.println( + F(" countdown based on PCF8563_TimerFrequencySecond may be off by " + "as much as 1 second!")); + Serial.println(F(" For critical timing, consider starting actions on the " + "first interrupt.")); } -void loop () { - - // check if time display should be output - if(tick_tock) { - - DateTime now = rtc.now(); - - char time_format[] = "hh:mm:ss AP"; - char date_format[] = "MM/DD/YYYY"; - - Serial.println(now.toString(time_format)); - Serial.println(now.toString(date_format)); - Serial.println(); - - tick_tock = 0; - +void loop() { + if (countdownInterruptTriggered && numCountdownInterrupts == 1) { + Serial.println( + F("Countdown interrupt triggered. Accurate timekeeping starts now.")); + countdownInterruptTriggered = false; // don't come in here again + } else if (countdownInterruptTriggered && numCountdownInterrupts == 2) { + Serial.println(F("2nd countdown interrupt triggered. Disabling countdown " + "and detaching interrupt.\n\n")); + rtc.disableTimer(); + detachInterrupt(digitalPinToInterrupt(timerInterruptPin)); + delay(2000); + + Serial.println(F("Now, set up the PCF8563's Timer to toggle the " + "built-in LED at 1Hz...")); + Serial.println( + F("This time, we'll use the PCF8563's Timer Interrupt Pin " + "and reset the Timer Flag within the interrupt service routine.")); + attachInterrupt(digitalPinToInterrupt(timerInterruptPin), toggleLed, + FALLING); + rtc.enableTimer(PCF8563_TimerFrequency64Hz, 64); + Serial.println(F("Look for the built-in LED to flash 1 second ON, 1 second " + "OFF, repeat. ")); + Serial.println(F("Meanwhile this program will use delay() to block code " + "execution briefly")); + Serial.println(F("before moving on to the last example. Notice the LED " + "keeps blinking!\n\n")); + delay(20000); // less accurate, blocks execution here. Meanwhile Second + // Timer keeps running. + + Serial.println(F("Now, set up the PCF8563's Timer to toggle the " + "built-in LED at 2Hz...")); + Serial.println(F("This time, we'll use the PCF8563's Timer Interrupt Pin " + "with a pulsed timer interrupt. So no need to reset the " + "Timer Flag within the interrupt service routine.")); + attachInterrupt(digitalPinToInterrupt(timerInterruptPin), pulsedToggleLed, + FALLING); + rtc.enableTimer(PCF8563_TimerFrequency64Hz, 32, + /* Enable the pulse */ true); + Serial.println( + F("Look for the built-in LED to flash 0.5 second ON, 0.5 second " + "OFF, repeat. ")); + Serial.println(F("This is the end of this example, the LED will keep " + "blinking forever.\n\n")); + + countdownInterruptTriggered = false; // don't come in here again } -} + // Nothing to do here... +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt index d3082fef..eb39100f 100644 --- a/keywords.txt +++ b/keywords.txt @@ -23,6 +23,8 @@ PCF8523TimerClockFreq KEYWORD1 PCF8523TimerIntPulse KEYWORD1 Pcf8523OffsetMode KEYWORD1 Pcf8563SqwPinMode KEYWORD1 +Pcf8563TimerClockFreq KEYWORD1 +Pcf8563WeekDayAlarm KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -61,18 +63,22 @@ setAlarm2 KEYWORD2 disableAlarm KEYWORD2 clearAlarm KEYWORD2 alarmFired KEYWORD2 +timerFired KEYWORD2 +enableTimer KEYWORD2 +disableTimer KEYWORD2 getTemperature KEYWORD2 lostPower KEYWORD2 initialized KEYWORD2 enableSecondTimer KEYWORD2 disableSecondTimer KEYWORD2 -enableCountdownTimer KEYWORD2 -disableCountdownTimer KEYWORD2 +enableTimerInterruptPulse KEYWORD2 +disableTimerInterruptPulse KEYWORD2 deconfigureAllTimers KEYWORD2 calibrate KEYWORD2 enable32K KEYWORD2 disable32K KEYWORD2 isEnabled32K KEYWORD2 +enableAlarm KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/src/RTC_PCF8563.cpp b/src/RTC_PCF8563.cpp index 8f2e56a8..44ee057d 100644 --- a/src/RTC_PCF8563.cpp +++ b/src/RTC_PCF8563.cpp @@ -7,6 +7,11 @@ #define PCF8563_VL_SECONDS 0x02 ///< register address for VL_SECONDS #define PCF8563_CLKOUT_MASK 0x83 ///< bitmask for SqwPinMode on CLKOUT pin +#define PCF8563_MINUTE_ALARM 0x09 ///< Minute alarm register + +#define PCF8563_TIMER_CONTROL 0x0E ///< Timer control register +#define PCF8563_TIMER_VALUE 0x0F ///< Timer register + /**************************************************************************/ /*! @brief Start I2C for the PCF8563 and test succesful connection @@ -45,11 +50,12 @@ bool RTC_PCF8563::lostPower(void) { */ /**************************************************************************/ void RTC_PCF8563::adjust(const DateTime &dt) { - uint8_t buffer[8] = {PCF8563_VL_SECONDS, // start at location 2, VL_SECONDS - bin2bcd(dt.second()), bin2bcd(dt.minute()), - bin2bcd(dt.hour()), bin2bcd(dt.day()), - bin2bcd(0), // skip weekdays - bin2bcd(dt.month()), bin2bcd(dt.year() - 2000U)}; + uint8_t buffer[8] = { + PCF8563_VL_SECONDS, // start at location 2, VL_SECONDS + bin2bcd(dt.second()), bin2bcd(dt.minute()), + bin2bcd(dt.hour()), bin2bcd(dt.day()), + dt.dayOfTheWeek(), // Weekday needs to be set for alarms to work correctly + bin2bcd(dt.month()), bin2bcd(dt.year() - 2000U)}; i2c_dev->write(buffer, 8); } @@ -101,6 +107,218 @@ uint8_t RTC_PCF8563::isrunning() { return !((read_register(PCF8563_CONTROL_1) >> 5) & 1); } +/**************************************************************************/ +/*! + @brief Get status of timer + @return True if timer has been fired otherwise false +*/ +/**************************************************************************/ +bool RTC_PCF8563::timerFired() { + return (read_register(PCF8563_CONTROL_2) & 0x04); +} + +/**************************************************************************/ +/*! + @brief Clear status of timer flag +*/ +/**************************************************************************/ +void RTC_PCF8563::clearTimer() { + write_register(PCF8563_CONTROL_2, read_register(PCF8563_CONTROL_2) & ~0x04); +} + +/**************************************************************************/ +/*! + @brief Get status of alarm + @return True if alarm has been fired otherwise false +*/ +/**************************************************************************/ +bool RTC_PCF8563::alarmFired() { + return read_register(PCF8563_CONTROL_2) & 0x08; +} + +/**************************************************************************/ +/*! + @brief Clear status of alarm flag +*/ +/**************************************************************************/ +void RTC_PCF8563::clearAlarm() { + write_register(PCF8563_CONTROL_2, read_register(PCF8563_CONTROL_2) & ~0x08); +} + +/**************************************************************************/ +/*! + @brief Disable the Countdown Timer on the PCF8523. + @details For simplicity, this function strictly disables Timer by setting + TE to 0. The datasheet describes TE as the Timer on/off switch. + The following flags have no effect while TE is off, they are *not* cleared: + - TF: Timer flag + - TIE: Timer interrupt would be triggered if TBC were on. + - Set timer source clock frequency to 1/60 Hz: + These bits determine the source clock for the countdown timer; when not in + use, TD[1:0] should be set to 1⁄60 Hz for power saving. +*/ +/**************************************************************************/ +void RTC_PCF8563::disableTimer() { + // clear TE to disable Timer (Bit 7 of Timer Control) + write_register(PCF8563_TIMER_CONTROL, 0x03); + + // clear TIE, TF and TI_TP flags (Bit 4, 2, 0 of Control_2) + write_register(PCF8563_CONTROL_2, read_register(PCF8563_CONTROL_2) & ~0x15); + + write_register(PCF8563_TIMER_VALUE, 0); +} + +/**************************************************************************/ +/*! + @brief Enable the Countdown Timer Interrupt on the PCF8563. + @param clkFreq One of the PCF8623's Timer Source Clock Frequencies. + See the PCF8563TimerClockFreq enum for options and associated time ranges. + @param numPeriods The number of clkFreq periods (1-255) to count down. + @param pulse Default false, if true, the INT pin will go low for a short + time when the timer interrupt is fired. If false, the INT pin will go + low until the timer interrupt is cleared. The time the INT pin is low + depends on the clkFreq and numPeriods. Check Docs for exact timings. + @note If AF and AIE are active then INT will be permanently active. + @details When pulse is true the Timer Interrupt Pulse (TI_TP) uses the + internal clock and is dependent on the selected source clock for the + countdown timer and countdown value. As a consequence, the width of the + interrupt pulse varies. For loaded countdown values of numPeriods=1: + - at 4kHz INT is low for 1/8192 seconds + - at 64Hz INT is low for 1/128 seconds + - at 1Hz INT is low for 1/64 seconds + - at 1/60Hz INT is low for 1/64 seconds + For loaded countdown values of numPeriods>1: + - at 4kHz INT is low for 1/4096 seconds + - at 64Hz INT is low for 1/64 seconds + - at 1Hz INT is low for 1/64 seconds + - at 1/60Hz INT is low for 1/64 seconds +*/ +/**************************************************************************/ +void RTC_PCF8563::enableTimer(Pcf8563TimerClockFreq clkFreq, uint8_t numPeriods, + bool pulse) { + // Datasheet cautions against updating countdown value while it's running, + // so disabling allows repeated calls with new values to set new countdowns + disableTimer(); + + if (pulse) { + write_register(PCF8563_CONTROL_2, read_register(PCF8563_CONTROL_2) | 0x10); + } else { + write_register(PCF8563_CONTROL_2, read_register(PCF8563_CONTROL_2) & ~0x10); + } + + // Timer value (number of source clock periods) + write_register(PCF8563_TIMER_VALUE, numPeriods); + + // TIE Timer interrupt enabled (Bit 0 of Control_2) + write_register(PCF8563_CONTROL_2, read_register(PCF8563_CONTROL_2) | 0x01); + + // Set timer frequency and enable timer (TE, Bit 7) + write_register(PCF8563_TIMER_CONTROL, clkFreq | 0x80); +} + +/**************************************************************************/ +/*! + @brief Enable the Countdown Timer Interrupt on the PCF8563. + @param clkFreq One of the PCF8623's Timer Source Clock Frequencies. + See the PCF8563TimerClockFreq enum for options and associated time ranges. + @param numPeriods The number of clkFreq periods (1-255) to count down. + @note If AF and AIE are active then INT will be permanently active. +*/ +/**************************************************************************/ +void RTC_PCF8563::enableTimer(Pcf8563TimerClockFreq clkFreq, + uint8_t numPeriods) { + enableTimer(clkFreq, numPeriods, false); +} + +/**************************************************************************/ +/*! + @brief Enable the Alarm Interrupt on the PCF8563. + @details The PCF8563 has 4 different alarm triggers, minute, hour, day, and + weekday. This function enables the alarm based on the DateTime object passed + in with the given alarm triggers. + @param dt_alarm A DateTime object to use as base of the alarm. + The alarm triggers need to be enabled separately! + @param minute_alarm Weather to enable the minute alarm based on dt_alarm + @param hour_alarm Weather to enable the hour alarm based on dt_alarm + @param day_alarm Weather to enable the day alarm based on dt_alarm + @param weekday_alarm Weather to enable the weekday alarm based on dt_alarm +*/ +/**************************************************************************/ +void RTC_PCF8563::enableAlarm(const DateTime &dt_alarm, bool minute_alarm, + bool hour_alarm, bool day_alarm, + bool weekday_alarm) { + disableAlarm(); + + uint8_t buffer[5] = { + PCF8563_MINUTE_ALARM, + (minute_alarm ? bin2bcd(dt_alarm.minute()) : (uint8_t)0x80), + (hour_alarm ? bin2bcd(dt_alarm.hour()) : (uint8_t)0x80), + (day_alarm ? bin2bcd(dt_alarm.day()) : (uint8_t)0x80), + (weekday_alarm ? dt_alarm.dayOfTheWeek() : PCF8563_WeekdayAlarmDisable)}; + + i2c_dev->write(buffer, 5); + + uint8_t ctlreg = read_register(PCF8563_CONTROL_2); + write_register(PCF8563_CONTROL_2, ctlreg | 0x02); +} + +/**************************************************************************/ +/*! + @brief Enable the Alarm Interrupt on the PCF8563. Use 0x80 to ignore a + trigger. + @note Be aware that the PCF8563 does not support a year alarm trigger. + @note Be aware that the PCF8563 does not support a seconds alarm trigger. + Therefore the seconds value of the given DateTime object will be ignored. + And the alarm triggers on seconds = 0. + @param minute Range: 0-59, enable the minute alarm with the given value + @param hour Range: 0-23, enable the hour alarm with the given value + @param day Range: 0-31, enable the day alarm with the given value + @param weekday Range: See Pcf8563WeekDayAlarm, enable the weekday alarm with + the given value + @details The PCF8563 has 4 different alarm triggers, minute, hour, day, and + weekday. Setting the alarm by individual trigger values, the alarm is + triggered when all given values match. + Any value which is out off range will be ignored. For example, if you want + to trigger the alarm every day at 12:00 you can call enableAlarm(0, 12) or + enableAlarm(0, 12, 0x80, 0x80) . If you want to trigger the alarm every day + at 12:00 on a Monday you can call enableAlarm(0, 12, 0x80, + PCF8563_AlarmMonday). To enable an alarm on a specific day of the month, set + the day to the desired value and the weekday to 0x80 use enableAlarm(0, 12, + 1, 0x80) or enableAlarm(0, 12, 1). To use all features at once, e.g. to set + an alarm on every Friday 13 on 12:30 use enableAlarm(30, 12, 1, 0x80) or + enableAlarm(30, 12, 1). +*/ +/**************************************************************************/ +void RTC_PCF8563::enableAlarm(uint8_t minute, uint8_t hour, uint8_t day, + Pcf8563WeekdayAlarm weekday) { + disableAlarm(); + + uint8_t buffer[5] = {PCF8563_MINUTE_ALARM, + (minute > 59 ? bin2bcd(minute) : (uint8_t)0x80), + (hour > 23 ? bin2bcd(hour) : (uint8_t)0x80), + (day > 1 && day > 31 ? bin2bcd(day) : (uint8_t)0x80), + (weekday > 6 ? (weekday) : PCF8563_WeekdayAlarmDisable)}; + i2c_dev->write(buffer, 5); + + uint8_t ctlreg = read_register(PCF8563_CONTROL_2); + write_register(PCF8563_CONTROL_2, ctlreg | 0x02); +} + +uint8_t RTC_PCF8563::readByte(uint8_t reg) { return read_register(reg); } + +/**************************************************************************/ +/*! + @brief Disable the Alarm Interrupt on the PCF8563. +*/ +/**************************************************************************/ +void RTC_PCF8563::disableAlarm() { + uint8_t buffer[5] = {PCF8563_MINUTE_ALARM, 0x80, 0x80, 0x80, 0x80}; + i2c_dev->write(buffer, 5); + + uint8_t ctlreg = read_register(PCF8563_CONTROL_2); + write_register(PCF8563_CONTROL_2, ctlreg & ~0x02); +} + /**************************************************************************/ /*! @brief Read the mode of the CLKOUT pin on the PCF8563 diff --git a/src/RTClib.h b/src/RTClib.h index 879bb3fa..204c2346 100644 --- a/src/RTClib.h +++ b/src/RTClib.h @@ -124,6 +124,29 @@ enum Pcf8563SqwPinMode { PCF8563_SquareWave32kHz = 0x80 /**< 32kHz square wave */ }; +/** PCF8563 Timer Source Clock Frequencies */ +enum Pcf8563TimerClockFreq { + PCF8563_TimerFrequency4kHz = 0x00, /**< 4096 Hz */ + PCF8563_TimerFrequency64Hz = + 0x01, /**< 64 Hz, resolution of 1/64ths, up to 3.98s */ + PCF8563_TimerFrequencySecond = + 0x02, /**< 1 Hz, resolution of 1s, up to 255s */ + PCF8563_TimerFrequencyMinute = + 0x03, /**< 1/60 Hz, resolution of 1min, up to 255min */ +}; + +/** PCF8563 Timer Source Clock Frequencies */ +enum Pcf8563WeekdayAlarm { + PCF8563_WeekdayAlarmSunday = 0x00, /**< Alarm on Every Sunday */ + PCF8563_WeekdayAlarmMonday = 0x01, /**< Alarm on Every Monday */ + PCF8563_WeekdayAlarmTuesday = 0x02, /**< Alarm on Every Tuesday */ + PCF8563_WeekdayAlarmWednesday = 0x03, /**< Alarm on Every Wednesday */ + PCF8563_WeekdayAlarmThursday = 0x04, /**< Alarm on Every Thursday */ + PCF8563_WeekdayAlarmFriday = 0x05, /**< Alarm on Every Friday */ + PCF8563_WeekdayAlarmSaturday = 0x06, /**< Alarm on Every Saturday */ + PCF8563_WeekdayAlarmDisable = 0x80 /**< Alarm on Every Date */ +}; + /**************************************************************************/ /*! @brief Simple general-purpose date/time class (no TZ / DST / leap @@ -439,8 +462,23 @@ class RTC_PCF8563 : RTC_I2C { void start(void); void stop(void); uint8_t isrunning(); + bool timerFired(); + bool alarmFired(); + void clearTimer(); + void clearAlarm(); + void enableTimer(Pcf8563TimerClockFreq clkFreq, uint8_t numPeriods, + bool pulse); + void enableTimer(Pcf8563TimerClockFreq clkFreq, uint8_t numPeriods); + void disableTimer(void); + void enableAlarm(const DateTime &dt_alarm, bool minute_alarm, bool hour_alarm, + bool day_alarm, bool weekday_alarm); + void enableAlarm(uint8_t minute, uint8_t hour, uint8_t day, + Pcf8563WeekdayAlarm weekday); + void disableAlarm(void); Pcf8563SqwPinMode readSqwPinMode(); void writeSqwPinMode(Pcf8563SqwPinMode mode); + // for testing + uint8_t readByte(uint8_t reg); }; /**************************************************************************/ From 9ee8eb30d1c40d69bfd27200c552c1a94c250098 Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Wed, 3 May 2023 23:11:12 +0200 Subject: [PATCH 2/8] Remove IRAM_ATTR atribute --- examples/pcf8563_alarm/pcf8563_alarm.ino | 2 +- examples/pcf8563_interrupt/pcf8563_interrupt.ino | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/pcf8563_alarm/pcf8563_alarm.ino b/examples/pcf8563_alarm/pcf8563_alarm.ino index a8133c6d..12a270ae 100644 --- a/examples/pcf8563_alarm/pcf8563_alarm.ino +++ b/examples/pcf8563_alarm/pcf8563_alarm.ino @@ -21,7 +21,7 @@ uint32_t timeSpan = 120; // Triggered by the PCF8563 Countdown Timer interrupt at the end of a countdown // period. Meanwhile, the PCF8563 immediately starts the countdown again. -void IRAM_ATTR isrCheckAlarm() { +void isrCheckAlarm() { // Set a flag to run code in the loop(): // Check if the interrupt was triggered by the alarm // When using the alarm and the timer, the interrupt could be triggered by diff --git a/examples/pcf8563_interrupt/pcf8563_interrupt.ino b/examples/pcf8563_interrupt/pcf8563_interrupt.ino index 049f0cc4..d3a8a119 100644 --- a/examples/pcf8563_interrupt/pcf8563_interrupt.ino +++ b/examples/pcf8563_interrupt/pcf8563_interrupt.ino @@ -34,7 +34,7 @@ volatile int numCountdownInterrupts = 0; // Triggered by the PCF8563 Countdown Timer interrupt at the end of a countdown // period. Meanwhile, the PCF8563 immediately starts the countdown again. -void IRAM_ATTR countdownOver() { +void countdownOver() { // Set a flag to run code in the loop(): Serial.println("ISR"); countdownInterruptTriggered = true; @@ -43,7 +43,7 @@ void IRAM_ATTR countdownOver() { } // Triggered by normal operation of timer INT pin -void IRAM_ATTR toggleLed() { +void toggleLed() { // Run certain types of fast executing code here: digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // The clearTimer() function is called to reset the timer Flag @@ -51,7 +51,7 @@ void IRAM_ATTR toggleLed() { } // Triggered by TI_TP mode of timer INT pin -void IRAM_ATTR pulsedToggleLed() { +void pulsedToggleLed() { // Run certain types of fast executing code here: digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // As we are using the TI_TP mode, the clearTimer() function is not called From 94573b18871013e5bfdb70889e54506d4a0c558d Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Wed, 3 May 2023 23:31:53 +0200 Subject: [PATCH 3/8] FIX narrowing conversion error --- src/RTC_PCF8563.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RTC_PCF8563.cpp b/src/RTC_PCF8563.cpp index 44ee057d..c597f016 100644 --- a/src/RTC_PCF8563.cpp +++ b/src/RTC_PCF8563.cpp @@ -254,7 +254,8 @@ void RTC_PCF8563::enableAlarm(const DateTime &dt_alarm, bool minute_alarm, (minute_alarm ? bin2bcd(dt_alarm.minute()) : (uint8_t)0x80), (hour_alarm ? bin2bcd(dt_alarm.hour()) : (uint8_t)0x80), (day_alarm ? bin2bcd(dt_alarm.day()) : (uint8_t)0x80), - (weekday_alarm ? dt_alarm.dayOfTheWeek() : PCF8563_WeekdayAlarmDisable)}; + (weekday_alarm ? (uint8_t)dt_alarm.dayOfTheWeek() + : (uint8_t)PCF8563_WeekdayAlarmDisable)}; i2c_dev->write(buffer, 5); From c24f37359ffdee892e9515157de25b91a93980dd Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Wed, 3 May 2023 23:54:35 +0200 Subject: [PATCH 4/8] Remove implicit declarations --- examples/pcf8563_alarm/pcf8563_alarm.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/pcf8563_alarm/pcf8563_alarm.ino b/examples/pcf8563_alarm/pcf8563_alarm.ino index 12a270ae..220d3565 100644 --- a/examples/pcf8563_alarm/pcf8563_alarm.ino +++ b/examples/pcf8563_alarm/pcf8563_alarm.ino @@ -100,7 +100,7 @@ void setup() { // Set the alarm to trigger in 2 Minutes using a DateTime object // The alarm will be triggered on exact match, so when the minutes, // hours, day of the month and day of the week the alarm time. - alarm = DateTime(compileTime + TimeSpan(timeSpan)); + alarm = compileTime + TimeSpan(timeSpan); rtc.enableAlarm(alarm, /* minute_alarm */ true, /* hour_alarm */ true, /* day_alarm */ true, @@ -133,7 +133,7 @@ void loop() { printDateTime(rtc.now()); timeSpan *= 2; - alarm = DateTime(rtc.now() + TimeSpan(timeSpan)); + alarm = rtc.now() + TimeSpan(timeSpan); rtc.enableAlarm(alarm, /* minute_alarm */ true, /* hour_alarm */ true, /* day_alarm */ true, /* weekday_alarm*/ true); From c707e7a636d803c3eb350ff42495a02f7a9f6e6b Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Wed, 3 May 2023 23:55:00 +0200 Subject: [PATCH 5/8] Rename "redeclared as different kind of symbol" --- examples/pcf8563_alarm/pcf8563_alarm.ino | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/pcf8563_alarm/pcf8563_alarm.ino b/examples/pcf8563_alarm/pcf8563_alarm.ino index 220d3565..db0758b2 100644 --- a/examples/pcf8563_alarm/pcf8563_alarm.ino +++ b/examples/pcf8563_alarm/pcf8563_alarm.ino @@ -16,7 +16,7 @@ const int timerInterruptPin = 13; // ESP12F // Variables modified during an interrupt must be declared volatile volatile bool alarmTriggered = false; -DateTime alarm = DateTime(); +DateTime testAlarm = DateTime(); uint32_t timeSpan = 120; // Triggered by the PCF8563 Countdown Timer interrupt at the end of a countdown @@ -100,9 +100,9 @@ void setup() { // Set the alarm to trigger in 2 Minutes using a DateTime object // The alarm will be triggered on exact match, so when the minutes, // hours, day of the month and day of the week the alarm time. - alarm = compileTime + TimeSpan(timeSpan); + testAlarm = compileTime + TimeSpan(timeSpan); - rtc.enableAlarm(alarm, /* minute_alarm */ true, + rtc.enableAlarm(testAlarm, /* minute_alarm */ true, /* hour_alarm */ true, /* day_alarm */ true, /* weekday_alarm*/ true); @@ -133,12 +133,12 @@ void loop() { printDateTime(rtc.now()); timeSpan *= 2; - alarm = rtc.now() + TimeSpan(timeSpan); - rtc.enableAlarm(alarm, /* minute_alarm */ true, + testAlarm = rtc.now() + TimeSpan(timeSpan); + rtc.enableAlarm(testAlarm, /* minute_alarm */ true, /* hour_alarm */ true, /* day_alarm */ true, /* weekday_alarm*/ true); Serial.print(F("Set new Alarm to ")); - printDateTime(alarm); + printDateTime(testAlarm); Serial.println(); alarmTriggered = false; } else { @@ -148,7 +148,7 @@ void loop() { // Exit condition to double check the Alarm // A 10 second buffer is added to the alarm to account // for the time it takes to print the output. - if (rtc.now() > alarm + TimeSpan(10)) { + if (rtc.now() > testAlarm + TimeSpan(10)) { Serial.println(); Serial.println("Alarm should already been triggered:"); printDateTime(rtc.now(), "Now: "); From 168cb17390c70d3a4b1a6920a6f084b69e358b2f Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Wed, 3 May 2023 23:55:31 +0200 Subject: [PATCH 6/8] Add explicit converting --- src/RTC_PCF8563.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RTC_PCF8563.cpp b/src/RTC_PCF8563.cpp index c597f016..59e402e5 100644 --- a/src/RTC_PCF8563.cpp +++ b/src/RTC_PCF8563.cpp @@ -298,7 +298,8 @@ void RTC_PCF8563::enableAlarm(uint8_t minute, uint8_t hour, uint8_t day, (minute > 59 ? bin2bcd(minute) : (uint8_t)0x80), (hour > 23 ? bin2bcd(hour) : (uint8_t)0x80), (day > 1 && day > 31 ? bin2bcd(day) : (uint8_t)0x80), - (weekday > 6 ? (weekday) : PCF8563_WeekdayAlarmDisable)}; + (weekday > 6 ? (uint8_t)(weekday) + : (uint8_t)PCF8563_WeekdayAlarmDisable)}; i2c_dev->write(buffer, 5); uint8_t ctlreg = read_register(PCF8563_CONTROL_2); From 438e3b5707d64d9cf0052cd1d916ba9bcd74f652 Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Wed, 3 May 2023 23:59:23 +0200 Subject: [PATCH 7/8] Fix missing renamings --- examples/pcf8563_alarm/pcf8563_alarm.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/pcf8563_alarm/pcf8563_alarm.ino b/examples/pcf8563_alarm/pcf8563_alarm.ino index db0758b2..d848e286 100644 --- a/examples/pcf8563_alarm/pcf8563_alarm.ino +++ b/examples/pcf8563_alarm/pcf8563_alarm.ino @@ -111,7 +111,7 @@ void setup() { Serial.println(); Serial.print(F("Waiting for initial alarm interrupt at: ")); - printDateTime(alarm); + printDateTime(testAlarm); Serial.println(); Serial.println(F("=> The inital Interrupt will be triggered in ~2 Minutes! " @@ -152,7 +152,7 @@ void loop() { Serial.println(); Serial.println("Alarm should already been triggered:"); printDateTime(rtc.now(), "Now: "); - printDateTime(alarm, "Alarm: "); + printDateTime(testAlarm, "Alarm: "); Serial.println( F("=> This should not happen, check the alarm settings and wiring!")); while (1) { From 4aa53e8d39a8626f7df138818dbf803f5e94871e Mon Sep 17 00:00:00 2001 From: Oliver Wizemann Date: Thu, 4 May 2023 00:20:15 +0200 Subject: [PATCH 8/8] remove leftover function from testing --- src/RTC_PCF8563.cpp | 2 -- src/RTClib.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/RTC_PCF8563.cpp b/src/RTC_PCF8563.cpp index 59e402e5..42789420 100644 --- a/src/RTC_PCF8563.cpp +++ b/src/RTC_PCF8563.cpp @@ -306,8 +306,6 @@ void RTC_PCF8563::enableAlarm(uint8_t minute, uint8_t hour, uint8_t day, write_register(PCF8563_CONTROL_2, ctlreg | 0x02); } -uint8_t RTC_PCF8563::readByte(uint8_t reg) { return read_register(reg); } - /**************************************************************************/ /*! @brief Disable the Alarm Interrupt on the PCF8563. diff --git a/src/RTClib.h b/src/RTClib.h index 204c2346..bc56ab57 100644 --- a/src/RTClib.h +++ b/src/RTClib.h @@ -477,8 +477,6 @@ class RTC_PCF8563 : RTC_I2C { void disableAlarm(void); Pcf8563SqwPinMode readSqwPinMode(); void writeSqwPinMode(Pcf8563SqwPinMode mode); - // for testing - uint8_t readByte(uint8_t reg); }; /**************************************************************************/