Context. Full timing visibility for a cyclic motion deployment
spans two layers: the executor (when did the NC task fire, how long
did its logic take) and the connector (how long was the wire round,
did every device participate, were inputs fresh). A single layer
cannot measure all of it. Critically, taktora-cyclic-fieldbus is
#![no_std] with no clock — it can time a wire round only if
the bus driver hands it a duration, and it cannot timestamp absolute
cadence at all. exchange() also owns cycle timing, so an outside
observer cannot separate “waiting for the cycle phase” from “the wire
round” from “the NC task’s own work”.
Decision. Split measurement by what each layer can actually
observe:
Neither layer double-counts; the two snapshots compose into the full
picture.
Clock-source contract. In v1 both durations are derived from a
single host monotonic clock owned by the bus driver, so they share
a clock domain and are directly additive. A future EtherCAT connector
may source the wire-round duration from Distributed Clock (DC)
timestamps for sub-microsecond accuracy; because the seam carries each
duration as an opaque u32 nanosecond value, that change needs no
API change — only an added “different clock domain” caveat on the
decomposition note below.
Composition (non-normative). The cycle decomposes as
execute_duration ≈ phase_wait + wire_round + NC work, where
execute_duration is the executor’s NC-task figure (Wire-round duration statistics (REQ_0262)
note) and phase_wait/wire_round are the connector’s. Because
exchange() owns the phase wait, the slack lives inside the
executor’s execute_duration (it is idle-inside-exchange(), not
idle-between-fires). Consequently phase_wait.min → 0 is the leading
indicator that execute_duration is about to trip deadline lateness
(Per-task deadline lateness (REQ_0106)). The two layers are joined by a shared
cycle_index (Per-task scan index and fau... (REQ_0107), Connector statistics query API (REQ_0265)). This is a
diagnostic relationship for consumers, not a runtime-checked invariant:
the two figures are measured independently and a strict inequality
check would false-positive on rounding and (future) clock skew.
Alternatives considered.
Connector self-instruments everything, including cadence/jitter.
Symmetric and uniform, but requires a monotonic clock inside the
no_std connector seam — smuggling std (or a clock trait)
into a layer that deliberately has none. Rejected.
Executor brackets ``exchange()`` from outside and owns all timing.
One stats engine, but the executor cannot separate cycle-phase wait
from wire round from NC work, so bus-cycle and wire-round quantities
are simply not observable. Rejected: the most diagnostic numbers
would be unmeasurable.
Consequences.
✅ Each quantity is measured where it is actually observable; no
clock is forced into no_std.
✅ The connector telemetry reuses Shared no_std taktora-stats... (ADR_0062) and stays
allocation-free.
❌ A consumer wanting the “full” timing picture must read two
snapshots (executor + connector) and compose them by cycle_index.
The composition rule is documented above; acceptable given the
layering.
❌ The shared cycle_index imposes a dual obligation on the
executor: it must increment its scan count and emit on_cycle_stats
on a faulted scan too (Per-task scan index and fau... (REQ_0107)), or the counters desync from
the first fault. The boundary is paid for on both sides.
|