esp-idf-hal/examples/pcnt_rotary_encoder.rs
ivmarkov 2af4695aac
Remove Peripheral/PeripheralRef (#529)
* Remove Peripheral/PeripheralRef

* Make ADC unit and channel structs more readable by avoiding const generics

* Remove unused parameter

* fix forgotten reference to the old ADCU syntax

* Mention that we have removed the prelude module

* try_into methods for changing the pin type
2025-09-03 14:13:32 +03:00

154 lines
5.2 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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 i32 value result
//!
#![allow(unknown_lints)]
#![allow(unexpected_cfgs)]
#[cfg(not(esp_idf_version_at_least_6_0_0))]
#[cfg(any(esp32, esp32s2, esp32s3))]
fn main() -> anyhow::Result<()> {
use anyhow::Context;
use encoder::Encoder;
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::peripherals::Peripherals;
// 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_hal::sys::link_patches();
println!("setup pins");
let peripherals = Peripherals::take().context("failed to take Peripherals")?;
let pin_a = peripherals.pins.gpio4;
let pin_b = peripherals.pins.gpio5;
println!("setup encoder");
let encoder = Encoder::new(peripherals.pcnt0, pin_a, pin_b)?;
let mut last_value = 0i32;
loop {
let value = encoder.get_value()?;
if value != last_value {
println!("value: {value}");
last_value = value;
}
FreeRtos::delay_ms(100u32);
}
}
#[cfg(any(esp_idf_version_at_least_6_0_0, not(any(esp32, esp32s2, esp32s3))))]
fn main() {
use esp_idf_hal::delay::FreeRtos;
#[cfg(not(any(esp32, esp32s2, esp32s3)))]
println!("PCNT is not supported on this device");
#[cfg(esp_idf_version_at_least_6_0_0)]
println!("PCNT is not yet available when building against ESP-IDF 6.0+");
loop {
FreeRtos::delay_ms(100u32);
}
}
#[cfg(not(esp_idf_version_at_least_6_0_0))]
#[cfg(any(esp32, esp32s2, esp32s3))]
// esp-idf encoder implementation using v4 pcnt api
mod encoder {
use std::cmp::min;
use std::sync::atomic::AtomicI32;
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_sys::EspError;
const LOW_LIMIT: i16 = -100;
const HIGH_LIMIT: i16 = 100;
pub struct Encoder<'d> {
unit: PcntDriver<'d>,
approx_value: Arc<AtomicI32>,
}
impl<'d> Encoder<'d> {
pub fn new(
pcnt: impl Pcnt + 'd,
pin_a: impl InputPin + 'd,
pin_b: 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,
&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,
&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(AtomicI32::new(0));
// unsafe interrupt code to catch the upper and lower limits from the encoder
// and track the overflow in `value: Arc<AtomicI32>` - 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 i32, Ordering::SeqCst);
}
if status.contains(PcntEvent::LowLimit) {
approx_value.fetch_add(LOW_LIMIT as i32, 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<i32, EspError> {
let value =
self.approx_value.load(Ordering::Relaxed) + self.unit.get_counter_value()? as i32;
Ok(value)
}
}
}