Hi Sandy, Thanks for the great explanation and suggestions! Much clearer than what I could puzzle together from reference manual. But I want to show you my code and a trace of what I do because something doesn't make sense. Also, for now I want to try to use the simple code you suggested above that goes right in to VLPS from RUN mode and exits back to that. When I was spending weeks last fall on trying to get VLPS to work I was adjusting the clock settings and trying to go through VLPR mode and had problems with occasional hard faults. I am not seeing that at all now which is great.
So my code that goes into low power mode is:
static void
enter_low_power_mode(void)
{
/* Declare variable that can be used to force a read of a register
* without getting a compiler warning that nothing is done with the value.
*/
volatile uint8_t dummy_read __attribute__ ((unused));
bool systick_enabled = false; // True if SYSTICK counter is enabled
/* Always expect to be in PEE power mode when we start this function */
assert(mcg_mode_check() == MCG_MODE_PEE);
/* We want to finish all serial debug output and shut it down before
* stopping the processor since the UART is disabled in low power modes.
*/
SL_DEBUG_PRINT("Z");
if (sl_debug_enabled) {
Serial1.end();
}
ELOG(7, 1, "power enter_low_power", ELOG_ALWAYS);
/* Make sure clock monitor is off so we don't get spurious reset */
MCG_C6 &= ~MCG_C6_CME0;
/* Turn off the SYSTICK timer while we are stopped so that elapsed time
* interrupts don't happen. We had problems that these interrupts caused
* aborts from stop mode.
*/
if (SYST_CSR & SYST_CSR_ENABLE) {
SYST_CSR &= ~SYST_CSR_ENABLE;
systick_enabled = true;
}
/* We don't want to go to sleep if, since our last check, we have gotten
* an interrupt indicating that we shouldn't sleep. We do this check in a
* critical section coupled with going to sleep so we don't miss any
* interrupts.
*/
__disable_irq();
if (power_users == 0) {
/* Set the power mode control register (PMCTRL) STOPM field to select
* the VLPS mode.
*/
SMC_PMCTRL = SMC_PMCTRL_STOPM(0x2);
/* Wait for write to complete to SMC before stopping core */
dummy_read = SMC_PMCTRL;
/* Set the SLEEPDEEP bit to enable deep sleep mode (STOP). We don't
* want other bits in register (eg SLEEPONEXIT) set.
*/
SCB_SCR = SCB_SCR_SLEEPDEEP;
/* WFI instruction will start entry into LLS mode */
ELOG(7, 2, "power WFI", ELOG_ALWAYS);
asm("WFI");
ELOG(7, 3, "power WFI done", ELOG_ALWAYS);
} else {
ELOG(7, 4, "power WFI skipped", ELOG_ALWAYS);
}
__enable_irq();
/* Start back up the serial port if needed. Note, output may not actually
* work right if we have to restore the clock settings until we get that
* done.
*/
if (sl_debug_enabled) {
Serial1.begin(SL_DEBUG_BAUD);
ELOG(7, 7, "power serial started back up", ELOG_ALWAYS);
}
/* When we are using VLPS low power mode, we always exit back to PEE mode.
*/
assert(mcg_mode_check() == MCG_MODE_PEE);
/* Turn back on external clock monitor */
MCG_C6 |= MCG_C6_CME0;
/* Reset the SysTick counter and enable it to start a new countdown cycle.
* We stopped it before going into LLS mode.
*/
if (systick_enabled) {
// TODO We lose all sense of elapsed time doing this. Should we do something
// different? As long as we use the RTC timer for all elapsed time measurements
// that can span power down intervals then things are ok.
SYST_CVR = 0;
SYST_CSR |= SYST_CSR_ENABLE;
}
/* We should have gone into low power mode and woken up on an interrupt.
* However, there could have been some issue entering the low power mode
* in which case an "abort" will be signaled by the STOPA bit in the PMCTRL
* register. For now we just display a different letter on the debug output
* to indicate the problem. We expect to see an 'N' printed right after the
* 'Z' printed at the beginning of this function.
*/
SL_DEBUG_PRINTLN((SMC_PMCTRL & SMC_PMCTRL_STOPA) ? "A" : "N");
}
When the code runs the debug output is always "ZA" when using VLPS mode. When I change the code to use LLS mode it worked right and showed "ZN" based on the SL_DEBUG_PRINT of Z at the beginning of the routine and A or N at the end based on the PMCTRL_STOPA value.
Now, here is the odd thing. See the ELOG() calls in the code? Those are call to an event tracing system I use in embedded systems like this to see how things are working. Each call saves 4 bytes of data in a circular buffer. These are the first two parms from each call (1 byte each) and a 16 bit timestamp from the RTC_TPR register. After running I can retrieve the event data and print it to show what happened. Here is a piece of the data showing two low power cycles (I have the LPTMR running off LPO set to interrupt every 1/2 second):
Time Delta Event (type, value, description)
0:069.183 69.183 103 1 serial1 uart0_status_isr enter
0:069.183 0 2 128 <<data value>>
0:069.214 31 103 1 serial1 uart0_status_isr enter
0:069.214 0 2 128 <<data value>>
0:069.214 0 103 1 serial1 uart0_status_isr enter
0:069.214 0 2 128 <<data value>>
0:069.550 336 103 1 serial1 uart0_status_isr enter
0:069.550 0 2 128 <<data value>>
0:069.702 153 103 1 serial1 uart0_status_isr enter
0:069.702 0 2 128 <<data value>>
0:070.068 366 103 1 serial1 uart0_status_isr enter
0:070.068 0 2 192 <<data value>>
0:070.068 0 7 1 power enter_low_power
0:070.068 0 7 2 power WFI
0:574.310 504.242 7 3 power WFI done
0:574.341 31 0 1 sl_time lptmr_isr
0:574.341 0 7 7 power serial started back up
0:574.341 0 103 1 serial1 uart0_status_isr enter
0:574.341 0 2 128 <<data value>>
0:574.341 0 103 1 serial1 uart0_status_isr enter
0:574.341 0 2 128 <<data value>>
0:574.341 0 103 1 serial1 uart0_status_isr enter
0:574.341 0 2 128 <<data value>>
0:574.677 336 103 1 serial1 uart0_status_isr enter
0:574.677 0 2 128 <<data value>>
0:575.043 366 103 1 serial1 uart0_status_isr enter
0:575.043 0 2 192 <<data value>>
0:577.362 2.319 103 1 serial1 uart0_status_isr enter
0:577.362 0 2 192 <<data value>>
0:577.362 0 103 1 serial1 uart0_status_isr enter
0:577.362 0 2 128 <<data value>>
0:577.545 183 103 1 serial1 uart0_status_isr enter
0:577.576 31 2 192 <<data value>>
0:577.576 0 7 1 power enter_low_power
0:577.576 0 7 2 power WFI
1:079.498 501.923 7 3 power WFI done
1:079.498 0 0 1 sl_time lptmr_isr
1:079.498 0 7 7 power serial started back up
1:079.498 0 103 1 serial1 uart0_status_isr enter
1:079.498 0 2 128 <<data value>>
There is a "Delta" time field that shows how long after the previous event the current event happened. The time values are shown in seconds:msec.usec. So the odd thing is that after flushing all the serial data and disabling the UART I do the WFI instruction and it does take 1/2 second till I return. And I return and go into the LPTMR interrupt just as I would expect. But when I get the STOPA value and print it the bit is set. That is what doesn't make sense.
Your suggestion to make sure the SYSTICK timer wasn't ticking was great. Too bad I wasn't talking to you last fall when it took me a few days to realize I had to turn off SYSTICK before going into low power. Before I did that I would return from the WFI instruction with the abort set pretty much right away.
So Sandy, any ideas/suggestions? I know pretty much everything I have the processor doing. The one thing I haven't really looked into much at all is the USB interface. We use it and it works but all the driver SW for it was done by the person who developed and sells the Teensy board we are using. I don't do anything with that driver code relative to low power but it doesn't look like it is causing any interference. I did put ELOG calls in the USB interrupt handler code so would see an event log if we got a USB interrupt.
As I've said before, your advice on this has been great! Fred