SLEEP at 1.40 mW beats IDLE at 16.89 mW by 15.49 mW every second it lasts, but each round trip pays the 14.073 µs wake plus the 2.917 µs entry at elevated power. Naps shorter than the pay-back lose energy instead of saving it, and a driver that sleeps on every idle poll can burn more than one that never sleeps. This lesson finds the shortest nap worth taking.
Break-even is the nap length where the sleep saving equals the transition cost. Saving accrues at the IDLE-minus-SLEEP rate; the cost is the entry-plus-wake energy, plus an illustrative switch-and-restore overhead of 50 nJ per round trip for the inrush and state handling the NanoSoC tables do not price. The script below works the NanoSoC numbers: the saving rate is 15.49 mW, the transition cost is about 1.26 µJ, and break-even lands at about 82 µs (about 80 µs if the 17 µs of the transition is credited at SLEEP power). The wake is charged at the 82.74 mW RUN power, a conservative upper bound, because the CPU is in reset for most of the 14 µs. Any predicted idle longer than a few hundred microseconds should sleep; anything shorter should stay in IDLE. The margin is comfortable because the wake is short and the saving rate is large.
# Sleep break-even: IDLE power vs SLEEP plus round-trip cost.
# Powers NanoSoC teaching values; 50 nJ overhead illustrative.
P_IDLE_MW, P_SLEEP_MW = 16.89, 1.40
T_ENTRY_US, T_WAKE_US = 2.917, 14.073
OVERHEAD_NJ = 50.0 # illustrative switch + restore energy per round trip
save_mw = P_IDLE_MW - P_SLEEP_MW
assert abs(save_mw - 15.49) < 0.005
# Transition energy: entry burns near IDLE, wake near RUN (1 mW * 1 us = 1 nJ).
E_entry_nj = P_IDLE_MW * T_ENTRY_US
E_wake_nj = 82.74 * T_WAKE_US
E_total_nj = E_entry_nj + E_wake_nj + OVERHEAD_NJ
assert abs(E_entry_nj - 49.27) < 0.05, E_entry_nj
assert abs(E_wake_nj - 1164.4) < 1.0, E_wake_nj
breakeven_us = E_total_nj / save_mw # nJ / mW = us
assert abs(E_total_nj - 1263.7) < 1.0, E_total_nj
assert 81.0 < breakeven_us < 82.0, breakeven_us
# Credit the 17 us of the transition at SLEEP power (IDLE would have burned
# 16.89 mW over it, the sleeping chip burns 1.40 mW): the more careful figure.
t_trans = T_ENTRY_US + T_WAKE_US
careful_us = (E_total_nj - P_SLEEP_MW * t_trans) / save_mw
assert 79.5 < careful_us < 80.5, careful_us
# A nap of 70 us loses energy, one of 100 us wins.
def saved_nj(nap_us):
return save_mw * nap_us - E_total_nj
assert saved_nj(70.0) < 0.0 < saved_nj(100.0)
print("PASS: saving 15.49 mW; round trip ~1.26 uJ; break-even %.1f us (%.1f us credited)" % (breakeven_us, careful_us))The break-even nap is computed from the project power numbers with the overhead stated and sourced, not assumed zero.
The driver policy references the number: sleep only on predicted idles above several break-even intervals.
The worst-case wake latency, 14.073 µs plus interrupt synchronisation, is checked against the product's real-time deadlines before any saving is claimed.
Measure a sleep round trip on the bench with a GPIO wake from the bench fixture: energy per trip should match the script within the overhead margin. If short naps lose energy, the overhead is larger than modelled, usually the PLL relock at full RUN power or firmware that polls before sleeping; lengthen the policy threshold rather than doubting the arithmetic. If wake latency misses a deadline, the answer is IDLE for that path, not a faster SLEEP.
No: 50 µs is below the 80 µs break-even, so each round trip loses energy. Stay in IDLE until the storm passes, then sleep. The policy needs a storm detector or a minimum-sleep timer, not just an idle detector.