Go Down

Topic: SAMD21 wake up time from deep sleep (Read 5013 times) previous topic - next topic

sslupsky

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?

MartinL

#1
May 23, 2019, 10:01 am Last Edit: May 23, 2019, 10:08 am by MartinL
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.

sslupsky

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?

MartinL

#3
May 23, 2019, 05:38 pm Last Edit: May 23, 2019, 05:46 pm by MartinL
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.

sslupsky

#4
May 23, 2019, 06:09 pm Last Edit: May 23, 2019, 06:10 pm by sslupsky
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.

sslupsky

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?

sslupsky

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

sslupsky

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

MartinL

#8
May 25, 2019, 10:19 am Last Edit: May 25, 2019, 05:44 pm by MartinL
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

MartinL

#9
May 27, 2019, 12:10 pm Last Edit: May 27, 2019, 12:15 pm by MartinL
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.

sslupsky

#10
May 29, 2019, 03:52 pm Last Edit: May 29, 2019, 03:53 pm by sslupsky
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.

sslupsky

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

sslupsky

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.

MartinL

#13
Jun 03, 2019, 09:54 am Last Edit: Jun 03, 2019, 10:31 am by MartinL
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:



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

sslupsky

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

Go Up