diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml
index bb9331e300e..e8558e381a2 100644
--- a/esp-hal-common/Cargo.toml
+++ b/esp-hal-common/Cargo.toml
@@ -51,11 +51,11 @@ ufmt-write = { version = "0.1.0", optional = true }
# Each supported device MUST have its PAC included below along with a
# corresponding feature. We rename the PAC packages because we cannot
# have dependencies and features with the same names.
-esp32 = { version = "0.18.0", features = ["critical-section"], optional = true }
-esp32c2 = { version = "0.5.1", features = ["critical-section"], optional = true }
+esp32 = { version = "0.19.0", features = ["critical-section"], optional = true }
+esp32c2 = { version = "0.6.0", features = ["critical-section"], optional = true }
esp32c3 = { version = "0.9.0", features = ["critical-section"], optional = true }
-esp32s2 = { version = "0.8.0", features = ["critical-section"], optional = true }
-esp32s3 = { version = "0.12.0", features = ["critical-section"], optional = true }
+esp32s2 = { version = "0.9.0", features = ["critical-section"], optional = true }
+esp32s3 = { version = "0.13.0", features = ["critical-section"], optional = true }
[features]
esp32 = ["esp32/rt" , "xtensa", "xtensa-lx/esp32", "xtensa-lx-rt/esp32", "lock_api"]
diff --git a/esp-hal-common/src/gpio/esp32.rs b/esp-hal-common/src/gpio/esp32.rs
index 0c7831c862f..bd9822852c4 100644
--- a/esp-hal-common/src/gpio/esp32.rs
+++ b/esp-hal-common/src/gpio/esp32.rs
@@ -116,26 +116,26 @@ pub enum InputSignal {
PWM0_F2 = 36,
GPIO_BT_ACTIVE = 37,
GPIO_BT_PRIORITY = 38,
- PCNT_SIG_CH0_0 = 39,
- PCNT_SIG_CH1_0 = 40,
- PCNT_CTRL_CH0_0 = 41,
- PCNT_CTRL_CH1_0 = 42,
- PCNT_SIG_CH0_1 = 43,
- PCNT_SIG_CH1_1 = 44,
- PCNT_CTRL_CH0_1 = 45,
- PCNT_CTRL_CH1_1 = 46,
- PCNT_SIG_CH0_2 = 47,
- PCNT_SIG_CH1_2 = 48,
- PCNT_CTRL_CH0_2 = 49,
- PCNT_CTRL_CH1_2 = 50,
- PCNT_SIG_CH0_3 = 51,
- PCNT_SIG_CH1_3 = 52,
- PCNT_CTRL_CH0_3 = 53,
- PCNT_CTRL_CH1_3 = 54,
- PCNT_SIG_CH0_4 = 55,
- PCNT_SIG_CH1_4 = 56,
- PCNT_CTRL_CH0_4 = 57,
- PCNT_CTRL_CH1_4 = 58,
+ PCNT0_SIG_CH0 = 39,
+ PCNT0_SIG_CH1 = 40,
+ PCNT0_CTRL_CH0 = 41,
+ PCNT0_CTRL_CH1 = 42,
+ PCNT1_SIG_CH0 = 43,
+ PCNT1_SIG_CH1 = 44,
+ PCNT1_CTRL_CH0 = 45,
+ PCNT1_CTRL_CH1 = 46,
+ PCNT2_SIG_CH0 = 47,
+ PCNT2_SIG_CH1 = 48,
+ PCNT2_CTRL_CH0 = 49,
+ PCNT2_CTRL_CH1 = 50,
+ PCNT3_SIG_CH0 = 51,
+ PCNT3_SIG_CH1 = 52,
+ PCNT3_CTRL_CH0 = 53,
+ PCNT3_CTRL_CH1 = 54,
+ PCNT4_SIG_CH0 = 55,
+ PCNT4_SIG_CH1 = 56,
+ PCNT4_CTRL_CH0 = 57,
+ PCNT4_CTRL_CH1 = 58,
HSPICS1 = 61,
HSPICS2 = 62,
VSPICLK = 63,
@@ -146,18 +146,18 @@ pub enum InputSignal {
VSPICS0 = 68,
VSPICS1 = 69,
VSPICS2 = 70,
- PCNT_SIG_CH0_5 = 71,
- PCNT_SIG_CH1_5 = 72,
- PCNT_CTRL_CH0_5 = 73,
- PCNT_CTRL_CH1_5 = 74,
- PCNT_SIG_CH0_6 = 75,
- PCNT_SIG_CH1_6 = 76,
- PCNT_CTRL_CH0_6 = 77,
- PCNT_CTRL_CH1_6 = 78,
- PCNT_SIG_CH0_7 = 79,
- PCNT_SIG_CH1_7 = 80,
- PCNT_CTRL_CH0_7 = 81,
- PCNT_CTRL_CH1_7 = 82,
+ PCNT5_SIG_CH0 = 71,
+ PCNT5_SIG_CH1 = 72,
+ PCNT5_CTRL_CH0 = 73,
+ PCNT5_CTRL_CH1 = 74,
+ PCNT6_SIG_CH0 = 75,
+ PCNT6_SIG_CH1 = 76,
+ PCNT6_CTRL_CH0 = 77,
+ PCNT6_CTRL_CH1 = 78,
+ PCNT7_SIG_CH0 = 79,
+ PCNT7_SIG_CH1 = 80,
+ PCNT7_CTRL_CH0 = 81,
+ PCNT7_CTRL_CH1 = 82,
RMT_SIG_0 = 83,
RMT_SIG_1 = 84,
RMT_SIG_2 = 85,
diff --git a/esp-hal-common/src/gpio/esp32s2.rs b/esp-hal-common/src/gpio/esp32s2.rs
index f4ac7cb7636..ed2959cfbdb 100644
--- a/esp-hal-common/src/gpio/esp32s2.rs
+++ b/esp-hal-common/src/gpio/esp32s2.rs
@@ -102,6 +102,22 @@ pub enum InputSignal {
I2S0I_WS = 28,
I2CEXT0_SCL = 29,
I2CEXT0_SDA = 30,
+ PCNT0_SIG_CH0 = 39,
+ PCNT0_SIG_CH1 = 40,
+ PCNT0_CTRL_CH0 = 41,
+ PCNT0_CTRL_CH1 = 42,
+ PCNT1_SIG_CH0 = 43,
+ PCNT1_SIG_CH1 = 44,
+ PCNT1_CTRL_CH0 = 45,
+ PCNT1_CTRL_CH1 = 46,
+ PCNT2_SIG_CH0 = 47,
+ PCNT2_SIG_CH1 = 48,
+ PCNT2_CTRL_CH0 = 49,
+ PCNT2_CTRL_CH1 = 50,
+ PCNT3_SIG_CH0 = 51,
+ PCNT3_SIG_CH1 = 52,
+ PCNT3_CTRL_CH0 = 53,
+ PCNT3_CTRL_CH1 = 54,
USB_OTG_IDDIG = 64,
USB_OTG_AVALID = 65,
USB_SRP_BVALID = 66,
diff --git a/esp-hal-common/src/gpio/esp32s3.rs b/esp-hal-common/src/gpio/esp32s3.rs
index c4437d8ba5e..b63d80e67ac 100644
--- a/esp-hal-common/src/gpio/esp32s3.rs
+++ b/esp-hal-common/src/gpio/esp32s3.rs
@@ -63,6 +63,22 @@ pub enum InputSignal {
I2S1I_SD = 30,
I2S1I_BCK = 31,
I2S1I_WS = 32,
+ PCNT0_SIG_CH0 = 33,
+ PCNT0_SIG_CH1 = 34,
+ PCNT0_CTRL_CH0 = 35,
+ PCNT0_CTRL_CH1 = 36,
+ PCNT1_SIG_CH0 = 37,
+ PCNT1_SIG_CH1 = 38,
+ PCNT1_CTRL_CH0 = 39,
+ PCNT1_CTRL_CH1 = 40,
+ PCNT2_SIG_CH0 = 41,
+ PCNT2_SIG_CH1 = 42,
+ PCNT2_CTRL_CH0 = 43,
+ PCNT2_CTRL_CH1 = 44,
+ PCNT3_SIG_CH0 = 45,
+ PCNT3_SIG_CH1 = 46,
+ PCNT3_CTRL_CH0 = 47,
+ PCNT3_CTRL_CH1 = 48,
I2S0I_SD1 = 51,
I2S0I_SD2 = 52,
I2S0I_SD3 = 53,
diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs
index f5a2f812121..cb8fbe00f6a 100644
--- a/esp-hal-common/src/lib.rs
+++ b/esp-hal-common/src/lib.rs
@@ -60,6 +60,8 @@ pub mod ledc;
pub mod mcpwm;
#[cfg(usb_otg)]
pub mod otg_fs;
+#[cfg(any(esp32, esp32s2, esp32s3))]
+pub mod pcnt;
pub mod peripheral;
pub mod prelude;
#[cfg(rmt)]
diff --git a/esp-hal-common/src/pcnt/channel.rs b/esp-hal-common/src/pcnt/channel.rs
new file mode 100644
index 00000000000..00cf3492c1d
--- /dev/null
+++ b/esp-hal-common/src/pcnt/channel.rs
@@ -0,0 +1,243 @@
+use super::unit;
+use crate::{
+ gpio::{
+ types::{InputSignal, ONE_INPUT, ZERO_INPUT},
+ InputPin,
+ },
+ peripheral::Peripheral,
+ peripherals::GPIO,
+};
+
+/// Channel number
+#[derive(PartialEq, Eq, Copy, Clone, Debug)]
+pub enum Number {
+ Channel0,
+ Channel1,
+}
+
+/// PCNT channel action on signal edge
+#[derive(Debug, Copy, Clone, Default)]
+pub enum EdgeMode {
+ /// Hold current count value
+ Hold = 0,
+ /// Increase count value
+ #[default]
+ Increment = 1,
+ /// Decrease count value
+ Decrement = 2,
+}
+
+/// PCNT channel action on control level
+#[derive(Debug, Copy, Clone, Default)]
+pub enum CtrlMode {
+ /// Keep current count mode
+ Keep = 0,
+ /// Invert current count mode (increase -> decrease, decrease -> increase)
+ #[default]
+ Reverse = 1,
+ /// Hold current count value
+ Disable = 2,
+}
+
+/// Pulse Counter configuration for a single channel
+#[derive(Debug, Copy, Clone, Default)]
+pub struct Config {
+ /// PCNT low control mode
+ pub lctrl_mode: CtrlMode,
+ /// PCNT high control mode
+ pub hctrl_mode: CtrlMode,
+ /// PCNT signal positive edge count mode
+ pub pos_edge: EdgeMode,
+ /// PCNT signal negative edge count mode
+ pub neg_edge: EdgeMode,
+ pub invert_ctrl: bool,
+ pub invert_sig: bool,
+}
+
+/// PcntPin can be always high, always low, or an actual pin
+#[derive(Clone, Copy)]
+pub struct PcntSource {
+ source: u8,
+}
+
+impl PcntSource {
+ pub fn from_pin<'a, P: InputPin>(pin: impl Peripheral
+ 'a) -> Self {
+ crate::into_ref!(pin);
+ Self {
+ source: pin.number(),
+ }
+ }
+ pub fn always_high() -> Self {
+ Self { source: ONE_INPUT }
+ }
+ pub fn always_low() -> Self {
+ Self { source: ZERO_INPUT }
+ }
+}
+
+pub struct Channel {
+ unit: unit::Number,
+ channel: Number,
+}
+
+impl Channel {
+ /// return a new Channel
+ pub(super) fn new(unit: unit::Number, channel: Number) -> Self {
+ Self { unit, channel }
+ }
+
+ /// Configure the channel
+ pub fn configure(&mut self, ctrl_signal: PcntSource, edge_signal: PcntSource, config: Config) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ let conf0 = match self.unit {
+ unit::Number::Unit0 => &pcnt.u0_conf0,
+ unit::Number::Unit1 => &pcnt.u1_conf0,
+ unit::Number::Unit2 => &pcnt.u2_conf0,
+ unit::Number::Unit3 => &pcnt.u3_conf0,
+ #[cfg(esp32)]
+ unit::Number::Unit4 => &pcnt.u4_conf0,
+ #[cfg(esp32)]
+ unit::Number::Unit5 => &pcnt.u5_conf0,
+ #[cfg(esp32)]
+ unit::Number::Unit6 => &pcnt.u6_conf0,
+ #[cfg(esp32)]
+ unit::Number::Unit7 => &pcnt.u7_conf0,
+ };
+ match self.channel {
+ Number::Channel0 => {
+ conf0.modify(|_, w| unsafe {
+ w.ch0_hctrl_mode()
+ .bits(config.hctrl_mode as u8)
+ .ch0_lctrl_mode()
+ .bits(config.lctrl_mode as u8)
+ .ch0_neg_mode()
+ .bits(config.neg_edge as u8)
+ .ch0_pos_mode()
+ .bits(config.pos_edge as u8)
+ });
+ }
+ Number::Channel1 => {
+ conf0.modify(|_, w| unsafe {
+ w.ch1_hctrl_mode()
+ .bits(config.hctrl_mode as u8)
+ .ch1_lctrl_mode()
+ .bits(config.lctrl_mode as u8)
+ .ch1_neg_mode()
+ .bits(config.neg_edge as u8)
+ .ch1_pos_mode()
+ .bits(config.pos_edge as u8)
+ });
+ }
+ }
+ self.set_ctrl_signal(ctrl_signal, config.invert_ctrl);
+ self.set_edge_signal(edge_signal, config.invert_sig);
+ }
+
+ /// Set the control signal (pin/high/low) for this channel
+ pub fn set_ctrl_signal(&self, source: PcntSource, invert: bool) -> &Self {
+ let signal = match self.unit {
+ unit::Number::Unit0 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT0_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT0_CTRL_CH1,
+ },
+ unit::Number::Unit1 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT1_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT1_CTRL_CH1,
+ },
+ unit::Number::Unit2 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT2_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT2_CTRL_CH1,
+ },
+ unit::Number::Unit3 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT3_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT3_CTRL_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit4 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT4_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT4_CTRL_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit5 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT5_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT5_CTRL_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit6 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT6_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT6_CTRL_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit7 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT7_CTRL_CH0,
+ Number::Channel1 => InputSignal::PCNT7_CTRL_CH1,
+ },
+ };
+
+ if (signal as usize) <= crate::types::INPUT_SIGNAL_MAX as usize {
+ unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe {
+ w.sel()
+ .set_bit()
+ .in_inv_sel()
+ .bit(invert)
+ .in_sel()
+ .bits(source.source)
+ });
+ }
+ self
+ }
+
+ /// Set the edge signal (pin/high/low) for this channel
+ pub fn set_edge_signal(&self, source: PcntSource, invert: bool) -> &Self {
+ let signal = match self.unit {
+ unit::Number::Unit0 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT0_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT0_SIG_CH1,
+ },
+ unit::Number::Unit1 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT1_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT1_SIG_CH1,
+ },
+ unit::Number::Unit2 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT2_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT2_SIG_CH1,
+ },
+ unit::Number::Unit3 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT3_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT3_SIG_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit4 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT4_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT4_SIG_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit5 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT5_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT5_SIG_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit6 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT6_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT6_SIG_CH1,
+ },
+ #[cfg(esp32)]
+ unit::Number::Unit7 => match self.channel {
+ Number::Channel0 => InputSignal::PCNT7_SIG_CH0,
+ Number::Channel1 => InputSignal::PCNT7_SIG_CH1,
+ },
+ };
+
+ if (signal as usize) <= crate::types::INPUT_SIGNAL_MAX as usize {
+ unsafe { &*GPIO::PTR }.func_in_sel_cfg[signal as usize].modify(|_, w| unsafe {
+ w.sel()
+ .set_bit()
+ .in_inv_sel()
+ .bit(invert)
+ .in_sel()
+ .bits(source.source)
+ });
+ }
+ self
+ }
+}
diff --git a/esp-hal-common/src/pcnt/mod.rs b/esp-hal-common/src/pcnt/mod.rs
new file mode 100644
index 00000000000..28d27cffe43
--- /dev/null
+++ b/esp-hal-common/src/pcnt/mod.rs
@@ -0,0 +1,30 @@
+use self::unit::Unit;
+use crate::{
+ peripheral::{Peripheral, PeripheralRef},
+ system::PeripheralClockControl,
+};
+
+pub mod channel;
+pub mod unit;
+
+pub struct PCNT<'d> {
+ _instance: PeripheralRef<'d, crate::peripherals::PCNT>,
+}
+
+impl<'d> PCNT<'d> {
+ /// Return a new PCNT
+ pub fn new(
+ _instance: impl Peripheral
+ 'd,
+ peripheral_clock_control: &mut PeripheralClockControl,
+ ) -> Self {
+ crate::into_ref!(_instance);
+ // Enable the PCNT peripherals clock in the system peripheral
+ peripheral_clock_control.enable(crate::system::Peripheral::Pcnt);
+ PCNT { _instance }
+ }
+
+ /// Return a unit
+ pub fn get_unit(&self, number: unit::Number) -> Unit {
+ Unit::new(number)
+ }
+}
diff --git a/esp-hal-common/src/pcnt/unit.rs b/esp-hal-common/src/pcnt/unit.rs
new file mode 100644
index 00000000000..9703d9caa6f
--- /dev/null
+++ b/esp-hal-common/src/pcnt/unit.rs
@@ -0,0 +1,392 @@
+use critical_section::CriticalSection;
+
+use super::channel;
+
+/// Unit number
+#[derive(PartialEq, Eq, Copy, Clone, Debug)]
+pub enum Number {
+ Unit0,
+ Unit1,
+ Unit2,
+ Unit3,
+ #[cfg(esp32)]
+ Unit4,
+ #[cfg(esp32)]
+ Unit5,
+ #[cfg(esp32)]
+ Unit6,
+ #[cfg(esp32)]
+ Unit7,
+}
+
+/// Unit errors
+#[derive(Debug)]
+pub enum Error {
+ /// Invalid filter threshold value
+ InvalidFilterThresh,
+ /// Invalid low limit - must be < 0
+ InvalidLowLimit,
+ /// Invalid high limit - must be > 0
+ InvalidHighLimit,
+}
+
+/// the current status of the counter.
+#[derive(Copy, Clone, Debug, Default)]
+pub enum ZeroMode {
+ /// pulse counter decreases from positive to 0.
+ #[default]
+ PosZero = 0,
+ /// pulse counter increases from negative to 0
+ NegZero = 1,
+ /// pulse counter is negative (not implemented?)
+ Negitive = 2,
+ /// pulse counter is positive (not implemented?)
+ Positive = 3,
+}
+
+impl From for ZeroMode {
+ fn from(value: u8) -> Self {
+ match value {
+ 0 => Self::PosZero,
+ 1 => Self::NegZero,
+ 2 => Self::Negitive,
+ 3 => Self::Positive,
+ _ => unreachable!(), // TODO: is this good enoough? should we use some default?
+ }
+ }
+}
+
+// Events
+#[derive(Copy, Clone, Debug, Default)]
+pub struct Events {
+ pub low_limit: bool,
+ pub high_limit: bool,
+ pub thresh0: bool,
+ pub thresh1: bool,
+ pub zero: bool,
+}
+
+/// Unit configuration
+#[derive(Copy, Clone, Default)]
+pub struct Config {
+ pub low_limit: i16,
+ pub high_limit: i16,
+ pub thresh0: i16,
+ pub thresh1: i16,
+ pub filter: Option,
+}
+
+pub struct Unit {
+ number: Number,
+}
+
+impl Unit {
+ /// return a new Unit
+ pub(super) fn new(number: Number) -> Self {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ let conf0 = match number {
+ Number::Unit0 => &pcnt.u0_conf0,
+ Number::Unit1 => &pcnt.u1_conf0,
+ Number::Unit2 => &pcnt.u2_conf0,
+ Number::Unit3 => &pcnt.u3_conf0,
+ #[cfg(esp32)]
+ Number::Unit4 => &pcnt.u4_conf0,
+ #[cfg(esp32)]
+ Number::Unit5 => &pcnt.u5_conf0,
+ #[cfg(esp32)]
+ Number::Unit6 => &pcnt.u6_conf0,
+ #[cfg(esp32)]
+ Number::Unit7 => &pcnt.u7_conf0,
+ };
+ // disable filter and all events
+ conf0.modify(|_, w| unsafe {
+ w.filter_en()
+ .clear_bit()
+ .filter_thres()
+ .bits(0)
+ .thr_l_lim_en()
+ .clear_bit()
+ .thr_h_lim_en()
+ .clear_bit()
+ .thr_thres0_en()
+ .clear_bit()
+ .thr_thres1_en()
+ .clear_bit()
+ .thr_zero_en()
+ .clear_bit()
+ });
+ Self { number }
+ }
+
+ pub fn configure(&mut self, config: Config) -> Result<(), Error> {
+ // low limit must be >= or the limit is -32768 and when thats
+ // hit the event status claims it was the high limit.
+ // tested on an esp32s3
+ if config.low_limit >= 0 {
+ return Err(Error::InvalidLowLimit);
+ }
+ if config.high_limit <= 0 {
+ return Err(Error::InvalidHighLimit);
+ }
+ let (filter_en, filter) = match config.filter {
+ Some(filter) => (true, filter),
+ None => (false, 0),
+ };
+ // filter must be less than 1024
+ if filter > 1023 {
+ return Err(Error::InvalidFilterThresh);
+ }
+
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ let (conf0, conf1, conf2) = match self.number {
+ Number::Unit0 => (&pcnt.u0_conf0, &pcnt.u0_conf1, &pcnt.u0_conf2),
+ Number::Unit1 => (&pcnt.u1_conf0, &pcnt.u1_conf1, &pcnt.u1_conf2),
+ Number::Unit2 => (&pcnt.u2_conf0, &pcnt.u2_conf1, &pcnt.u2_conf2),
+ Number::Unit3 => (&pcnt.u3_conf0, &pcnt.u3_conf1, &pcnt.u3_conf2),
+ #[cfg(esp32)]
+ Number::Unit4 => (&pcnt.u4_conf0, &pcnt.u4_conf1, &pcnt.u4_conf2),
+ #[cfg(esp32)]
+ Number::Unit5 => (&pcnt.u5_conf0, &pcnt.u5_conf1, &pcnt.u5_conf2),
+ #[cfg(esp32)]
+ Number::Unit6 => (&pcnt.u6_conf0, &pcnt.u6_conf1, &pcnt.u6_conf2),
+ #[cfg(esp32)]
+ Number::Unit7 => (&pcnt.u7_conf0, &pcnt.u7_conf1, &pcnt.u7_conf2),
+ };
+ conf2.write(|w| unsafe {
+ w.cnt_l_lim()
+ .bits(config.low_limit as u16)
+ .cnt_h_lim()
+ .bits(config.high_limit as u16)
+ });
+ conf1.write(|w| unsafe {
+ w.cnt_thres0()
+ .bits(config.thresh0 as u16)
+ .cnt_thres1()
+ .bits(config.thresh1 as u16)
+ });
+ conf0.modify(|_, w| unsafe { w.filter_thres().bits(filter).filter_en().bit(filter_en) });
+ self.pause();
+ self.clear();
+ Ok(())
+ }
+
+ pub fn get_channel(&self, number: channel::Number) -> super::channel::Channel {
+ super::channel::Channel::new(self.number, number)
+ }
+
+ pub fn clear(&self) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ critical_section::with(|_cs| {
+ match self.number {
+ Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u0().set_bit()),
+ Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u1().set_bit()),
+ Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u2().set_bit()),
+ Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u3().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u4().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u5().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u6().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u7().set_bit()),
+ }
+ // TODO: does this need a delay? (liebman / Jan 2 2023)
+ match self.number {
+ Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u0().clear_bit()),
+ Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u1().clear_bit()),
+ Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u2().clear_bit()),
+ Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u3().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u4().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u5().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u6().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_rst_u7().clear_bit()),
+ }
+ });
+ }
+
+ /// Pause the counter
+ pub fn pause(&self) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ critical_section::with(|_cs| match self.number {
+ Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u0().set_bit()),
+ Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u1().set_bit()),
+ Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u2().set_bit()),
+ Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u3().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u4().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u5().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u6().set_bit()),
+ #[cfg(esp32)]
+ Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u7().set_bit()),
+ });
+ }
+
+ /// Resume the counter
+ pub fn resume(&self) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ critical_section::with(|_cs| match self.number {
+ Number::Unit0 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u0().clear_bit()),
+ Number::Unit1 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u1().clear_bit()),
+ Number::Unit2 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u2().clear_bit()),
+ Number::Unit3 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u3().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit4 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u4().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit5 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u5().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit6 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u6().clear_bit()),
+ #[cfg(esp32)]
+ Number::Unit7 => pcnt.ctrl.modify(|_, w| w.cnt_pause_u7().clear_bit()),
+ });
+ }
+
+ /// Enable which events generate interrupts on this unit.
+ pub fn events(&self, events: Events) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ let conf0 = match self.number {
+ Number::Unit0 => &pcnt.u0_conf0,
+ Number::Unit1 => &pcnt.u1_conf0,
+ Number::Unit2 => &pcnt.u2_conf0,
+ Number::Unit3 => &pcnt.u3_conf0,
+ #[cfg(esp32)]
+ Number::Unit4 => &pcnt.u4_conf0,
+ #[cfg(esp32)]
+ Number::Unit5 => &pcnt.u5_conf0,
+ #[cfg(esp32)]
+ Number::Unit6 => &pcnt.u6_conf0,
+ #[cfg(esp32)]
+ Number::Unit7 => &pcnt.u7_conf0,
+ };
+ conf0.modify(|_, w| {
+ w.thr_l_lim_en()
+ .bit(events.low_limit)
+ .thr_h_lim_en()
+ .bit(events.high_limit)
+ .thr_thres0_en()
+ .bit(events.thresh0)
+ .thr_thres1_en()
+ .bit(events.thresh1)
+ .thr_zero_en()
+ .bit(events.zero)
+ });
+ }
+
+ /// Get the latest events for this unit.
+ pub fn get_events(&self) -> Events {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ let status = pcnt.u_status[self.number as usize].read();
+
+ Events {
+ low_limit: status.l_lim().bit(),
+ high_limit: status.h_lim().bit(),
+ thresh0: status.thres0().bit(),
+ thresh1: status.thres1().bit(),
+ zero: status.zero().bit(),
+ }
+ }
+
+ /// Get the mode of the last zero crossing
+ pub fn get_zero_mode(&self) -> ZeroMode {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ pcnt.u_status[self.number as usize]
+ .read()
+ .zero_mode()
+ .bits()
+ .into()
+ }
+
+ /// Enable interrupts for this unit.
+ pub fn listen(&self) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ critical_section::with(|_cs| {
+ pcnt.int_ena.modify(|_, w| match self.number {
+ Number::Unit0 => w.cnt_thr_event_u0().set_bit(),
+ Number::Unit1 => w.cnt_thr_event_u1().set_bit(),
+ Number::Unit2 => w.cnt_thr_event_u2().set_bit(),
+ Number::Unit3 => w.cnt_thr_event_u3().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit4 => w.cnt_thr_event_u4().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit5 => w.cnt_thr_event_u5().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit6 => w.cnt_thr_event_u6().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit7 => w.cnt_thr_event_u7().set_bit(),
+ })
+ });
+ }
+
+ /// Disable interrupts for this unit.
+ pub fn unlisten(&self, _cs: CriticalSection) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ critical_section::with(|_cs| {
+ pcnt.int_ena.write(|w| match self.number {
+ Number::Unit0 => w.cnt_thr_event_u0().clear_bit(),
+ Number::Unit1 => w.cnt_thr_event_u1().clear_bit(),
+ Number::Unit2 => w.cnt_thr_event_u2().clear_bit(),
+ Number::Unit3 => w.cnt_thr_event_u3().clear_bit(),
+ #[cfg(esp32)]
+ Number::Unit4 => w.cnt_thr_event_u4().clear_bit(),
+ #[cfg(esp32)]
+ Number::Unit5 => w.cnt_thr_event_u5().clear_bit(),
+ #[cfg(esp32)]
+ Number::Unit6 => w.cnt_thr_event_u6().clear_bit(),
+ #[cfg(esp32)]
+ Number::Unit7 => w.cnt_thr_event_u7().clear_bit(),
+ })
+ });
+ }
+
+ /// Returns true if an interrupt is active for this unit.
+ pub fn interrupt_set(&self) -> bool {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ match self.number {
+ Number::Unit0 => pcnt.int_st.read().cnt_thr_event_u0().bit(),
+ Number::Unit1 => pcnt.int_st.read().cnt_thr_event_u1().bit(),
+ Number::Unit2 => pcnt.int_st.read().cnt_thr_event_u2().bit(),
+ Number::Unit3 => pcnt.int_st.read().cnt_thr_event_u3().bit(),
+ #[cfg(esp32)]
+ Number::Unit4 => pcnt.int_st.read().cnt_thr_event_u4().bit(),
+ #[cfg(esp32)]
+ Number::Unit5 => pcnt.int_st.read().cnt_thr_event_u5().bit(),
+ #[cfg(esp32)]
+ Number::Unit6 => pcnt.int_st.read().cnt_thr_event_u6().bit(),
+ #[cfg(esp32)]
+ Number::Unit7 => pcnt.int_st.read().cnt_thr_event_u7().bit(),
+ }
+ }
+
+ /// Clear the interrupt bit for this unit.
+ pub fn reset_interrupt(&self) {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ critical_section::with(|_cs| {
+ pcnt.int_clr.write(|w| match self.number {
+ Number::Unit0 => w.cnt_thr_event_u0().set_bit(),
+ Number::Unit1 => w.cnt_thr_event_u1().set_bit(),
+ Number::Unit2 => w.cnt_thr_event_u2().set_bit(),
+ Number::Unit3 => w.cnt_thr_event_u3().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit4 => w.cnt_thr_event_u4().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit5 => w.cnt_thr_event_u5().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit6 => w.cnt_thr_event_u6().set_bit(),
+ #[cfg(esp32)]
+ Number::Unit7 => w.cnt_thr_event_u7().set_bit(),
+ })
+ });
+ }
+
+ /// Get the current counter value.
+ pub fn get_value(&self) -> i16 {
+ let pcnt = unsafe { &*crate::peripherals::PCNT::ptr() };
+ pcnt.u_cnt[self.number as usize].read().cnt().bits() as i16
+ }
+}
diff --git a/esp-hal-common/src/system.rs b/esp-hal-common/src/system.rs
index fb2f70e4b65..93ebf234713 100644
--- a/esp-hal-common/src/system.rs
+++ b/esp-hal-common/src/system.rs
@@ -30,6 +30,8 @@ pub enum Peripheral {
Mcpwm0,
#[cfg(any(esp32, esp32s3))]
Mcpwm1,
+ #[cfg(any(esp32, esp32s2, esp32s3))]
+ Pcnt,
#[cfg(any(esp32c2, esp32c3))]
ApbSarAdc,
#[cfg(gdma)]
@@ -108,6 +110,11 @@ impl PeripheralClockControl {
perip_clk_en0.modify(|_, w| w.pwm1_clk_en().set_bit());
perip_rst_en0.modify(|_, w| w.pwm1_rst().clear_bit());
}
+ #[cfg(any(esp32, esp32s2, esp32s3))]
+ Peripheral::Pcnt => {
+ perip_clk_en0.modify(|_, w| w.pcnt_clk_en().set_bit());
+ perip_rst_en0.modify(|_, w| w.pcnt_rst().clear_bit());
+ }
#[cfg(any(esp32c2, esp32c3))]
Peripheral::ApbSarAdc => {
perip_clk_en0.modify(|_, w| w.apb_saradc_clk_en().set_bit());
diff --git a/esp32-hal/examples/pcnt_encoder.rs b/esp32-hal/examples/pcnt_encoder.rs
new file mode 100644
index 00000000000..819e03128ea
--- /dev/null
+++ b/esp32-hal/examples/pcnt_encoder.rs
@@ -0,0 +1,144 @@
+//! PCNT Encoder Demo
+//!
+//! This example decodes a quadrature encoder
+//!
+//! Since the PCNT units reset to zero when they reach their limits
+//! we enable an interrupt on the upper and lower limits and
+//! track the overflow in an AtomicI32
+
+#![no_std]
+#![no_main]
+use core::{
+ cell::RefCell,
+ cmp::min,
+ sync::atomic::{AtomicI32, Ordering},
+};
+
+use critical_section::Mutex;
+use esp32_hal as esp_hal;
+use esp_backtrace as _;
+use esp_hal::{
+ clock::ClockControl,
+ interrupt,
+ pcnt::{channel, channel::PcntSource, unit, PCNT},
+ peripherals::{self, Peripherals},
+ prelude::*,
+ timer::TimerGroup,
+ Rtc,
+ IO,
+};
+use esp_println::println;
+use xtensa_lx_rt::entry;
+
+static UNIT0: Mutex>> = Mutex::new(RefCell::new(None));
+static VALUE: AtomicI32 = AtomicI32::new(0);
+
+#[entry]
+fn main() -> ! {
+ let peripherals = Peripherals::take();
+ let mut system = peripherals.DPORT.split();
+ let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
+
+ let mut rtc = Rtc::new(peripherals.RTC_CNTL);
+ let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
+ let mut wdt = timer_group0.wdt;
+ let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
+
+ // Disable MWDT and RWDT (Watchdog) flash boot protection
+ wdt.disable();
+ rtc.rwdt.disable();
+
+ let unit_number = unit::Number::Unit1;
+
+ // setup a pulse couter
+ println!("setup pulse counter unit 0");
+ let pcnt = PCNT::new(peripherals.PCNT, &mut system.peripheral_clock_control);
+ let mut u0 = pcnt.get_unit(unit_number);
+ u0.configure(unit::Config {
+ low_limit: -100,
+ high_limit: 100,
+ filter: Some(min(10u16 * 80, 1023u16)),
+ ..Default::default()
+ })
+ .unwrap();
+
+ println!("setup channel 0");
+ let mut ch0 = u0.get_channel(channel::Number::Channel0);
+ let mut pin_a = io.pins.gpio22.into_pull_up_input();
+ let mut pin_b = io.pins.gpio23.into_pull_up_input();
+
+ ch0.configure(
+ PcntSource::from_pin(&mut pin_a),
+ PcntSource::from_pin(&mut pin_b),
+ channel::Config {
+ lctrl_mode: channel::CtrlMode::Reverse,
+ hctrl_mode: channel::CtrlMode::Keep,
+ pos_edge: channel::EdgeMode::Decrement,
+ neg_edge: channel::EdgeMode::Increment,
+ invert_ctrl: false,
+ invert_sig: false,
+ },
+ );
+
+ println!("setup channel 1");
+ let mut ch1 = u0.get_channel(channel::Number::Channel1);
+ ch1.configure(
+ PcntSource::from_pin(&mut pin_b),
+ PcntSource::from_pin(&mut pin_a),
+ channel::Config {
+ lctrl_mode: channel::CtrlMode::Reverse,
+ hctrl_mode: channel::CtrlMode::Keep,
+ pos_edge: channel::EdgeMode::Increment,
+ neg_edge: channel::EdgeMode::Decrement,
+ invert_ctrl: false,
+ invert_sig: false,
+ },
+ );
+ println!("subscribing to events");
+ u0.events(unit::Events {
+ low_limit: true,
+ high_limit: true,
+ thresh0: false,
+ thresh1: false,
+ zero: false,
+ });
+
+ println!("enabling interrupts");
+ u0.listen();
+ println!("resume pulse counter unit 0");
+ u0.resume();
+
+ critical_section::with(|cs| UNIT0.borrow_ref_mut(cs).replace(u0));
+
+ interrupt::enable(peripherals::Interrupt::PCNT, interrupt::Priority::Priority2).unwrap();
+
+ let mut last_value: i32 = 0;
+ loop {
+ critical_section::with(|cs| {
+ let mut u0 = UNIT0.borrow_ref_mut(cs);
+ let u0 = u0.as_mut().unwrap();
+ let value: i32 = u0.get_value() as i32 + VALUE.load(Ordering::SeqCst);
+ if value != last_value {
+ println!("value: {value}");
+ last_value = value;
+ }
+ });
+ }
+}
+
+#[interrupt]
+fn PCNT() {
+ critical_section::with(|cs| {
+ let mut u0 = UNIT0.borrow_ref_mut(cs);
+ let u0 = u0.as_mut().unwrap();
+ if u0.interrupt_set() {
+ let events = u0.get_events();
+ if events.high_limit {
+ VALUE.fetch_add(100, Ordering::SeqCst);
+ } else if events.low_limit {
+ VALUE.fetch_add(-100, Ordering::SeqCst);
+ }
+ u0.reset_interrupt();
+ }
+ });
+}
diff --git a/esp32-hal/src/lib.rs b/esp32-hal/src/lib.rs
index a213b5a1813..c9c584d36ae 100644
--- a/esp32-hal/src/lib.rs
+++ b/esp32-hal/src/lib.rs
@@ -19,6 +19,7 @@ pub use esp_hal_common::{
ledc,
macros,
mcpwm,
+ pcnt,
peripherals,
prelude,
pulse_control,
diff --git a/esp32s2-hal/examples/pcnt_encoder.rs b/esp32s2-hal/examples/pcnt_encoder.rs
new file mode 100644
index 00000000000..2bdfab326a5
--- /dev/null
+++ b/esp32s2-hal/examples/pcnt_encoder.rs
@@ -0,0 +1,144 @@
+//! PCNT Encoder Demo
+//!
+//! This example decodes a quadrature encoder
+//!
+//! Since the PCNT units reset to zero when they reach their limits
+//! we enable an interrupt on the upper and lower limits and
+//! track the overflow in an AtomicI32
+
+#![no_std]
+#![no_main]
+use core::{
+ cell::RefCell,
+ cmp::min,
+ sync::atomic::{AtomicI32, Ordering},
+};
+
+use critical_section::Mutex;
+use esp32s2_hal as esp_hal;
+use esp_backtrace as _;
+use esp_hal::{
+ clock::ClockControl,
+ interrupt,
+ pcnt::{channel, channel::PcntSource, unit, PCNT},
+ peripherals::{self, Peripherals},
+ prelude::*,
+ timer::TimerGroup,
+ Rtc,
+ IO,
+};
+use esp_println::println;
+use xtensa_lx_rt::entry;
+
+static UNIT0: Mutex>> = Mutex::new(RefCell::new(None));
+static VALUE: AtomicI32 = AtomicI32::new(0);
+
+#[entry]
+fn main() -> ! {
+ let peripherals = Peripherals::take();
+ let mut system = peripherals.SYSTEM.split();
+ let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
+
+ let mut rtc = Rtc::new(peripherals.RTC_CNTL);
+ let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
+ let mut wdt = timer_group0.wdt;
+ let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
+
+ // Disable MWDT and RWDT (Watchdog) flash boot protection
+ wdt.disable();
+ rtc.rwdt.disable();
+
+ let unit_number = unit::Number::Unit1;
+
+ // setup a pulse couter
+ println!("setup pulse counter unit 0");
+ let pcnt = PCNT::new(peripherals.PCNT, &mut system.peripheral_clock_control);
+ let mut u0 = pcnt.get_unit(unit_number);
+ u0.configure(unit::Config {
+ low_limit: -100,
+ high_limit: 100,
+ filter: Some(min(10u16 * 80, 1023u16)),
+ ..Default::default()
+ })
+ .unwrap();
+
+ println!("setup channel 0");
+ let mut ch0 = u0.get_channel(channel::Number::Channel0);
+ let mut pin_a = io.pins.gpio5.into_pull_up_input();
+ let mut pin_b = io.pins.gpio6.into_pull_up_input();
+
+ ch0.configure(
+ PcntSource::from_pin(&mut pin_a),
+ PcntSource::from_pin(&mut pin_b),
+ channel::Config {
+ lctrl_mode: channel::CtrlMode::Reverse,
+ hctrl_mode: channel::CtrlMode::Keep,
+ pos_edge: channel::EdgeMode::Decrement,
+ neg_edge: channel::EdgeMode::Increment,
+ invert_ctrl: false,
+ invert_sig: false,
+ },
+ );
+
+ println!("setup channel 1");
+ let mut ch1 = u0.get_channel(channel::Number::Channel1);
+ ch1.configure(
+ PcntSource::from_pin(&mut pin_b),
+ PcntSource::from_pin(&mut pin_a),
+ channel::Config {
+ lctrl_mode: channel::CtrlMode::Reverse,
+ hctrl_mode: channel::CtrlMode::Keep,
+ pos_edge: channel::EdgeMode::Increment,
+ neg_edge: channel::EdgeMode::Decrement,
+ invert_ctrl: false,
+ invert_sig: false,
+ },
+ );
+ println!("subscribing to events");
+ u0.events(unit::Events {
+ low_limit: true,
+ high_limit: true,
+ thresh0: false,
+ thresh1: false,
+ zero: false,
+ });
+
+ println!("enabling interrupts");
+ u0.listen();
+ println!("resume pulse counter unit 0");
+ u0.resume();
+
+ critical_section::with(|cs| UNIT0.borrow_ref_mut(cs).replace(u0));
+
+ interrupt::enable(peripherals::Interrupt::PCNT, interrupt::Priority::Priority2).unwrap();
+
+ let mut last_value: i32 = 0;
+ loop {
+ critical_section::with(|cs| {
+ let mut u0 = UNIT0.borrow_ref_mut(cs);
+ let u0 = u0.as_mut().unwrap();
+ let value: i32 = u0.get_value() as i32 + VALUE.load(Ordering::SeqCst);
+ if value != last_value {
+ println!("value: {value}");
+ last_value = value;
+ }
+ });
+ }
+}
+
+#[interrupt]
+fn PCNT() {
+ critical_section::with(|cs| {
+ let mut u0 = UNIT0.borrow_ref_mut(cs);
+ let u0 = u0.as_mut().unwrap();
+ if u0.interrupt_set() {
+ let events = u0.get_events();
+ if events.high_limit {
+ VALUE.store(VALUE.load(Ordering::SeqCst) + 100, Ordering::SeqCst);
+ } else if events.low_limit {
+ VALUE.store(VALUE.load(Ordering::SeqCst) - 100, Ordering::SeqCst);
+ }
+ u0.reset_interrupt();
+ }
+ });
+}
diff --git a/esp32s2-hal/src/lib.rs b/esp32s2-hal/src/lib.rs
index 1c9f481fb53..1b4c59ec1cb 100644
--- a/esp32s2-hal/src/lib.rs
+++ b/esp32s2-hal/src/lib.rs
@@ -18,6 +18,7 @@ pub use esp_hal_common::{
ledc,
macros,
otg_fs,
+ pcnt,
peripherals,
prelude,
pulse_control,
diff --git a/esp32s3-hal/examples/pcnt_encoder.rs b/esp32s3-hal/examples/pcnt_encoder.rs
new file mode 100644
index 00000000000..201fce31d46
--- /dev/null
+++ b/esp32s3-hal/examples/pcnt_encoder.rs
@@ -0,0 +1,144 @@
+//! PCNT Encoder Demo
+//!
+//! This example decodes a quadrature encoder
+//!
+//! Since the PCNT units reset to zero when they reach their limits
+//! we enable an interrupt on the upper and lower limits and
+//! track the overflow in an AtomicI32
+
+#![no_std]
+#![no_main]
+use core::{
+ cell::RefCell,
+ cmp::min,
+ sync::atomic::{AtomicI32, Ordering},
+};
+
+use critical_section::Mutex;
+use esp32s3_hal as esp_hal;
+use esp_backtrace as _;
+use esp_hal::{
+ clock::ClockControl,
+ interrupt,
+ pcnt::{channel, channel::PcntSource, unit, PCNT},
+ peripherals::{self, Peripherals},
+ prelude::*,
+ timer::TimerGroup,
+ Rtc,
+ IO,
+};
+use esp_println::println;
+use xtensa_lx_rt::entry;
+
+static UNIT0: Mutex>> = Mutex::new(RefCell::new(None));
+static VALUE: AtomicI32 = AtomicI32::new(0);
+
+#[entry]
+fn main() -> ! {
+ let peripherals = Peripherals::take();
+ let mut system = peripherals.SYSTEM.split();
+ let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
+
+ let mut rtc = Rtc::new(peripherals.RTC_CNTL);
+ let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
+ let mut wdt = timer_group0.wdt;
+ let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
+
+ // Disable MWDT and RWDT (Watchdog) flash boot protection
+ wdt.disable();
+ rtc.rwdt.disable();
+
+ let unit_number = unit::Number::Unit1;
+
+ // setup a pulse couter
+ println!("setup pulse counter unit 0");
+ let pcnt = PCNT::new(peripherals.PCNT, &mut system.peripheral_clock_control);
+ let mut u0 = pcnt.get_unit(unit_number);
+ u0.configure(unit::Config {
+ low_limit: -100,
+ high_limit: 100,
+ filter: Some(min(10u16 * 80, 1023u16)),
+ ..Default::default()
+ })
+ .unwrap();
+
+ println!("setup channel 0");
+ let mut ch0 = u0.get_channel(channel::Number::Channel0);
+ let mut pin_a = io.pins.gpio5.into_pull_up_input();
+ let mut pin_b = io.pins.gpio6.into_pull_up_input();
+
+ ch0.configure(
+ PcntSource::from_pin(&mut pin_a),
+ PcntSource::from_pin(&mut pin_b),
+ channel::Config {
+ lctrl_mode: channel::CtrlMode::Reverse,
+ hctrl_mode: channel::CtrlMode::Keep,
+ pos_edge: channel::EdgeMode::Decrement,
+ neg_edge: channel::EdgeMode::Increment,
+ invert_ctrl: false,
+ invert_sig: false,
+ },
+ );
+
+ println!("setup channel 1");
+ let mut ch1 = u0.get_channel(channel::Number::Channel1);
+ ch1.configure(
+ PcntSource::from_pin(&mut pin_b),
+ PcntSource::from_pin(&mut pin_a),
+ channel::Config {
+ lctrl_mode: channel::CtrlMode::Reverse,
+ hctrl_mode: channel::CtrlMode::Keep,
+ pos_edge: channel::EdgeMode::Increment,
+ neg_edge: channel::EdgeMode::Decrement,
+ invert_ctrl: false,
+ invert_sig: false,
+ },
+ );
+ println!("subscribing to events");
+ u0.events(unit::Events {
+ low_limit: true,
+ high_limit: true,
+ thresh0: false,
+ thresh1: false,
+ zero: false,
+ });
+
+ println!("enabling interrupts");
+ u0.listen();
+ println!("resume pulse counter unit 0");
+ u0.resume();
+
+ critical_section::with(|cs| UNIT0.borrow_ref_mut(cs).replace(u0));
+
+ interrupt::enable(peripherals::Interrupt::PCNT, interrupt::Priority::Priority2).unwrap();
+
+ let mut last_value: i32 = 0;
+ loop {
+ critical_section::with(|cs| {
+ let mut u0 = UNIT0.borrow_ref_mut(cs);
+ let u0 = u0.as_mut().unwrap();
+ let value: i32 = u0.get_value() as i32 + VALUE.load(Ordering::SeqCst);
+ if value != last_value {
+ println!("value: {value}");
+ last_value = value;
+ }
+ });
+ }
+}
+
+#[interrupt]
+fn PCNT() {
+ critical_section::with(|cs| {
+ let mut u0 = UNIT0.borrow_ref_mut(cs);
+ let u0 = u0.as_mut().unwrap();
+ if u0.interrupt_set() {
+ let events = u0.get_events();
+ if events.high_limit {
+ VALUE.fetch_add(100, Ordering::SeqCst);
+ } else if events.low_limit {
+ VALUE.fetch_add(-100, Ordering::SeqCst);
+ }
+ u0.reset_interrupt();
+ }
+ });
+}
diff --git a/esp32s3-hal/src/lib.rs b/esp32s3-hal/src/lib.rs
index 870ada55901..d265c1d6ca0 100644
--- a/esp32s3-hal/src/lib.rs
+++ b/esp32s3-hal/src/lib.rs
@@ -20,6 +20,7 @@ pub use esp_hal_common::{
macros,
mcpwm,
otg_fs,
+ pcnt,
peripherals,
prelude,
pulse_control,