Low-Power Modes & Wakeup Sources¶
A parked car's battery is not switched off. Dozens of ECUs stay connected to the 12 V rail for weeks, and the vehicle must still start after a month at an airport car park. That gives every module a quiescent current budget — often a few tens of microamps per ECU, negotiated at vehicle level and audited on a bench with a precision ammeter. Firmware owns most of that number. This module covers the S32K1's power modes, how to enter and leave them, every wakeup source that matters, and FlexCAN's Pretended Networking — the feature that lets a sleeping node listen for one specific frame without waking the CPU.
The mode map¶
The S32K1 exposes its modes through the SMC (System Mode Controller), with the PMC (Power Management Controller) handling regulators and voltage detection. Unlike the Kinetis parts it descends from, the S32K1 has no LLS or VLLS modes — the deepest state is VLPS, and it retains RAM and register state.
| Mode | Core | Peripherals | Typical use |
|---|---|---|---|
| HSRUN | Full speed (up to 112 MHz on S32K14x) | All | Short compute bursts; some peripherals restricted |
| RUN | Up to 80 MHz | All | Normal operation |
| VLPR | ≤ 4 MHz, SIRC-sourced | Most, reduced | Awake but idle — low-rate polling |
| WAIT | Clock gated (WFI) | All running | Cheapest possible sleep; instant wake |
| VLPW | Clock gated | Reduced | WAIT entered from VLPR |
| STOP1 / STOP2 | Stopped | Partial (PSTOPO selects) | Keep some buses alive while the core sleeps |
| VLPS | Stopped | Minimal; SIRC/LPO available | Deep sleep — the "parked car" mode |
Two transitions are worth memorizing. You cannot enter VLPR from a fast
clock — the core must already be at or below the VLPR limit, so you
switch the clock configuration first. And the mode you are allowed to
enter is gated by SMC->PMPROT, which is write-once after reset: if
firmware does not permit VLPS early in boot, no later code can enter it.
Entering a mode¶
The SDK's power manager takes a table of configurations and an index:
#include "power_manager.h"
static const power_manager_user_config_t runCfg = {
.powerMode = POWER_MANAGER_RUN,
.sleepOnExitValue = false,
};
static const power_manager_user_config_t sleepCfg = {
.powerMode = POWER_MANAGER_VLPS,
.sleepOnExitValue = false, /* true = go straight back to sleep
after the waking ISR returns */
};
static const power_manager_user_config_t *const pwrCfgs[] = {
&runCfg, &sleepCfg,
};
#define PWR_MODE_RUN 0u
#define PWR_MODE_SLEEP 1u
void power_init(void)
{
POWER_SYS_Init(&pwrCfgs[0], 2u, NULL, 0u);
}
void app_enter_sleep(void)
{
outputs_force_safe(); /* module 9: safe state before sleep */
can_transceiver_standby(true); /* SBC / transceiver into standby */
(void)POWER_SYS_SetMode(PWR_MODE_SLEEP, POWER_MANAGER_POLICY_AGREEMENT);
/* execution resumes HERE after a wakeup interrupt has been taken */
can_transceiver_standby(false);
app_log_wakeup_source();
}
POWER_MANAGER_POLICY_AGREEMENT lets registered driver callbacks veto the
transition — a driver mid-transfer says "not now." POWER_MANAGER_POLICY_FORCIBLE
ignores them. Use agreement unless you have a specific reason not to;
forcibly stopping a peripheral mid-transaction is how you corrupt an
EEPROM write.
Sleep is a state transition, not a function call
The line after POWER_SYS_SetMode executes after the wakeup ISR has
already run. Anything the ISR needs must be valid before you sleep,
and anything you re-initialize afterwards (clocks, PLL relock,
peripherals that lost configuration) must be done before the first
real work. Write sleep and wake as one reviewed pair.
Wakeup sources¶
Any enabled NVIC interrupt can wake the core from STOP and VLPS, but the peripheral generating it must still have a clock in that mode. The practical list:
| Source | Works from | Notes |
|---|---|---|
| PORT pin interrupt | STOP, VLPS | Asynchronous edge detect; the digital filter needs PORTx->DFCR set to the LPO clock to survive stop |
| LPTMR | STOP, VLPS | The standard periodic wake — runs from the 1 kHz LPO tap or SIRC |
| RTC alarm / seconds | STOP, VLPS | Long timebases, 32 kHz source |
| LPUART RX edge | STOP, VLPS | Wake on start bit; the first byte is usually lost |
| CMP (analog comparator) | STOP, VLPS | Threshold crossing without the ADC |
| FlexCAN self-wake | STOP, VLPS | MCR[SLFWAK], CTRL1[WAKMSK] — wakes on any bus activity |
| FlexCAN Pretended Networking | STOP, VLPS | Wakes only on a matching frame — see below |
A minimal periodic wake with the LPTMR:
#include "lptmr_driver.h"
static const lptmr_config_t lptmrCfg = {
.workMode = LPTMR_WORKMODE_TIMER,
.dmaRequest = false,
.interruptEnable = true,
.freeRun = false,
.clockSelect = LPTMR_CLOCKSOURCE_1KHZ_LPO, /* alive in VLPS */
.prescaler = LPTMR_PRESCALE_2,
.bypassPrescaler = true,
.counterUnits = LPTMR_COUNTER_UNITS_MICROSECONDS,
.compareValue = 500000u, /* wake every 500 ms */
};
void wake_timer_init(void)
{
LPTMR_DRV_Init(0u, &lptmrCfg, false);
INT_SYS_EnableIRQ(LPTMR0_IRQn);
LPTMR_DRV_StartCounter(0u);
}
Pretended Networking: sleeping through the noise¶
Plain FlexCAN self-wake has an expensive problem: a vehicle bus is rarely silent, so any activity wakes the node, and a node that wakes ten times a second is not asleep at all. Pretended Networking (available on the S32K1's FlexCAN0) puts the CAN protocol engine in a low-power state where it keeps receiving and filters frames in hardware, waking the core only on a match — by ID, by DLC, by payload content, or after N matches.
/* Wake only on the network-management frame 0x0A0.
Field names vary slightly across SDK versions — check yours. */
static const flexcan_pn_config_t pnCfg = {
.wakeUpTimeout = false,
.wakeUpMatch = true,
.numMatches = 1u,
.filterComb = FLEXCAN_FILTER_ID,
.idFilterType = FLEXCAN_FILTER_MATCH_EXACT,
.idType = FLEXCAN_MSG_ID_STD,
.idLower = 0x0A0u,
};
void can_sleep_with_pn(void)
{
FLEXCAN_DRV_ConfigPN(INST_CAN0, true, &pnCfg);
/* ...enter VLPS. On wake, the frames received during PN are in the
wake-up message buffers: */
flexcan_msgbuff_t wmb;
FLEXCAN_DRV_GetWMB(INST_CAN0, 0u, &wmb);
}
The registers behind this — MCR[PNET_EN], CTRL1_PN, FLT_ID1,
FLT_DLC, PL1_LO/PL1_HI, and WU_MTC for the match count and wake
status — are worth reading in the reference manual, because the SDK
exposes only a subset of the filter combinations.
Automotive concerns¶
- Budget the current, then verify it. "We enter VLPS" is not a measurement. Put a precision ammeter in series with the ECU supply and read the actual sleep current; the difference between 30 µA and 3 mA is usually one pin left driving a pull-up, a transceiver never put into standby, or a debug LED nobody removed.
- Pin state in sleep is your responsibility. Outputs keep driving through STOP/VLPS. Inputs left floating oscillate and burn current. Go through the pin map (module 4's table) and define a sleep state for every pin — this is a standard review artifact.
- The transceiver usually dominates. A CAN transceiver in normal mode draws far more than the sleeping MCU. Putting it into standby — via its STB pin or over SPI to the SBC (module 3) — is typically the single biggest saving available.
- Log why you woke. Store the wake source and a counter in retained RAM or NVM. A node with 400 wakeups per night has a bug that a current measurement alone will never localize.
- Debounce the wake decision. A single edge on a wake pin can be noise. Waking, sampling, confirming, and going back to sleep is normal; waking and immediately taking action is how a car's lights turn on in a thunderstorm.
- Safe state before sleep, always. Outputs to their defined safe values before the mode change, not after the wake — module 9's rule applied to the one code path where forgetting it lasts for hours.
- Watch what the watchdog does. The WDOG can be configured to keep running in stop modes; if it does, your sleep interval must be shorter than its timeout, or your "low-power mode" is a reset loop.
Cheat sheet¶
| Item | Notes |
|---|---|
| Controllers | SMC (mode control), PMC (regulators, LVD) |
| Mode set | HSRUN · RUN · VLPR · WAIT · VLPW · STOP1/STOP2 · VLPS |
| No LLS/VLLS | S32K1 removed them — VLPS is the deepest mode, state is retained |
SMC->PMPROT |
Write-once at boot; permits the modes you may later enter |
| VLPR limit | Core ≤ 4 MHz from SIRC — switch clocks before the mode change |
| SDK entry | POWER_SYS_Init then POWER_SYS_SetMode(idx, POWER_MANAGER_POLICY_AGREEMENT) |
| Resume point | Code after SetMode runs after the wakeup ISR |
| Periodic wake | LPTMR from the 1 kHz LPO tap (LPTMR_CLOCKSOURCE_1KHZ_LPO) — alive in VLPS |
| Pin wake | PORT interrupts are asynchronous; filter needs PORTx->DFCR on LPO |
| CAN self-wake | MCR[SLFWAK] + CTRL1[WAKMSK] — wakes on any bus activity |
| Pretended Networking | MCR[PNET_EN], CTRL1_PN, FLT_ID1, WU_MTC — wake on a matching frame |
| Biggest saving | Transceiver standby, then pin states, then the MCU mode |
How It Actually Works¶
Each S32K power mode corresponds to a specific set of clock domains and voltage regulator states physically switched by the SMC (System Mode Controller) and PMC (Power Management Controller) — this isn't a software "sleep flag," it's real gating hardware. In RUN, the main voltage regulator (which can supply higher current) powers the core at full clock speed. Entering VLPR (Very Low Power Run) doesn't just lower the clock — the PMC actually switches to a lower-current regulator mode, and because that regulator physically cannot source enough current for a fast core, hardware enforces a maximum core-clock ceiling (typically 4 MHz) as a real electrical safety limit, not a suggestion.
STOP modes go further: after the WFI (Wait For Interrupt) instruction — a real Cortex-M4F pipeline instruction that halts instruction fetch and puts the core in a low-power state at the hardware level — the SMC sequences clock gating to entire domains, and in VLPS (Very Low Power Stop) it can also request the PMC to drop the core's internal supply rail. Waking up isn't instantaneous specifically because of this: the LLWU (Low Leakage Wake-up Unit) is a small always-on block of latches and comparators watching designated wake pins/peripherals even while the rest of the chip is powered down, and on a wake event it triggers the PMC/SMC to re-sequence power rails and re-lock the PLL before releasing the core to fetch its next instruction — this rail-and-clock resequencing, done in a fixed hardware order to avoid glitches, is the real source of STOP-mode wake latency.
Retention SRAM banks in the deepest low-power states are kept alive by a dedicated low-current retention supply separate from the main SRAM array's read/write supply — this is why some low-power modes preserve RAM contents and others don't: it's determined by which physical supply rail feeds which SRAM bank, not by a configuration bit alone.
(Described from the S32K reference manual's SMC/PMC/LLWU chapters; not measured on physical silicon in this course.)
Exercise¶
Turn your capstone node into a sleeping ECU with a defensible current
budget. (1) Write the pin sleep table: every pin, its sleep state, and
one sentence of justification — this document is the deliverable, the code
is downstream of it. (2) Implement app_enter_sleep() / wake handling with
VLPS, an LPTMR wake every 500 ms for a heartbeat check, and a PORT pin
wake on your board's button. (3) Measure sleep current with a meter and
record three numbers: as first written, after transceiver standby, and
after fixing your pin states. If the first and last differ by less than
10×, look again — something is still driving. (4) Replace plain CAN
self-wake with Pretended Networking filtered on a single ID, and prove the
difference by flooding the bus with unrelated traffic: the self-wake
version should wake continuously, the PN version should stay asleep. (5)
Add a retained wake-reason log exported in your status frame after every
wake. No board? Do steps (1) and (5), and write the mode-transition state
machine including what must be re-initialized after VLPS — that list is
the thing people forget.