Skip to content

Instantly share code, notes, and snippets.

@jefftenney
Last active January 19, 2026 18:00
Show Gist options
  • Select an option

  • Save jefftenney/02b313fe649a14b4c75237f925872d72 to your computer and use it in GitHub Desktop.

Select an option

Save jefftenney/02b313fe649a14b4c75237f925872d72 to your computer and use it in GitHub Desktop.
No-Drift FreeRTOS tick/tickless for STM32 via LPTIM
// The original github gist for lptimTick.c is no longer maintained. It grew into a
// full-fledged repository, https://github.com/jefftenney/LPTIM-Tick/, which integrates
// lptimTick.c into a project for the STM32L476.
//
// Another repo, https://github.com/jefftenney/LPTIM-Tick-U5/, integrates a newer
// version of lptimTick.c into a project for STM32U585. The newer version of
// lptimTick.c supports the newest revision of the LPTIM timer silicon.
//
// Thus there are two actively maintained versions of lptimTick.c, and you must choose
// the correct one for your LPTIM silicon:
//
// Type 1 or 2: https://github.com/jefftenney/LPTIM-Tick/blob/main/Core/Src/lptimTick.c
// Type 3: https://github.com/jefftenney/LPTIM-Tick-U5/blob/main/Core/Src/lptimTick.c
//
// To determine which LPTIM variant is in your STM32, see AN4865 from ST.
//
// This gist is not actively maintained, but it still contains all original revisions
// of lptimTick.c and github comments for historical value.
@jefftenney
Copy link
Author

Hi @kdubovenko, sounds good. For reference, please see the post above with guidance on the pre- and post-sleep functions.

@peterleif
Copy link

peterleif commented Jan 16, 2021

Hi @jefftenney, thank you for providing a great piece of code. It is really helping me.
I am testing this and I found a case in which scheduling fails with this vPortSuppressTicksAndSleep function.

I use an EXTI interrupt (occurs 0.5-1 times per second) for waking up the MCU, and inside the ISR I call xTimerResetFromISR to start a software timer (about 100ms, 100 ticks).
The software timer is expected to expire 100ms after the interrupt, but when I use this vPortSuppressTicksAndSleep function the timer expires just after the interrupt.
If I call xTimerReset outside the ISR (using a queue from the ISR to switch to a task that starts the software timer) it works fine.
This happens regardless of the configLPTIM_ENABLE_PRECISION option and does not happen when I use the official GCC ARM_CM4F port.
I am trying to investigate this problem but am struggling.

@jefftenney
Copy link
Author

jefftenney commented Jan 17, 2021

Hi @peterleif, I believe there is a conflict between tickless idle and FromISR() Timer functions. When your ISR interrupts a tickless idle period, the ISR executes before the tickless logic adjusts the tick count to account for time spent in tickless idle. And unfortunately, the FromISR() Timer functions use the tick count captured in the ISR instead of the tick count at the time the Timer task finally acts on the command. Before the Timer task acts on the command, the tickless logic adjusts the tick count by hundreds of milliseconds in your case. When the Timer task acts on the command here, it thinks the timer expired before it was added to the active timer list.

This behavior is not specific to lptimTick.c. If you use the default tickless implementation, and do not use STOP mode, you will encounter the same error, subject to timing considerations. However, using STOP mode with the default tickless implementation actually masks the problem because the default implementation can't track the passage of time in STOP mode.

The FreeRTOS team would be justified in changing how the Timer task handles commands from ISRs to resolve this conflict, but that's unlikely to happen. I would suggest keeping your alternative implementation where you signal a task to reset the timer.

https://forums.freertos.org/t/xtimerstartfromisr-and-xtimerresetfromisr-with-tickless-idle/11560

@peterleif
Copy link

Hi @jefftenney, thank you for the information!
I'll use the alternative implementation.

@kdubovenko
Copy link

kdubovenko commented Jan 20, 2021

Hi @kdubovenko, sounds good. For reference, please see the post above with guidance on the pre- and post-sleep functions.

Thank you, @jefftenney changing my original pre- and post-sleep routines to the ones above solved the issue I was experiencing.

@jefftenney
Copy link
Author

Hi @kdubovenko, glad to hear it works now.

For everyone-

Code in lptimTick.c requires that the post-sleep function take less than one OS tick (typically 1ms). If your application uses HSE with a crystal, and if your post-sleep function waits for the RCC module to get all the clocks going again, it can take too long. For reference, the post-sleep function example above does not wait. For further reference, HAL functions setting up clocks do wait.

If you are not using HSE, don't worry about this issue. Even if you use the HAL functions and wait for the clocks to be fully restored, your post-sleep function will take less than 50us.

If you are using HSE, then don't wait for the clocks. Use the example post-sleep function above instead of HAL code.

So what to do if your application uses HSE and you can't allow your application code to execute until HSE is working? Treat HSE as a peripheral that requires access controls. Modify your application code to register when it really needs HSE and when it doesn't. Allow STOP modes only when you don't need HSE (see deepSleepPermittedFlags above). Implement the registrar to wait for HSE to be ready after registering a user and before returning to the user.

@Corn-101
Copy link

Hi,

First of all, thank you for this lib. I would like to implement this to my first rtos project. I did modify FreeRTOSConfig.h with the needed modifications. Included in the main.c the ulp.h. That RTOSConfig modification did throw immediately the mcu to deep sleep. I checked your main.c and only found vUlpInit(); which refers to this library. I assume that your example is directly driving the MCU to stop mode? I'm trying to understand how should I utilize this in case where one condition inside task commence mcu to sleep and button interrupt wake up. Hope somebody could spare just a bit of their precisious time to point me to right direction? Thank you.

@jefftenney
Copy link
Author

jefftenney commented Jan 31, 2021

Hi @Corn-101, if you use lptimTick.c, then you are using the "tickless idle" feature of FreeRTOS. That means your task code must allow FreeRTOS to manage the MCU state. No task code should commence MCU sleep. Instead, FreeRTOS knows whether your tasks are ready to run or if they are delayed (etc), so FreeRTOS decides when to sleep the MCU and for how long. It's magic.

However, FreeRTOS needs your help in deciding (and configuring) the depth of the sleep. You as the application developer provide that help in the pre- and post-sleep functions. In the github repo I'm building as an example integration for lptimTick.c, the pre- and post-sleep functions are provided in ulp.c.

For perspective, you can use tickless idle without lptimTick.c. Just set setting configUSE_TICKLESS_IDLE to 1. But then the kernel can't track the passage of time whenever you use stop mode.

@Corn-101
Copy link

Corn-101 commented Feb 1, 2021

Hi @jefftenney, thank you, this cleared a lot, however I'm still strugling... Would you mind open whether your example in github repo is complete and working? What confuses me is that in freertos.c there are functions which obviously need to be filled, like vPortSuppressTicksAndSleep(). But then again I'm finding this same function in lptimTick.c. I'm assuming these comments and functions added in freertos.c are not yours?

For some reason my test implementation brings the mcu directly to stop mode and it is not respecting the two tasks which should be woken every 250ms and 4us. I'm not sure what am I missing. So eager to make this work.

@jefftenney
Copy link
Author

jefftenney commented Feb 1, 2021

Hi @Corn-101, yes the github repo example works. As of today Feb 1 2021, it simply blips the LED every 5 seconds and uses STOP 2 whenever idle.

CubeMX generates freertos.c, and I left it untouched. As you noticed, the real vPortSuppressTicksAndSleep() resides in lptimTick.c. Integrating lptimTick.c into a CubeMX project will look very different than integrating it into a Keil or IAR project.

I assume you mean every 4 milliseconds and not 4 microseconds.

If I had to guess, I'd say LSE isn't running. Please see here for an important note. If that's not it, please feel free to send me an email.

EDIT: Looking at your code on the FreeRTOS support forum, I noticed you are using TIM16 and TIM17 for your 250ms and 4ms timers. Bad news is TIM16 and TIM17 don't operate in stop mode. Good news is you can use FreeRTOS timers (with auto-reload enabled) instead.

@maebli
Copy link

maebli commented May 24, 2021

@jefftenney thank you for this excellent tickless implementation. It seems to be running nicely. The kernel needs to wake up every ~2 seconds to prevent a LPTIM1 overflow, is there any way to raise this to something around 10 to 30 seconds. The regular wake ups are doubling my stop2 consumption from around 1.5uA to 3uA

@jefftenney
Copy link
Author

Hi @maebli, normally I would suggest using the LPTIM prescaler. In fact, a previous revision of lptimTick.c supported the prescaler. But LPTIM has a silicon bug (as described in lptimTick.c) that essentially erases any benefit you would expect from waking up less often. The CPU gets stuck in run mode for one full count of LPTIM, typically once per rollover (and sometimes more often). Depending on which STM32 you're using, most of the "extra" quiescent current you're seeing could be due to this LPTIM error.

I'm trying to get my hands on the STM32U5 to see if the silicon bug continues.

@maebli
Copy link

maebli commented May 25, 2021

@jefftenney thank you for your input. I'm using the STM32WL

I have a further question. I'm using the HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); for my PreSleepProcessing routine as opposed to your implementation in ulp.c. When I disable debugging this works fine. However when I enable Debugging

	HAL_EnableDBGSleepMode();
	HAL_EnableDBGStandbyMode();
	HAL_EnableDBGStopMode();

I never return from sleep, the app stays in lptimTick.c in the do while loop ending in:

} while (idealCmp == expectedEndCmp && eTaskConfirmSleepModeStatus() != eAbortSleep);

and never leaves. The same goes fro STOP1. If however I enter Sleep in the PreSleepProcessing, the application works with debugging. Why could this be?

I'm not using HSE, I'm using HSI with PLL to 48Mhz Sysclock.

Also I am using the RTC to setup an Alarm after the xModifiableIdleTime inside the PreSleepProcessing is this necessary?

@jefftenney
Copy link
Author

Also I am using the RTC to setup an Alarm after the xModifiableIdleTime inside the PreSleepProcessing is this necessary?

No, you don't need to do that. The LPTIM will generate an interrupt after xModifiableIdleTime to end the sleep/stop.

With regard to getting stuck in the do-while loop, the debugger might be delaying the LPTIM ISR without stopping LPTIM. That would cause the LPTIM ISR to "rule out" the interrupt as a valid tick. When you're stuck in the do-while loop, add a break point to the LPTIM ISR. When the breakpoint is hit, double check DBGMCU->APB1FZR1 to make sure DBGMCU_APB1FZR1_DBG_LPTIM1_STOP is still set. Also check the current (stopped) value of LPTIM1->CNT and see how it compares to LPTIM1->CMP. I suspect it takes too long from the point of the interrupt to the ISR actually executing.

If you haven't already checked the errata for your MCU, that is also worth doing.

@maebli
Copy link

maebli commented May 26, 2021

Thank you for the feedback.

No, you don't need to do that. The LPTIM will generate an interrupt after xModifiableIdleTime to end the sleep/stop

OK, somehow I still have not gotten it to work with STOP2. My attempt is on github
https://github.com/maebli/LPTIM-Tick-STM32WLE5xx

I would highly appreciate if you could give me hints as to what it could be. I've documented my attempts in the README.md

I have looked at the errata but did not find anything that could be the cause so far.

@jefftenney
Copy link
Author

Looks like the STOP-mode configuration isn't quite right. PWR_CR1_LPMS_0 represents the value of bit zero in the LPMS field, and PWR_CR1_LPMS_1 represents the value of bit 1 in the LPMS field.

If I were you, I would revert back to using PWR_CR1_LPMS_STOP1 and PWR_CR1_LPMS_STOP2, but you'll have to define them yourself, perhaps in ulp.c, like this:

#define PWR_CR1_LPMS_STOP0 (0)
#define PWR_CR1_LPMS_STOP1 (PWR_CR1_LPMS_0)
#define PWR_CR1_LPMS_STOP2 (PWR_CR1_LPMS_1)
#define PWR_CR1_LPMS_STANDBY (PWR_CR1_LPMS_0 | PWR_CR1_LPMS_1)
#define PWR_CR1_LPMS_SHUTDOWN (PWR_CR1_LPMS_2)

@maebli
Copy link

maebli commented Jul 14, 2021

@jefftenney I tested your comment that

LPTIM has a silicon bug (as described in lptimTick.c) that essentially erases any benefit you would expect from waking up less often..

using the STM32WLE5xx, I did not see the bug. as it is a new chip it may be that it is not affected. I was able to reduce the consumption to 1.65uA from 2.3uA by using a pre-scaler of 8.

@jefftenney
Copy link
Author

Thanks! That sounds like very good news. You can double-check your findings with this code placed at the beginning of the ISR:

   static volatile uint32_t stuckInIsrCounter = 0;
   if ( ( LPTIM->ISR & (LPTIM_ISR_CMPOK | LPTIM_ISR_CMPM) ) == 0 )
   {
      stuckInIsrCounter++;
   }

Let the system run for a few minutes and then check the value of stuckInIsrCounter. Just prior to LPTIM rollover, there is a high probability of the anomaly occurring, so that's why you might want to let it run for several 16-second periods.

@jefftenney
Copy link
Author

STM32U5 Support

Please see LPTIM-Tick-U5 for STM32U5 support. The version of lptimTick.c in that repo is adapted to the upgraded LPTIM found in the U5 family.

The STM32U5 seems not to have the silicon bug that occasionally causes the CPU to be stuck in the LPTIM interrupt handler for a full count of the LPTIM. As a result, the STM32U5 version of lptimTick.c supports the prescaler again.

@KiraVerSace
Copy link

Thank you for your program, but it seems not well if I use LPTIM instead of the SYSTICK.
Here is some of program. I copy the lptimTick with out any change.
FreeRTOSConfig_Defautl.h

#if (configUSE_TICKLESS_IDLE == 2)    // Integrate lptimTick.c -- Start of Block
    /**
     * @brief Preprocessor code in lptimTick.c requires that configTICK_RATE_HZ be
     *        a preprocessor-friendly numeric literal.
     *        As a result, the application *ignores* the CubeMX configuration of the FreeRTOS tick rate.
     */
    #undef  configTICK_RATE_HZ
    #define configTICK_RATE_HZ (1000UL) // 之所以不能用之前定义的主要因为#if语法中不支持((TickType_t)1000)

    #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 6

    extern void freeRTOSPreStopProcessing (uint32_t ulExpectedIdleTime);
    extern void freeRTOSPostStopProcessing(uint32_t ulExpectedIdleTime);
    #define configPRE_SLEEP_PROCESSING(x)       freeRTOSPreStopProcessing(x)
    #define configPOST_SLEEP_PROCESSING(x)      freeRTOSPostStopProcessing(x)
#endif // configUSE_TICKLESS_IDLE == 2

Pre and Post code:

#if (configUSE_TICKLESS_IDLE == 2)
static uint32_t rccCFGRSave;
static uint32_t rccCRSave;
void freeRTOSPreStopProcessing(uint32_t ulExpectedIdleTime)
{
    UNUSED(ulExpectedIdleTime);

    digitalWrite(LED, LOW);

    HAL_SuspendTick();
    HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);

    rccCRSave   =  RCC->CR;
    rccCFGRSave =  RCC->CFGR;
    SCB->SCR    |= SCB_SCR_SLEEPDEEP_Msk;
}

void freeRTOSPostStopProcessing(uint32_t ulExpectedIdleTime)
{
    UNUSED(ulExpectedIdleTime);

    digitalWrite(LED, HIGH);

    if (SCB->SCR & SCB_SCR_SLEEPDEEP_Msk)
    {
        RCC->CR   =  rccCRSave;
        RCC->CFGR =  rccCFGRSave;
        SCB->SCR  &= ~SCB_SCR_SLEEPDEEP_Msk;
    }

    HAL_ResumeTick();
}
#endif

For the low power, I will suspend some threads after power-on about 2 minutes.

void displaySleepTimerCB(const void *parameter)
{
    UNUSED(parameter);

	xDisplay.sleep();

    if (xGlobalWorkMode == WM_LOWPOWER)
    {
        xDisplay.enterLowPowerMode();

        osThreadSuspend(displayThreadID);

        attachInterrupt(digitalPinToInterrupt(sButton.getPinNumber()), buttonClickTriggerIRQ, FALLING);
        attachInterrupt(digitalPinToInterrupt(cButton.getPinNumber()), buttonClickTriggerIRQ, FALLING);
        osThreadSuspend(periphRunThreadID);
    }
}

Then I use EXTI interrupt, if it catched the button pressed, it will resume key detect thread.

void buttonClickTriggerIRQ(void)
{
    osThreadResume(periphRunThreadID);

    xDisplay.run();
    osTimerStart(displaySleepTimerID, DISPLAY_HOLD_TIME);

    detachInterrupt(digitalPinToInterrupt(sButton.getPinNumber()));
    detachInterrupt(digitalPinToInterrupt(cButton.getPinNumber()));

    logDebug("Button-Click");
}

Then the periphThread will check the button and response to the user Shell during the next 3 minutes depend on the
osTimerStart(displaySleepTimerID, DISPLAY_HOLD_TIME); -> DISPLAY_HOLD_TIME
Here is the periphThread

void periphRunThread(void const *argument)
{
	logDebug("periphRunTask...[%d]", uxTaskGetStackHighWaterMark(NULL));

	for (;;)
	{
		xShellRunTask();	    // xShell Loop

		sButton.tick();
		cButton.tick();

		osDelay(10);
	}
}

Then if button pressed, it will resume the display thread, and after the timer is over time, it will suspend again/
But usually, it can not work well after I press the button, sometimes it will be normal, I can certain that if I use the SYSTICK it will be OK.

Could you give me some tips, thank you!

@BaselHn
Copy link

BaselHn commented Jun 30, 2025

Hi Jeff,

I am trying to start a software timer within an ISR handler, the workflow is as described below:

  1. Suppress the tick
  2. Enter sleep mode, waiting for an interrupt, and then use WFI.
    Then I wake up the MCU by an external interrupt, for example, a push button
    The MCU wakes up and enables the interrupts, then the ISR starts to run and tries to start the software timer from the ISR.
    What happens is that the software timer fires immediately, which I suspect that the timer fires because the tick is not yet restored and the OS tick is not stable.
    How can I solve this Issue?

Note: I am using STM32H562 MCU with tickless idle, and LPtimer following your code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment