actuator: make arming sequences owner-local

This commit is contained in:
Onur Özkan committed 2026-08-09 13:56:45 +03:00
1 parent f200e88ef0
commit c44286bdcf
3 files changed
+59 -83

No files matched your search

+26 -35
View File
@@ -1,7 +1,6 @@
//! DShot framing, timing, and arming policy. //! DShot framing, timing, and arming policy.
use crate::ArmingDuration; use crate::ArmingDuration;
use core::sync::atomic::{AtomicBool, AtomicU16, Ordering};
pub const MIN_THROTTLE: u16 = 48; pub const MIN_THROTTLE: u16 = 48;
pub const MOTOR_STOP: u16 = 0; pub const MOTOR_STOP: u16 = 0;
@@ -147,58 +146,50 @@ impl FrameValue {
} }
pub struct ArmSequence { pub struct ArmSequence {
armed_throttle: AtomicU16, armed_throttle: Throttle,
zero_frames: AtomicU16, zero_frames: u16,
remaining: AtomicU16, remaining: u16,
armed: AtomicBool, armed: bool,
} }
impl ArmSequence { impl ArmSequence {
pub const fn new(armed_throttle: Throttle, zero_frames: u16) -> Self { pub const fn new(armed_throttle: Throttle, zero_frames: u16) -> Self {
Self { Self {
armed_throttle: AtomicU16::new(armed_throttle.get()), armed_throttle,
zero_frames: AtomicU16::new(zero_frames), zero_frames,
remaining: AtomicU16::new(0), remaining: 0,
armed: AtomicBool::new(false), armed: false,
} }
} }
pub fn arm(&self) { /// Starts the fixed configured stop-frame sequence. The driver which owns
self.remaining /// this state must acquire external safety authorization before calling
.store(self.zero_frames.load(Ordering::Acquire), Ordering::Release); /// this method and before every subsequent nonzero output commit.
self.armed.store(true, Ordering::Release); pub fn arm(&mut self) {
self.remaining = self.zero_frames;
self.armed = true;
} }
pub fn configure_zero_frames(&self, zero_frames: u16) { /// Stops the sequence; the physical output owner must emit an explicit
self.zero_frames.store(zero_frames, Ordering::Release); /// stop frame independently of this local state transition.
pub fn disarm(&mut self) {
self.armed = false;
self.remaining = 0;
} }
pub fn configure_armed_throttle(&self, armed_throttle: Throttle) { pub const fn is_armed(&self) -> bool {
self.armed_throttle self.armed
.store(armed_throttle.get(), Ordering::Release);
} }
pub fn disarm(&self) { pub fn next_frame(&mut self) -> Option<FrameValue> {
self.armed.store(false, Ordering::Release);
self.remaining.store(0, Ordering::Release);
}
pub fn is_armed(&self) -> bool {
self.armed.load(Ordering::Acquire)
}
pub fn next_frame(&self) -> Option<FrameValue> {
if !self.is_armed() { if !self.is_armed() {
return None; return None;
} }
if self.remaining.load(Ordering::Acquire) != 0 { if self.remaining != 0 {
self.remaining.fetch_sub(1, Ordering::AcqRel); self.remaining -= 1;
return Some(FrameValue::Stop); return Some(FrameValue::Stop);
} }
match Throttle::new(self.armed_throttle.load(Ordering::Acquire)) { Some(FrameValue::Throttle(self.armed_throttle))
Some(throttle) => Some(FrameValue::Throttle(throttle)),
None => unreachable!(),
}
} }
} }
@@ -237,7 +228,7 @@ mod tests {
#[test] #[test]
fn arm_sequence_holds_stop_for_the_configured_interval() { fn arm_sequence_holds_stop_for_the_configured_interval() {
let throttle = Throttle::new(300).unwrap(); let throttle = Throttle::new(300).unwrap();
let sequence = ArmSequence::new(throttle, 2); let mut sequence = ArmSequence::new(throttle, 2);
sequence.arm(); sequence.arm();
assert_eq!(sequence.next_frame(), Some(FrameValue::Stop)); assert_eq!(sequence.next_frame(), Some(FrameValue::Stop));
assert_eq!(sequence.next_frame(), Some(FrameValue::Stop)); assert_eq!(sequence.next_frame(), Some(FrameValue::Stop));
+7 -11
View File
@@ -1,4 +1,10 @@
//! Protocol-neutral motor-output policy. //! Protocol-neutral physical-motor protocol reference.
//!
//! This crate deliberately remains in the workspace for future, separately
//! reviewed physical-flight work. No checked-in board platform links it, and
//! its types model DShot/PWM framing and
//! local arming sequences only; they grant neither peripheral access nor
//! flight authority.
#![no_std] #![no_std]
@@ -27,13 +33,3 @@ pub enum MotorProtocol {
Pwm(pwm::Config), Pwm(pwm::Config),
Dshot(dshot::Config), Dshot(dshot::Config),
} }
/// Flight-control and MAVLink boundary for motor outputs.
pub trait MotorOutput {
/// Enables the configured armed output sequence.
fn arm(&mut self);
/// Stops output and returns all motor channels to their safe state.
fn disarm(&mut self);
/// Returns whether the output has accepted an arm request.
fn is_armed(&self) -> bool;
}
+26 -37
View File
@@ -1,7 +1,6 @@
//! Conventional PWM framing and arming policy. //! Conventional PWM framing and arming policy.
use crate::ArmingDuration; use crate::ArmingDuration;
use core::sync::atomic::{AtomicBool, AtomicU16, Ordering};
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config { pub struct Config {
@@ -104,60 +103,50 @@ pub enum FrameValue {
} }
pub struct ArmSequence { pub struct ArmSequence {
armed_idle: AtomicU16, armed_idle: PulseWidth,
minimum_frames: AtomicU16, minimum_frames: u16,
remaining: AtomicU16, remaining: u16,
armed: AtomicBool, armed: bool,
} }
impl ArmSequence { impl ArmSequence {
pub const fn new(armed_idle: PulseWidth, minimum_frames: u16) -> Self { pub const fn new(armed_idle: PulseWidth, minimum_frames: u16) -> Self {
Self { Self {
armed_idle: AtomicU16::new(armed_idle.microseconds()), armed_idle,
minimum_frames: AtomicU16::new(minimum_frames), minimum_frames,
remaining: AtomicU16::new(0), remaining: 0,
armed: AtomicBool::new(false), armed: false,
} }
} }
pub fn arm(&self) { /// Starts the fixed configured minimum-frame sequence. The driver which
self.remaining.store( /// owns this state must acquire external safety authorization before
self.minimum_frames.load(Ordering::Acquire), /// calling this method and before every subsequent non-minimum commit.
Ordering::Release, pub fn arm(&mut self) {
); self.remaining = self.minimum_frames;
self.armed.store(true, Ordering::Release); self.armed = true;
} }
pub fn configure_minimum_frames(&self, minimum_frames: u16) { /// Stops the sequence; the physical output owner must drive its explicit
self.minimum_frames.store(minimum_frames, Ordering::Release); /// minimum pulse independently of this local state transition.
pub fn disarm(&mut self) {
self.armed = false;
self.remaining = 0;
} }
pub fn configure_armed_idle(&self, armed_idle: PulseWidth) { pub const fn is_armed(&self) -> bool {
self.armed_idle self.armed
.store(armed_idle.microseconds(), Ordering::Release);
} }
pub fn disarm(&self) { pub fn next_frame(&mut self) -> Option<FrameValue> {
self.armed.store(false, Ordering::Release);
self.remaining.store(0, Ordering::Release);
}
pub fn is_armed(&self) -> bool {
self.armed.load(Ordering::Acquire)
}
pub fn next_frame(&self) -> Option<FrameValue> {
if !self.is_armed() { if !self.is_armed() {
return None; return None;
} }
if self.remaining.load(Ordering::Acquire) != 0 { if self.remaining != 0 {
self.remaining.fetch_sub(1, Ordering::AcqRel); self.remaining -= 1;
return Some(FrameValue::Minimum); return Some(FrameValue::Minimum);
} }
match PulseWidth::new(self.armed_idle.load(Ordering::Acquire)) { Some(FrameValue::ArmedIdle(self.armed_idle))
Some(pulse) => Some(FrameValue::ArmedIdle(pulse)),
None => unreachable!(),
}
} }
} }
@@ -176,7 +165,7 @@ mod tests {
#[test] #[test]
fn sequence_holds_minimum_before_armed_idle() { fn sequence_holds_minimum_before_armed_idle() {
let pulse = PulseWidth::new(1_100).unwrap(); let pulse = PulseWidth::new(1_100).unwrap();
let sequence = ArmSequence::new(pulse, 2); let mut sequence = ArmSequence::new(pulse, 2);
sequence.arm(); sequence.arm();
assert_eq!(sequence.next_frame(), Some(FrameValue::Minimum)); assert_eq!(sequence.next_frame(), Some(FrameValue::Minimum));
assert_eq!(sequence.next_frame(), Some(FrameValue::Minimum)); assert_eq!(sequence.next_frame(), Some(FrameValue::Minimum));