High Performance Monotonic Clock for robotics
This crate provides a monotonic clock and a mockable clock for testing and replay. It has a wide range of support for robots running on OSes and bare metal.
It is no-std compatible with some limitations, see below.
Low level CPU counter implementations are provided for:
- x86-64
- arm64
- armv7
- risc-v
We also provide a TOV (Time Of Validity) enum for tagging sensor data.
It can fall back to the posix monotonic clock for other platforms (with std).
It has been created originally for the Copper runtime but can be perfectly used independently. See the main crate cu29 for more information about the overall Copper project.
Examples
clock.sync_status() is available with or without the clock-sync feature and
returns Option<sync::SyncStatus>. It returns None without the feature or before
discipline is published. Reusable tasks can inspect quality without enabling
synchronization; enable the feature to attach a live controller. Feature-disabled
status reads require no counter read or synchronization storage.
Reference Source and Epoch
clock.origin() returns a ClockOrigin containing the advertised upstream
ClockSource and timestamp ClockEpoch. These are informational: GNSS provenance
can accompany GPS or TAI timestamps. Local counter calibration does not imply an
RTC epoch or an atomic source. A manually supplied initial timestamp has an unknown
epoch unless reference observations establish one. Use
from_ref_time_with_origin(ns, origin) or the from_ref_time_with_rtc_and_origin
constructor to initialize a named epoch without live synchronization.
Reference observations provide parent_ns and origin together. RobotClock keeps
nanoseconds in the provider's epoch, including bare GPS time or an application-defined
ClockEpoch::Other(id). It performs no UTC or calendar conversion. TAI metadata may
include tai_minus_utc_seconds when the provider knows it; display code can use it.
Clones see metadata published with the execution curve. Initial acquisition adopts
the reference epoch; holdover and expiration retain it. Resynchronization preserves
the running execution epoch and rejects observations from a different epoch. Create
a new clock to select a different epoch. TAI UTC-offset updates do not change epochs.
Snapshots record metadata for replay without reference I/O. Origin reads do not read
the counter, allocate, or change now() and recent() behavior. Without clock-sync,
the accessor returns the clock's local metadata. Source/epoch metadata is diagnostic,
not a promise of traceability or a scheduling API.
Basic Clock Usage
use ;
// Create a new robot clock
let clock = new;
// Get the current time (returns CuTime, which is nanoseconds since clock start)
let now = clock.now;
println!; // Displays with appropriate units (ns, µs, ms, s, etc.)
// Get a less precise but faster time reading
let recent = clock.recent;
// Clock is cloneable and all clones share the same time reference
let clock_clone = clock.clone;
assert_eq!; // more or less :)
// Create a clock with a specific reference time
let ref_time_ns = 1_000_000_000; // 1 second
let clock_with_ref = from_ref_time;
Raw Counter Time and Busy Waits
Each RobotClock owns its counter calibration. raw_now() reads undisciplined
local time as CuInstant, while now() reads the robot's execution timeline
as CuTime. These types cannot be compared, subtracted, or used interchangeably
as deadlines or message timestamps. Both use nanoseconds; elapsed intervals use
CuDuration.
Clones share the same raw origin; compare raw readings within one clock and its
clones. Synchronization adjusts the execution timeline and leaves raw time unchanged.
use ;
let clock = new;
let start = clock.raw_now;
clock.busy_wait_for;
let elapsed = clock.raw_now - start;
let deadline = clock.raw_now + from_micros;
clock.busy_wait_until;
CuInstant readings come from clock.raw_now(). Reconstruct a captured raw
reading with CuInstant::from_nanos(nanos) using that clock's origin. A recorded
ClockSnapshot::at(instant) maps a raw instant to execution CuTime.
Replace instance-free CuInstant::now()/Instant::now() and free
busy_wait_for(duration) and busy_wait_until(deadline) calls with the
corresponding methods on that clock. Raw reads and waits are available in
both std and no_std builds. Busy waits require an advancing clock; a mock must
be advanced through its control handle.
Working with Time Durations
use ;
use Duration;
// Create durations
let duration1 = from;
let duration2 = from; // 500ms in nanoseconds
// Arithmetic operations
let sum = duration1 + duration2; // 600ms
let diff = duration2 - duration1; // 400ms
let scaled = duration1 * 2u32; // 200ms
let divided = duration2 / 5u32; // 100ms
// Display formatting automatically chooses appropriate units
println!; // "600.000 ms"
println!; // "1.000 h"
// Min/max operations
let min_duration = duration1.min;
let max_duration = duration1.max;
// Convert to standard Duration
let std_duration: Duration = duration1.into;
Mock Clock for Testing and Simulation
use ;
use Duration;
// Create a mock clock that you can control
let = mock;
// The clock starts at 0
assert_eq!;
// Advance the clock by specific amounts
mock_control.increment;
assert_eq!; // 100ms in nanoseconds
// Jump to absolute time values
mock_control.set_value; // 5 seconds
assert_eq!;
// You can also decrement (breaks monotonicity, use with caution)
mock_control.decrement;
assert_eq!; // 4 seconds
// Get current mock time directly from the mock controller
let current_mock_time = mock_control.now;
let current_raw_value = mock_control.value;
// All clones of the clock are synchronized with the mock
let clock_clone = clock.clone;
assert_eq!;
Time of Validity (Tov)
Tov represents when sensor data was actually valid, which is crucial for robotics applications:
use ;
use Duration;
let clock = new;
let current_time = clock.now;
// Single timestamp - most common case
let tov_single = Time;
// Time range - for sensors that collect data over time (e.g., lidar scans)
let tov_range = Range;
// No valid time - for error conditions or initialization
let tov_none = None;
// Converting from common types
let tov_from_option: Tov = Some.into;
let tov_from_duration: Tov = current_time.into;
// Pattern matching on Tov in algorithms
// Display Tov values with automatic formatting
println!; // "1.500 s"
println!; // "[1.500 s – 1.510 s]"
println!; // "None"
// Create time ranges from collections of timestamps
let timestamps = ;
let range_from_slice = from;
// range_from_slice.start = 100, range_from_slice.end = 150
Optional Time Values
use ;
// Efficient optional time representation (avoids 128-bit Option overhead)
let some_time = from;
let no_time = none;
// Check if time is present
if ! some_time.is_none
// Convert to/from standard Option
let std_option: = some_time.into;
let back_to_option_time: OptionCuTime = std_option.into;
// Display formatting
println!; // "1.000 µs"
println!; // "None"
Platform-Specific High-Performance Timing
The clock automatically uses the best available timing mechanism for your platform:
- x86-64: RDTSC instruction for sub-nanosecond precision
- ARM64: ARM generic timer counters
- ARM32: 64-bit ARM performance counters
- RISC-V: Cycle counter
- Other platforms: Falls back to system monotonic clock (std only)
use ;
// The clock automatically calibrates frequency at first use
let clock = new;
// For no-std environments, frequency calibration is simplified
// but still provides high precision timing suitable for robotics