Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for extending the cycle counter to u64 #6

Merged
merged 11 commits into from
Feb 15, 2022
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();
}
}