Added stm32 counter abstraction
This commit is contained in:
@@ -11,6 +11,7 @@ license.workspace = true
|
|||||||
comms = []
|
comms = []
|
||||||
single-packet-msgs = []
|
single-packet-msgs = []
|
||||||
usb = ["embassy-usb"]
|
usb = ["embassy-usb"]
|
||||||
|
counter = []
|
||||||
stm32 = ["embassy-stm32", "physical/stm32"]
|
stm32 = ["embassy-stm32", "physical/stm32"]
|
||||||
|
|
||||||
[dependencies.physical]
|
[dependencies.physical]
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
//! Hardware pulse counter using a timer in External Clock Mode.
|
||||||
|
//!
|
||||||
|
//! Counts edges on an external signal entirely in hardware. Supports three
|
||||||
|
//! pin sources per timer: the dedicated ETR pin, or any CH1/CH2 pin.
|
||||||
|
|
||||||
|
use embassy_stm32::gpio::{AfType, Flex, Pull};
|
||||||
|
use embassy_stm32::pac::timer::vals::{Etp, Etps};
|
||||||
|
use embassy_stm32::timer::low_level::{
|
||||||
|
FilterValue, InputCaptureMode, InputTISelection, SlaveMode, Timer, TriggerSource,
|
||||||
|
};
|
||||||
|
use embassy_stm32::timer::{
|
||||||
|
Ch1, Ch2, Channel, ExternalTriggerPin, GeneralInstance4Channel, TimerPin,
|
||||||
|
};
|
||||||
|
use embassy_stm32::Peri;
|
||||||
|
|
||||||
|
/// Which edge increments the counter.
|
||||||
|
#[derive(Clone, Copy, Default, defmt::Format)]
|
||||||
|
pub enum CountEdge {
|
||||||
|
#[default]
|
||||||
|
Rising,
|
||||||
|
Falling,
|
||||||
|
/// Count on both rising and falling edges.
|
||||||
|
/// Only supported with channel pins (CH1/CH2), not ETR.
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pulse counter configuration.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PulseCounterConfig {
|
||||||
|
pub edge: CountEdge,
|
||||||
|
pub filter: FilterValue,
|
||||||
|
pub pull: Pull,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PulseCounterConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
edge: CountEdge::Rising,
|
||||||
|
filter: FilterValue::NO_FILTER,
|
||||||
|
pull: Pull::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hardware pulse counter driven by an external signal.
|
||||||
|
///
|
||||||
|
/// Takes ownership of the timer and pin, preventing reuse elsewhere.
|
||||||
|
pub struct PulseCounter<'d, T: GeneralInstance4Channel> {
|
||||||
|
inner: Timer<'d, T>,
|
||||||
|
_pin: Flex<'d>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Constructors ----
|
||||||
|
|
||||||
|
impl<'d, T: GeneralInstance4Channel> PulseCounter<'d, T> {
|
||||||
|
/// Count pulses on the timer's ETR (External Trigger) pin.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if `config.edge` is [`CountEdge::Both`] — ETR only supports single-edge detection.
|
||||||
|
pub fn new_etr(
|
||||||
|
tim: Peri<'d, T>,
|
||||||
|
pin: Peri<'d, impl ExternalTriggerPin<T>>,
|
||||||
|
config: PulseCounterConfig,
|
||||||
|
) -> Self {
|
||||||
|
assert!(
|
||||||
|
!matches!(config.edge, CountEdge::Both),
|
||||||
|
"ETR does not support both-edge detection"
|
||||||
|
);
|
||||||
|
|
||||||
|
let af_num = pin.af_num();
|
||||||
|
let mut flex_pin = Flex::new(pin);
|
||||||
|
flex_pin.set_as_af_unchecked(af_num, AfType::input(config.pull));
|
||||||
|
|
||||||
|
let inner = Timer::new(tim);
|
||||||
|
|
||||||
|
inner.set_slave_mode(SlaveMode::EXT_CLOCK_MODE);
|
||||||
|
inner.set_trigger_source(TriggerSource::ETRF);
|
||||||
|
inner.set_external_trigger_filter(config.filter);
|
||||||
|
inner.set_external_trigger_prescaler(Etps::DIV1);
|
||||||
|
inner.set_external_trigger_polarity(match config.edge {
|
||||||
|
CountEdge::Rising => Etp::from(0),
|
||||||
|
CountEdge::Falling => Etp::from(1),
|
||||||
|
CountEdge::Both => unreachable!(),
|
||||||
|
});
|
||||||
|
|
||||||
|
inner.start();
|
||||||
|
Self { inner, _pin: flex_pin }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count pulses on a CH1 pin.
|
||||||
|
pub fn new_ch1(
|
||||||
|
tim: Peri<'d, T>,
|
||||||
|
pin: Peri<'d, impl TimerPin<T, Ch1>>,
|
||||||
|
config: PulseCounterConfig,
|
||||||
|
) -> Self {
|
||||||
|
let af_num = pin.af_num();
|
||||||
|
let mut flex_pin = Flex::new(pin);
|
||||||
|
flex_pin.set_as_af_unchecked(af_num, AfType::input(config.pull));
|
||||||
|
|
||||||
|
let inner = Timer::new(tim);
|
||||||
|
Self::configure_channel(&inner, Channel::Ch1, TriggerSource::TI1FP1, &config);
|
||||||
|
|
||||||
|
inner.start();
|
||||||
|
Self { inner, _pin: flex_pin }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count pulses on a CH2 pin.
|
||||||
|
pub fn new_ch2(
|
||||||
|
tim: Peri<'d, T>,
|
||||||
|
pin: Peri<'d, impl TimerPin<T, Ch2>>,
|
||||||
|
config: PulseCounterConfig,
|
||||||
|
) -> Self {
|
||||||
|
let af_num = pin.af_num();
|
||||||
|
let mut flex_pin = Flex::new(pin);
|
||||||
|
flex_pin.set_as_af_unchecked(af_num, AfType::input(config.pull));
|
||||||
|
|
||||||
|
let inner = Timer::new(tim);
|
||||||
|
Self::configure_channel(&inner, Channel::Ch2, TriggerSource::TI2FP2, &config);
|
||||||
|
|
||||||
|
inner.start();
|
||||||
|
Self { inner, _pin: flex_pin }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Common channel setup: put the channel in input mode, configure its
|
||||||
|
/// filter and edge polarity, then wire it as the slave-mode trigger.
|
||||||
|
fn configure_channel(
|
||||||
|
inner: &Timer<'d, T>,
|
||||||
|
channel: Channel,
|
||||||
|
trigger: TriggerSource,
|
||||||
|
config: &PulseCounterConfig,
|
||||||
|
) {
|
||||||
|
// Channel must be in input mode for the ICxF filter bits to exist.
|
||||||
|
// Normal = direct mapping (TI1→IC1, TI2→IC2).
|
||||||
|
inner.set_input_ti_selection(channel, InputTISelection::Normal);
|
||||||
|
inner.set_input_capture_filter(channel, config.filter);
|
||||||
|
inner.set_input_capture_mode(channel, match config.edge {
|
||||||
|
CountEdge::Rising => InputCaptureMode::Rising,
|
||||||
|
CountEdge::Falling => InputCaptureMode::Falling,
|
||||||
|
CountEdge::Both => InputCaptureMode::BothEdges,
|
||||||
|
});
|
||||||
|
|
||||||
|
inner.set_trigger_source(trigger);
|
||||||
|
inner.set_slave_mode(SlaveMode::EXT_CLOCK_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Reading ----
|
||||||
|
|
||||||
|
impl<'d, T: GeneralInstance4Channel> PulseCounter<'d, T> {
|
||||||
|
/// Read the current count (lower 16 bits).
|
||||||
|
#[inline]
|
||||||
|
pub fn count(&self) -> u16 {
|
||||||
|
self.inner.regs_gp16().cnt().read().cnt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the counter to zero.
|
||||||
|
#[inline]
|
||||||
|
pub fn reset(&self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: embassy_stm32::timer::GeneralInstance32bit4Channel> PulseCounter<'d, T> {
|
||||||
|
/// Read the current count as a full 32-bit value.
|
||||||
|
#[inline]
|
||||||
|
pub fn count_32(&self) -> u32 {
|
||||||
|
self.inner.regs_gp32().cnt().read()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
#[cfg(feature = "usb")]
|
#[cfg(feature = "usb")]
|
||||||
pub mod usb;
|
pub mod usb;
|
||||||
|
#[cfg(feature = "counter")]
|
||||||
|
pub mod counter;
|
||||||
|
|||||||
Reference in New Issue
Block a user