[SOLVED] Wake up the Arduino MKR WiFi with a TC overflow. (SAMD21)

Hello,

I'm trying to get the Arduino MKR 1010 in a deep sleep state and wake it up every minute with a timer (TC) interrupt. I've been trying for so long, but I can't get it.

In the code, which I enclose, the time cofiguration is 10s.

Hope you can help me,

Martin.

bool valor=LOW;


void setup() {
  
  Serial.begin(9600);
  pinMode(LED_BUILTIN,OUTPUT);
  
  GCLK_Config();
  TimerConfig();
  EventConfig();
  InterruptConfig();
  
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;                              // Put the SAMD21 in deep sleep upon executing the __WFI() function
  NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_SLEEPPRM_DISABLED;
  __DSB();
}

void loop() {
  
  while(!Serial);
  Serial.println("LOOP");
//  delay(5000);
//  __WFI();
//  Serial.println("Abajo");
}

void GCLK_Config(){
    

  
  REG_SYSCTRL_XOSC32K = 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
 
  
  REG_GCLK_GENDIV = GCLK_GENDIV_ID(4) |   // GCLK0 como fuente del CLK genérico.
                    GCLK_GENDIV_DIV(1);  // Factor de division del GCLK.
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization __DSB();
                     
  REG_GCLK_GENCTRL = GCLK_GENCTRL_ID(4) |       // Seleccionamos GCLK0.
                     GCLK_GENCTRL_SRC_XOSC32K | //La fuente del CLK es el oscilador.
                     GCLK_GENCTRL_GENEN |       //Habilitación del GCLK
                     
                     GCLK_GENCTRL_RUNSTDBY;     //Activo durante el bajo consumo.
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_ID_TC4_TC5 |    //GCLK alimenta a TCC2 y TC3
                     GCLK_CLKCTRL_GEN_GCLK4 |    //Seleccionamos GCLK0
                     GCLK_CLKCTRL_CLKEN;         //Habilitación
  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
                      
}

void InterruptConfig(){

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

void TimerConfig(){
  
  REG_PM_APBCMASK |= PM_APBCMASK_TC4; // TC bus clock (PM_component (aparece como "TC4_"))
 
  REG_TC4_COUNT16_CC0=0x13FF; //0x77FF=(30719+1)/512=60s

  REG_TC4_INTFLAG |= TC_INTFLAG_OVF;   //Limpiamos el flag de overflow
  REG_TC4_INTENSET = TC_INTENSET_OVF;  //Habilitación de la interrupción por overflow
                      
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV64 |  //32768/64=512Hz
                   TC_CTRLA_WAVEGEN_MFRQ |    //Match Frecuency Generation
                   TC_CTRLA_RUNSTDBY |        //Activo en modo bajo consumo
                   TC_CTRLA_ENABLE;           //Habilitacion del timer
  __DSB();
                   
}

void EventConfig(){
  
  REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
  
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |  //Ningún canal (por ahora)
                   EVSYS_USER_USER(0x13);   //Seleccionamos el TC4.

  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_CHANNEL(0) |            //Canal 0
                      EVSYS_CHANNEL_PATH_SYNCHRONOUS |      //Comparten GCLK
                      //EVSYS_CHANNEL_PATH_RESYNCHRONIZED | //No comparten GCLK
                      EVSYS_CHANNEL_EDGSEL_RISING_EDGE |
                      //EVSYS_CHANNEL_EVGEN(0x37);          //El generador del evento es un match en TC4.
                      EVSYS_CHANNEL_EVGEN(0x36);            //El generador es un overflow en TC4.
                      
  
}

void EVSYS_Handler()                    
{         
      valor=!valor;
      digitalWrite(LED_BUILTIN,valor);
      
    REG_TC4_INTFLAG |= TC_INTFLAG_OVF;      // Borrar flag de interrupcion por overflow
    EVSYS->INTFLAG.bit.EVD0 = 1;            // Borrar flag interrupcion
    //__WFI();
    
}

Registros.ino (4.66 KB)

Hi Martin,

While it's possible to use TC timer to wake the microcontroller from deep sleep every 60 seconds, normally you'd use the RTC (Real Time Counter).

The following code sets up the 32.768kHz external crystal to run in standby mode. The generic clock peripheral divides this clock down to 1.024kHz, (32.768kHz/(2^^(4 + 1))) on GCLK4. The RTC's prescaler then divides this down further by 1024 to generate a 1Hz or 1 second tick. The RTC then simply counts from 0 to 59, whereupon it overflows and triggers an interrupt after 1 minute. After setting up the RTC the SAMD21 goes into deep sleep, only to be woken by the interrupt every 60 seconds:

// Put SAMD21 into deep sleep and RTC interrupts set to trigger every 60s (PER) 
void setup(){
  PORT->Group[PORTA].DIRSET.reg = PORT_PA21;  // Output to D7 to check that the ISR is being called every 60 seconds

// 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)) to generate 1.024kHz
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization
 
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(4) |        // Select GCLK4
                      GCLK_GENCTRL_SRC_XOSC32K |  // Select generic clock 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

// 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(59);                   // Interrupt time 60s: 1Hz/(59 + 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 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 RTC_Handler(void)
{
  //if (RTC->MODE1.INTFLAG.bit.OVF && RTC->MODE1.INTENSET.bit.OVF) {  // Check if an overflow caused the interrupt
     PORT->Group[PORTA].OUTTGL.reg = PORT_PA21;                       // Toggle digital pin D7
     RTC->MODE1.INTFLAG.bit.OVF = 1;                                  // Reset the overflow interrupt flag
  //}
}

By the way, I've just added two lines to the loop() function in the code above to disable/enable the SysTick interrupts before/after entering sleep mode. (The SysTick timer is used to generate delay() and delayMicroseconds() timing functions):

SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;           // Disable SysTick interrupts
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;            // Enable SysTick interrupts

This is due to the SysTick interrupts becoming active before the flash memory has time to recover from sleep mode, causing a hardfault and locking up the processor. A more detailed discussion can be found here: https://www.avrfreaks.net/forum/samd21-samd21e16b-sporadically-locks-and-does-not-wake-standby-sleep-mode.