diff --git a/README.adoc b/README.adoc index c7bc164..f7dd6f5 100644 --- a/README.adoc +++ b/README.adoc @@ -1,6 +1,9 @@ = RTC Library for Arduino = -The RTC library enables an Arduino Zero or MKR1000 board to take control of the internal RTC. +The RTC library enables a supported board to take control of the internal RTC. +Supported boards are: +SAMD21-based boards (Arduino Zero, MKR1000, Adafruit Feather M0, Sparkfun SAMD21 Mini Breakout, etc), +SAMD51-based boards (Adafruit Grand Central M4, SparkFun SAMD51 Thing Plus, etc). For more information about this library please visit us at http://arduino.cc/en/Reference/RTC diff --git a/library.properties b/library.properties index df69458..8b37c6c 100644 --- a/library.properties +++ b/library.properties @@ -2,8 +2,8 @@ name=RTCZero version=1.6.0 author=Arduino maintainer=Arduino -sentence=Allows to use the RTC functionalities. For Arduino Zero, MKRZero and MKR1000 only. -paragraph=With this library you can use the RTC peripheral of an Arduino Zero or MKR1000 in order to program actions related to date and time. +sentence=Allows to use the RTC functionalities. For Arduino Zero, MKRZero, MKR1000 (SAMD21), and SAMD51 boards. +paragraph=With this library you can use the RTC peripheral of a supported board in order to program actions related to date and time. Supported boards are: SAMD21-based boards (Arduino Zero, MKR1000, Adafruit Feather M0, Sparkfun SAMD21 Mini Breakout, etc), SAMD51-based boards (Adafruit Grand Central M4, SparkFun SAMD51 Thing Plus, etc). category=Timing url=http://www.arduino.cc/en/Reference/RTC architectures=samd diff --git a/src/RTCZero.cpp b/src/RTCZero.cpp index a7c334c..166dad9 100644 --- a/src/RTCZero.cpp +++ b/src/RTCZero.cpp @@ -41,47 +41,37 @@ RTCZero::RTCZero() void RTCZero::begin(bool resetTime) { - uint16_t tmp_reg = 0; - +#ifdef __SAMD51__ + MCLK->APBAMASK.reg |= MCLK_APBAMASK_RTC; +#else // SAMD21 PM->APBAMASK.reg |= PM_APBAMASK_RTC; // turn on digital interface clock +#endif + config32kOSC(); // If the RTC is in clock mode and the reset was // not due to POR or BOD, preserve the clock time // POR causes a reset anyway, BOD behaviour is? bool validTime = false; - RTC_MODE2_CLOCK_Type oldTime; + RTC_MODE2_CLOCK_Type oldTime; // same for SAMD21 and SAMD51 - if ((!resetTime) && (PM->RCAUSE.reg & (PM_RCAUSE_SYST | PM_RCAUSE_WDT | PM_RCAUSE_EXT))) { - if (RTC->MODE2.CTRL.reg & RTC_MODE2_CTRL_MODE_CLOCK) { - validTime = true; - oldTime.reg = RTC->MODE2.CLOCK.reg; - } + if ((!resetTime) && isHotReset() && RTCisClock()) { + validTime = true; + oldTime.reg = RTC->MODE2.CLOCK.reg; } - // Setup clock GCLK2 with OSC32K divided by 32 configureClock(); RTCdisable(); RTCreset(); - tmp_reg |= RTC_MODE2_CTRL_MODE_CLOCK; // set clock operating mode - tmp_reg |= RTC_MODE2_CTRL_PRESCALER_DIV1024; // set prescaler to 1024 for MODE2 - tmp_reg &= ~RTC_MODE2_CTRL_MATCHCLR; // disable clear on match - - //According to the datasheet RTC_MODE2_CTRL_CLKREP = 0 for 24h - tmp_reg &= ~RTC_MODE2_CTRL_CLKREP; // 24h time representation - - RTC->MODE2.READREQ.reg &= ~RTC_READREQ_RCONT; // disable continuously mode - - RTC->MODE2.CTRL.reg = tmp_reg; - while (RTCisSyncing()) - ; + RTCconfigureRegs(); NVIC_EnableIRQ(RTC_IRQn); // enable RTC interrupt NVIC_SetPriority(RTC_IRQn, 0x00); + // same for SAMD21 and SAMD51 RTC->MODE2.INTENSET.reg |= RTC_MODE2_INTENSET_ALARM0; // enable alarm interrupt RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = MATCH_OFF; // default alarm match is off (disabled) @@ -93,9 +83,10 @@ void RTCZero::begin(bool resetTime) // If desired and valid, restore the time value, else use first valid time value if ((!resetTime) && (validTime) && (oldTime.reg != 0L)) { - RTC->MODE2.CLOCK.reg = oldTime.reg; + RTC->MODE2.CLOCK.reg = oldTime.reg; // same for SAMD21 and SAMD51 } else { + // same for SAMD21 and SAMD51 RTC->MODE2.CLOCK.reg = RTC_MODE2_CLOCK_YEAR(DEFAULT_YEAR - 2000) | RTC_MODE2_CLOCK_MONTH(DEFAULT_MONTH) | RTC_MODE2_CLOCK_DAY(DEFAULT_DAY) | RTC_MODE2_CLOCK_HOUR(DEFAULT_HOUR) | RTC_MODE2_CLOCK_MINUTE(DEFAULT_MINUTE) | RTC_MODE2_CLOCK_SECOND(DEFAULT_SECOND); @@ -112,13 +103,14 @@ void RTC_Handler(void) RTC_callBack(); } + // same for SAMD21 and SAMD51 RTC->MODE2.INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM0; // must clear flag at end } void RTCZero::enableAlarm(Alarm_Match match) { if (_configured) { - RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = match; + RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = match; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -127,7 +119,7 @@ void RTCZero::enableAlarm(Alarm_Match match) void RTCZero::disableAlarm() { if (_configured) { - RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = 0x00; + RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = 0x00; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -147,6 +139,7 @@ void RTCZero::standbyMode() { // Entering standby mode when connected // via the native USB port causes issues. + // same for SAMD21 and SAMD51 SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __DSB(); __WFI(); @@ -159,67 +152,67 @@ void RTCZero::standbyMode() uint8_t RTCZero::getSeconds() { RTCreadRequest(); - return RTC->MODE2.CLOCK.bit.SECOND; + return RTC->MODE2.CLOCK.bit.SECOND; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getMinutes() { RTCreadRequest(); - return RTC->MODE2.CLOCK.bit.MINUTE; + return RTC->MODE2.CLOCK.bit.MINUTE; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getHours() { RTCreadRequest(); - return RTC->MODE2.CLOCK.bit.HOUR; + return RTC->MODE2.CLOCK.bit.HOUR; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getDay() { RTCreadRequest(); - return RTC->MODE2.CLOCK.bit.DAY; + return RTC->MODE2.CLOCK.bit.DAY; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getMonth() { RTCreadRequest(); - return RTC->MODE2.CLOCK.bit.MONTH; + return RTC->MODE2.CLOCK.bit.MONTH; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getYear() { RTCreadRequest(); - return RTC->MODE2.CLOCK.bit.YEAR; + return RTC->MODE2.CLOCK.bit.YEAR; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getAlarmSeconds() { - return RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND; + return RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getAlarmMinutes() { - return RTC->MODE2.Mode2Alarm[0].ALARM.bit.MINUTE; + return RTC->MODE2.Mode2Alarm[0].ALARM.bit.MINUTE; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getAlarmHours() { - return RTC->MODE2.Mode2Alarm[0].ALARM.bit.HOUR; + return RTC->MODE2.Mode2Alarm[0].ALARM.bit.HOUR; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getAlarmDay() { - return RTC->MODE2.Mode2Alarm[0].ALARM.bit.DAY; + return RTC->MODE2.Mode2Alarm[0].ALARM.bit.DAY; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getAlarmMonth() { - return RTC->MODE2.Mode2Alarm[0].ALARM.bit.MONTH; + return RTC->MODE2.Mode2Alarm[0].ALARM.bit.MONTH; // same for SAMD21 and SAMD51 } uint8_t RTCZero::getAlarmYear() { - return RTC->MODE2.Mode2Alarm[0].ALARM.bit.YEAR; + return RTC->MODE2.Mode2Alarm[0].ALARM.bit.YEAR; // same for SAMD21 and SAMD51 } /* @@ -229,7 +222,7 @@ uint8_t RTCZero::getAlarmYear() void RTCZero::setSeconds(uint8_t seconds) { if (_configured) { - RTC->MODE2.CLOCK.bit.SECOND = seconds; + RTC->MODE2.CLOCK.bit.SECOND = seconds; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -238,7 +231,7 @@ void RTCZero::setSeconds(uint8_t seconds) void RTCZero::setMinutes(uint8_t minutes) { if (_configured) { - RTC->MODE2.CLOCK.bit.MINUTE = minutes; + RTC->MODE2.CLOCK.bit.MINUTE = minutes; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -247,7 +240,7 @@ void RTCZero::setMinutes(uint8_t minutes) void RTCZero::setHours(uint8_t hours) { if (_configured) { - RTC->MODE2.CLOCK.bit.HOUR = hours; + RTC->MODE2.CLOCK.bit.HOUR = hours; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -265,7 +258,7 @@ void RTCZero::setTime(uint8_t hours, uint8_t minutes, uint8_t seconds) void RTCZero::setDay(uint8_t day) { if (_configured) { - RTC->MODE2.CLOCK.bit.DAY = day; + RTC->MODE2.CLOCK.bit.DAY = day; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -274,7 +267,7 @@ void RTCZero::setDay(uint8_t day) void RTCZero::setMonth(uint8_t month) { if (_configured) { - RTC->MODE2.CLOCK.bit.MONTH = month; + RTC->MODE2.CLOCK.bit.MONTH = month; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -283,7 +276,7 @@ void RTCZero::setMonth(uint8_t month) void RTCZero::setYear(uint8_t year) { if (_configured) { - RTC->MODE2.CLOCK.bit.YEAR = year; + RTC->MODE2.CLOCK.bit.YEAR = year; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -301,7 +294,7 @@ void RTCZero::setDate(uint8_t day, uint8_t month, uint8_t year) void RTCZero::setAlarmSeconds(uint8_t seconds) { if (_configured) { - RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = seconds; + RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = seconds; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -310,7 +303,7 @@ void RTCZero::setAlarmSeconds(uint8_t seconds) void RTCZero::setAlarmMinutes(uint8_t minutes) { if (_configured) { - RTC->MODE2.Mode2Alarm[0].ALARM.bit.MINUTE = minutes; + RTC->MODE2.Mode2Alarm[0].ALARM.bit.MINUTE = minutes; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -319,7 +312,7 @@ void RTCZero::setAlarmMinutes(uint8_t minutes) void RTCZero::setAlarmHours(uint8_t hours) { if (_configured) { - RTC->MODE2.Mode2Alarm[0].ALARM.bit.HOUR = hours; + RTC->MODE2.Mode2Alarm[0].ALARM.bit.HOUR = hours; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -337,7 +330,7 @@ void RTCZero::setAlarmTime(uint8_t hours, uint8_t minutes, uint8_t seconds) void RTCZero::setAlarmDay(uint8_t day) { if (_configured) { - RTC->MODE2.Mode2Alarm[0].ALARM.bit.DAY = day; + RTC->MODE2.Mode2Alarm[0].ALARM.bit.DAY = day; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -346,7 +339,7 @@ void RTCZero::setAlarmDay(uint8_t day) void RTCZero::setAlarmMonth(uint8_t month) { if (_configured) { - RTC->MODE2.Mode2Alarm[0].ALARM.bit.MONTH = month; + RTC->MODE2.Mode2Alarm[0].ALARM.bit.MONTH = month; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -355,7 +348,7 @@ void RTCZero::setAlarmMonth(uint8_t month) void RTCZero::setAlarmYear(uint8_t year) { if (_configured) { - RTC->MODE2.Mode2Alarm[0].ALARM.bit.YEAR = year; + RTC->MODE2.Mode2Alarm[0].ALARM.bit.YEAR = year; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; } @@ -374,7 +367,7 @@ time_t RTCZero::getEpoch() { RTCreadRequest(); RTC_MODE2_CLOCK_Type clockTime; - clockTime.reg = RTC->MODE2.CLOCK.reg; + clockTime.reg = RTC->MODE2.CLOCK.reg; // same for SAMD21 and SAMD51 struct tm tm; @@ -424,7 +417,7 @@ void RTCZero::setEpoch(uint32_t ts) gmtime_r(&t, &tmp); - RTC_MODE2_CLOCK_Type clockTime; + RTC_MODE2_CLOCK_Type clockTime; // same for SAMD21 and SAMD51 clockTime.bit.YEAR = tmp.tm_year - EPOCH_TIME_YEAR_OFF; clockTime.bit.MONTH = tmp.tm_mon + 1; @@ -433,7 +426,7 @@ void RTCZero::setEpoch(uint32_t ts) clockTime.bit.MINUTE = tmp.tm_min; clockTime.bit.SECOND = tmp.tm_sec; - RTC->MODE2.CLOCK.reg = clockTime.reg; + RTC->MODE2.CLOCK.reg = clockTime.reg; // same for SAMD21 and SAMD51 while (RTCisSyncing()) ; @@ -449,6 +442,17 @@ void RTCZero::setY2kEpoch(uint32_t ts) /* Attach peripheral clock to 32k oscillator */ void RTCZero::configureClock() { +#ifdef __SAMD51__ + +#ifdef CRYSTALLESS + OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL_ULP1K; +#else + OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL_XOSC1K; +#endif + +#else // SAMD21 + + // Setup clock GCLK2 with OSC32K divided by 32 GCLK->GENDIV.reg = GCLK_GENDIV_ID(2)|GCLK_GENDIV_DIV(4); while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY) ; @@ -462,15 +466,47 @@ void RTCZero::configureClock() { GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos))); while (GCLK->STATUS.bit.SYNCBUSY) ; + +#endif } /* * Private Utility Functions */ +/* whether cpu rebooted from a reasonably good running state */ +inline bool RTCZero::isHotReset(void) { +#ifdef __SAMD51__ + return (RSTC->RCAUSE.reg & (RSTC_RCAUSE_SYST | RSTC_RCAUSE_WDT | RSTC_RCAUSE_EXT)); +#else // SAMD21 + return (PM->RCAUSE.reg & (PM_RCAUSE_SYST | PM_RCAUSE_WDT | PM_RCAUSE_EXT)); +#endif +} + /* Configure the 32768Hz Oscillator */ void RTCZero::config32kOSC() { +#ifdef __SAMD51__ + +#ifdef CRYSTALLESS + // enable 1k output + OSC32KCTRL->OSCULP32K.reg |= OSC32KCTRL_OSCULP32K_EN1K; +#else + // XOSC32K activated in startup.c but activating again in case USER-code stopped it + // enable 1k output + OSC32KCTRL->XOSC32K.reg = OSC32KCTRL_XOSC32K_ENABLE | + OSC32KCTRL_XOSC32K_EN32K | + OSC32KCTRL_XOSC32K_EN1K | + OSC32KCTRL_XOSC32K_CGM_XT | + OSC32KCTRL_XOSC32K_RUNSTDBY | + OSC32KCTRL_XOSC32K_ONDEMAND | + OSC32KCTRL_XOSC32K_XTALEN; + while( (OSC32KCTRL->STATUS.reg & OSC32KCTRL_STATUS_XOSC32KRDY) == 0 ) + ; +#endif + +#else // SAMD21 + #ifndef CRYSTALLESS SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_ONDEMAND | SYSCTRL_XOSC32K_RUNSTDBY | @@ -479,12 +515,55 @@ void RTCZero::config32kOSC() SYSCTRL_XOSC32K_STARTUP(6) | SYSCTRL_XOSC32K_ENABLE; #endif + +#endif +} + +/* whether RTC is configured as a clock (MODE2) */ +inline bool RTCZero::RTCisClock(void) { +#ifdef __SAMD51__ + return (RTC->MODE2.CTRLA.reg & RTC_MODE2_CTRLA_MODE_CLOCK); +#else // SAMD21 + return (RTC->MODE2.CTRL.reg & RTC_MODE2_CTRL_MODE_CLOCK); +#endif +} + +/* configure the device registers */ +inline void RTCZero::RTCconfigureRegs(void) { +#ifdef __SAMD51__ + uint16_t tmp_reg = 0; + tmp_reg |= RTC_MODE2_CTRLA_MODE_CLOCK; // set clock operating mode + tmp_reg |= RTC_MODE2_CTRLA_PRESCALER_DIV1024; // set prescaler to 1024 for MODE2 + tmp_reg &= ~RTC_MODE2_CTRLA_MATCHCLR; // disable clear on match + //According to the datasheet RTC_MODE2_CTRLA_CLKREP = 0 for 24h + tmp_reg &= ~RTC_MODE2_CTRLA_CLKREP; // 24h time representation + tmp_reg |= RTC_MODE2_CTRLA_CLOCKSYNC; // synchronize reads + + RTC->MODE2.CTRLA.reg = tmp_reg; +#else // SAMD21 + uint16_t tmp_reg = 0; + tmp_reg |= RTC_MODE2_CTRL_MODE_CLOCK; // set clock operating mode + tmp_reg |= RTC_MODE2_CTRL_PRESCALER_DIV1024; // set prescaler to 1024 for MODE2 + tmp_reg &= ~RTC_MODE2_CTRL_MATCHCLR; // disable clear on match + //According to the datasheet RTC_MODE2_CTRL_CLKREP = 0 for 24h + tmp_reg &= ~RTC_MODE2_CTRL_CLKREP; // 24h time representation + + RTC->MODE2.READREQ.reg &= ~RTC_READREQ_RCONT; // disable continuously mode + + RTC->MODE2.CTRL.reg = tmp_reg; +#endif + while (RTCisSyncing()) + ; } /* Synchronise the CLOCK register for reading*/ inline void RTCZero::RTCreadRequest() { if (_configured) { +#ifdef __SAMD51__ + // CTRLA.CLOCKSYNC set so no action needed here +#else // SAMD21 RTC->MODE2.READREQ.reg = RTC_READREQ_RREQ; +#endif while (RTCisSyncing()) ; } @@ -493,33 +572,53 @@ inline void RTCZero::RTCreadRequest() { /* Wait for sync in write operations */ inline bool RTCZero::RTCisSyncing() { +#ifdef __SAMD51__ + return (RTC->MODE2.SYNCBUSY.reg); +#else // SAMD21 return (RTC->MODE2.STATUS.bit.SYNCBUSY); +#endif } void RTCZero::RTCdisable() { +#ifdef __SAMD51__ + RTC->MODE2.CTRLA.reg &= ~RTC_MODE2_CTRLA_ENABLE; // disable RTC +#else // SAMD21 RTC->MODE2.CTRL.reg &= ~RTC_MODE2_CTRL_ENABLE; // disable RTC +#endif while (RTCisSyncing()) ; } void RTCZero::RTCenable() { +#ifdef __SAMD51__ + RTC->MODE2.CTRLA.reg |= RTC_MODE2_CTRLA_ENABLE; // enable RTC +#else // SAMD21 RTC->MODE2.CTRL.reg |= RTC_MODE2_CTRL_ENABLE; // enable RTC +#endif while (RTCisSyncing()) ; } void RTCZero::RTCreset() { +#ifdef __SAMD51__ + RTC->MODE2.CTRLA.reg |= RTC_MODE2_CTRLA_SWRST; // software reset +#else // SAMD21 RTC->MODE2.CTRL.reg |= RTC_MODE2_CTRL_SWRST; // software reset +#endif while (RTCisSyncing()) ; } void RTCZero::RTCresetRemove() { +#ifdef __SAMD51__ + RTC->MODE2.CTRLA.reg &= ~RTC_MODE2_CTRLA_SWRST; // software reset remove +#else // SAMD21 RTC->MODE2.CTRL.reg &= ~RTC_MODE2_CTRL_SWRST; // software reset remove +#endif while (RTCisSyncing()) ; } diff --git a/src/RTCZero.h b/src/RTCZero.h index e0bfb64..08745d5 100644 --- a/src/RTCZero.h +++ b/src/RTCZero.h @@ -104,8 +104,11 @@ class RTCZero { private: bool _configured; + bool isHotReset(void); void config32kOSC(void); void configureClock(void); + bool RTCisClock(void); + void RTCconfigureRegs(void); void RTCreadRequest(); bool RTCisSyncing(void); void RTCdisable();