Skip to main content
GPomp.1
Associate III
September 14, 2026
Question

STM32WB5MMG — FreeRTOS tickless idle: HW Timer Server caps Stop2 sleep at ~989 ms. Request for confirmation and review of a workaround

  • September 14, 2026
  • 0 replies
  • 16 views

Dear Community,

we are writing to report a behaviour observed on the STM32WB5MMG module, and to submit a modification we applied, asking for your assessment of its correctness.

Some context on why this matters to us: the product is a battery-powered device, and a primary design goal is to minimise power consumption, hence to maximise the time spent in Stop2. Every exit from Stop2 carries a fixed cost — clock restoration, external oscillator start-up, peripheral re-enabling — which we pay in full even when the wake-up has nothing to do. This is why waking once per second, on a device whose nearest application deadline is tens of seconds away, is a concrete problem for us rather than a cosmetic one.

Configuration

  • FreeRTOS V10.3.1 in tickless idle (configUSE_TICKLESS_IDLE = 2), tick rate 1000 Hz
  • Low power mode Stop2, CFG_LPM_SUPPORTED = 1
  • Wake-up from Stop2 through the RTC Wakeup Timer, driven by your HW Timer Server (hw_timerserver.c), as in the BLE_HeartRateFreeRTOS example
  • LSE at 32774 Hz; RTC prescalers PREDIV_A = 13, PREDIV_S = 2340 (1 Hz calendar); WUCKSEL RTCCLK/16, giving a WUT tick of 2048.375 Hz
  •  In our firmware the timer server has a single client: the FreeRTOS tickless idle. No application timer and no BLE stack component registers a timer with HW_TS

Observed behaviour

Any sleep request longer than ~989 ms is truncated by the timer server, which wakes up, re-arms the WUT and repeats. As a result the device leaves Stop2 roughly once per second, even when the nearest application deadline is tens of seconds away.

The origin is in HW_TS_Init():

MaxWakeupTimerSetup = ((SynchPrescalerUserConfig - 1) * AsynchPrescalerUserConfig
- CFG_HW_TS_RTC_HANDLER_MAX_DELAY) >> WakeupTimerDivider;

which with our prescalers evaluates to 2027 ticks ≈ 989 ms, i.e. just under one wrap of the SSR register.

We understand that this cap coincides with the limit of the measurement method: ReturnTimeElapsed() derives the elapsed time from RTC->SSR alone and handles a single wrap. Beyond one second the measurement becomes ambiguous — 1.3 s and 2.3 s yield the same result — so capping below one wrap is a consistent design choice rather than an arithmetic error.

For reference, the same symptom has already been reported on your community forum, with no official resolution:

We have also verified that ReturnTimeElapsed() is unchanged on the STM32CubeWB master branch (latest tag v1.24.0, November 2025) with respect to our copy, so we are not aware of any upstream fix being available.

We considered the technique recommended by one of your colleagues in the thread how get the remaining time of RTC wakeup timer (https://community.st.com/t5/stm32-mcus-embedded-software/how-get-the-remaining-time-of-rtc-wakeup-timer/td-p/784513), but it requires PREDIV_S = 0, that is, giving up the 1 Hz calendar. This is not viable for us: the calendar is the application's time base.

The modification we applied

Since the limitation lies in the measurement method rather than in the hardware, we replaced the elapsed-time measurement inside the FreeRTOS port, leaving the timer server untouched:

  1. In LpTimerStart() / LpGetElapsedTime() (freertos_port.c), the slept time is now computed as the difference between two absolute instants read from the RTC calendar (RTC->TR + RTC->SSR), instead of using HW_TS_RTC_ReadLeftTicksToCount(). The measurement stays unambiguous up to 24 hours.
  2. MaxWakeupTimerSetup was raised to 62,464 ticks (~30.5 s), clamped at 0xFFFE so as not to collide with TIMER_LIST_EMPTY.
  3. ReturnTimeElapsed() and the timer list logic were not modified.

The calendar read uses a double-read protocol on SSR, since RTC_CR_BYPSHAD is enabled (set by HW_TS_Init()):

do {
s1 = READ_BIT(RTC->SSR, RTC_SSR_SS);
tr = READ_BIT(RTC->TR, (RTC_TR_HT|RTC_TR_HU|RTC_TR_MNT|RTC_TR_MNU|RTC_TR_ST|RTC_TR_SU));
s2 = READ_BIT(RTC->SSR, RTC_SSR_SS);
} while(s2 > s1); /* wrap occurred during the read: TR is unreliable, read again */

Bench results: maximum sleep 29,994 ms (the value expected by construction), wake-ups reduced by roughly 15x with the device at rest, and no functional anomaly over about four hours of testing, including builds with SBSFU and flash protections enabled.

Our questions

  1. Can you confirm that the ~989 ms cap is a known limitation of the timer server when a 1 Hz RTC calendar is used, rather than a defect in our configuration?
  2. Is there a recommended approach to obtain tickless sleeps longer than one second on STM32WB while keeping both HW_TS and the 1 Hz calendar?
  3. Does our modification introduce risks we have not considered? Specifically, the timer server's internal bookkeeping remains inconsistent beyond one second. We believe this is inert in our case because the callback registered by the port is empty, FreeRTOS wakes on the interrupt rather than on the callback, and HW_TS_Stop() is called at every wake-up. Can you confirm there are no other internal couplings within HW_TS that we may be missing?
  4. Is raising MaxWakeupTimerSetup to 62,464 safe? We note that ReturnTimeElapsed() returns a uint16_t and saturates at 65,535 ticks (≈31.99 s), exactly the WUT maximum, and that TIMER_LIST_EMPTY is 0xFFFF. Are there other implicit constraints on that value?
  5. An independent question, about the FreeRTOS port. In vPortSuppressTicksAndSleep() the SysTick is stopped mid-count and restarted from zero on wake-up (portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL), discarding the fraction of a tick already elapsed. The generic FreeRTOS Cortex-M port instead preserves it in the reload value. We measured that this difference costs on average ~0.4 ms per sleep entry, which under load amounts to a tick drift of several minutes per day. Is this a deliberate simplification of the low-power port, or an oversight?

We remain available to provide logs, the full configuration, or a minimal reproducible project.

Best regards,

Giampietro Pomponio