Keeping the timer or millis() alive while in standby on the M0 Pro

Hi,

I'm hoping someone can point me in the right direction.

I'm trying to put an Arduino M0 Pro to sleep but keep either millis() or another timer running to in the background, possibly even runnning off the same GCLK as the RTC (that would be GCLK2 I believe).

I've been through the datasheet and I can see that I need to keep the GCLK and the relevant Timer running in standby, along with the 32K oscillator.

Can someone please help me confirm if I'm setting this up correctly since I think my timer code might be freezing the micro:

#include "LowPower.h"

#define LED_PIN 13

#define CPU_HZ 48000000
#define TIMER_PRESCALER_DIV 1024

void startTimer(int frequencyHz);
void setTimerFrequency(int frequencyHz);
void TC3_Handler();

bool isLEDOn = false;
volatile long NewMillis = 0; 

const int pin = 0;
unsigned char count = 10;

void setup() {
  SerialUSB.begin(115200);
    // Wait for serial USB port to open
  while(!SerialUSB);
  SerialUSB.println("***** ATSAMD21 Standby Mode Example *****");
  
  // ***** IMPORTANT *****
  // Delay is required to allow the USB interface to be active during
  // sketch upload process
  SerialUSB.println("Entering standby mode in:");
  for (count; count > 0; count--)
  {
    SerialUSB.print(count); 
    SerialUSB.println(" s");
    delay(1000);
  }
  // *********************
    
  // External interrupt on pin (example: press of an active low button)
  // A pullup resistor is used to hold the signal high when no button press
  attachInterrupt(pin, blink, LOW);
  startTimer(1000);
}

void loop() {
  SerialUSB.println("Entering standby mode.");
  SerialUSB.println("Apply low signal to wake the processor.");
  SerialUSB.println("Zzzz...");
  // Detach USB interface
  USBDevice.detach();
  // Enter standby mode
  LowPower.standby();  
  // Attach USB interface
  USBDevice.attach();
  // Wait for serial USB port to open
  while(!SerialUSB);
  // Serial USB is blazing fast, you might miss the messages
  delay(1000);  
  SerialUSB.println("M:  " + String(millis()));
  SerialUSB.println("nM: " + String(NewMillis));
  SerialUSB.println("Awake!");
  SerialUSB.println("Send any character to enter standby mode again");
  // Wait for user response
  while(!SerialUSB.available()){

  }
  while(SerialUSB.available() > 0)
  {
    SerialUSB.read();
  }



  }

  
void blink(void)
{
       digitalWrite(LED_PIN,HIGH);
       delay(100);
       digitalWrite(LED_PIN,LOW);
}

void setTimerFrequency(int frequencyHz) {
  int compareValue = (CPU_HZ / (TIMER_PRESCALER_DIV * frequencyHz)) - 1;
  TcCount16* TC = (TcCount16*) TC3;
  // Make sure the count is in a proportional position to where it was
  // to prevent any jitter or disconnect when changing the compare value.
  TC->COUNT.reg = map(TC->COUNT.reg, 0, TC->CC[0].reg, 0, compareValue);
  TC->CC[0].reg = compareValue;
  SerialUSB.println(TC->COUNT.reg);
  SerialUSB.println(TC->CC[0].reg);
  while (TC->STATUS.bit.SYNCBUSY == 1);
}

void startTimer(int frequencyHz) {

  SYSCTRL->XOSC32K.bit.RUNSTDBY = 1;
  
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3) ;
  while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync

  const uint8_t GCLK_SRC = 0;

  TcCount16* TC = (TcCount16*) TC3;

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN |
            GCLK_GENCTRL_SRC_XOSC32K |
            GCLK_GENCTRL_ID(GCLK_SRC) |
            GCLK_GENCTRL_RUNSTDBY;
    while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);


  // Use the 16-bit timer
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // Use match mode so that the timer counter resets when the count matches the compare register
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // Set prescaler to 1024
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // Set timer to run in standby
  TC->CTRLA.reg |= TC_CTRLA_RUNSTDBY;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  setTimerFrequency(frequencyHz);

  // Enable the compare interrupt
  TC->INTENSET.reg = 0;
  TC->INTENSET.bit.MC0 = 1;

  NVIC_EnableIRQ(TC3_IRQn);

  TC->CTRLA.reg |= TC_CTRLA_ENABLE;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
}

void TC3_Handler() {
  TcCount16* TC = (TcCount16*) TC3;
  // If this interrupt is due to the compare register matching the timer count
  // we toggle the LED.
  if (TC->INTFLAG.bit.MC0 == 1) {
    TC->INTFLAG.bit.MC0 = 1;
    NewMillis++;
    // Write callback here!!!
    //digitalWrite(LED_PIN, isLEDOn);
    //isLEDOn = !isLEDOn;
  }
}

I'm pulling the setup code from

https://forum.arduino.cc/index.php?topic=412465.0

What am I doing wrong that the timer locks up the micro when I called the function to start the timer in setup?

Thanks in advance

PS, I'm assuming that running this Timer while asleep will wake the micro up each time the interrupt fires and therefore will have an effect on the battery life?

The purpose of this is that I need a way to debounce a pulse from a read switch on an interrupt and keeping the micro alive while I wait for the pulse to clear will have an effect long term (worse) on the battery life.

Hi Zaprime,

Looking at your code, I'm not able to determine what the relationship is between the reed switch interrupt and the timer.

It's possible to place the SAMD21 into deep sleep and have it wake-up upon receiving an interrupt on a pin just using the __WFI() (Wait For Interrupt) function. In this instance, setting up a timer wouldn't be necessary.

Going into low power mode will shutdown the 48MHz clock to the SAMD21's native USB peripheral and effectively terminate your board's connection with you host PC/Laptop. Your M0 Pro will still run, but it won't be possible to communicate with the console using SerialUSB.

In your code, you're reassigning the 48MHz generic clock 0 to the external oscillator (XOSC32K), this means that when your microcontroller wakes up it will be clocked at 32.768kHz rather than 48MHz. It's usually better to leave GCLK0 and assign either GCLK2 at 32.768kHz, or an unused generic clock GCLK4-GCLK7.

When accessing the TC timer's registers care must be taken to ensure that it's registers are read and write synchronized:

while (TC3->COUNT16.STATUS.bit.SYNCBUSY);   // Read synchronization
count = TC3->COUNT16.COUNT.reg;             // Read from the TC3 COUNT register

count = TC3->COUNT16.COUNT.reg;             // Write to the TC3 COUNT register
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);   // Write synchronization

It's also possible to set up the TC timer to toggle an output, without having rely on an interrupt service routine.