Go Down

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

sslupsky

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.

MartinL

#16
Jun 04, 2019, 11:38 am Last Edit: Jun 04, 2019, 02:15 pm by MartinL
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

sslupsky

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.

sslupsky

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.

sslupsky

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();

MartinL

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.

sslupsky

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

sslupsky

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?

sslupsky

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.

MartinL

#24
Jun 06, 2019, 09:35 am Last Edit: Jun 20, 2019, 10:39 pm by MartinL
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

avelo

Probably off-topic or newbie, but I've read this about hard lookups

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

sslupsky

Hi avelo,

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

Steve

MartinL

Hi avelo,

Thanks for the link and explanation, I've added systick interrupt disable/enable lines to the example code above.

avelo

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

Go Up