mirror of
https://github.com/esp-rs/esp-idf-hal.git
synced 2025-09-27 12:21:02 +00:00
PCNT implementation for v4 esp-idf api (will work for on v5 with v4 api) (#157)
* WIP - PCNT implementation for idf v4 * use from core not std pass pins as PeripheralRef store PinDriver in PcntPin * try a allocate/deallocate stratagy for PCNT UNITS (like esp-idf v5) * update pcnt example * WIP - PCNT implementation for idf v4 * use from core not std pass pins as PeripheralRef store PinDriver in PcntPin * try a allocate/deallocate stratagy for PCNT UNITS (like esp-idf v5) * update pcnt example * use Arc/Mutex for PcntPin so Pnct can be Send * update pcnt encoder example * add partial esp-idf 5 pulse_cnt support (no watches/events) requires update in esp-idf-sys to choose implementation uses pcnt on esp-idf 4 uses pulse_cnt by default on esp-idf 5 * fix pcnt compilation on esp-idf 4 * implement interrupt service * added some documentation * fix subscribe - trampoline did not work with captured values. update example to support either * no need to initialize logging in example * use esp_idf_sys::* * Implement From not Into * switch from bitflags to enumset * typo * switch #[doc] to /// style * gate uses of Box with alloc feature and use alloc::boxed::Box * rename to PcntDriver and make Pcnt Peripherals * use `impl Peripheral<P = impl InputPin>` instead of `AnyInputPin` * small cleanup * rename config() -> channel_config() implement defaults on PcntChannelConfig * some cleanup on idf v5 pulse_ctr * no need for PcntChanConfig, just use flags * use comment style documentation not `#[doc=""]` * small tweak to pnct example * review fixes (the easy ones) * added deps for the pcnt example (dev-dependencies) * cargo fmt * pub const fn new for PcntChanFlags * pub const fn new for PcntUnitFlags * fix compilation for esp-idf 4 * pass in all pins in PcntDriver::new use PinIndex to specify pins to a channel * remove unused lifetime * use a simple macro to convert Option<> pin to a pin number * cargo fmt * remove V5 for now fix #cfg's for when/where pcnt is available * fix #cfg's for when/where pcnt is available * fix #cfg's for when/where pcnt is available * gate access to ISR_HANDLERS in drop with `alloc` no need for `not(feature = "riscv-ulp-hal")` as pcnt is not included for `riscv-ulp-hal` * cargo fmt / clippy * stub out the pcnt example on an unsuported device * - drop esp-idf-svc from dev-deps (compile error on esp-idf 5) - use println!() instead of log::info!() in pcnt example * fix ocnt example compile for riscv32imc-esp-espidf --------- Co-authored-by: Chris Liebman <chris.l@taboola.com>
This commit is contained in:
parent
d477d54e2b
commit
72c0aacf74
143
examples/pcnt_i64_encoder.rs
Normal file
143
examples/pcnt_i64_encoder.rs
Normal file
@ -0,0 +1,143 @@
|
||||
//! PCNT decoding a rotary encoder
|
||||
//!
|
||||
//! To try this out, connect a rotary encoder to pins 5 and 6, the common should be grounded
|
||||
//!
|
||||
//! Note that PCNT only track a singed 16bit value. We use interrupts to detect a LOW and HIGH
|
||||
//! threshold and track how much that accounts for and provide an i64 value result
|
||||
//!
|
||||
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
use anyhow::Context;
|
||||
use encoder::Encoder;
|
||||
use esp_idf_hal::delay::FreeRtos;
|
||||
use esp_idf_hal::prelude::*;
|
||||
|
||||
// Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
|
||||
// or else some patches to the runtime implemented by esp-idf-sys might not link properly.
|
||||
esp_idf_sys::link_patches();
|
||||
|
||||
println!("setup pins");
|
||||
let peripherals = Peripherals::take().context("failed to take Peripherals")?;
|
||||
let mut pin_a = peripherals.pins.gpio5;
|
||||
let mut pin_b = peripherals.pins.gpio6;
|
||||
println!("setup encoder");
|
||||
let encoder = Encoder::new(peripherals.pcnt0, &mut pin_a, &mut pin_b)?;
|
||||
|
||||
let mut last_value = 0i64;
|
||||
loop {
|
||||
let value = encoder.get_value()?;
|
||||
if value != last_value {
|
||||
println!("value: {value}");
|
||||
last_value = value;
|
||||
}
|
||||
FreeRtos::delay_ms(100u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3))))]
|
||||
fn main() {
|
||||
use esp_idf_hal::delay::FreeRtos;
|
||||
println!("pcnt peripheral not supported on this device!");
|
||||
loop {
|
||||
FreeRtos::delay_ms(100u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
// esp-idf encoder implementation using v4 pcnt api
|
||||
mod encoder {
|
||||
use std::cmp::min;
|
||||
use std::sync::atomic::AtomicI64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use esp_idf_hal::gpio::AnyInputPin;
|
||||
use esp_idf_hal::gpio::InputPin;
|
||||
use esp_idf_hal::pcnt::*;
|
||||
use esp_idf_hal::peripheral::Peripheral;
|
||||
use esp_idf_sys::EspError;
|
||||
|
||||
const LOW_LIMIT: i16 = -100;
|
||||
const HIGH_LIMIT: i16 = 100;
|
||||
|
||||
pub struct Encoder<'d> {
|
||||
unit: PcntDriver<'d>,
|
||||
approx_value: Arc<AtomicI64>,
|
||||
}
|
||||
|
||||
impl<'d> Encoder<'d> {
|
||||
pub fn new<PCNT: Pcnt>(
|
||||
pcnt: impl Peripheral<P = PCNT> + 'd,
|
||||
pin_a: impl Peripheral<P = impl InputPin> + 'd,
|
||||
pin_b: impl Peripheral<P = impl InputPin> + 'd,
|
||||
) -> Result<Self, EspError> {
|
||||
let mut unit = PcntDriver::new(
|
||||
pcnt,
|
||||
Some(pin_a),
|
||||
Some(pin_b),
|
||||
Option::<AnyInputPin>::None,
|
||||
Option::<AnyInputPin>::None,
|
||||
)?;
|
||||
unit.channel_config(
|
||||
PcntChannel::Channel0,
|
||||
PinIndex::Pin0,
|
||||
PinIndex::Pin1,
|
||||
&mut PcntChannelConfig {
|
||||
lctrl_mode: PcntControlMode::Reverse,
|
||||
hctrl_mode: PcntControlMode::Keep,
|
||||
pos_mode: PcntCountMode::Decrement,
|
||||
neg_mode: PcntCountMode::Increment,
|
||||
counter_h_lim: HIGH_LIMIT,
|
||||
counter_l_lim: LOW_LIMIT,
|
||||
},
|
||||
)?;
|
||||
unit.channel_config(
|
||||
PcntChannel::Channel1,
|
||||
PinIndex::Pin1,
|
||||
PinIndex::Pin0,
|
||||
&mut PcntChannelConfig {
|
||||
lctrl_mode: PcntControlMode::Reverse,
|
||||
hctrl_mode: PcntControlMode::Keep,
|
||||
pos_mode: PcntCountMode::Increment,
|
||||
neg_mode: PcntCountMode::Decrement,
|
||||
counter_h_lim: HIGH_LIMIT,
|
||||
counter_l_lim: LOW_LIMIT,
|
||||
},
|
||||
)?;
|
||||
|
||||
unit.set_filter_value(min(10 * 80, 1023))?;
|
||||
unit.filter_enable()?;
|
||||
|
||||
let approx_value = Arc::new(AtomicI64::new(0));
|
||||
// unsafe interrupt code to catch the upper and lower limits from the encoder
|
||||
// and track the overflow in `value: Arc<AtomicI64>` - I plan to use this for
|
||||
// a wheeled robot's odomerty
|
||||
unsafe {
|
||||
let approx_value = approx_value.clone();
|
||||
unit.subscribe(move |status| {
|
||||
let status = PcntEventType::from_repr_truncated(status);
|
||||
if status.contains(PcntEvent::HighLimit) {
|
||||
approx_value.fetch_add(HIGH_LIMIT as i64, Ordering::SeqCst);
|
||||
}
|
||||
if status.contains(PcntEvent::LowLimit) {
|
||||
approx_value.fetch_add(LOW_LIMIT as i64, Ordering::SeqCst);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
unit.event_enable(PcntEvent::HighLimit)?;
|
||||
unit.event_enable(PcntEvent::LowLimit)?;
|
||||
unit.counter_pause()?;
|
||||
unit.counter_clear()?;
|
||||
unit.counter_resume()?;
|
||||
|
||||
Ok(Self { unit, approx_value })
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> Result<i64, EspError> {
|
||||
let value =
|
||||
self.approx_value.load(Ordering::Relaxed) + self.unit.get_counter_value()? as i64;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
@ -44,6 +44,8 @@ pub mod ledc;
|
||||
pub mod mac;
|
||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||
pub mod modem;
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pub mod pcnt;
|
||||
pub mod peripheral;
|
||||
pub mod peripherals;
|
||||
pub mod prelude;
|
||||
|
588
src/pcnt.rs
Normal file
588
src/pcnt.rs
Normal file
@ -0,0 +1,588 @@
|
||||
use core::fmt::Debug;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use esp_idf_sys::*;
|
||||
|
||||
use enumset::EnumSetType;
|
||||
|
||||
use crate::gpio::InputPin;
|
||||
use crate::peripheral::Peripheral;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum PcntChannel {
|
||||
Channel0,
|
||||
Channel1,
|
||||
}
|
||||
|
||||
impl From<PcntChannel> for pcnt_channel_t {
|
||||
fn from(value: PcntChannel) -> Self {
|
||||
match value {
|
||||
PcntChannel::Channel0 => pcnt_channel_t_PCNT_CHANNEL_0,
|
||||
PcntChannel::Channel1 => pcnt_channel_t_PCNT_CHANNEL_1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PCNT channel action on signal edge
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub enum PcntCountMode {
|
||||
/// Hold current count value
|
||||
Hold,
|
||||
/// Increase count value
|
||||
#[default]
|
||||
Increment,
|
||||
/// Decrease count value
|
||||
Decrement,
|
||||
}
|
||||
|
||||
impl From<PcntCountMode> for pcnt_count_mode_t {
|
||||
fn from(value: PcntCountMode) -> Self {
|
||||
match value {
|
||||
PcntCountMode::Hold => pcnt_channel_edge_action_t_PCNT_CHANNEL_EDGE_ACTION_HOLD,
|
||||
PcntCountMode::Increment => {
|
||||
pcnt_channel_edge_action_t_PCNT_CHANNEL_EDGE_ACTION_INCREASE
|
||||
}
|
||||
PcntCountMode::Decrement => {
|
||||
pcnt_channel_edge_action_t_PCNT_CHANNEL_EDGE_ACTION_DECREASE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PCNT channel action on control level
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub enum PcntControlMode {
|
||||
/// Keep current count mode
|
||||
Keep,
|
||||
/// Invert current count mode (increase -> decrease, decrease -> increase)
|
||||
#[default]
|
||||
Reverse,
|
||||
/// Hold current count value
|
||||
Disable,
|
||||
}
|
||||
|
||||
impl From<PcntControlMode> for pcnt_ctrl_mode_t {
|
||||
fn from(value: PcntControlMode) -> Self {
|
||||
match value {
|
||||
PcntControlMode::Keep => pcnt_channel_level_action_t_PCNT_CHANNEL_LEVEL_ACTION_KEEP,
|
||||
PcntControlMode::Reverse => {
|
||||
pcnt_channel_level_action_t_PCNT_CHANNEL_LEVEL_ACTION_INVERSE
|
||||
}
|
||||
PcntControlMode::Disable => pcnt_channel_level_action_t_PCNT_CHANNEL_LEVEL_ACTION_HOLD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumSetType)]
|
||||
#[enumset(repr = "u32")]
|
||||
pub enum PcntEvent {
|
||||
/// PCNT watch point event: threshold1 value event
|
||||
Threshold1 = 2, // pcnt_evt_type_t_PCNT_EVT_THRES_1 = 0x04,
|
||||
/// PCNT watch point event: threshold0 value event
|
||||
Threshold0 = 3, // pcnt_evt_type_t_PCNT_EVT_THRES_0 = 0x08,
|
||||
/// PCNT watch point event: Minimum counter value
|
||||
LowLimit = 4, // pcnt_evt_type_t_PCNT_EVT_L_LIM = 0x10,
|
||||
/// PCNT watch point event: Maximum counter value
|
||||
HighLimit = 5, // pcnt_evt_type_t_PCNT_EVT_H_LIM = 0x20,
|
||||
/// PCNT watch point event: counter value zero event
|
||||
Zero = 6, // pcnt_evt_type_t_PCNT_EVT_ZERO = 0x40,
|
||||
}
|
||||
|
||||
pub type PcntEventType = enumset::EnumSet<PcntEvent>;
|
||||
|
||||
/// Pulse Counter configuration for a single channel
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct PcntChannelConfig {
|
||||
/// PCNT low control mode
|
||||
pub lctrl_mode: PcntControlMode,
|
||||
/// PCNT high control mode
|
||||
pub hctrl_mode: PcntControlMode,
|
||||
/// PCNT positive edge count mode
|
||||
pub pos_mode: PcntCountMode,
|
||||
/// PCNT negative edge count mode
|
||||
pub neg_mode: PcntCountMode,
|
||||
/// Maximum counter value
|
||||
pub counter_h_lim: i16,
|
||||
/// Minimum counter value
|
||||
pub counter_l_lim: i16,
|
||||
}
|
||||
|
||||
impl PcntChannelConfig {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum PinIndex {
|
||||
Pin0 = 0,
|
||||
Pin1 = 1,
|
||||
Pin2 = 2,
|
||||
Pin3 = 3,
|
||||
}
|
||||
|
||||
pub struct PcntDriver<'d> {
|
||||
unit: pcnt_unit_t,
|
||||
pins: [i32; 4],
|
||||
_p: PhantomData<&'d mut ()>,
|
||||
}
|
||||
|
||||
macro_rules! pin_to_number {
|
||||
($pin:ident) => {
|
||||
match $pin {
|
||||
Some(pin) => {
|
||||
crate::into_ref!(pin);
|
||||
pin.pin()
|
||||
}
|
||||
None => PCNT_PIN_NOT_USED,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'d> PcntDriver<'d> {
|
||||
pub fn new<PCNT: Pcnt>(
|
||||
_pcnt: impl Peripheral<P = PCNT> + 'd,
|
||||
pin0: Option<impl Peripheral<P = impl InputPin> + 'd>,
|
||||
pin1: Option<impl Peripheral<P = impl InputPin> + 'd>,
|
||||
pin2: Option<impl Peripheral<P = impl InputPin> + 'd>,
|
||||
pin3: Option<impl Peripheral<P = impl InputPin> + 'd>,
|
||||
) -> Result<Self, EspError> {
|
||||
// consume the pins and keep only the pin number.
|
||||
let pins = [
|
||||
pin_to_number!(pin0),
|
||||
pin_to_number!(pin1),
|
||||
pin_to_number!(pin2),
|
||||
pin_to_number!(pin3),
|
||||
];
|
||||
Ok(Self {
|
||||
unit: PCNT::unit(),
|
||||
pins,
|
||||
_p: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure Pulse Counter chanel
|
||||
/// @note
|
||||
/// This function will disable three events: PCNT_EVT_L_LIM, PCNT_EVT_H_LIM, PCNT_EVT_ZERO.
|
||||
///
|
||||
/// @param channel Channel to configure
|
||||
/// @param pulse_pin Pulse signal input pin
|
||||
/// @param ctrl_pin Control signal input pin
|
||||
/// @param pconfig Reference of PcntConfig
|
||||
///
|
||||
/// @note Set the signal input to PCNT_PIN_NOT_USED if unused.
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn channel_config(
|
||||
&mut self,
|
||||
channel: PcntChannel,
|
||||
pulse_pin: PinIndex,
|
||||
ctrl_pin: PinIndex,
|
||||
pconfig: &PcntChannelConfig,
|
||||
) -> Result<(), EspError> {
|
||||
let config = pcnt_config_t {
|
||||
pulse_gpio_num: self.pins[pulse_pin as usize],
|
||||
ctrl_gpio_num: self.pins[ctrl_pin as usize],
|
||||
lctrl_mode: pconfig.lctrl_mode.into(),
|
||||
hctrl_mode: pconfig.hctrl_mode.into(),
|
||||
pos_mode: pconfig.pos_mode.into(),
|
||||
neg_mode: pconfig.neg_mode.into(),
|
||||
counter_h_lim: pconfig.counter_h_lim,
|
||||
counter_l_lim: pconfig.counter_l_lim,
|
||||
channel: channel.into(),
|
||||
unit: self.unit,
|
||||
};
|
||||
|
||||
unsafe { esp!(pcnt_unit_config(&config as *const pcnt_config_t)) }
|
||||
}
|
||||
|
||||
/// Get pulse counter value
|
||||
///
|
||||
/// returns
|
||||
/// - i16
|
||||
/// - EspError
|
||||
pub fn get_counter_value(&self) -> Result<i16, EspError> {
|
||||
let mut value = 0i16;
|
||||
unsafe {
|
||||
esp!(pcnt_get_counter_value(self.unit, &mut value as *mut i16))?;
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Pause PCNT counter of PCNT unit
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn counter_pause(&self) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_counter_pause(self.unit)) }
|
||||
}
|
||||
|
||||
/// Resume counting for PCNT counter
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn counter_resume(&self) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_counter_resume(self.unit)) }
|
||||
}
|
||||
|
||||
/// Clear and reset PCNT counter value to zero
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn counter_clear(&self) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_counter_clear(self.unit)) }
|
||||
}
|
||||
|
||||
/// Enable PCNT interrupt for PCNT unit
|
||||
/// @note
|
||||
/// Each Pulse counter unit has five watch point events that share the same interrupt.
|
||||
/// Configure events with pcnt_event_enable() and pcnt_event_disable()
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn intr_enable(&self) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_intr_enable(self.unit)) }
|
||||
}
|
||||
|
||||
/// Disable PCNT interrupt for PCNT unit
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn intr_disable(&self) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_intr_disable(self.unit)) }
|
||||
}
|
||||
|
||||
/// Enable PCNT event of PCNT unit
|
||||
///
|
||||
/// @param evt_type Watch point event type.
|
||||
/// All enabled events share the same interrupt (one interrupt per pulse counter unit).
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn event_enable(&self, evt_type: PcntEvent) -> Result<(), EspError> {
|
||||
let evt_type: pcnt_evt_type_t = PcntEventType::only(evt_type).as_repr();
|
||||
unsafe { esp!(pcnt_event_enable(self.unit, evt_type)) }
|
||||
}
|
||||
|
||||
/// Disable PCNT event of PCNT unit
|
||||
///
|
||||
/// @param evt_type Watch point event type.
|
||||
/// All enabled events share the same interrupt (one interrupt per pulse counter unit).
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn event_disable(&self, evt_type: PcntEvent) -> Result<(), EspError> {
|
||||
let evt_type: pcnt_evt_type_t = PcntEventType::only(evt_type).as_repr();
|
||||
unsafe { esp!(pcnt_event_disable(self.unit, evt_type)) }
|
||||
}
|
||||
|
||||
fn only_one_event_type(evt_type: PcntEventType) -> Result<pcnt_evt_type_t, EspError> {
|
||||
match evt_type.iter().count() {
|
||||
1 => Ok(evt_type.as_repr()),
|
||||
_ => Err(EspError::from(ESP_ERR_INVALID_ARG as esp_err_t).unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set PCNT event value of PCNT unit
|
||||
///
|
||||
/// @param evt_type Watch point event type.
|
||||
/// All enabled events share the same interrupt (one interrupt per pulse counter unit).
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn set_event_value(&self, evt_type: PcntEventType, value: i16) -> Result<(), EspError> {
|
||||
let evt_type = Self::only_one_event_type(evt_type)?;
|
||||
unsafe { esp!(pcnt_set_event_value(self.unit, evt_type, value)) }
|
||||
}
|
||||
|
||||
/// Get PCNT event value of PCNT unit
|
||||
///
|
||||
/// @param evt_type Watch point event type.
|
||||
/// All enabled events share the same interrupt (one interrupt per pulse counter unit).
|
||||
///
|
||||
/// returns
|
||||
/// - i16
|
||||
/// - EspError
|
||||
pub fn get_event_value(&self, evt_type: PcntEventType) -> Result<i16, EspError> {
|
||||
let evt_type = Self::only_one_event_type(evt_type)?;
|
||||
let mut value = 0i16;
|
||||
unsafe {
|
||||
esp!(pcnt_get_event_value(
|
||||
self.unit,
|
||||
evt_type,
|
||||
&mut value as *mut i16
|
||||
))?;
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Get PCNT event status of PCNT unit
|
||||
///
|
||||
/// returns
|
||||
/// - i32
|
||||
/// - EspError
|
||||
// TODO: status is a bit field!
|
||||
pub fn get_event_status(&self) -> Result<u32, EspError> {
|
||||
let mut value = 0u32;
|
||||
unsafe {
|
||||
esp!(pcnt_get_event_status(self.unit, &mut value as *mut u32))?;
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
// TODO: not implementing until we can do it safely! Will need to reconfigure channels?
|
||||
//
|
||||
// /// Configure PCNT pulse signal input pin and control input pin
|
||||
// ///
|
||||
// /// @param channel PcntChannel
|
||||
// /// @param pulse_io Pulse signal input pin
|
||||
// /// @param ctrl_io Control signal input pin
|
||||
// ///
|
||||
// /// @note Set the signal input to PCNT_PIN_NOT_USED if unused.
|
||||
// ///
|
||||
// /// returns
|
||||
// /// - ()
|
||||
// /// - EspError
|
||||
// pub fn set_pin<'a>(
|
||||
// &mut self,
|
||||
// channel: PcntChannel,
|
||||
// pulse_pin: Option<impl Peripheral<P = impl InputPin> + 'a>,
|
||||
// ctrl_pin: Option<impl Peripheral<P = impl InputPin> + 'a>,
|
||||
// ) -> Result<(), EspError> {
|
||||
// }
|
||||
|
||||
/// Enable PCNT input filter
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn filter_enable(&self) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_filter_enable(self.unit)) }
|
||||
}
|
||||
|
||||
/// Disable PCNT input filter
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn filter_disable(&self) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_filter_disable(self.unit)) }
|
||||
}
|
||||
|
||||
/// Set PCNT filter value
|
||||
///
|
||||
/// @param filter_val PCNT signal filter value, counter in APB_CLK cycles.
|
||||
/// Any pulses lasting shorter than this will be ignored when the filter is enabled.
|
||||
/// @note
|
||||
/// filter_val is a 10-bit value, so the maximum filter_val should be limited to 1023.
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn set_filter_value(&self, value: u16) -> Result<(), EspError> {
|
||||
unsafe { esp!(pcnt_set_filter_value(self.unit, value)) }
|
||||
}
|
||||
|
||||
/// Get PCNT filter value
|
||||
///
|
||||
/// returns
|
||||
/// - i16
|
||||
/// - EspError
|
||||
pub fn get_filter_value(&self) -> Result<u16, EspError> {
|
||||
let mut value = 0u16;
|
||||
unsafe {
|
||||
esp!(pcnt_get_filter_value(self.unit, &mut value as *mut u16))?;
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Set PCNT counter mode
|
||||
///
|
||||
/// @param channel PCNT channel number
|
||||
/// @param pos_mode Counter mode when detecting positive edge
|
||||
/// @param neg_mode Counter mode when detecting negative edge
|
||||
/// @param hctrl_mode Counter mode when control signal is high level
|
||||
/// @param lctrl_mode Counter mode when control signal is low level
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
pub fn set_mode(
|
||||
&self,
|
||||
channel: PcntChannel,
|
||||
pos_mode: PcntCountMode,
|
||||
neg_mode: PcntCountMode,
|
||||
hctrl_mode: PcntControlMode,
|
||||
lctrl_mode: PcntControlMode,
|
||||
) -> Result<(), EspError> {
|
||||
unsafe {
|
||||
esp!(pcnt_set_mode(
|
||||
self.unit,
|
||||
channel.into(),
|
||||
pos_mode.into(),
|
||||
neg_mode.into(),
|
||||
hctrl_mode.into(),
|
||||
lctrl_mode.into()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Add ISR handler for specified unit.
|
||||
///
|
||||
/// This ISR handler will be called from an ISR. So there is a stack
|
||||
/// size limit (configurable as \"ISR stack size\" in menuconfig). This
|
||||
/// limit is smaller compared to a global PCNT interrupt handler due
|
||||
/// to the additional level of indirection.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones)
|
||||
/// in the callback passed to this function, as it is executed in an ISR context.
|
||||
///
|
||||
/// @param callback Interrupt handler function.
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
#[cfg(feature = "alloc")]
|
||||
pub unsafe fn subscribe<C>(&self, callback: C) -> Result<(), EspError>
|
||||
where
|
||||
C: FnMut(u32) + Send + 'static,
|
||||
{
|
||||
enable_isr_service()?;
|
||||
|
||||
self.unsubscribe()?;
|
||||
let callback: alloc::boxed::Box<dyn FnMut(u32) + 'static> =
|
||||
alloc::boxed::Box::new(callback);
|
||||
ISR_HANDLERS[self.unit as usize] = Some(callback);
|
||||
esp!(pcnt_isr_handler_add(
|
||||
self.unit,
|
||||
Some(Self::handle_isr),
|
||||
self.unit as *mut core::ffi::c_void,
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove ISR handler for specified unit.
|
||||
///
|
||||
/// returns
|
||||
/// - ()
|
||||
/// - EspError
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn unsubscribe(&self) -> Result<(), EspError> {
|
||||
unsafe {
|
||||
esp!(pcnt_isr_handler_remove(self.unit))?;
|
||||
ISR_HANDLERS[self.unit as usize] = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
unsafe extern "C" fn handle_isr(data: *mut core::ffi::c_void) {
|
||||
let unit = data as pcnt_unit_t;
|
||||
if let Some(f) = &mut ISR_HANDLERS[unit as usize] {
|
||||
let mut value = 0u32;
|
||||
esp!(pcnt_get_event_status(unit, &mut value as *mut u32))
|
||||
.expect("failed to fetch event status!");
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PcntDriver<'_> {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.counter_pause();
|
||||
let _ = self.intr_disable();
|
||||
#[cfg(feature = "alloc")]
|
||||
unsafe {
|
||||
pcnt_isr_handler_remove(self.unit);
|
||||
ISR_HANDLERS[self.unit as usize] = None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
static ISR_SERVICE_ENABLED: core::sync::atomic::AtomicBool =
|
||||
core::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
static PCNT_CS: crate::task::CriticalSection = crate::task::CriticalSection::new();
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
fn enable_isr_service() -> Result<(), EspError> {
|
||||
use core::sync::atomic::Ordering;
|
||||
|
||||
if !ISR_SERVICE_ENABLED.load(Ordering::SeqCst) {
|
||||
let _cs = PCNT_CS.enter();
|
||||
|
||||
if !ISR_SERVICE_ENABLED.load(Ordering::SeqCst) {
|
||||
esp!(unsafe { pcnt_isr_service_install(0) })?;
|
||||
|
||||
ISR_SERVICE_ENABLED.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
type IsrHandler = Option<Box<dyn FnMut(u32)>>;
|
||||
#[cfg(feature = "alloc")]
|
||||
static mut ISR_HANDLERS: [IsrHandler; pcnt_unit_t_PCNT_UNIT_MAX as usize] = [
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(esp32)]
|
||||
None,
|
||||
#[cfg(esp32)]
|
||||
None,
|
||||
#[cfg(esp32)]
|
||||
None,
|
||||
#[cfg(esp32)]
|
||||
None,
|
||||
];
|
||||
|
||||
pub trait Pcnt {
|
||||
fn unit() -> pcnt_unit_t;
|
||||
}
|
||||
|
||||
macro_rules! impl_pcnt {
|
||||
($pcnt:ident: $unit:expr) => {
|
||||
crate::impl_peripheral!($pcnt);
|
||||
|
||||
impl Pcnt for $pcnt {
|
||||
#[inline(always)]
|
||||
fn unit() -> pcnt_unit_t {
|
||||
$unit
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_pcnt!(PCNT0: pcnt_unit_t_PCNT_UNIT_0);
|
||||
impl_pcnt!(PCNT1: pcnt_unit_t_PCNT_UNIT_1);
|
||||
impl_pcnt!(PCNT2: pcnt_unit_t_PCNT_UNIT_2);
|
||||
impl_pcnt!(PCNT3: pcnt_unit_t_PCNT_UNIT_3);
|
||||
#[cfg(esp32)]
|
||||
impl_pcnt!(PCNT4: pcnt_unit_t_PCNT_UNIT_4);
|
||||
#[cfg(esp32)]
|
||||
impl_pcnt!(PCNT5: pcnt_unit_t_PCNT_UNIT_5);
|
||||
#[cfg(esp32)]
|
||||
impl_pcnt!(PCNT6: pcnt_unit_t_PCNT_UNIT_6);
|
||||
#[cfg(esp32)]
|
||||
impl_pcnt!(PCNT7: pcnt_unit_t_PCNT_UNIT_7);
|
@ -13,6 +13,8 @@ use crate::ledc;
|
||||
use crate::mac;
|
||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||
use crate::modem;
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
use crate::pcnt;
|
||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||
use crate::rmt;
|
||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||
@ -59,6 +61,22 @@ pub struct Peripherals {
|
||||
pub spi3: spi::SPI3,
|
||||
pub adc1: adc::ADC1,
|
||||
pub adc2: adc::ADC2,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pub pcnt0: pcnt::PCNT0,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pub pcnt1: pcnt::PCNT1,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pub pcnt2: pcnt::PCNT2,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pub pcnt3: pcnt::PCNT3,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pub pcnt4: pcnt::PCNT4,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pub pcnt5: pcnt::PCNT5,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pub pcnt6: pcnt::PCNT6,
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pub pcnt7: pcnt::PCNT7,
|
||||
#[cfg(all(esp32, esp_idf_version_major = "4"))]
|
||||
pub hall_sensor: crate::hall::HallSensor,
|
||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||
@ -178,6 +196,22 @@ impl Peripherals {
|
||||
spi3: spi::SPI3::new(),
|
||||
adc1: adc::ADC1::new(),
|
||||
adc2: adc::ADC2::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pcnt0: pcnt::PCNT0::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pcnt1: pcnt::PCNT1::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pcnt2: pcnt::PCNT2::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||
pcnt3: pcnt::PCNT3::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pcnt4: pcnt::PCNT4::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pcnt5: pcnt::PCNT5::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pcnt6: pcnt::PCNT6::new(),
|
||||
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32)))]
|
||||
pcnt7: pcnt::PCNT7::new(),
|
||||
#[cfg(all(esp32, esp_idf_version_major = "4"))]
|
||||
hall_sensor: crate::hall::HallSensor::new(),
|
||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||
|
Loading…
x
Reference in New Issue
Block a user