Merge branch 'main' of github.com:Orkavian/kaptan
This commit is contained in:
commit
f8ee799208
7 files changed
+136
-146
No files matched your search
@@ -1,8 +1,7 @@
|
|||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use kaptan_actuator::{
|
use kaptan_actuator::dshot::{
|
||||||
DshotConfig,
|
ArmSequence, Config, FRAME_SYMBOLS, FrameValue, Throttle, Timing, symbols,
|
||||||
dshot::{ArmSequence, FRAME_SYMBOLS, FrameValue, Throttle, Timing, symbols},
|
|
||||||
};
|
};
|
||||||
use stm32f7::stm32f722;
|
use stm32f7::stm32f722;
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ impl Tim3Dshot {
|
|||||||
rcc: &stm32f722::RCC,
|
rcc: &stm32f722::RCC,
|
||||||
timer: stm32f722::TIM3,
|
timer: stm32f722::TIM3,
|
||||||
scheduler: stm32f722::TIM5,
|
scheduler: stm32f722::TIM5,
|
||||||
config: DshotConfig,
|
config: Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rate = config.rate();
|
let rate = config.rate();
|
||||||
let dshot_timer_hz = rate.timer_base_hz();
|
let dshot_timer_hz = rate.timer_base_hz();
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use core::sync::atomic::{AtomicU16, Ordering};
|
use core::sync::atomic::{AtomicU16, Ordering};
|
||||||
|
|
||||||
use kaptan_actuator::{
|
use kaptan_actuator::pwm::{ArmSequence, Config, FrameValue, PulseWidth};
|
||||||
PwmConfig,
|
|
||||||
pwm::{ArmSequence, FrameValue, PulseWidth},
|
|
||||||
};
|
|
||||||
use stm32f7::stm32f722;
|
use stm32f7::stm32f722;
|
||||||
|
|
||||||
const APB1_TIMER_CLOCK_HZ: u32 = 108_000_000;
|
const APB1_TIMER_CLOCK_HZ: u32 = 108_000_000;
|
||||||
@@ -28,7 +25,7 @@ pub(super) struct Tim3Pwm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Tim3Pwm {
|
impl Tim3Pwm {
|
||||||
pub(super) fn new(rcc: &stm32f722::RCC, timer: stm32f722::TIM3, config: PwmConfig) -> Self {
|
pub(super) fn new(rcc: &stm32f722::RCC, timer: stm32f722::TIM3, config: Config) -> Self {
|
||||||
let period_us = match config.frame_rate().period_microseconds() {
|
let period_us = match config.frame_rate().period_microseconds() {
|
||||||
Some(period) => period,
|
Some(period) => period,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pub const LIDAR_ACQUISITION_MODE: AcquisitionMode = AcquisitionMode::WithReceive
|
|||||||
pub const LIDAR_STARTUP_TICKS: u8 = 3;
|
pub const LIDAR_STARTUP_TICKS: u8 = 3;
|
||||||
/// ESC protocol selected for this board's M1–M4 output route.
|
/// ESC protocol selected for this board's M1–M4 output route.
|
||||||
pub const MOTOR_PROTOCOL: MotorProtocol = MotorProtocol::Dshot(
|
pub const MOTOR_PROTOCOL: MotorProtocol = MotorProtocol::Dshot(
|
||||||
match kaptan_actuator::DshotConfig::new(
|
match kaptan_actuator::dshot::Config::new(
|
||||||
kaptan_actuator::dshot::Rate::Dshot600,
|
kaptan_actuator::dshot::Rate::Dshot600,
|
||||||
match kaptan_actuator::dshot::Throttle::new(300) {
|
match kaptan_actuator::dshot::Throttle::new(300) {
|
||||||
Some(throttle) => throttle,
|
Some(throttle) => throttle,
|
||||||
|
|||||||
@@ -1,11 +1,48 @@
|
|||||||
//! DShot framing, timing, and arming policy.
|
//! DShot framing, timing, and arming policy.
|
||||||
|
|
||||||
|
use crate::ArmingDuration;
|
||||||
use core::sync::atomic::{AtomicBool, AtomicU16, Ordering};
|
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;
|
||||||
pub const FRAME_SYMBOLS: usize = 18;
|
pub const FRAME_SYMBOLS: usize = 18;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Config {
|
||||||
|
rate: Rate,
|
||||||
|
armed_idle_throttle: Throttle,
|
||||||
|
arming_duration: ArmingDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub const fn new(
|
||||||
|
rate: Rate,
|
||||||
|
armed_idle_throttle: Throttle,
|
||||||
|
arming_duration: ArmingDuration,
|
||||||
|
) -> Option<Self> {
|
||||||
|
if rate.frames_for_millis(arming_duration.milliseconds()) == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Self {
|
||||||
|
rate,
|
||||||
|
armed_idle_throttle,
|
||||||
|
arming_duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn rate(self) -> Rate {
|
||||||
|
self.rate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn armed_idle_throttle(self) -> Throttle {
|
||||||
|
self.armed_idle_throttle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn arming_duration(self) -> ArmingDuration {
|
||||||
|
self.arming_duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum Rate {
|
pub enum Rate {
|
||||||
Dshot150,
|
Dshot150,
|
||||||
@@ -186,7 +223,8 @@ pub const fn symbols(packet: u16, zero_high: u16, one_high: u16) -> [u16; FRAME_
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ArmSequence, FrameValue, Rate, Throttle, Timing, packet, symbols};
|
use super::{ArmSequence, Config, FrameValue, Rate, Throttle, Timing, packet, symbols};
|
||||||
|
use crate::ArmingDuration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn packet_matches_standard_dshot_vectors() {
|
fn packet_matches_standard_dshot_vectors() {
|
||||||
@@ -219,4 +257,11 @@ mod tests {
|
|||||||
assert!(Timing::from_timer_clock(rate.timer_base_hz(), rate).is_some());
|
assert!(Timing::from_timer_clock(rate.timer_base_hz(), rate).is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn requires_an_arming_interval_with_frames() {
|
||||||
|
let duration = ArmingDuration::from_millis(2_000).unwrap();
|
||||||
|
let throttle = Throttle::new(300).unwrap();
|
||||||
|
assert!(Config::new(Rate::Dshot600, throttle, duration).is_some());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,30 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
pub mod dshot;
|
pub mod dshot;
|
||||||
mod protocol;
|
|
||||||
pub mod pwm;
|
pub mod pwm;
|
||||||
|
|
||||||
pub use protocol::{ArmingDuration, DshotConfig, MotorProtocol, PwmConfig};
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct ArmingDuration(u16);
|
||||||
|
|
||||||
|
impl ArmingDuration {
|
||||||
|
pub const fn from_millis(milliseconds: u16) -> Option<Self> {
|
||||||
|
if milliseconds != 0 && milliseconds <= 8_000 {
|
||||||
|
Some(Self(milliseconds))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn milliseconds(self) -> u16 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MotorProtocol {
|
||||||
|
Pwm(pwm::Config),
|
||||||
|
Dshot(dshot::Config),
|
||||||
|
}
|
||||||
|
|
||||||
/// Flight-control and MAVLink boundary for motor outputs.
|
/// Flight-control and MAVLink boundary for motor outputs.
|
||||||
pub trait MotorOutput {
|
pub trait MotorOutput {
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
use crate::{dshot, pwm};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub struct ArmingDuration(u16);
|
|
||||||
|
|
||||||
impl ArmingDuration {
|
|
||||||
pub const fn from_millis(milliseconds: u16) -> Option<Self> {
|
|
||||||
if milliseconds != 0 && milliseconds <= 8_000 {
|
|
||||||
Some(Self(milliseconds))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn milliseconds(self) -> u16 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub struct PwmConfig {
|
|
||||||
frame_rate: pwm::Rate,
|
|
||||||
minimum_pulse: pwm::PulseWidth,
|
|
||||||
armed_idle_pulse: pwm::PulseWidth,
|
|
||||||
arming_duration: ArmingDuration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PwmConfig {
|
|
||||||
pub const fn new(
|
|
||||||
frame_rate: pwm::Rate,
|
|
||||||
minimum_pulse: pwm::PulseWidth,
|
|
||||||
armed_idle_pulse: pwm::PulseWidth,
|
|
||||||
arming_duration: ArmingDuration,
|
|
||||||
) -> Option<Self> {
|
|
||||||
match frame_rate.period_microseconds() {
|
|
||||||
Some(period)
|
|
||||||
if minimum_pulse.microseconds() <= armed_idle_pulse.microseconds()
|
|
||||||
&& armed_idle_pulse.microseconds() <= period =>
|
|
||||||
{
|
|
||||||
Some(Self {
|
|
||||||
frame_rate,
|
|
||||||
minimum_pulse,
|
|
||||||
armed_idle_pulse,
|
|
||||||
arming_duration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn frame_rate(self) -> pwm::Rate {
|
|
||||||
self.frame_rate
|
|
||||||
}
|
|
||||||
pub const fn minimum_pulse(self) -> pwm::PulseWidth {
|
|
||||||
self.minimum_pulse
|
|
||||||
}
|
|
||||||
pub const fn armed_idle_pulse(self) -> pwm::PulseWidth {
|
|
||||||
self.armed_idle_pulse
|
|
||||||
}
|
|
||||||
pub const fn arming_duration(self) -> ArmingDuration {
|
|
||||||
self.arming_duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub struct DshotConfig {
|
|
||||||
rate: dshot::Rate,
|
|
||||||
armed_idle_throttle: dshot::Throttle,
|
|
||||||
arming_duration: ArmingDuration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DshotConfig {
|
|
||||||
pub const fn new(
|
|
||||||
rate: dshot::Rate,
|
|
||||||
armed_idle_throttle: dshot::Throttle,
|
|
||||||
arming_duration: ArmingDuration,
|
|
||||||
) -> Option<Self> {
|
|
||||||
if rate.frames_for_millis(arming_duration.milliseconds()) == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(Self {
|
|
||||||
rate,
|
|
||||||
armed_idle_throttle,
|
|
||||||
arming_duration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn rate(self) -> dshot::Rate {
|
|
||||||
self.rate
|
|
||||||
}
|
|
||||||
pub const fn armed_idle_throttle(self) -> dshot::Throttle {
|
|
||||||
self.armed_idle_throttle
|
|
||||||
}
|
|
||||||
pub const fn arming_duration(self) -> ArmingDuration {
|
|
||||||
self.arming_duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum MotorProtocol {
|
|
||||||
Pwm(PwmConfig),
|
|
||||||
Dshot(DshotConfig),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::{ArmingDuration, DshotConfig, MotorProtocol, PwmConfig};
|
|
||||||
use crate::{dshot, pwm};
|
|
||||||
|
|
||||||
const ARMING: ArmingDuration = match ArmingDuration::from_millis(2_000) {
|
|
||||||
Some(duration) => duration,
|
|
||||||
None => panic!("test duration must be valid"),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pwm_configuration_requires_an_exact_frame_and_ordered_pulses() {
|
|
||||||
let rate = pwm::Rate::new(400).unwrap();
|
|
||||||
let minimum = pwm::PulseWidth::new(1_000).unwrap();
|
|
||||||
let idle = pwm::PulseWidth::new(1_100).unwrap();
|
|
||||||
assert!(PwmConfig::new(rate, minimum, idle, ARMING).is_some());
|
|
||||||
assert!(PwmConfig::new(pwm::Rate::new(333).unwrap(), minimum, idle, ARMING).is_none());
|
|
||||||
assert!(PwmConfig::new(rate, idle, minimum, ARMING).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn motor_protocol_contains_only_validated_policies() {
|
|
||||||
let idle = dshot::Throttle::new(300).unwrap();
|
|
||||||
let config = DshotConfig::new(dshot::Rate::Dshot600, idle, ARMING).unwrap();
|
|
||||||
assert_eq!(MotorProtocol::Dshot(config), MotorProtocol::Dshot(config));
|
|
||||||
assert!(ArmingDuration::from_millis(0).is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,56 @@
|
|||||||
//! Conventional PWM framing and arming policy.
|
//! Conventional PWM framing and arming policy.
|
||||||
|
|
||||||
|
use crate::ArmingDuration;
|
||||||
use core::sync::atomic::{AtomicBool, AtomicU16, Ordering};
|
use core::sync::atomic::{AtomicBool, AtomicU16, Ordering};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Config {
|
||||||
|
frame_rate: Rate,
|
||||||
|
minimum_pulse: PulseWidth,
|
||||||
|
armed_idle_pulse: PulseWidth,
|
||||||
|
arming_duration: ArmingDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub const fn new(
|
||||||
|
frame_rate: Rate,
|
||||||
|
minimum_pulse: PulseWidth,
|
||||||
|
armed_idle_pulse: PulseWidth,
|
||||||
|
arming_duration: ArmingDuration,
|
||||||
|
) -> Option<Self> {
|
||||||
|
match frame_rate.period_microseconds() {
|
||||||
|
Some(period)
|
||||||
|
if minimum_pulse.microseconds() <= armed_idle_pulse.microseconds()
|
||||||
|
&& armed_idle_pulse.microseconds() <= period =>
|
||||||
|
{
|
||||||
|
Some(Self {
|
||||||
|
frame_rate,
|
||||||
|
minimum_pulse,
|
||||||
|
armed_idle_pulse,
|
||||||
|
arming_duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn frame_rate(self) -> Rate {
|
||||||
|
self.frame_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn minimum_pulse(self) -> PulseWidth {
|
||||||
|
self.minimum_pulse
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn armed_idle_pulse(self) -> PulseWidth {
|
||||||
|
self.armed_idle_pulse
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn arming_duration(self) -> ArmingDuration {
|
||||||
|
self.arming_duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub struct Rate(u16);
|
pub struct Rate(u16);
|
||||||
|
|
||||||
@@ -114,7 +163,8 @@ impl ArmSequence {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ArmSequence, FrameValue, PulseWidth, Rate};
|
use super::{ArmSequence, Config, FrameValue, PulseWidth, Rate};
|
||||||
|
use crate::ArmingDuration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rate_describes_an_exact_400_hertz_frame() {
|
fn rate_describes_an_exact_400_hertz_frame() {
|
||||||
@@ -134,4 +184,15 @@ mod tests {
|
|||||||
sequence.disarm();
|
sequence.disarm();
|
||||||
assert_eq!(sequence.next_frame(), None);
|
assert_eq!(sequence.next_frame(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn requires_an_exact_frame_and_ordered_pulses() {
|
||||||
|
let duration = ArmingDuration::from_millis(2_000).unwrap();
|
||||||
|
let rate = Rate::new(400).unwrap();
|
||||||
|
let minimum = PulseWidth::new(1_000).unwrap();
|
||||||
|
let idle = PulseWidth::new(1_100).unwrap();
|
||||||
|
assert!(Config::new(rate, minimum, idle, duration).is_some());
|
||||||
|
assert!(Config::new(Rate::new(333).unwrap(), minimum, idle, duration).is_none());
|
||||||
|
assert!(Config::new(rate, idle, minimum, duration).is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user