mirror of
https://github.com/esp-rs/esp-idf-hal.git
synced 2025-09-28 21:01:26 +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;
|
pub mod mac;
|
||||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||||
pub mod modem;
|
pub mod modem;
|
||||||
|
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||||
|
pub mod pcnt;
|
||||||
pub mod peripheral;
|
pub mod peripheral;
|
||||||
pub mod peripherals;
|
pub mod peripherals;
|
||||||
pub mod prelude;
|
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;
|
use crate::mac;
|
||||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||||
use crate::modem;
|
use crate::modem;
|
||||||
|
#[cfg(all(not(feature = "riscv-ulp-hal"), any(esp32, esp32s2, esp32s3)))]
|
||||||
|
use crate::pcnt;
|
||||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||||
use crate::rmt;
|
use crate::rmt;
|
||||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||||
@ -59,6 +61,22 @@ pub struct Peripherals {
|
|||||||
pub spi3: spi::SPI3,
|
pub spi3: spi::SPI3,
|
||||||
pub adc1: adc::ADC1,
|
pub adc1: adc::ADC1,
|
||||||
pub adc2: adc::ADC2,
|
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"))]
|
#[cfg(all(esp32, esp_idf_version_major = "4"))]
|
||||||
pub hall_sensor: crate::hall::HallSensor,
|
pub hall_sensor: crate::hall::HallSensor,
|
||||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||||
@ -178,6 +196,22 @@ impl Peripherals {
|
|||||||
spi3: spi::SPI3::new(),
|
spi3: spi::SPI3::new(),
|
||||||
adc1: adc::ADC1::new(),
|
adc1: adc::ADC1::new(),
|
||||||
adc2: adc::ADC2::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"))]
|
#[cfg(all(esp32, esp_idf_version_major = "4"))]
|
||||||
hall_sensor: crate::hall::HallSensor::new(),
|
hall_sensor: crate::hall::HallSensor::new(),
|
||||||
#[cfg(not(feature = "riscv-ulp-hal"))]
|
#[cfg(not(feature = "riscv-ulp-hal"))]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user