SysTick vs. keeping an Arduino Due in Sleep mode

There have been lots of posts in the past about getting an Arduino Due to wait in low-power mode for an interrupt. People were getting unexpected results; "waking up" too soon and/or drawing too much power. I've untangled the mess a bit and managed to get it working for my application. I'd like this information to be usable by others, but I'm not sure where to post it. So here it is.

There are four operating modes for the SAM3 family (including the Due's CPU). In descending order of power consumption:

  • Active: Pretty self-explanatory.
  • Sleep: The processor's clock is halted, but peripherals' clocks are still active. It can become Active again effectively instantly.
  • Wait: All peripherals' clocks are also halted. Everything still has power; it can become Active again within 10μs given an appropriate signal.
  • Backup: Nearly everything, including the CPU itself, is unpowered. It can become Active again within 0.5 milliseconds given an appropriate signal, but any program state is lost.
    Sleep is good when fast response time is needed, the wait time is short, or a peripheral (like the PIO controller or UARTs) must be active during the wait. (Sleep actually has two modes: waiting for interrupt and waiting for event. I think there's no useful difference between the two for Arduino users.) Wait is good when the wait is quite long, such as a "once an hour" or "once a day" type task. Backup is useful for applications where the Arduino gets externally reset whenever it's needed.

People have been using code like:

const int WAKE_PIN = 10;

void interrupt() {}

void setup() {
  pinMode(WAKE_PIN, INPUT);
  attachInterrupt(WAKE_PIN, interrupt, RISING);
  // ...
}

void loop() {
  // enter Sleep Mode, to wait until the WAKE_PIN rises
  pmc_enable_sleepmode(0);
  // (long-running, synchronous stuff in response to rising edge on WAKE_PIN)
}

They would then find that their code resumes execution practically immediately, instead of waiting for whatever pin they configured as an interrupt source like they expect. They're only interested in a single interrupt source, and they haven't enabled any others, so they are surprised by this near-immediate wakeup. Some concluded that Sleep mode hadn't been entered at all. The truth is, it had, but the Arduino was woken up a very short time later by SysTick.

The Arduino configures SysTick to fire an interrupt every millisecond, and uses this interrupt to provide accurate timing for millis(), delay(), etc.

You should, instead, enter Sleep in a loop until the event you're waiting for has actually occurred:

const int WAKE_PIN = 10;
volatile bool woke_up;

void interrupt() {
  woke_up = true;
}

void setup() {
  pinMode(WAKE_PIN, INPUT);
  attachInterrupt(WAKE_PIN, interrupt, RISING);
  // ...
}

void loop() {
  // repeatedly enter Sleep Mode until the WAKE_PIN rises
  woke_up = false;
  while(!woke_up)
    pmc_enable_sleepmode(0);
  // (long-running, synchronous stuff in response to rising edge on WAKE_PIN)
}

If you really need to save the power, and you don't need accurate timing across sleeps, you can disable SysTick during the sleep:

const int WAKE_PIN = 10;
volatile bool woke_up;

void interrupt() {
  woke_up = true;
}

void setup() {
  pinMode(WAKE_PIN, INPUT);
  attachInterrupt(WAKE_PIN, interrupt, RISING);
  // ...
}

void loop() {
  // disable SysTick for now
  SysTick->CTRL = 0;
  // repeatedly enter Sleep Mode until the WAKE_PIN rises
  woke_up = false;
  while(!woke_up)
    pmc_enable_sleepmode(0);
  // turn SysTick back on
  SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk
                  | SysTick_CTRL_TICKINT_Msk
                  | SysTick_CTRL_ENABLE_Msk;
  // (long-running, synchronous stuff in response to rising edge on WAKE_PIN)
}

I tested this approach with code that (carefully) uses Serial and delay(), and it works.

(Note that you should still use a loop, since spurious wake-ups will occasionally happen!)

If you don't make any use of delay(), millis(), etc. at all, you can even disable SysTick entirely within your setup() function:

void setup() {
  // Save some power by disabling SysTick entirely.
  // (Don't be surprised if you call delay() and it hangs forever.)
  SysTick->CTRL = 0;
  // The Arduino library automatically starts the ADC clock. If you don't use
  // the ADC, you can save a little more power by turning its clock off.
  pmc_disable_periph_clk(ID_ADC);
  // ...
}

But ... isn't sysTick part of the ARM core, run off the processor clock rather than a peripheral clock?

Low power Mode is always an interesting topic to dig in.

Note that in Backup Mode, you can save parameters before turning your board into this mode in the 8 32-bit General Backup Registers and retrieve them afterwards, once the boards wakes up.

westfw:
But ... isn't sysTick part of the ARM core, run off the processor clock rather than a peripheral clock?

Yeah. The bit about the ADC clock in the last code is unrelated, except that it saves a tiny bit more power. (It's the only peripheral clock the Arduino setup code turns on without any action on our part.)

I found additional discussion on the matter - apparently the exact details of where SysTick is clocked from are "implementation dependent." Too bad that it doesn't seem very well documented in the implementation manual (ie SAM3X datasheet.) Sigh.

https://community.arm.com/processors/f/discussions/4946/why-or-how-does-systick-interrupt-wakeup-the-processor