SAMD21 wake up time from deep sleep

Hi Martin,

I found some of your other (extensive) posts regarding the event system. I think the information in those posts will help me build a solution using the RTC Periodic Events so I'll dig further into them. Thanks for posting that info, it is quite helpful! :slight_smile:

Best regards,
Steve

Hi Steve,

Will the sleep be delayed until the SYNCBUSY is complete in that instance or does the device go to sleep right away?

In the absence of any information in the SAMD21 datasheet, I decided to run a small test that wrote to the RTC's write synchronised ALARM0 register, called the __WFI() (Wait For Interrupt) function to put the microcontroller into deep sleep, but used RTC's the SYNCRDY interrupt to wake the process once synchronisation was complete.

It turns out that the RTC's interrupt service routine (ISR) is called exactly 5.8ms after going to sleep, indicating that synchronisation process continues in the RTC during despite being in standby.

However, the SAMD21 didn't behave entirely as expected. The microcontroller wakes up after only 3ms, (before the 5.8ms has elapsed) and starts executing the loop() code before calling the ISR. Furthermore, it enters the loop() before the synchronisation SYNCBUSY bit has been cleared in the RTC's STATUS register. I don't think another interrupt causing this, because if I disable the SYNCRDY interrupt the microcontroller never wakes up from the __WFI() function.

I'm trying to investigate what exactly is going on, as normally I'd expect the WFI() function to wake up the microcontroller and enter the ISR() directly after receiving an interrupt.

As you mention, it might be worth activating the event system and using the __WFE() (Wait For Event) function instead.

Kind regards,
Martin

I've tried slowing down the RTC clock, running it at 512Hz and this causes the microcontroller to wake after 6ms and the synchronisation interrupt to be called at 11.8ms, essentially doubling the timing.

This means that the SAMD21 core is preempting the pending synchronisation interrupt, waking up a few generic clock cycles in advance, presumably to giving itself time to return normal operation, so that it can handle the interrupt with minimal jitter or delay.

Upon waking from the __WFI() function control is returned to the main loop(). I think that it's the RTC's slow 1.024kHz generic clock that means that after the processor has woken up, instructions in the main loop() after executed before the RTC's synchronisation ISR is called.

If the peripheral on the other hand was being clocked by a 48MHz generic clock, the ISR would be called only after a few 20.83ns clock cycles. This gives the impression that the ISR is called immediately after the processor has woken up, which is how it's described in most of the literature I've seen.

Hi Martin,

MartinL:
However, the SAMD21 didn't behave entirely as expected. The microcontroller wakes up after only 3ms, (before the 5.8ms has elapsed) and starts executing the loop() code before calling the ISR. Furthermore, it enters the loop() before the synchronisation SYNCBUSY bit has been cleared in the RTC's STATUS register. I don't think another interrupt causing this, because if I disable the SYNCRDY interrupt the microcontroller never wakes up from the __WFI() function.

Indeed, this is quit strange behaviour. There must be something in the SYNCBUSY state machine logic that indicates the interrupt prematurely? I do not recall reviewing any detailed information that describes exactly how the SAMD21 is doing the synchronization. This maybe something documented by ARM itself?

At any rate, it appears that the sync does actually happen when it is sleeping? So if the sleep period is greater than 6ms, there shouldn't be any problem? I'll give it a try and report back.

Hi Martin,

I changed the RTC setAlarmEpoch() method to use the non blocking technique and that appears to have eliminated the 6ms delay. The pulse width of my loop() is now about 6us which makes a lot more sense. For the moment this approach works since my RTC tick is 10s. This change appears to have exposed some sort of race condition with the I2C bus now. I'll post another message about that.

I haven't attempted the event method using the RTC periodic events yet. You mentioned in your earlier email that to sleep and wait for an event you need to use the WFE instruction. I was not yet aware of that detail, thank you for pointing that out. I am sure I would have banged my head over that for countless hours. :slight_smile: When I require a tick less than 1 second then I will need to use the event technique.

Steve

Hi Martin,

The 6ms delay is gone but I think I prematurely concluded it was "working".

I updated the RTCZero setAlarmEpoch() to use a non-blocking write as follows:

void RTCZero::setAlarmEpoch(uint32_t ts)
{
  if (_configured) {
    if (ts < EPOCH_TIME_OFF) {
      ts = EPOCH_TIME_OFF;
    }

    time_t t = ts;
    struct tm* tmp = gmtime(&t);
    RTC_MODE2_CLOCK_Type alarmTime;

    alarmTime.bit.YEAR = tmp->tm_year - EPOCH_TIME_YEAR_OFF;
    alarmTime.bit.MONTH = tmp->tm_mon + 1;
    alarmTime.bit.DAY = tmp->tm_mday;
    alarmTime.bit.HOUR = tmp->tm_hour;
    alarmTime.bit.MINUTE = tmp->tm_min;
    alarmTime.bit.SECOND = tmp->tm_sec;

    if ( !RTCisSyncing() ) {
      RTC->MODE2.Mode2Alarm[0].ALARM.reg = alarmTime.reg;
    }
//    while (RTCisSyncing())
//      ;

  }

Unfortunately, I am observing that after the second awake cycle, the application appears to hang and my watchdog timer times out and resets the MCU. This is likely caused by "missing" writes to the alarm because the SYNCBUSY flag is set. I surmise now that the register sync is suspended during sleep, not processed. So, when I wake the second time, the SYNCBUSY flag is set and I do not write the next alarm wake time. Thus, the MCU cannot wake and the watchdog resets the MCU.

I think the only way around this problem is to use the event system and the RTC periodic events.

Hi Steve,

Here's the loop() and ISR routines of my test code:

void loop()
{
  PORT->Group[PORTA].OUTSET.reg = PORT_PA14;                // Toggle digital output pin  
  if (!RTC->MODE2.STATUS.bit.SYNCBUSY)                      // Test if synchronization isn't in operation      
  { 
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = (RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND + 1) % 60;   // Increment the ALARM0 compare register 
  }
  __DSB();                                                  // Complete outstanding memory operations - not required for SAMD21
  __WFI();                                                  // Put the SAMD21 in deep sleep - woken by SYNCRDY interrupt
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA14;                // Toggle digital output pin
  delay(10);                                                // Wait 10ms
}

void RTC_Handler(void)
{
  PORT->Group[PORTA].OUTSET.reg = PORT_PA15;                // Toggle digital output pin   
  RTC->MODE2.INTFLAG.reg = RTC_MODE2_INTFLAG_SYNCRDY;       // Clear the interrupt flag
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA15;                // Toggle digital output pin     
}

The output from the code is shown in the oscilloscope image below:

RTC_DeepSleep.jpg

At the beginning of the loop() function the loop activity output (in yellow) goes high. The ALARM0 register is written to, initiating a write synchronization and the microcontoller is put into deep sleep with __WFI().

3ms or so later, the microcontroller wakes up, fires up its Digital Frequency Locked Loop (DFLL) at 48MHz and returns control to the loop() function, as the loop activity output now goes low. At this point, if the peripheral was running at 48MHz, a few 20.83ns generic clock cycles later the ISR would be called, however as we're only running the RTC clock at 1.024kHz, a few generic clock cycles takes around another 3ms. In the meantime the CPU continues executing instructions in the loop(), even though synchronization of the ALARM0 register hasn't completed.

Exactly 5.8ms after entering writing to the ALARM0 register and going into sleep mode the synchronization ready (SYNCRDY) ISR is executed, shown by the tiny (barely visible) glitch pulse (in light blue) on channel 2. Upon returning from the ISR, the loop() code continues to wait for the remainder of the 10ms delay before repeating the cycle oncemore.

This indicates to me that the synchronization process is indeed continuing in the RTC peripheral during the sleep process, as it occurs exactly 5.8ms after writing to the ALARM0 register. However, what complicates the situation is that having woken up, the CPU continues executing code in the loop() function before servicing the ISR.

Kind regards,
Martin

RTC_DeepSleep.jpg

Hi Martin,

Inspecting the code, it occurs to me that since the SYNCRDY interrupt wakes the processor, then PA14 (channel 1) should go low immediately after waking and then transition high 10 ms later. That doesn't appear to be what we see in the scope trace.

Moreover, the code indicates PA14 goes low when the MCU wakes from sleep. Thus, PA14 should be high when the SYNCRDY interrupt fires.

From the screen shot it looks like the scope is in "Auto" sweep mode. Could this be causing the visual artifacts? I've noticed in the past that "Auto" can sometimes cause confusing convolutions of signals because the scope combines multiple sweeps.

Steve

Hi Martin,

I've been trying to get the RTC periodic events to work but have encountered a problem that has slowed me down. I am trying to configure the EVSYS peripheral so that the RTC periodic event 7 (1 Hz) generates an interrupt using EVSYS channel 0.

I have added the following code snippet to the RTCZero begin() method:

  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;  // turn on the event system peripheral

  NVIC_EnableIRQ(EVSYS_IRQn); // enable EVSYS interrupt 
  NVIC_SetPriority(EVSYS_IRQn, 0x00);

  EVSYS->USER.reg = EVSYS_USER_CHANNEL(0x01) | EVSYS_USER_USER(EVSYS_ID_USER_DMAC_CH_0);
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_RTC_PER_7) |    //  Period = 2^(7+3)
                       EVSYS_CHANNEL_CHANNEL(0);                        //  Event Channel 0
  EVSYS->INTENSET.reg |= EVSYS_INTENSET_EVD0;     //  enable interrupt for channel 0

  while (RTCisSyncing())
    ;

I also augmented the configureClock() method to connect GCLK2 to the Event System:

  // Feed GCLK2 to Event System Channel 0
  GCLK->CLKCTRL.reg = (uint32_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_ID_EVSYS_0);
  while (GCLK->STATUS.bit.SYNCBUSY)
    ;

I added some functions to configure the interrupt callback:

void EVSYS_Handler(void)
{
  digitalWrite( 2, HIGH );
  digitalWrite( 2, LOW );
  if (EVSYS_callBack != NULL) {
    EVSYS_callBack();
  }

  EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVD0; // must clear interrupt flag at end of handler
}

void RTCZero::attachPeriodicInterrupt(voidFuncPtr callback)
{
  EVSYS_callBack = callback;
}

void RTCZero::detachPeriodicInterrupt()
{
  EVSYS_callBack = NULL;
}

And created a callback which just toggles a pin a couple times:

void rtcPeriodicEvent() {
// EVSYS periodic event interrupt callback
//  rtcTick += RTC_PERIODIC_EVENT_TIMEOUT;
  ISB_DEBUG( digitalWrite(HOST_INT1, HIGH); );  // DEBUG ONLY:  use pin to trigger scope when MCU wakes up
  ISB_DEBUG( digitalWrite(HOST_INT1, LOW); );  // DEBUG ONLY:  use pin to trigger scope when MCU wakes up
  ISB_DEBUG( digitalWrite(HOST_INT1, HIGH); );  // DEBUG ONLY:  use pin to trigger scope when MCU wakes up
  ISB_DEBUG( digitalWrite(HOST_INT1, LOW); );  // DEBUG ONLY:  use pin to trigger scope when MCU wakes up
}

The problem is that when I attempt to enable the periodic event as follows:

  RTC->MODE2.EVCTRL.reg |= _periodicEvent;

where _periodicEvent = RTC_MODE2_EVCTRL_PEREO7;

The MCU appears to freeze and eventually the watchdog times out and reboots. I checked the datasheet and I think I am initializing the event system correctly but something must be wrong. I initially attempted to configure channel 0 with no user (instead of DMAC_CH_0) and that configuration exhibits the same problem. I tried using a different event channel (channel 4 instead of channel 0) and this exhibits the same behaviour.

Hi Steve,

From the screen shot it looks like the scope is in "Auto" sweep mode. Could this be causing the visual artifacts? I've noticed in the past that "Auto" can sometimes cause confusing convolutions of signals because the scope combines multiple sweeps.

I tried running the scope in "Normal" trigger mode, but the output is the same.

After running some further tests, I'm beginning to suspect that the SAMD21 (and SAMD51) don't support the __WFE() function. Calls to __WFE() fail to put the microcontroller into sleep mode. There's also no mention of the __WFE() function in the SAMD21 datasheet, although __WFI() on the other hand does get mentioned. The __WFE() function does however get discussed in the Arduino Due's SAM3X8E datasheet.

There's also an unfortunate crossover of terminology, as an "event" on the microcontroller's ARM core is different from the "event system" used by and between the SAMD21's peripherals. Although confusingly it's also possible for manufacturer's microcontroller to allow peripherals to signal an "event" to the ARM core itself. This ARM core functionality is optional and may not necessarily be supported by microcontroller's manufacturer.

I think using __WFI() is your only option. Furthermore, I don't think it's possible to write to a write-synchronized RTC register without incurring a 6ms synchronization delay (for a 1.024kHz generic clock).

Regarding the event system, to generate an event system interrupt it's necessary to connect it to a generic clock and configure it for a synchronous or resynchronized paths. The synchronous path should be used when the event generater (sender) use the same generic clock and resynchronized when they're different:

EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_RISING_EDGE |                    // Rising event edge detection
                       EVSYS_CHANNEL_PATH_SYNCHRONOUS |                    // Set event path as synchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) |    // Set event generator (sender) as external interrupt 3
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

Hi Martin,

Thanks for the feedback on the clock configuration. The last thing I tried yesterday (but didn't update my post) was exactly that. I changed the event path configuration to use synchronous (and resychronized) paths.

  EVSYS->USER.reg = EVSYS_USER_CHANNEL(RTCZERO_EVSYS_CHANNEL + 1) | EVSYS_USER_USER(EVSYS_ID_USER_ADC_START);  // Set user event channel to n + 1
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_RISING_EDGE |
                       EVSYS_CHANNEL_PATH_RESYNCHRONIZED |
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_RTC_PER_7) |    //  Period = 2^(7+3)
                       EVSYS_CHANNEL_CHANNEL(RTCZERO_EVSYS_CHANNEL);    //  Event Channel
  EVSYS->INTENSET.reg |= RTCZERO_EVSYS_INTENSET;                        //  enable interrupt for event channel

But these changes do not fix the fault that I experience when I enable the periodic interrupt in the RTC EVCTRL register. I'll check a bit further to determine if the write to the register is causing the fault or if the SYNCBUSY wait is in a wait forever loop.

I think I have narrowed the problem to the clock configuration for the event channel. For whatever reason I do not understand yet, I cannot enable the clock for any of the event system channels. When I read back the CLKEN from the CLKCTRL register, it reads back as zero.

#define RTCZERO_GCLK_CLKCTRL_ID GCLK_CLKCTRL_ID_EVSYS_4


  // Feed clock to RTC Event System Channel - Note, this register is not write synchronized so does not require SYNCBUSY wait
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | RTCZERO_GCLK_CLKCTRL_ID;

--------------------------- GCLK
GCLK_MAIN: GEN00 (always)
GCLK_DFLL48M_REF: GEN01
GCLK_DPLL: --disabled--
GCLK_DPLL_32K: --disabled--
GCLK_WDT: GEN02
GCLK_RTC: GEN02
GCLK_EIC: --disabled--
GCLK_USB: GEN00
GCLK_EVSYS_CHANNEL_0: --disabled--
GCLK_EVSYS_CHANNEL_1: --disabled--
GCLK_EVSYS_CHANNEL_2: --disabled--
GCLK_EVSYS_CHANNEL_3: --disabled--
GCLK_EVSYS_CHANNEL_4: --disabled--
GCLK_EVSYS_CHANNEL_5: --disabled--
GCLK_EVSYS_CHANNEL_6: --disabled--
GCLK_EVSYS_CHANNEL_7: --disabled--
GCLK_EVSYS_CHANNEL_8: --disabled--
GCLK_EVSYS_CHANNEL_9: --disabled--
GCLK_EVSYS_CHANNEL_10: --disabled--
GCLK_EVSYS_CHANNEL_11: --disabled--

Any ideas why this is happening? I found some vague references on AVR Freaks to issues related to the CLKEN for TCC0 and the EIC but it is not clear from there what the underlying issue is.

Ok, I think I figured out why the CKEN bit reads zero. If the GCLKREQ bit is zero then I think just reading the register doesn't actually connect the clock so it shows zero when read. If I set the GCLKREQ bit in the EVSYS->CTRL register, then I can confirm that the clock is configured.

Now that I have confirmed the clock is configured, that has not solved the stall problem. The MCU still locks up.

I have confirmed it is stuck in the HardFault_Handler();

When I read back the CLKEN from the CLKCTRL register, it reads back as zero.

The GCLK peripheral's GENCTRL, GENDIV and CLKCTRL registers are indirectly addressed. To read back from these generic clock registers, it's first necessary to do an 8-bit write to the register's ID bitfield, to identify the GCLK before performing a read.

Hi Martin,

Thank you for pointing that out. I did come across that in my search to solve this problem. I use the ZeroRegs library to dump the SAMD21 registers and I did confirm that ZeroRegs correctly addresses the GCLK registers.

I have not figured out what is causing the hard fault yet. It must be something to do with the configuration of the event system. From what I have read so far I believe my configuration is correct yet the moment I enable the periodic event in the RTC, a hard fault occurs.

Steve

I think I have narrowed it down to writing the EVCTRL register. I can read the register but the exception occurs when I attempt to write the register.

I checked the PAC2 register. It has the reset value (0x00800000). If I try to write to the PAC2 register, I get an exception as well.

Has anyone else experienced issues writing the PAC or EVCTRL registers?

Ok. I finally figure this out. There is an "Enable Protection" function that applies to the EVCTRL register. I was "enabling" the periodic event after the RTC was enabled. It turns out "Enable Protection" prevents writing the EVCTRL register after the RTC is enabled. Hence, my problem.

Wow, that was three days of my life wasted ...

Thanks for the help on this. Always good to be able to talk it through with someone.

Hi Steve,

The synchronization and enable protection make working with the SAMD21's registers difficult.

Here's some additional consideration that I discovered while trying to get it to work:

1) RTC Enable

The RTC needs to be explicity disabled before writing to the RTC's EVCTRL register.

2) EVCTRL Register

Writing to the RTC's EVCTRL register will also fail if it's written to after the RTC's MASK register in MODE2. I guess this is an undocumented hardware bug. It's best to set the EVCTRL register immediately after disabling the RTC.

3) Event System User

It's not necessary to establish an Event System User (receiver) for this peripheral's interrupts to be used.

4) SAMD21 Errata

The SAMD21 errata states that in order to ensure correct operation in sleep modes, the power reduction register must be disabled in the Non-Volatile Memory Controller (NVMCTRL):

NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_SLEEPPRM_DISABLED;

Here's some code that gets the RTC to generate an event every second. The event is received and generates an interrupt in the event system handler ISR. Each time the interrupt completes, the microcontroller returns to the loop() function and goes into deep sleep:

// Set-up Event System to generate interrupt from RTC ALARM0 output event
void setup(){
  PORT->Group[PORTA].DIRSET.reg = PORT_PA14;              // Set digital pin D13 to an OUTPUT
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;                  // Switch on the event system peripheral

// Configure the external crystal----------------------------------------------
  SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_ONDEMAND |       // Enble one demand mode
                         SYSCTRL_XOSC32K_RUNSTDBY |       // Enable run-on-standby mode
                         SYSCTRL_XOSC32K_EN32K |          // Enable the crystal oscillator IO pads
                         SYSCTRL_XOSC32K_XTALEN |         // Enable the crystal oscillator
                         SYSCTRL_XOSC32K_STARTUP(6) |     // Set the crystal start-up time
                         SYSCTRL_XOSC32K_ENABLE;          // Enable the oscillator

// Configure clock source and clock generators (gclk.h)------------------------    
  GCLK->GENDIV.reg =  GCLK_GENDIV_ID(4) |   // Select GLCK4
                      GCLK_GENDIV_DIV(4);           // Select clock divisor to divide by 32 (2 ^ (4 + 1))
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization
 
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(4) |          // Select GCLK4
                      GCLK_GENCTRL_SRC_XOSC32K |    // Select GCLK source as the external 32.768kHz crystal         
                      GCLK_GENCTRL_IDC |            // Improve duty cycle for odd div factors
                      //GCLK_GENCTRL_RUNSTDBY |       // Enable run standby mode  
                      GCLK_GENCTRL_DIVSEL |         // Set GLCK divisor as 2 to the power of (divisor) value                        
                      GCLK_GENCTRL_GENEN;           // Enable GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization
     
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN_GCLK4 |      // Select GCLK4
                      GCLK_CLKCTRL_ID_RTC |         // Connect to the RTC
                      GCLK_CLKCTRL_CLKEN;           // Enable GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN_GCLK4 |      // Select GCLK4
                      GCLK_CLKCTRL_ID_EVSYS_0 |     // Feed the Event System channel 0
                      GCLK_CLKCTRL_CLKEN;           // Enable GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

// RTC configuration (rtc.h)--------------------------------------------------                                                
  RTC->MODE2.CTRL.bit.ENABLE = 0;                      // Enable the RTC
  while (RTC->MODE2.STATUS.bit.SYNCBUSY);              // Wait for synchronization

  RTC->MODE2.CTRL.bit.SWRST = 1;                       // Software reset the RTC
  while (RTC->MODE2.STATUS.bit.SYNCBUSY);              // Wait for synchronization

  RTC->MODE2.EVCTRL.reg = RTC_MODE2_EVCTRL_ALARMEO0;             // Set RTC to generate an event output on ALARM0
  
  RTC->MODE2.CTRL.reg = RTC_MODE2_CTRL_PRESCALER_DIV1024 |     // Set prescaler to 1024                    
                        RTC_MODE2_CTRL_MODE_CLOCK;             // Set RTC to mode 2, clock/calendar mode

  RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = RTC_MODE2_ALARM_SECOND(0);   // Set the ALARM0 compare register
  while (RTC->MODE2.STATUS.bit.SYNCBUSY)                                   // Wait for synchronization
  
  RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = RTC_MODE2_MASK_SEL_SS;  // Set the RTC clock/calender interrupt mask to match seconds only
  while (RTC->MODE2.STATUS.bit.SYNCBUSY)                          // Wait for synchronization

// Event System configuration (rtc.h)-------------------------------------------------- 
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(0);                                 // No event user channel
  
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_RISING_EDGE |                  // Rising event edge detection
                       //EVSYS_CHANNEL_PATH_SYNCHRONOUS |                 // Set event path as synchronous
                       EVSYS_CHANNEL_PATH_RESYNCHRONIZED |                 // Set event path as resynchronized
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_RTC_CMP_0) |       // Set event generator (sender) as compare channel 0
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

// Configure Event interrupts ------------------------------------------
  NVIC_SetPriority(EVSYS_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for Event System
  NVIC_EnableIRQ(EVSYS_IRQn);         // Connect Event System to Nested Vector Interrupt Controller (NVIC)
  
  EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD0;           // Set Event System interrupt on event detection                           

// Enable RTC--------------------------------------------------------------
  RTC->MODE2.CLOCK.reg = 0x0000000;                    // Set the RTC counter to zero
  while (RTC->MODE2.STATUS.bit.SYNCBUSY);              // Wait for synchronization
  
  RTC->MODE2.CTRL.bit.ENABLE = 1;                      // Enable the RTC
  while (RTC->MODE2.STATUS.bit.SYNCBUSY);              // Wait for synchronization

  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;                              // Put the SAMD21 in deep sleep upon executing the __WFI() function
  NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_SLEEPPRM_DISABLED;
}

void loop()
{
  // Due to a hardware bug on the SAMD21, the SysTick interrupts become active before the flash has powered up from sleep, causing a hard fault
  // To prevent this the SysTick interrupts are disabled before entering sleep mode
  SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;           // Disable SysTick interrupts
  __DSB();                                              // Complete outstanding memory operations - not required for SAMD21 ARM Cortex M0+
  __WFI();                                              // Put the SAMD21 into deep sleep, Zzzzzzzz...
  SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;            // Enable SysTick interrupts
}

void EVSYS_Handler(void)                               // Event System interrupt handler
{ 
  PORT->Group[PORTA].OUTTGL.reg = PORT_PA14;           // Set digital pin D13 to an OUTPUT
  if (!RTC->MODE2.STATUS.bit.SYNCBUSY)                 // Test if synchronization isn't in operation      
  { 
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = (RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND + 1) % 60;   // Increment the ALARM0 compare register 
  }
  EVSYS->INTFLAG.bit.EVD0 = 1;                              // Clear the interrupt flag
}

Kind regards,
Martin

Probably off-topic or newbie, but I've read this about hard lookups

https://community.atmel.com/comment/2625116#comment-2625116

it's suggested to disable and reenable systick interrupt:

before sleep:

SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;

right after wakeup:

SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;

In my case this didn't solve the issue (mkrfox freezes after a day or so), but who knows for you

Hi avelo,

I did a PR for this a few weeks ago. I recall it was merged.

Steve