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}