SAMD21 not waking up using Timer Interrupt from deep sleep

Hi all
I need to wake up the SAMD21 from deep sleep in every ‘X’ sec interval,do some tasks & put it back to sleep again.By far,i’ve configured TC4 interrupt followed by Mr MartinL’s code in another forum & the interrupt is working correctly with a LED state change test code.But,whenever I am putting the CPU to sleep,it is unable to wake up.I also don’t have the 32.768k RTC crystal & unable to run the RTC event interrupt.Please take a look at the code below to wake up & suggest what can be done…

volatile bool LED_state = 0;

void InitTC(void)
{
  PM->APBCMASK.reg |= PM_APBCMASK_TC4;  
  
  //Configure Clock Generator 4 to give output to Timer modules to generate PWM signal
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |  //50-50 Duty
                      GCLK_GENCTRL_GENEN |        //Enable generic clock generator
                      GCLK_GENCTRL_SRC_DFLL48M |  //48MHZ clock as the input of the general clock generator as source
                      GCLK_GENCTRL_ID(4);         //Select GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);               //Wait for the settings to be synchronized

  //Set Clock divider for GCLK4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(48) |         //Divide 48MHz by 48 i.e clock output of GCLK4 is 1MHz
                     GCLK_GENDIV_ID(4);           //GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              //Wait for the settings to be synchronized

  //Connect GCLK4 output to TC modules
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        //Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    //Select GCLK4
                      GCLK_CLKCTRL_ID_TC4_TC5;  //Feed GCLK4 output to TC4 & TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              //Wait for the settings to be synchronized

  //Configure TC4 module
  TC4->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE ;  //Disable CTRLA to write configs
  TC4->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;  //16 bit counter enable

  TC4->COUNT16.CC[0].reg = 0x393F;  //TOP value for 15s
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  TC4->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024 |  //Division factor 1024
                            TC_CTRLA_WAVEGEN_MFRQ |   //Enable Matched Frequency Waveform generation
                            TC_CTRLA_ENABLE;  //Enable Timer
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                          

  // Configure interrupt request
  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC4_IRQn, 0);
  NVIC_EnableIRQ(TC4_IRQn);

  REG_TC4_INTFLAG |= TC_INTFLAG_OVF;              // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_OVF;             // Enable TC4 interrupts

  TC4->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE ; //Enable TC4
}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{     
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT16.INTFLAG.bit.OVF && TC4->COUNT16.INTENSET.bit.OVF)             
  {
    // Put your timer overflow (OVF) code here:     
    // ...
    LED_state = !LED_state;
    digitalWrite(LED_BUILTIN,LED_state);
       
    REG_TC4_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }
}

void setup() {
  // put your setup code here, to run once:
  pinMode(LED_BUILTIN,OUTPUT);
  InitTC();
  
  SCB->SCR |= 1 << 2; // Enable deep-sleep mode
  //EIC->WAKEUP.reg = (1 << 2);//Wake up using EXINT2(Digital pin 1/PA02);
  
}

void loop() {

    __WFI();

}

Hi SHARANYADAS,

The issue is that to run the timer from the 48MHz Digtial Frequency Locked Loop (DFLL48M) during sleep, the DFLL, generic clock (GCLK) and the TC timer have to be set to run standby mode (RUNSTDBY).

However, during testing the TC timer period wasn't what I expected. After routing the GCLK4 output to digital pin D6 (PA20), I noticed there's problem with the DFLL or GCLK4 during sleep. Everytime sleep mode is activated the GCLK frequency is more than halved, 48MHz switches to around 21MHz. Not sure why this is happening with the DFLL, as 32kHz clock sources by contrast are solid.

A workarouind is to use a different clock source. In the absence of an external crystal on your board, the next best option is to use the SAMD21's internal Ultra Low Power 32kHz clock source (OSCULP32K) instead. It's not super accurate, but allows you to generate 15s interrupts with TC4:

volatile bool LED_state = 0;

void InitTC(void)
{
  // Output GCLK4 on digital pin D6 (PA20)
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXE_H;

  //Set Clock divider for GCLK4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(4) |         //Divide 32.768kHz / 2^(4 + 1)
                     GCLK_GENDIV_ID(4);           //GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              //Wait for the settings to be synchronized
  
  //Configure Clock Generator 4 to give output to Timer modules to generate PWM signal
  GCLK->GENCTRL.reg = GCLK_GENCTRL_RUNSTDBY |     // Set GCLK4 to run in standby mode
                      GCLK_GENCTRL_OE |           // Output GCLK4 on D6 (PA20)
                      GCLK_GENCTRL_DIVSEL |       // Set divisor to 2^(4 + 1)
                      GCLK_GENCTRL_IDC |          //50-50 Duty
                      GCLK_GENCTRL_GENEN |        //Enable generic clock generator
                      //GCLK_GENCTRL_SRC_DFLL48M |  //48MHZ clock as the input of the general clock generator as source
                      GCLK_GENCTRL_SRC_OSCULP32K |  // Use the Ultra Low Power 32kHz oscillator
                      GCLK_GENCTRL_ID(4);         //Select GCLK 4*/

  //Connect GCLK4 output to TC modules
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        //Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    //Select GCLK4
                      GCLK_CLKCTRL_ID_TC4_TC5;    //Feed GCLK4 output to TC4 & TC5

  TC4->COUNT16.CTRLA.reg |=  TC_CTRLA_PRESCSYNC_PRESC |     // Trigger next cycle on prescaler clock (not GCLK)
                             TC_CTRLA_RUNSTDBY |            // Set the timer to run in standby mode
                             TC_CTRLA_PRESCALER_DIV1024 |   // Division factor 1024
                             TC_CTRLA_WAVEGEN_MFRQ |        // Enable Matched Frequency Waveform generation
                             TC_CTRLA_MODE_COUNT16;         // Enable 16-bit COUNT mode
                      
  TC4->COUNT16.CC[0].reg = 14;                    //TOP value for 15s
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       //Wait for synchronization
                                                   
  // Configure TC4 interrupt request
  NVIC_SetPriority(TC4_IRQn, 0);
  NVIC_EnableIRQ(TC4_IRQn);
  
  TC4->COUNT16.INTENSET.reg = TC_INTENSET_OVF;            // Enable TC4 interrupts

  TC4->COUNT16.CTRLA.bit.ENABLE = 1;                      // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);               // Wait for synchronization
}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{     
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT16.INTENSET.bit.OVF)             
  {
    TC4->COUNT16.INTFLAG.bit.OVF = 1;         // Clear the OVF interrupt flag
    
    // Put your timer overflow (OVF) code here:     
    // ...
    LED_state = !LED_state;
    digitalWrite(LED_BUILTIN,LED_state);
       
    
  }
}

void setup() {
  // put your setup code here, to run once:
  pinMode(LED_BUILTIN,OUTPUT);
  InitTC();
 
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;// | SCB_SCR_SLEEPONEXIT_Msk;  // Put the SAMD21 in deep sleep upon executing the __WFI() function
  NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_SLEEPPRM_DISABLED;        // Disable auto power reduction during sleep - SAMD21 Errata 1.14.2
 
}

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  
}

Hi SHARANYADAS,

I tracked down the problem with the DFLL, I’d forgotten that the DFLL is driven by the 32.768kHz external crystal (XOSC32). When the microcontroller goes to sleep the XOSC32 clock source is shutdown and consequently the DFLL loses lock. Simply setting the XOSC32K clock source to RUNSTANDY mode solves this.

However, now the problem is that the TC4_Handler() routine doesn’t get called, although the TC4 timer is now clocking correctly (output on pin SCL (PA22)). Here’s the code using the DFLL to clock the TC4:

volatile bool LED_state = 0;

void InitTC(void)
{
  SYSCTRL->VREG.bit.RUNSTDBY = 1;                  // Keep the voltage regulator in normal configuration during run stanby
  SYSCTRL->XOSC32K.bit.RUNSTDBY = 1;               // Set the external 32kHz crystal to run standby mode
  SYSCTRL->DFLLCTRL.bit.RUNSTDBY = 1;              // Set the DFLL48M to run standby mode
  while(!SYSCTRL->PCLKSR.bit.DFLLRDY);             // Wait for synchronization
  
  // Output GCLK4 on digital pin D6 (PA20)
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXE_H;

  // Output TC4 timer on SDA pin (PA22)
  PORT->Group[g_APinDescription[SDA].ulPort].PINCFG[g_APinDescription[SDA].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[SDA].ulPort].PMUX[g_APinDescription[SDA].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;

  //Set Clock divider for GCLK4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(48) |         //Divide by 48MHz/48=1MHz
                     GCLK_GENDIV_ID(4);           //GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              //Wait for the settings to be synchronized
  
  //Configure Clock Generator 4 to give output to Timer modules to generate PWM signal
  GCLK->GENCTRL.reg = GCLK_GENCTRL_RUNSTDBY |     // Set GCLK4 to run in standby mode
                      GCLK_GENCTRL_OE |           // Output GCLK4 on D6 (PA20)
                      //GCLK_GENCTRL_DIVSEL |       // Set divisor to 2^(4 + 1)
                      GCLK_GENCTRL_IDC |          //50-50 Duty
                      GCLK_GENCTRL_GENEN |        //Enable generic clock generator
                      GCLK_GENCTRL_SRC_DFLL48M |  //48MHZ clock as the input of the general clock generator as source
                      GCLK_GENCTRL_ID(4);         //Select GCLK 4

  //Connect GCLK4 output to TC modules
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        //Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    //Select GCLK4
                      GCLK_CLKCTRL_ID_TC4_TC5;    //Feed GCLK4 output to TC4 & TC5

  TC4->COUNT16.CTRLA.reg = TC_CTRLA_PRESCSYNC_PRESC |     // Trigger next cycle on prescaler clock (not GCLK)
                           TC_CTRLA_RUNSTDBY |            // Set the timer to run in standby mode
                           TC_CTRLA_PRESCALER_DIV1024 |   // Division factor 1024
                           TC_CTRLA_WAVEGEN_MFRQ |        // Enable Matched Frequency Waveform generation
                           TC_CTRLA_MODE_COUNT16;         // Enable 16-bit COUNT mode
                      
  TC4->COUNT16.CC[0].reg = 0x393F;                    //TOP value for 15s
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       //Wait for synchronization
                                                   
  // Configure TC4 interrupt request
  NVIC_SetPriority(TC4_IRQn, 0);
  NVIC_EnableIRQ(TC4_IRQn);
  
  TC4->COUNT16.INTENSET.reg = TC_INTENSET_OVF;            // Enable TC4 interrupts

  TC4->COUNT16.CTRLA.bit.ENABLE = 1;                      // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);               // Wait for synchronization
}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{     
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT16.INTENSET.bit.OVF && TC4->COUNT16.INTFLAG.bit.OVF)             
  {
    TC4->COUNT16.INTFLAG.bit.OVF = 1;         // Clear the OVF interrupt flag
    
    // Put your timer overflow (OVF) code here:     
    // ...
    LED_state = !LED_state;
    digitalWrite(LED_BUILTIN,LED_state);
  }
}

void setup() {
  // put your setup code here, to run once:
  pinMode(LED_BUILTIN,OUTPUT);
  InitTC();
  
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;// | SCB_SCR_SLEEPONEXIT_Msk;  // Put the SAMD21 in deep sleep upon executing the __WFI() function
  NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_SLEEPPRM_DISABLED;        // Disable auto power reduction during sleep - SAMD21 Errata 1.14.2
}

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  
}

If your board is crystalless then it’s likely that it’s running off the internal 32kHz oscillator (OSC32K). In that case, just change RUNSTDBY setting to:

SYSCTRL->OSC32K.bit.RUNSTDBY = 1;               // Set the internal 32kHz oscillator to run standby mode

Furthermore, the above code does work correctly when the CPU is placed in IDLE rather than DEEPSLEEP mode, by commenting out the line:

//SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;// | SCB_SCR_SLEEPONEXIT_Msk;  // Put the SAMD21 in deep sleep upon executing the __WFI() function

I haven’t yet figured out why the TC4_Handler() function gets called for the 32kHz clock source and not for the 48MHz DFLL?

Hi SHARANYADAS,

Ploughing a bit deeper I discovered the problem. The issue was that the SAMD21's voltage regulator also enters low power mode during sleep and doesn't have enough oomph to drive the DFLL at 48MHz plus an LED.

The solution is to keep the voltage regulator in normal mode during deep sleep with the following line of code:

SYSCTRL->VREG.bit.RUNSTDBY = 1;                  // Keep the voltage regulator in normal configuration during run stanby

I've added this to the code in the above post (#2).

Wow...now I got multiple options!!

I followed one of your code here & modded the code to taget the LED on-off for 15s.It is working correctly as expected.Please take a look whether I should add/remove something !

Actually,I've bulit a wristwatch & the SAMD21 should wake & communicate with a MAX17048 to get battery info & parallelly check for USB attachment for charging.Also,the watch should respond to external interrupt for time checking using a button press!
My whole watch is working as expected.But previously I used adafruit sleepydog library to wake the CPU in desired interval but that is drastically increasing the current consumption within sleep(approximate 1mA to 10mA range).

But,whenever,I am omitting the "wake up in interval part / the sleepydog library",the current consumption drops to 13uA during sleep which is expected.

Now,I want to examine power consumption with this code.As the code for the whole Wristwatch is very big,I am testing with LED for now!!

Thanks in advance for your tremendous support...

volatile bool LED_state = 0;

void InitRTCInt(void)
{
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |  //50-50 Duty(Though,it will not reflect on any output pin)
                      GCLK_GENCTRL_GENEN |        //Enable generic clock generator
                      GCLK_GENCTRL_SRC_OSCULP32K |  //Internal 32kHz low power clock as source
                      GCLK_GENCTRL_ID(4) |         //Select GCLK 4
                      GCLK_GENCTRL_DIVSEL |      //et GLCK divisor as 2 to the power of (divisor) value
                      GCLK_GENCTRL_RUNSTDBY;  //Run on standby.IS THIS LINE NECESSARY??
  while (GCLK->STATUS.bit.SYNCBUSY);

  //Set Clock divider for GCLK4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(4) |         //Select clock divisor to divide by 32 (2 ^ (4 + 1))
                     GCLK_GENDIV_ID(4);           //GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              //Wait for the settings to be synchronized

 //Connect GCLK4 output to RTC
  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

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

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

  RTC->MODE1.CTRL.reg |= RTC_MODE1_CTRL_PRESCALER_DIV1024 |     // Set prescaler to 1024
                         RTC_MODE1_CTRL_MODE_COUNT16;           // Set RTC to mode 1, 16-bit timer

  RTC->MODE1.PER.reg = RTC_MODE1_PER_PER(14);                   // Interrupt time 15s: 1Hz/(14 + 1)
  while (RTC->MODE1.STATUS.bit.SYNCBUSY);                       // Wait for synchronization

  // Configure RTC interrupts ------------------------------------------
  RTC->MODE1.INTENSET.reg = RTC_MODE1_INTENSET_OVF;             // Enable RTC overflow interrupts

  NVIC_SetPriority(RTC_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for RTC
  NVIC_EnableIRQ(RTC_IRQn);         // Connect RTC to Nested Vector Interrupt Controller (NVIC)

  
  // Enable Deep Sleep Mode--------------------------------------------------------------
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;// | SCB_SCR_SLEEPONEXIT_Msk;  // Put the SAMD21 in deep sleep upon executing the __WFI() function
  NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_SLEEPPRM_DISABLED;        // Disable auto power reduction during sleep - SAMD21 Errata 1.14.2
  

  // Enable RTC--------------------------------------------------------------
  RTC->MODE1.CTRL.bit.ENABLE = 1;                       // Enable the RTC
  while (RTC->MODE1.STATUS.bit.SYNCBUSY);               // Wait for synchronization
}

void RTC_Handler(void)
{
  LED_state = !LED_state;
  digitalWrite(LED_BUILTIN, LED_state);
  RTC->MODE1.INTFLAG.bit.OVF = 1;                                  // Reset the overflow interrupt flag
}


void setup() {
  // put your setup code here, to run once:
  pinMode(LED_BUILTIN,OUTPUT);
  InitRTCInt();

}

void loop() {
  // put your main code here, to run repeatedly:
  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
}

Hi SHARANYADAS,

Please take a look whether I should add/remove something !

Your code looks good to me.

Thanks a lot sir....