esp_hal/
touch.rs

1//! # Capacitive Touch Sensor
2//!
3//! ## Overview
4//!
5//! The touch sensor peripheral allows for cheap and robust user interfaces by
6//! e.g., dedicating a part of the pcb as touch button.
7//!
8//! ## Examples
9//!
10//! ```rust, no_run
11#![doc = crate::before_snippet!()]
12//! # use esp_hal::touch::{Touch, TouchPad};
13//! let touch_pin0 = peripherals.GPIO2;
14//! let touch = Touch::continuous_mode(peripherals.TOUCH, None);
15//! let mut touchpad = TouchPad::new(touch_pin0, &touch);
16//! // ... give the peripheral some time for the measurement
17//! let touch_val = touchpad.read();
18//! # Ok(())
19//! # }
20//! ```
21//! 
22//! ## Implementation State:
23//!
24//! Mostly feature complete, missing:
25//!
26//! - Touch sensor slope control
27//! - Deep Sleep support (wakeup from Deep Sleep)
28
29use core::marker::PhantomData;
30
31use crate::{
32    Async,
33    Blocking,
34    DriverMode,
35    gpio::TouchPin,
36    peripherals::{LPWR, SENS, TOUCH},
37    private::{Internal, Sealed},
38    rtc_cntl::Rtc,
39};
40
41/// A marker trait describing the mode the touch pad is set to.
42pub trait TouchMode: Sealed {}
43
44/// Marker struct for the touch peripherals manual trigger mode. In the
45/// technical reference manual, this is referred to as "start FSM via software".
46#[derive(Debug)]
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48pub struct OneShot;
49
50/// Marker struct for the touch peripherals continuous reading mode. In the
51/// technical reference manual, this is referred to as "start FSM via timer".
52#[derive(Debug)]
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
54pub struct Continuous;
55
56impl TouchMode for OneShot {}
57impl TouchMode for Continuous {}
58impl Sealed for OneShot {}
59impl Sealed for Continuous {}
60
61/// Touchpad threshold type.
62#[derive(Debug, Copy, Clone)]
63#[cfg_attr(feature = "defmt", derive(defmt::Format))]
64pub enum ThresholdMode {
65    /// Pad is considered touched if value is greater than threshold.
66    GreaterThan,
67    /// Pad is considered touched if value is less than threshold.
68    LessThan,
69}
70
71/// Configurations for the touch pad driver
72#[derive(Debug, Copy, Clone, Default)]
73#[cfg_attr(feature = "defmt", derive(defmt::Format))]
74pub struct TouchConfig {
75    /// The [`ThresholdMode`] for the pads. Defaults to
76    /// `ThresholdMode::LessThan`
77    pub threshold_mode: Option<ThresholdMode>,
78    /// Duration of a single measurement (in cycles of the 8 MHz touch clock).
79    /// Defaults to `0x7fff`
80    pub measurement_duration: Option<u16>,
81    /// Sleep cycles for the touch timer in [`Continuous`]-mode. Defaults to
82    /// `0x100`
83    pub sleep_cycles: Option<u16>,
84}
85
86/// This struct marks a successfully initialized touch peripheral
87pub struct Touch<'d, Tm: TouchMode, Dm: DriverMode> {
88    _inner: TOUCH<'d>,
89    _touch_mode: PhantomData<Tm>,
90    _mode: PhantomData<Dm>,
91}
92impl<Tm: TouchMode, Dm: DriverMode> Touch<'_, Tm, Dm> {
93    /// Common initialization of the touch peripheral.
94    fn initialize_common(config: Option<TouchConfig>) {
95        let rtccntl = LPWR::regs();
96        let sens = SENS::regs();
97
98        let mut threshold_mode = false;
99        let mut meas_dur = 0x7fff;
100
101        if let Some(config) = config {
102            threshold_mode = match config.threshold_mode {
103                Some(ThresholdMode::LessThan) => false,
104                Some(ThresholdMode::GreaterThan) => true,
105                None => false,
106            };
107            if let Some(dur) = config.measurement_duration {
108                meas_dur = dur;
109            }
110        }
111
112        // stop touch fsm
113        rtccntl
114            .state0()
115            .write(|w| w.touch_slp_timer_en().clear_bit());
116        // Disable touch interrupt
117        rtccntl.int_ena().write(|w| w.touch().clear_bit());
118        // Clear pending interrupts
119        rtccntl.int_clr().write(|w| w.touch().bit(true));
120
121        // Disable all interrupts and touch pads
122        sens.sar_touch_enable().write(|w| unsafe {
123            w.touch_pad_outen1()
124                .bits(0b0)
125                .touch_pad_outen2()
126                .bits(0b0)
127                .touch_pad_worken()
128                .bits(0b0)
129        });
130
131        sens.sar_touch_ctrl1().write(|w| unsafe {
132            w
133                // Default to trigger when touch is below threshold
134                .touch_out_sel()
135                .bit(threshold_mode)
136                // Interrupt only on set 1
137                .touch_out_1en()
138                .set_bit()
139                .touch_meas_delay()
140                .bits(meas_dur)
141                // TODO Chip Specific
142                .touch_xpd_wait()
143                .bits(0xff)
144        });
145    }
146
147    /// Common parts of the continuous mode initialization.
148    fn initialize_common_continuous(config: Option<TouchConfig>) {
149        let rtccntl = LPWR::regs();
150        let sens = SENS::regs();
151
152        // Default nr of sleep cycles from IDF
153        let mut sleep_cyc = 0x1000;
154        if let Some(config) = config {
155            if let Some(slp) = config.sleep_cycles {
156                sleep_cyc = slp;
157            }
158        }
159
160        Self::initialize_common(config);
161
162        sens.sar_touch_ctrl2().write(|w| unsafe {
163            w
164                // Reset existing touch measurements
165                .touch_meas_en_clr()
166                .set_bit()
167                .touch_sleep_cycles()
168                .bits(sleep_cyc)
169                // Configure FSM for timer mode
170                .touch_start_fsm_en()
171                .set_bit()
172                .touch_start_force()
173                .clear_bit()
174        });
175
176        // start touch fsm
177        rtccntl.state0().write(|w| w.touch_slp_timer_en().set_bit());
178    }
179}
180// Async mode and OneShot does not seem to be a sensible combination....
181impl<'d> Touch<'d, OneShot, Blocking> {
182    /// Initializes the touch peripheral and returns this marker struct.
183    /// Optionally accepts configuration options.
184    ///
185    /// ## Example
186    ///
187    /// ```rust, no_run
188    #[doc = crate::before_snippet!()]
189    /// # use esp_hal::touch::{Touch, TouchConfig};
190    /// let touch_cfg = Some(TouchConfig {
191    ///     measurement_duration: Some(0x2000),
192    ///     ..Default::default()
193    /// });
194    /// let touch = Touch::one_shot_mode(peripherals.TOUCH, touch_cfg);
195    /// # Ok(())
196    /// # }
197    /// ```
198    pub fn one_shot_mode(touch_peripheral: TOUCH<'d>, config: Option<TouchConfig>) -> Self {
199        let rtccntl = LPWR::regs();
200        let sens = SENS::regs();
201
202        // Default nr of sleep cycles from IDF
203        let mut sleep_cyc = 0x1000;
204        if let Some(config) = config {
205            if let Some(slp) = config.sleep_cycles {
206                sleep_cyc = slp;
207            }
208        }
209
210        Self::initialize_common(config);
211
212        sens.sar_touch_ctrl2().write(|w| unsafe {
213            w
214                // Reset existing touch measurements
215                .touch_meas_en_clr()
216                .set_bit()
217                .touch_sleep_cycles()
218                .bits(sleep_cyc)
219                // Configure FSM for SW mode
220                .touch_start_fsm_en()
221                .set_bit()
222                .touch_start_en()
223                .clear_bit()
224                .touch_start_force()
225                .set_bit()
226        });
227
228        // start touch fsm
229        rtccntl.state0().write(|w| w.touch_slp_timer_en().set_bit());
230
231        Self {
232            _inner: touch_peripheral,
233            _mode: PhantomData,
234            _touch_mode: PhantomData,
235        }
236    }
237}
238impl<'d> Touch<'d, Continuous, Blocking> {
239    /// Initializes the touch peripheral in continuous mode and returns this
240    /// marker struct. Optionally accepts configuration options.
241    ///
242    /// ## Example
243    ///
244    /// ```rust, no_run
245    #[doc = crate::before_snippet!()]
246    /// # use esp_hal::touch::{Touch, TouchConfig};
247    /// let touch_cfg = Some(TouchConfig {
248    ///     measurement_duration: Some(0x3000),
249    ///     ..Default::default()
250    /// });
251    /// let touch = Touch::continuous_mode(peripherals.TOUCH, touch_cfg);
252    /// # Ok(())
253    /// # }
254    /// ```
255    pub fn continuous_mode(touch_peripheral: TOUCH<'d>, config: Option<TouchConfig>) -> Self {
256        Self::initialize_common_continuous(config);
257
258        Self {
259            _inner: touch_peripheral,
260            _mode: PhantomData,
261            _touch_mode: PhantomData,
262        }
263    }
264}
265impl<'d> Touch<'d, Continuous, Async> {
266    /// Initializes the touch peripheral in continuous async mode and returns
267    /// this marker struct.
268    ///
269    /// ## Warning:
270    ///
271    /// This uses [`RTC_CORE`](crate::peripherals::Interrupt::RTC_CORE)
272    /// interrupts under the hood. So the whole async part breaks if you install
273    /// an interrupt handler with [`Rtc::set_interrupt_handler()`][1].
274    ///
275    /// [1]: ../rtc_cntl/struct.Rtc.html#method.set_interrupt_handler
276    ///
277    /// ## Parameters:
278    ///
279    /// - `rtc`: The RTC peripheral is needed to configure the required
280    ///   interrupts.
281    /// - `config`: Optional configuration options.
282    ///
283    /// ## Example
284    ///
285    /// ```rust, no_run
286    #[doc = crate::before_snippet!()]
287    /// # use esp_hal::rtc_cntl::Rtc;
288    /// # use esp_hal::touch::{Touch, TouchConfig};
289    /// let mut rtc = Rtc::new(peripherals.LPWR);
290    /// let touch = Touch::async_mode(peripherals.TOUCH, &mut rtc, None);
291    /// # Ok(())
292    /// # }
293    /// ```
294    pub fn async_mode(
295        touch_peripheral: TOUCH<'d>,
296        rtc: &mut Rtc<'_>,
297        config: Option<TouchConfig>,
298    ) -> Self {
299        Self::initialize_common_continuous(config);
300
301        rtc.set_interrupt_handler(asynch::handle_touch_interrupt);
302
303        Self {
304            _inner: touch_peripheral,
305            _mode: PhantomData,
306            _touch_mode: PhantomData,
307        }
308    }
309}
310
311/// A pin that is configured as a TouchPad.
312pub struct TouchPad<P: TouchPin, Tm: TouchMode, Dm: DriverMode> {
313    pin: P,
314    _touch_mode: PhantomData<Tm>,
315    _mode: PhantomData<Dm>,
316}
317impl<P: TouchPin> TouchPad<P, OneShot, Blocking> {
318    /// (Re-)Start a touch measurement on the pin. You can get the result by
319    /// calling [`read`](Self::read) once it is finished.
320    pub fn start_measurement(&mut self) {
321        crate::peripherals::RTC_IO::regs()
322            .touch_pad2()
323            .write(|w| unsafe {
324                w.start().set_bit();
325                w.xpd().set_bit();
326                // clear input_enable
327                w.fun_ie().clear_bit();
328                // Connect pin to analog / RTC module instead of standard GPIO
329                w.mux_sel().set_bit();
330                // Disable pull-up and pull-down resistors on the pin
331                w.rue().clear_bit();
332                w.rde().clear_bit();
333                w.tie_opt().clear_bit();
334                w.to_gpio().set_bit();
335                // Select function "RTC function 1" (GPIO) for analog use
336                w.fun_sel().bits(0b00)
337            });
338
339        crate::peripherals::SENS::regs()
340            .sar_touch_ctrl2()
341            .modify(|_, w| w.touch_start_en().clear_bit());
342        crate::peripherals::SENS::regs()
343            .sar_touch_ctrl2()
344            .modify(|_, w| w.touch_start_en().set_bit());
345    }
346}
347impl<P: TouchPin, Tm: TouchMode, Dm: DriverMode> TouchPad<P, Tm, Dm> {
348    /// Construct a new instance of [`TouchPad`].
349    ///
350    /// ## Parameters:
351    /// - `pin`: The pin that gets configured as touch pad
352    /// - `touch`: The [`Touch`] struct indicating that touch is configured.
353    pub fn new(pin: P, _touch: &Touch<'_, Tm, Dm>) -> Self {
354        // TODO revert this on drop
355        pin.set_touch(Internal);
356
357        Self {
358            pin,
359            _mode: PhantomData,
360            _touch_mode: PhantomData,
361        }
362    }
363
364    /// Read the current touch pad capacitance counter.
365    ///
366    /// Usually a lower value means higher capacitance, thus indicating touch
367    /// event.
368    ///
369    /// Returns `None` if the value is not yet ready. (Note: Measurement must be
370    /// started manually with [`start_measurement`](Self::start_measurement) if
371    /// the touch peripheral is in [`OneShot`] mode).
372    pub fn try_read(&mut self) -> Option<u16> {
373        if unsafe { &*crate::peripherals::SENS::ptr() }
374            .sar_touch_ctrl2()
375            .read()
376            .touch_meas_done()
377            .bit_is_set()
378        {
379            Some(self.pin.touch_measurement(Internal))
380        } else {
381            None
382        }
383    }
384}
385impl<P: TouchPin, Tm: TouchMode> TouchPad<P, Tm, Blocking> {
386    /// Blocking read of the current touch pad capacitance counter.
387    ///
388    /// Usually a lower value means higher capacitance, thus indicating touch
389    /// event.
390    ///
391    /// ## Note for [`OneShot`] mode:
392    ///
393    /// This function might block forever, if
394    /// [`start_measurement`](Self::start_measurement) was not called before. As
395    /// measurements are not cleared, the touch values might also be
396    /// outdated, if it has been some time since the last call to that
397    /// function.
398    pub fn read(&mut self) -> u16 {
399        while unsafe { &*crate::peripherals::SENS::ptr() }
400            .sar_touch_ctrl2()
401            .read()
402            .touch_meas_done()
403            .bit_is_clear()
404        {}
405        self.pin.touch_measurement(Internal)
406    }
407
408    /// Enables the touch_pad interrupt.
409    ///
410    /// The raised interrupt is actually
411    /// [`RTC_CORE`](crate::peripherals::Interrupt::RTC_CORE). A handler can
412    /// be installed with [`Rtc::set_interrupt_handler()`][1].
413    ///
414    /// [1]: ../rtc_cntl/struct.Rtc.html#method.set_interrupt_handler
415    ///
416    /// ## Parameters:
417    /// - `threshold`: The threshold above/below which the pin is considered
418    ///   touched. Above/below depends on the configuration of `touch` in
419    ///   [`new`](Self::new) (defaults to below).
420    ///
421    /// ## Example
422    pub fn enable_interrupt(&mut self, threshold: u16) {
423        self.pin.set_threshold(threshold, Internal);
424        internal_enable_interrupt(self.pin.touch_nr(Internal))
425    }
426
427    /// Disables the touch pad's interrupt.
428    ///
429    /// If no other touch pad interrupts are active, the touch interrupt is
430    /// disabled completely.
431    pub fn disable_interrupt(&mut self) {
432        internal_disable_interrupt(self.pin.touch_nr(Internal))
433    }
434
435    /// Clears a pending touch interrupt.
436    ///
437    /// ## Note on interrupt clearing behaviour:
438    ///
439    /// There is only a single interrupt for the touch pad.
440    /// [`is_interrupt_set`](Self::is_interrupt_set) can be used to check
441    /// which pins are touchted. However, this function clears the interrupt
442    /// status for all pins. So only call it when all pins are handled.
443    pub fn clear_interrupt(&mut self) {
444        internal_clear_interrupt()
445    }
446
447    /// Checks if the pad is touched, based on the configured threshold value.
448    pub fn is_interrupt_set(&mut self) -> bool {
449        internal_is_interrupt_set(self.pin.touch_nr(Internal))
450    }
451}
452
453fn internal_enable_interrupt(touch_nr: u8) {
454    // enable touch interrupts
455    LPWR::regs().int_ena().write(|w| w.touch().set_bit());
456
457    SENS::regs().sar_touch_enable().modify(|r, w| unsafe {
458        w.touch_pad_outen1()
459            .bits(r.touch_pad_outen1().bits() | (1 << touch_nr))
460    });
461}
462
463fn internal_disable_interrupt(touch_nr: u8) {
464    SENS::regs().sar_touch_enable().modify(|r, w| unsafe {
465        w.touch_pad_outen1()
466            .bits(r.touch_pad_outen1().bits() & !(1 << touch_nr))
467    });
468    if SENS::regs()
469        .sar_touch_enable()
470        .read()
471        .touch_pad_outen1()
472        .bits()
473        == 0
474    {
475        LPWR::regs().int_ena().write(|w| w.touch().clear_bit());
476    }
477}
478
479fn internal_disable_interrupts() {
480    SENS::regs()
481        .sar_touch_enable()
482        .write(|w| unsafe { w.touch_pad_outen1().bits(0) });
483    if SENS::regs()
484        .sar_touch_enable()
485        .read()
486        .touch_pad_outen1()
487        .bits()
488        == 0
489    {
490        LPWR::regs().int_ena().write(|w| w.touch().clear_bit());
491    }
492}
493
494fn internal_clear_interrupt() {
495    LPWR::regs()
496        .int_clr()
497        .write(|w| w.touch().clear_bit_by_one());
498    SENS::regs()
499        .sar_touch_ctrl2()
500        .write(|w| w.touch_meas_en_clr().set_bit());
501}
502
503fn internal_pins_touched() -> u16 {
504    // Only god knows, why the "interrupt flag" register is called "meas_en" on this
505    // chip...
506    SENS::regs().sar_touch_ctrl2().read().touch_meas_en().bits()
507}
508
509fn internal_is_interrupt_set(touch_nr: u8) -> bool {
510    internal_pins_touched() & (1 << touch_nr) != 0
511}
512
513mod asynch {
514    use core::{
515        sync::atomic::{AtomicU16, Ordering},
516        task::{Context, Poll},
517    };
518
519    use super::*;
520    use crate::{Async, asynch::AtomicWaker, handler, ram};
521
522    const NUM_TOUCH_PINS: usize = 10;
523
524    static TOUCH_WAKERS: [AtomicWaker; NUM_TOUCH_PINS] =
525        [const { AtomicWaker::new() }; NUM_TOUCH_PINS];
526
527    // Helper variable to store which pins need handling.
528    static TOUCHED_PINS: AtomicU16 = AtomicU16::new(0);
529
530    #[must_use = "futures do nothing unless you `.await` or poll them"]
531    pub struct TouchFuture {
532        touch_nr: u8,
533    }
534
535    impl TouchFuture {
536        pub fn new(touch_nr: u8) -> Self {
537            Self { touch_nr }
538        }
539    }
540
541    impl core::future::Future for TouchFuture {
542        type Output = ();
543
544        fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
545            TOUCH_WAKERS[self.touch_nr as usize].register(cx.waker());
546
547            let pins = TOUCHED_PINS.load(Ordering::Acquire);
548
549            if pins & (1 << self.touch_nr) != 0 {
550                // clear the pin to signal that this pin was handled.
551                TOUCHED_PINS.fetch_and(!(1 << self.touch_nr), Ordering::Release);
552                Poll::Ready(())
553            } else {
554                Poll::Pending
555            }
556        }
557    }
558
559    #[handler]
560    #[ram]
561    pub(super) fn handle_touch_interrupt() {
562        let touch_pads = internal_pins_touched();
563        for (i, waker) in TOUCH_WAKERS.iter().enumerate() {
564            if touch_pads & (1 << i) != 0 {
565                waker.wake();
566            }
567        }
568        TOUCHED_PINS.store(touch_pads, Ordering::Relaxed);
569        internal_clear_interrupt();
570        internal_disable_interrupts();
571    }
572
573    impl<P: TouchPin, Tm: TouchMode> TouchPad<P, Tm, Async> {
574        /// Wait for the pad to be touched.
575        pub async fn wait_for_touch(&mut self, threshold: u16) {
576            self.pin.set_threshold(threshold, Internal);
577            let touch_nr = self.pin.touch_nr(Internal);
578            internal_enable_interrupt(touch_nr);
579            TouchFuture::new(touch_nr).await;
580        }
581    }
582}