mirror of
https://github.com/esp-rs/esp-idf-hal.git
synced 2025-09-26 20:00:35 +00:00

* 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
154 lines
5.2 KiB
Rust
154 lines
5.2 KiB
Rust
//! 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)
|
||
}
|
||
}
|
||
}
|