Skip to content

Commit

Permalink
Merge #6
Browse files Browse the repository at this point in the history
6: add support for extending the cycle counter to u64 r=korken89 a=jordens

This enables tracking time intervals of more than a couple seconds on
modern processors.
Tested on hardware (stm32h743, 400 MHz, https://github.com/quartiq/stabilizer) with spawn across overflows.

Co-authored-by: Robert Jördens <[email protected]>
  • Loading branch information
bors[bot] and jordens authored Feb 15, 2022
2 parents 14b27b7 + 86fbb49 commit 726a357
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added

- CI changelog entry enforcer
- New feature `extend` to track DWT cycle counter overflows and extend
the range to `u64`.

## [v1.0.0] - 2021-12-25

Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "1.0.0"
authors = [
"The Real-Time Interrupt-driven Concurrency developers",
"Emil Fresk <[email protected]>",
"Robert Jördens <[email protected]>",
]
categories = ["concurrency", "embedded", "no-std"]
description = "RTIC Monotonic implemenation based on Systick and DWT"
Expand All @@ -15,9 +16,13 @@ edition = "2021"
[lib]
name = "dwt_systick_monotonic"

[features]
extend = []

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
cortex-m = "0.7"
cortex-m = "0.7.4"
rtic-monotonic = "1.0.0"
fugit = "0.3.0"
cfg-if = "1.0"
140 changes: 107 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,132 @@
//! # `Monotonic` implementation based on DWT and SysTick
//! # `Monotonic` implementation based on DWT cycle counter and SysTick

#![no_std]

use cortex_m::peripheral::{syst::SystClkSource, DCB, DWT, SYST};
pub use fugit::{self, ExtU32};
pub use fugit;
#[cfg(not(feature = "extend"))]
pub use fugit::ExtU32;
#[cfg(feature = "extend")]
pub use fugit::ExtU64;
use rtic_monotonic::Monotonic;

/// DWT and Systick combination implementing `embedded_time::Clock` and `rtic_monotonic::Monotonic`
/// DWT and Systick combination implementing `rtic_monotonic::Monotonic`.
///
/// This implementation is tickless. It does not use periodic interrupts to count
/// "ticks" (like `systick-monotonic`) but only to obtain actual desired compare
/// events and to manage overflows.
///
/// The frequency of the DWT and SysTick is encoded using the parameter `TIMER_HZ`.
/// They must be equal.
///
/// Note that the SysTick interrupt must not be disabled longer than half the
/// cycle counter overflow period (typically a couple seconds).
///
/// When the `extend` feature is enabled, the cycle counter width is extended to
/// `u64` by detecting and counting overflows.
pub struct DwtSystick<const TIMER_HZ: u32> {
dwt: DWT,
systick: SYST,
#[cfg(feature = "extend")]
last: u64,
}

impl<const TIMER_HZ: u32> DwtSystick<TIMER_HZ> {
/// Enable the DWT and provide a new `Monotonic` based on DWT and SysTick.
///
/// Note that the `sysclk` parameter should come from e.g. the HAL's clock generation function
/// so the real speed and the declared speed can be compared.
/// so the speed calculated at runtime and the declared speed (generic parameter
/// `TIMER_HZ`) can be compared.
#[inline(always)]
pub fn new(dcb: &mut DCB, dwt: DWT, systick: SYST, sysclk: u32) -> Self {
pub fn new(dcb: &mut DCB, mut dwt: DWT, mut systick: SYST, sysclk: u32) -> Self {
assert!(TIMER_HZ == sysclk);

dcb.enable_trace();
DWT::unlock();
assert!(DWT::has_cycle_counter());

// Clear the cycle counter here so scheduling (`set_compare()`) before `reset()`
// works correctly.
dwt.set_cycle_count(0);

unsafe { dwt.cyccnt.write(0) };
systick.set_clock_source(SystClkSource::Core);

// We do not start the counter here, it is started in `reset`.
// We do not start the counters here but in `reset()`.

DwtSystick { dwt, systick }
DwtSystick {
dwt,
systick,
#[cfg(feature = "extend")]
last: 0,
}
}
}

impl<const TIMER_HZ: u32> Monotonic for DwtSystick<TIMER_HZ> {
const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = true;

type Instant = fugit::TimerInstantU32<TIMER_HZ>;
type Duration = fugit::TimerDurationU32<TIMER_HZ>;

#[inline(always)]
fn now(&mut self) -> Self::Instant {
Self::Instant::from_ticks(self.dwt.cyccnt.read())
cfg_if::cfg_if! {
if #[cfg(not(feature = "extend"))] {
const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = true;

type Instant = fugit::TimerInstantU32<TIMER_HZ>;
type Duration = fugit::TimerDurationU32<TIMER_HZ>;

#[inline(always)]
fn now(&mut self) -> Self::Instant {
Self::Instant::from_ticks(DWT::cycle_count())
}
} else {
// Need to detect and track overflows.
const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false;

type Instant = fugit::TimerInstantU64<TIMER_HZ>;
type Duration = fugit::TimerDurationU64<TIMER_HZ>;

#[inline(always)]
fn now(&mut self) -> Self::Instant {
let mut high = (self.last >> 32) as u32;
let low = self.last as u32;
let now = DWT::cycle_count();

// Detect CYCCNT overflow
if now < low {
high = high.wrapping_add(1);
}
self.last = ((high as u64) << 32) | (now as u64);

Self::Instant::from_ticks(self.last)
}
}
}

unsafe fn reset(&mut self) {
self.dwt.enable_cycle_counter();

self.systick.set_clock_source(SystClkSource::Core);
self.systick.enable_counter();

self.dwt.cyccnt.write(0);
// Enable and reset the cycle counter to locate the epoch.
self.dwt.enable_cycle_counter();
self.dwt.set_cycle_count(0);
}

fn set_compare(&mut self, val: Self::Instant) {
// The input `val` is in the timer, but the SysTick is a down-counter.
// We need to convert into its domain.
let now = self.now();

let max = 0x00ff_ffff;

let dur = match val.checked_duration_since(now) {
None => 1, // In the past

// The input `val` refers to the cycle counter value (up-counter)
// but the SysTick is a down-counter with interrupt on zero.
let reload = val
.checked_duration_since(self.now())
// Minimum reload value if `val` is in the past
.map_or(0, |duration| duration.ticks())
// CYCCNT and SysTick have the same clock, no
// ticks conversion necessary, only clamping:
//
// ARM Architecture Reference Manual says:
// "Setting SYST_RVR to zero has the effect of
// disabling the SysTick counter independently
// of the counter enable bit.", so the min is 1
Some(x) => max.min(x.ticks()).max(1),
};
.max(1)
// SysTick is a 24 bit counter.
.min(0xff_ffff) as u32;

self.systick.set_reload(dur);
self.systick.set_reload(reload);
// Also clear the current counter. That doesn't cause a SysTick
// interrupt and loads the reload value on the next cycle.
self.systick.clear_current();
}

Expand All @@ -82,6 +137,25 @@ impl<const TIMER_HZ: u32> Monotonic for DwtSystick<TIMER_HZ> {

#[inline(always)]
fn clear_compare_flag(&mut self) {
// NOOP with SysTick interrupt
// SysTick exceptions don't need flag clearing.
//
// But when extending the cycle counter range, we need to keep
// the interrupts enabled to detect overflow.
// This function is always called in the interrupt handler early.
// Reset a maximum reload value in case `set_compare()` is not called.
// Otherwise the interrupt would keep firing at the previous set
// interval.
#[cfg(feature = "extend")]
{
self.systick.set_reload(0xff_ffff);
self.systick.clear_current();
}
}

#[cfg(feature = "extend")]
fn on_interrupt(&mut self) {
// Ensure `now()` is called regularly to track overflows.
// Since SysTick is narrower than CYCCNT, this is sufficient.
self.now();
}
}

0 comments on commit 726a357

Please sign in to comment.