Skip to content

stm32: mp_hal_ticks_us returns non-monotonic values. #17947

@iabdalkader

Description

@iabdalkader

Port, board and/or hardware

stm32, STM32N6.

MicroPython version

MicroPython 1.26.0

Reproduction

diff --git a/ports/stm32/main.c b/ports/stm32/main.c
index af4d7f8bb..7583148ee 100644
--- a/ports/stm32/main.c
+++ b/ports/stm32/main.c
@@ -549,6 +549,17 @@ void stm32_main(uint32_t reset_mode) {
 
     MICROPY_BOARD_BEFORE_SOFT_RESET_LOOP(&state);
 
+    while (true) {
+        mp_uint_t last_tick = mp_hal_ticks_us();
+        SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
+
+        if (mp_hal_ticks_us() < last_tick) {
+            MICROPY_BOARD_FATAL_ERROR("");
+        }
+
+        SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
+    }
+
 soft_reset:
 
     MICROPY_BOARD_TOP_SOFT_RESET_LOOP(&state);

Expected behaviour

Time is a river that never bends back.

Observed behaviour

mp_hal_ticks_us values can go backward by up to ~1 ms.

Additional Information

mp_hal_ticks_us values can go backward by up to ~1 ms, especially on faster MCUs (like the N6) and when IRQs occur around the calls. You’re probably aware of this possibility, judging by the comments in that function, so what I’m looking for is advice on whether this is something we should try to fix or just accept. Right now I'm using this workaround:

static inline mp_uint_t mp_hal_ticks_us_monotonic(void) {
    static mp_uint_t last_timestamp = 0;
    mp_uint_t current = mp_hal_ticks_us();
    
    // Ensure monotonic behavior
    if (current > last_timestamp) {
        last_timestamp = current;
    }
    
    return last_timestamp;
}

However, I'm using mp_hal_ticks_us as part of a code profiler, so accuracy is very important. The following function seems to fix the issue, and it might also be better because it doesn't disable IRQs. However, I might be missing something, or this loop may not be wanted, that's why I didn't send this as a fix:

mp_uint_t mp_hal_ticks_us(void) {
    uint32_t ms1, ms2, counter, load, ctrl;

    // Repeat until we get a consistent millisecond + VAL snapshot
    do {
        ms1 = HAL_GetTick();
        counter = SysTick->VAL;
        ctrl = SysTick->CTRL;  // Read CTRL to check COUNTFLAG
        ms2 = HAL_GetTick();
    } while (ms1 != ms2);

    load = SysTick->LOAD;

    // Check if SysTick rolled over but HAL_GetTick() hasn't been updated yet
    // This happens when IRQs are disabled or the interrupt is pending
    if (ctrl & SysTick_CTRL_COUNTFLAG_Msk) {
        // SysTick has rolled over since last interrupt
        // The counter value is from the new cycle, so increment milliseconds
        ms1++;
    }

    // Convert from decrementing to incrementing
    counter = load - counter;

    return ms1 * 1000 + (counter * 1000) / (load + 1);
}

Code of Conduct

Yes, I agree

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions