1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Bus-wide timing configuration ([`BusTiming`]) and the wire-occupancy
//! budget ([`bus_period`]).
//!
//! Pure config and arithmetic: nothing here touches the transport. The
//! constants are the values the crate has always used; [`BusTiming`] gathers
//! the tunable ones so a whole bus can be reconfigured in one call.
use Duration;
/// How long to listen for answers to a broadcast ID query.
const BROADCAST_WAIT: Duration = from_millis;
/// Gap between the five repetitions of a mode-switch frame.
const MODE_REPEAT_GAP: Duration = from_millis;
/// Gap between the five repetitions of a set-ID frame.
const SET_ID_REPEAT_GAP: Duration = from_millis;
/// Settling time after the set-ID sequence before re-querying.
const SET_ID_SETTLE: Duration = from_millis;
/// Gap between the frames of a [`M0601::safe_stop`](crate::M0601::safe_stop)
/// sequence (50 Hz).
const SAFE_STOP_GAP: Duration = from_millis;
/// Acceleration byte for the velocity-0 rounds of a stop sequence.
///
/// **Measured to make no difference.** `stop_ramp_capture`
/// (`m0601/tests/hardware.rs`) sweeps this byte across its whole range during
/// the velocity-0 rounds: an unloaded wheel stopping from 120 RPM sits at
/// 63 RPM after 100 ms at accel `0` and 64 RPM at accel `255` — a 1 RPM
/// spread, when the same two values differ by more than 250x on spin-*up*.
/// The byte shapes acceleration only. `5` is kept because it is what the
/// crate has always sent and costs nothing, not because it is gentler.
///
/// What the velocity-0 rounds *do* accomplish is real and worth keeping: the
/// same capture leaves a coasting wheel at 119 RPM after 100 ms, against
/// 63 RPM under velocity-0 frames. The ramp phase sheds nearly half the speed
/// before the brake rounds begin. It simply is not tunable.
///
/// Note which phase carries the current. `braking_current_capture` logs it
/// signed: the velocity-0 rounds show a single −0.63 A transient at the
/// setpoint change and then **~0.03 A mean** while the wheel sheds 60 RPM,
/// whereas the brake rounds show a −1.99 A transient followed by ~0.6–0.85 A
/// of sustained work. Unloaded, against a 3 A trip.
///
/// The consequence that holds firmly: a velocity-0 stop is effectively
/// **invisible** to any monitor watching reported current, so a low reading
/// during a stop does not mean nothing is happening. If a stop ever does trip
/// overcurrent, look at the brake — measured up to −2.28 A — and note that
/// this byte is not the lever on it either.
///
/// What is *not* established is where the energy goes. Telemetry on this link
/// cannot sample faster than ~8 ms (the limit is the USB-serial quantum, not
/// the crate's pacing), and the brake transient is demonstrably aliased at
/// that rate — it reads 1.99 A or 2.28 A depending on which samples land on
/// it. So "the current telemetry reports almost nothing" is a statement about
/// the telemetry, not proof that little crosses the bus.
///
/// One unloaded motor, one firmware. Under load the numbers will differ; that
/// the byte is inert on deceleration is the part unlikely to change.
/// [`Bus::with_stop_accel`](crate::Bus::with_stop_accel) still sets it.
const SAFE_STOP_ACCEL: u8 = 5;
/// Default minimum idle gap enforced between frames on a bus — see
/// [`Bus::with_min_gap`](crate::Bus::with_min_gap).
///
/// Sized to cover one reply frame (~0.9 ms at 115200 baud) plus an
/// allowance for the motor's turnaround, so the reply a fire-and-forget
/// drive frame elicits cannot still be on the wire when the next frame
/// starts. The turnaround component is an estimate, not a measurement —
/// when tighter scheduling matters, measure it and pass the real number to
/// [`Bus::with_min_gap`](crate::Bus::with_min_gap).
pub const DEFAULT_MIN_GAP: Duration = from_micros;
/// Default acceleration byte for
/// [`M0601::drive_velocity`](crate::M0601::drive_velocity) — the motor's
/// *fastest* ramp.
///
/// `1` is the motor's own default: the wiki says so ("when set to 0, it would
/// be the default value as 1"), and hardware measurement agrees — `0` and `1`
/// reach setpoint in the same time. Larger values are gentler; see
/// [`crate::protocol::frame_velocity`] for the measured numbers. Override the
/// default per handle with
/// [`M0601::with_default_accel`](crate::M0601::with_default_accel), or per
/// call with
/// [`M0601::drive_velocity_accel`](crate::M0601::drive_velocity_accel).
pub const DEFAULT_DRIVE_ACCEL: u8 = 1;
/// Tunable bus-wide timing and stop behavior for one physical bus.
///
/// Every field defaults to the value the crate has always used
/// ([`BusTiming::default`]), so a bus left unconfigured behaves exactly as
/// before. Override what you need — wholesale with
/// [`Bus::with_timing`](crate::Bus::with_timing), or one field at a time with
/// the matching builder ([`Bus::with_stop_accel`](crate::Bus::with_stop_accel),
/// [`Bus::with_min_gap`](crate::Bus::with_min_gap)). Like the idle gap, this
/// lives on the **shared** bus, not per handle: set it once at open time and
/// every motor minted from the bus sees it.
/// The minimum wall-clock a bus needs for one round of `n_drives`
/// fire-and-forget drive frames plus `n_polls` read exchanges, given the
/// enforced idle `min_gap` after every frame and the `reply_wait` each poll
/// blocks for.
///
/// This is the "budget the wire" arithmetic from the crate docs made
/// executable: a drive frame costs one
/// [`frame_time`](crate::protocol::frame_time) plus `min_gap`. A poll costs
/// *two* frame times plus `reply_wait` and `min_gap`: the transport sleeps
/// out its own wire time **and** the reply window
/// ([`Transport::send_recv`](crate::transport::Transport::send_recv) sleeps
/// `frame + reply_wait`), then the trailing idle gap re-budgets a full
/// `frame + min_gap` from the poll's return — the frame's wire time is
/// spaced once inside the transaction and once in the trailing gap. A
/// periodic multi-motor loop's cycle must exceed this, and stay at or under
/// [`drive_floor`](crate::protocol::drive_floor), or it cannot sustain its
/// own period. A loop driving four motors that also polls one per cycle,
/// for instance, sizes its period against `bus_period(4, 1, …)`.
///
/// ```
/// use std::time::Duration;
/// use m0601::bus_period;
/// let gap = Duration::from_millis(2);
/// // Four drives + one poll with a 2 ms reply window ≈ 17.21 ms.
/// let p = bus_period(4, 1, gap, gap);
/// assert!((17_000..17_600).contains(&(p.as_micros() as u64)));
/// ```