Arduino Forum

Products => MKR Boards => MKRWAN1300 => Topic started by: sslupsky on May 23, 2019, 01:24 am

Title: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 23, 2019, 01:24 am
I have a question that I cannot seem to find the answer to in the Microchip datasheet for the SAMD21.  How long does it take for the SAMD21 to wake from deep sleep?  The corollary to this question, how long does it take for the SAMD21 to go to sleep is another question I have.

Let me explain some observations.  I have a sketch that uses the RTCZero and Arduino Low Power libraries.  My main loop wakes, resets a watch dog, checks a couple variables and then goes to sleep.  Once every 60 seconds it blinks an LED.  The wake-sleep RTC cycle is 10 seconds.  That is, every 10 sections the RTC wakes the processor from sleep.  When I wake, I toggle an I/O high immediately when I wake and toggle the same I/O low immediately before I go back to sleep.

The "wake" time according to the scope trace is about 6ms.  This is the time the I/O pin is in its high state, meaning the time it goes high immadiately after waking and going low immediately before sleeping again.  This amount of time seems extraordinarily high to me.  Here is the loop:

Code: [Select]
void loop() {
  Watchdog.reset();
//  blink(1, BLINK_DURATION_SHORT, BLINK_LEVEL_VERBOSE, CRGB::Green);

  uint32_t t;
  t = rtc.getEpoch();

  if ( t >= temperatureSampleTick ) {

//    int i = NVConfig.temperatureSamplePeriod;
    temperatureSampleTick += TEMPERATURE_PERIOD;

//    readSensors();
//    blink(1, BLINK_DURATION_SHORT, BLINK_LEVEL_VERBOSE, CRGB::Green);
//    Log.verbose(F("[%l] level=verbose Temperature Tick" CR), millis());
  }

  if ( t >= heartBeatTick ) {
    blink(1, BLINK_DURATION_SHORT, BLINK_LEVEL_VERBOSE, CRGB::Green);
    heartBeatTick += HEARTBEAT_PERIOD;
//    Log.verbose(F("[%l] level=verbose Heartbeat Tick" CR), millis());
  }

  if ( hostDataAvailable > 0 ) {
    int i;

//    Log.verbose(F("[%l] level=verbose Received %d bytes from MKRDOCK: "), millis(), hostDataAvailable);
    for ( i = 0; i < hostDataAvailable; i++ ) {
      if ( i < (hostDataAvailable - 1) ) {
//        Log.verbose(F("%X, "), hostCommandBuf[i]);
      } else {
//        Log.verbose(F("%X" CR), hostCommandBuf[i]);
      }
    }
    blink(1, BLINK_DURATION_SHORT, BLINK_LEVEL_VERBOSE, CRGB::Magenta);
    hostDataAvailable = 0;
  }

  if ( hostDataRequest ) {
//    blink(1, BLINK_DURATION_SHORT, BLINK_LEVEL_VERBOSE, CRGB::Blue);
//    Log.verbose(F("[%l] level=verbose Host Requested Data" CR), millis());
    hostDataRequest = false;
  }

//  blink(1, BLINK_DURATION_SHORT, BLINK_LEVEL_VERBOSE, CRGB::Green);
//  Serial.flush();

  //  Detach and reattach USB device before and after sleep. 
  //  See https://github.com/arduino-libraries/ArduinoLowPower/pull/8
  USBDevice.detach();
  digitalWrite(HOST_INT1, LOW);
  LowPower.deepSleep();
  digitalWrite(HOST_INT1, HIGH);
  USBDevice.attach();
}


What is even stranger is looking at the current consumption.  The current increases as if coming out of sleep about 35 ms before I see the I/O pin toggle high.  Moreover, the current consumption remains high after the I/O pin toggle for an additional 10ms.  Total width of the "current spike" when the device comes out of sleep is about 55ms.  This is about 10x more than the time observed looking at the I/O pin.

This seems to suggest that the device is in some "waking" state before resuming execution and a "going to sleep state" before the power is shut down. 

I am trying to understand better how the SAMD21 wakes and goes to sleep.  The only information I can find in the datasheet that might begin to explain what is going on is related to the "lock time" for the DFLL48M oscillator.  This can be up to 500us according to the datasheet (Table 37-52).  Does resumption of the execution get delayed by the lock time of the DFLL?  The lock time is about 100x less than the delay I am seeing though so I do not think this is the culprit.

Has anyone else make any observations on the current versus apparent wake time of the SAMD21?  Does anyone have any ideas that could be causing this?
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on May 23, 2019, 10:01 am
Hi sslupsky,

Quote
The "wake" time according to the scope trace is about 6ms.  This is the time the I/O pin is in its high state, meaning the time it goes high immadiately after waking and going low immediately before sleeping again.  This amount of time seems extraordinarily high to me.
The 6ms delay you're seeing on your output is most probably due to your watchdog library. The watchdog CLEAR register that resets its timer is write synchronised, however most watchdog libraries block while waiting for synchronisation to complete:

Code: [Select]
WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;       // Clear the watchdog timer
while (WDT->STATUS.bit.SYNCBUSY);        // Wait for synchronization

As the watchdog itself is being clocked by a generic clock (GCLK) at 1.024kHz and write synchronisation introduces a delay of:

5 * GCLK + 2 * APB < delay < 6 * GCLK + 3 * APB

...then you're going to get a worst case delay of:

6 * (1/1.024kHz) + 3 * (1/48MHz)  = 5.86ms

where:
GCLK (Generic Clock) = 1.024kHz
APB (Advanced Peripheral Bus) = 48MHz

The workaround is to instead replace your watchdog reset with the following code that tests if synchronisation has completed with a non-blocking if statement, before reseting the watchdog's timer:

Code: [Select]
if (!WDT->STATUS.bit.SYNCBUSY)                // Check if the WDT registers are synchronized
{
  WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;       // Clear the watchdog timer
}

It still takes the watchdog's CLEAR register 5.86ms to synchronise, but the watchdog timer is only reset if the previous synchronisation has completed.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 23, 2019, 04:56 pm
Hi MartinL,

Thank you for the feedback.  I checked my watchdog.reset() method and I think I am using the non-blocking technique:

Code: [Select]
void WITAP_Watchdog::reset() {
  if (!WDT->STATUS.bit.SYNCBUSY) {               // Check if the WDT registers are synchronized
    REG_WDT_CLEAR = WDT_CLEAR_CLEAR_KEY;        // Clear the watchdog timer
  }
}


However, your suggestion gave me an idea and I looked further into the RTCZero library.  When it reads the RTC there is a blocking sync:

Code: [Select]
/* Synchronise the CLOCK register for reading*/
inline void RTCZero::RTCreadRequest() {
  if (_configured) {
    RTC->MODE2.READREQ.reg = RTC_READREQ_RREQ;
    while (RTCisSyncing())
      ;
  }
}


Since the RTC is clocked using the 1.024kHz clock, the sync delay will be similar I think.  There are many places in the RTCZero library that use the blocking sync.

As I dug through the RTCZero library and the datasheet it appears the RTC hardware on the SAMD21 has a major shortcoming.  There doesn't appear to be any way to read the RTC clock directly without the 6ms sync delay when you wake from sleep.  Moreover, you cannot adjust the alarm without hitting this roadblock as well.

I can probably design around this issue by using my own clock register but I pretty much loose most of the functionality of the RTCZero library because I will need to re-write many of the functions it provides.  Are you aware of anyone that may have already addressed this issue?
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on May 23, 2019, 05:38 pm
Hi sslupsky,

Glad to hear you found the source of the problem.

Yes, the SAMD21's synchronisation is a right pain, especially when you need to access peripherals driven from a slow generic clock, such as the WDT and RTC.

I don't think there's any way around this issue. The only other option available on the RTC is to use SYNCRDY interrupt flag rather than polling, however this still doesn't get around the fact that each register access takes 6ms or so to synchronise.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 23, 2019, 06:09 pm
Hi MartinL,

Further to my comment above, I think the RTCZero syncing may be the underlying cause of the 35ms bump in the power consumption I see before the I/O pins toggles.  I wake from sleep using an RTC interrupt.  My interrupt handler updates the RTC alarm to the next time I want to wake.  The RTC setAlarmEpoch() method has several delays in each of the underlying function calls:

Code: [Select]
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);

    setAlarmDate(tmp->tm_mday, tmp->tm_mon + 1, tmp->tm_year - EPOCH_TIME_YEAR_OFF);
    setAlarmTime(tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
  }
}


There is a SYNCBUSY delay for each of the DAY, MONTH, YEAR, HOUR, MINUTES, SECONDS.  So, 6 x 6ms = 36ms.  This occurs before resumption of my loop() hence what appears to be a "wake delay".  I can confirm that when I collapse the setAlarmEpoch() to one register update the "wake delay" is reduced to 6ms.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 24, 2019, 12:25 am
Hi MartinL,

I came across this thread a short while ago:

https://forum.arduino.cc/index.php?topic=553612.0

In that thread, you discuss using the ALARM0 compare to generate a 1 Hz interrupt (post #2).  Later (post #6 and #8) you describe avoiding the 6ms delay caused by the SYNCBUSY wait.  My question relates to sleeping immediately after incrementing the ALARM0 compare register before the SYNCBUSY is complete.  Will the sleep be delayed until the SYNCBUSY is complete in that instance or does the device go to sleep right away?
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 24, 2019, 08:07 pm
Hi MartinL,

I am considering using the Periodic Events to eliminate the write to the RTC register and consequential SYNCBUSY and 6ms delay after waking from sleep.  Have you worked with this aspect of the RTC?

Thank you for your feedback!
Steve
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 24, 2019, 09:34 pm
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!  :-)

Best regards,
Steve
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on May 25, 2019, 10:19 am
Hi Steve,

Quote
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
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on May 27, 2019, 12:10 pm
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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 29, 2019, 03:52 pm
Hi Martin,

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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 29, 2019, 11:59 pm
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. :-)    When I require a tick less than 1 second then I will need to use the event technique.

Steve
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on May 31, 2019, 07:23 pm
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:

Code: [Select]
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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on Jun 03, 2019, 09:54 am
Hi Steve,

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

Code: [Select]
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:

(https://forum.arduino.cc/index.php?action=dlattach;topic=617395.0;attach=311013)

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
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 03, 2019, 10:53 pm
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
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 03, 2019, 11:29 pm
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:

Code: [Select]
 
  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:

Code: [Select]

  // 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:

Code: [Select]

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:

Code: [Select]

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:

Code: [Select]

  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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on Jun 04, 2019, 11:38 am
Hi Steve,

Quote
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:

Code: [Select]
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
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 04, 2019, 05:35 pm
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.

Code: [Select]

  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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 04, 2019, 10:32 pm
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.

Code: [Select]

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



Quote
--------------------------- 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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 05, 2019, 12:12 am
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();
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on Jun 05, 2019, 03:48 pm
Quote
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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 05, 2019, 04:31 pm
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
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 05, 2019, 06:38 pm
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?
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 05, 2019, 08:50 pm
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.
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on Jun 06, 2019, 09:35 am
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):

Code: [Select]
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:

Code: [Select]
// 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
Title: Re: SAMD21 wake up time from deep sleep
Post by: avelo on Jun 20, 2019, 12:53 pm
Probably off-topic or newbie, but I've read this about hard lookups

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

it's suggested to disable and reenable systick interrupt:

before sleep:
Code: [Select]
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;

right after wakeup:
Code: [Select]
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
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Jun 20, 2019, 07:19 pm
Hi avelo,

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

Steve
Title: Re: SAMD21 wake up time from deep sleep
Post by: MartinL on Jun 20, 2019, 10:52 pm
Hi avelo,

Thanks for the link and explanation, I've added systick interrupt disable/enable lines to the example code above.
Title: Re: SAMD21 wake up time from deep sleep
Post by: avelo on Jun 20, 2019, 11:16 pm
Hi avelo,

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

Steve

I realized your PR to RTCZero (I was just taking a look before forking to do that), not yet merged.
Nevertheless it's merged on ArduinoLowPower
Title: Re: SAMD21 wake up time from deep sleep
Post by: sslupsky on Oct 05, 2019, 12:27 am
Yeah, I am not sure why it is not merged on RTCZero yet.  There was another change to avoid bus stalls that was included in the same PR for RTCZero ... maybe that is what is holding it up but I do not know because nobody has said anything.  I've touched the PR again and asked if it is going to be merged.