SAMD21 fails to wake from STANDBY sleep

Go To Last Post
2 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note: this is clearly related to SAMD21 (SAMD21E16B) sporadically locks up and does not wake up from STANDBY sleep mode but this example *always* locks up and does not involve SysTick.  (Note also this is an update from my previous post wake from sleep with and without debugger, albeit with a deeper understanding and fewer false starts...)

 

I have a simple program on the SAMD21.  In abstract, it simply does:

  • Turn LED off
  • Loop:
  •   Toggle LED
  •   Get the current RTC COUNT
  •   Set the RTC COMP register to interrupt one second in the future
  •   Sleep(STANDBY)

 

When run, the LED goes ON for one second, then off.  Then nothing more.  This indicates that the program is hanging somewhere after the SECOND call to "Toggle LED".

 

But if I add a short delay after the sleep it all works fine:

  • Turn LED off
  • Loop:
  •   Toggle LED
  •   Get the current RTC COUNT
  •   Set the RTC COMP register to interrupt one second in the future
  •   Sleep(STANDBY)
  •   delay_us(200)

 

My hypothesis is that RAM or some other system resource is not fully ready after returning from the SLEEP call, but adding the delay_us(200) gives it time to wake up.

 

The questions:

  1. Why is the program hanging?
  2. If the cause is that some system resource isn't ready immediately after waking from STANDBY, what is the correct way to determine when it's ready?  (delay_us(200) is not a good way...)

 

Details:

  • Processor: ATSAMD21J18A on a SAMD21 Xplained Pro development board
  • IDE/SDK: Atmel Studio 7 with ASF 4
  • Internal OSC8M => GCLK0 div 1 (8MHz) => CPU, SERCOM3
  • OSCULP32K => GCLK3 div 1 (32KHz, run in standby) => EIC, RTC

 

The Code:

Here's the code in its entirety.  (I have attached aZIP file of the AS7 project as well.)

// file: main.c

#include <atmel_start.h>
#include <stdint.h>

// RTC count advances this many times in one second
#define RTC_TICK_RATE 32768

static void rtc_cb_trampoline(struct calendar_dev *const dev);

int main(void) {
  atmel_start_init();

  // start with LED off
  gpio_set_pin_level(USER_LED_AL, true);

  // Initialize the RTC.  Use CALENDAR_0 since that's the only published
  // interface for managing RTC interrupts
  calendar_enable(&CALENDAR_0); // start RTC
  _calendar_register_callback(&CALENDAR_0.device, rtc_cb_trampoline);

  while (1) {
    // toggle the LED to show things are running
    gpio_toggle_pin_level(USER_LED_AL);

    // Set RTC COMP register to match one second in the future
    uint32_t now = hri_rtcmode0_read_COUNT_COUNT_bf(RTC);
    hri_rtcmode0_write_COMP_COMP_bf(RTC, 0, now + RTC_TICK_RATE);

    // restore RCONT and RREQ after writing an RTC register (section 19.8.4)
    hri_rtcmode0_set_READREQ_RCONT_bit(RTC);
    hri_rtcmode0_set_READREQ_RREQ_bit(RTC);

    sleep(3); // STANDBY until RTC interrupt matches

    // Commenting out the following line causes the program to hang after the
    // first loop.  See https://community.atmel.com/node/add/forum/9751
    delay_us(200);
  }
}

static void rtc_cb_trampoline(struct calendar_dev *const dev) {
  // Arrive here when the RTC count register matches the RTC compare register.
  // Take no action other than waking the processor from sleep...
  asm("nop");
}

 

Attachment(s): 

This topic has a solution.
Last Edited: Wed. Aug 12, 2020 - 10:47 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My Microchip FAE and his factory apps engineer figured it out.  To quote:

 

Ok, I think I know what is going on. With the way RCONT and RREQ are set, the read is initiated right before going to sleep. It is possible that read synchronization completes before going to sleep, but with the old value. Then you wake up, check for sync, it says that it is ready. You read the old value, add 1 second, but that one second is already in the past.

 

The fix is to basically manually request the synchronization before reading the value. And do not write RCONT.  This way you are absolutely sure that the value will be synchronized at the point this code is reached.  This seems to be the most reliable way if you use sleep:

So the revised code looks like this:

while (1) {
  // toggle the LED to show things are running
  gpio_toggle_pin_level(USER_LED_AL);

  // Assure that RREQ is set and synchronized before reading COUNT
  hri_rtcmode0_set_READREQ_RREQ_bit(RTC);
  hri_rtc_wait_for_sync(RTC);

  // Set RTC COMP register to match one second in the future
  uint32_t now = hri_rtcmode0_read_COUNT_COUNT_bf(RTC);
  hri_rtcmode0_write_COMP_COMP_bf(RTC, 0, now + RTC_TICK_RATE);

  sleep(3);  // Sleep in STANDBY mode until RTC COMP interrupt
}

(Actually, the explicit call to hri_rtc_wait_for_sync(RTC) isn't required since the implementation of hri_rtcmode0_read_COUNT_COUNT_bf(RTC) calls it.)

 

I've tested the code and it works.