From c44286bdcf9d366e4fe3d9c9cc5ad09f5b05db1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Sun, 9 Aug 2026 13:56:45 +0300 Subject: [PATCH] actuator: make arming sequences owner-local --- libs/kaptan-actuator/src/dshot.rs | 61 +++++++++++++----------------- libs/kaptan-actuator/src/lib.rs | 18 ++++----- libs/kaptan-actuator/src/pwm.rs | 63 +++++++++++++------------------ 3 files changed, 59 insertions(+), 83 deletions(-) diff --git a/libs/kaptan-actuator/src/dshot.rs b/libs/kaptan-actuator/src/dshot.rs index 7f72121..1ca0be1 100644 --- a/libs/kaptan-actuator/src/dshot.rs +++ b/libs/kaptan-actuator/src/dshot.rs @@ -1,7 +1,6 @@ //! DShot framing, timing, and arming policy. use crate::ArmingDuration; -use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; pub const MIN_THROTTLE: u16 = 48; pub const MOTOR_STOP: u16 = 0; @@ -147,58 +146,50 @@ impl FrameValue { } pub struct ArmSequence { - armed_throttle: AtomicU16, - zero_frames: AtomicU16, - remaining: AtomicU16, - armed: AtomicBool, + armed_throttle: Throttle, + zero_frames: u16, + remaining: u16, + armed: bool, } impl ArmSequence { pub const fn new(armed_throttle: Throttle, zero_frames: u16) -> Self { Self { - armed_throttle: AtomicU16::new(armed_throttle.get()), - zero_frames: AtomicU16::new(zero_frames), - remaining: AtomicU16::new(0), - armed: AtomicBool::new(false), + armed_throttle, + zero_frames, + remaining: 0, + armed: false, } } - pub fn arm(&self) { - self.remaining - .store(self.zero_frames.load(Ordering::Acquire), Ordering::Release); - self.armed.store(true, Ordering::Release); + /// Starts the fixed configured stop-frame sequence. The driver which owns + /// this state must acquire external safety authorization before calling + /// this method and before every subsequent nonzero output commit. + pub fn arm(&mut self) { + self.remaining = self.zero_frames; + self.armed = true; } - pub fn configure_zero_frames(&self, zero_frames: u16) { - self.zero_frames.store(zero_frames, Ordering::Release); + /// Stops the sequence; the physical output owner must emit an explicit + /// 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) { - self.armed_throttle - .store(armed_throttle.get(), Ordering::Release); + pub const fn is_armed(&self) -> bool { + self.armed } - pub fn disarm(&self) { - 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 { + pub fn next_frame(&mut self) -> Option { if !self.is_armed() { return None; } - if self.remaining.load(Ordering::Acquire) != 0 { - self.remaining.fetch_sub(1, Ordering::AcqRel); + if self.remaining != 0 { + self.remaining -= 1; return Some(FrameValue::Stop); } - match Throttle::new(self.armed_throttle.load(Ordering::Acquire)) { - Some(throttle) => Some(FrameValue::Throttle(throttle)), - None => unreachable!(), - } + Some(FrameValue::Throttle(self.armed_throttle)) } } @@ -237,7 +228,7 @@ mod tests { #[test] fn arm_sequence_holds_stop_for_the_configured_interval() { let throttle = Throttle::new(300).unwrap(); - let sequence = ArmSequence::new(throttle, 2); + let mut sequence = ArmSequence::new(throttle, 2); sequence.arm(); assert_eq!(sequence.next_frame(), Some(FrameValue::Stop)); assert_eq!(sequence.next_frame(), Some(FrameValue::Stop)); diff --git a/libs/kaptan-actuator/src/lib.rs b/libs/kaptan-actuator/src/lib.rs index 069c221..982e6ab 100644 --- a/libs/kaptan-actuator/src/lib.rs +++ b/libs/kaptan-actuator/src/lib.rs @@ -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] @@ -27,13 +33,3 @@ pub enum MotorProtocol { Pwm(pwm::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; -} diff --git a/libs/kaptan-actuator/src/pwm.rs b/libs/kaptan-actuator/src/pwm.rs index c27f5c7..41665a4 100644 --- a/libs/kaptan-actuator/src/pwm.rs +++ b/libs/kaptan-actuator/src/pwm.rs @@ -1,7 +1,6 @@ //! Conventional PWM framing and arming policy. use crate::ArmingDuration; -use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Config { @@ -104,60 +103,50 @@ pub enum FrameValue { } pub struct ArmSequence { - armed_idle: AtomicU16, - minimum_frames: AtomicU16, - remaining: AtomicU16, - armed: AtomicBool, + armed_idle: PulseWidth, + minimum_frames: u16, + remaining: u16, + armed: bool, } impl ArmSequence { pub const fn new(armed_idle: PulseWidth, minimum_frames: u16) -> Self { Self { - armed_idle: AtomicU16::new(armed_idle.microseconds()), - minimum_frames: AtomicU16::new(minimum_frames), - remaining: AtomicU16::new(0), - armed: AtomicBool::new(false), + armed_idle, + minimum_frames, + remaining: 0, + armed: false, } } - pub fn arm(&self) { - self.remaining.store( - self.minimum_frames.load(Ordering::Acquire), - Ordering::Release, - ); - self.armed.store(true, Ordering::Release); + /// Starts the fixed configured minimum-frame sequence. The driver which + /// owns this state must acquire external safety authorization before + /// calling this method and before every subsequent non-minimum commit. + pub fn arm(&mut self) { + self.remaining = self.minimum_frames; + self.armed = true; } - pub fn configure_minimum_frames(&self, minimum_frames: u16) { - self.minimum_frames.store(minimum_frames, Ordering::Release); + /// Stops the sequence; the physical output owner must drive its explicit + /// 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) { - self.armed_idle - .store(armed_idle.microseconds(), Ordering::Release); + pub const fn is_armed(&self) -> bool { + self.armed } - pub fn disarm(&self) { - 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 { + pub fn next_frame(&mut self) -> Option { if !self.is_armed() { return None; } - if self.remaining.load(Ordering::Acquire) != 0 { - self.remaining.fetch_sub(1, Ordering::AcqRel); + if self.remaining != 0 { + self.remaining -= 1; return Some(FrameValue::Minimum); } - match PulseWidth::new(self.armed_idle.load(Ordering::Acquire)) { - Some(pulse) => Some(FrameValue::ArmedIdle(pulse)), - None => unreachable!(), - } + Some(FrameValue::ArmedIdle(self.armed_idle)) } } @@ -176,7 +165,7 @@ mod tests { #[test] fn sequence_holds_minimum_before_armed_idle() { let pulse = PulseWidth::new(1_100).unwrap(); - let sequence = ArmSequence::new(pulse, 2); + let mut sequence = ArmSequence::new(pulse, 2); sequence.arm(); assert_eq!(sequence.next_frame(), Some(FrameValue::Minimum)); assert_eq!(sequence.next_frame(), Some(FrameValue::Minimum));