Module rtic_time::half_period_counter

source ·
Expand description

Utility to implement a race condition free half-period based monotonic.

§Background

Monotonics are continuous and never wrap (in a reasonable amount of time), while the underlying hardware usually wraps frequently and has interrupts to indicate that a wrap happened.

The biggest problem when implementing a monotonic from such hardware is that there exists a non-trivial race condition while reading data from the timer. Let’s assume we increment a period counter every time an overflow interrupt happens. Which should we then read first when computing the current time? The period counter or the timer value?

  • When reading the timer value first, an overflow interrupt could happen before we read the period counter, causing the calculated time to be much too high
  • When reading the period counter first, the timer value could overflow before we read it, causing the calculated time to be much too low

The reason this is non-trivil to solve is because even critical sections do not help much - the inherent problem here is that the timer value continues to change, and there is no way to read it together with the period counter in an atomic way.

§Solution

This module provides utilities to solve this problem in a reliable, race-condition free way. A second interrupt must be added at the half-period mark, which effectively converts the period counter to a half-period counter. This creates one bit of overlap between the timer value and the period counter, which makes it mathematically possible to solve the race condition.

The following steps have to be fulfilled to make this reliable:

  • The period counter gets incremented twice per period; once when the timer overflow happens and once at the half-period mark. For example, a 16-bit timer would require the period counter to be incremented at the values 0x0000 and 0x8000.
  • The timer value and the period counter must be in sync. After the overflow interrupt was processed, the period counter must be even, and after the half-way interrupt was processed, the period counter must be odd.
  • Both the overflow interrupt and the half-way interrupt must be processed within half a timer period. This means those interrupts should be the highest priority in the system - disabling them for more than half a period will cause the monotonic to misbehave.

If those conditions are fulfilled, the calculate_now function will reliably return the correct time value.

§Why does this work?

It’s complicated. In essence, this one bit of overlap gets used to make it irrelevant whether the period counter was already incremented or not. For example, during the second part of the timer period, it is irrelevant if the period counter is 2 (before the interrupt) or 3 (after the interrupt) - calculate_now will yield the same result. Then half a period later, in the first part of the next timer period, it is irrelevant if the period counter is 3 or 4 - they again will yield the same result.

This means that as long as we read the period counter before the timer value, we will always get the correct result, given that the interrupts are not delayed by more than half a period.

§Example

This example takes a 16-bit timer and uses a 32-bit period counter to extend the timer to 47-bit. Note that one bit gets lost because this method requires the period counter to be increased twice per period.

The resulting time value is returned as a u64.

use core::sync::atomic::{AtomicU32, Ordering};

static HALF_PERIOD_COUNTER: AtomicU32 = AtomicU32::new(0);

struct MyMonotonic;

impl MyMonotonic {
    fn init() {
        timer_stop();
        timer_reset();
        HALF_PERIOD_COUNTER.store(0, Ordering::SeqCst);
        timer_enable_overflow_interrupt();
        timer_enable_compare_interrupt(0x8000);
        // Both the period counter and the timer are reset
        // to zero and the interrupts are enabled.
        // This means the period counter and the timer value
        // are in sync, so we can now enable the timer.
        timer_start();
    }

    fn on_interrupt() {
        if overflow_interrupt_happened() {
            clear_overflow_interrupt();
            let prev = HALF_PERIOD_COUNTER.fetch_add(1, Ordering::Relaxed);
            assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!");
        }
        if compare_interrupt_happened() {
            clear_compare_interrupt();
            let prev = HALF_PERIOD_COUNTER.fetch_add(1, Ordering::Relaxed);
            assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!");
        }
    }

    fn now() -> u64 {
        rtic_time::half_period_counter::calculate_now(
            || HALF_PERIOD_COUNTER.load(Ordering::Relaxed),
            || timer_get_value(),
        )
    }
}

Traits§

Functions§

  • Calculates the current time from the half period counter and the timer value.