Skip to main content

embassy_ttp223b/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2// GPL-3.0-or-later
3
4#![no_std]
5#![forbid(unsafe_code)]
6
7//! # embassy-ttp223b
8//!
9//! Driver asynchrone `no_std` pour le capteur tactile TTP223B via GPIO.
10//! Élimine les faux positifs par un filtrage temporel logiciel (debounce)
11//! sans jamais bloquer le CPU, garantissant une efficacité énergétique maximale.
12//!
13//! Fonctionne avec n'importe quel pin implémentant `embedded-hal::digital::InputPin`
14//! et l'exécuteur asynchrone Embassy.
15//!
16//! # Exemple
17//!
18//! ```rust,no_run
19//! use embassy_ttp223b::{Ttp223, TouchState};
20//!
21//! let mut touch = Ttp223::new(pin);
22//!
23//! loop {
24//!     match touch.wait_for_change().await {
25//!         Ok(TouchState::Pressed)  => { /* réaction au toucher */ }
26//!         Ok(TouchState::Released) => { /* relâchement */ }
27//!         Err(_) => { /* gestion d'erreur */ }
28//!     }
29//! }
30//! ```
31
32use embassy_time::{Duration, Timer};
33use embedded_hal::digital::InputPin;
34
35/// Durée de debounce par défaut (ms).
36pub const DEBOUNCE_MS: u64 = 50;
37
38/// Durée de polling par défaut quand aucun changement n'est détecté (ms).
39pub const POLL_MS: u64 = 10;
40
41/// États possibles du capteur tactile.
42#[derive(Debug, PartialEq, Copy, Clone)]
43pub enum TouchState {
44    /// Le capteur est pressé (signal haut).
45    Pressed,
46    /// Le capteur est relâché (signal bas).
47    Released,
48}
49
50/// Erreurs spécifiques au capteur TTP223B.
51#[derive(Debug)]
52pub enum TouchError {
53    /// Problème de lecture matérielle sur le GPIO.
54    ReadError,
55    /// Le signal oscille trop pour être considéré fiable.
56    SignalUnstable,
57}
58
59/// Instance principale du driver TTP223B.
60///
61/// Fonctionne avec n'importe quel pin implémentant
62/// `embedded-hal::digital::InputPin`.
63pub struct Ttp223<P: InputPin> {
64    pin: P,
65    last_confirmed_state: bool,
66    debounce_ms: u64,
67    poll_ms: u64,
68}
69
70impl<P: InputPin> Ttp223<P> {
71    /// Initialise une nouvelle instance du driver avec un pin GPIO.
72    ///
73    /// Utilise les durées de debounce et de polling par défaut
74    /// ([`DEBOUNCE_MS`] et [`POLL_MS`]).
75    ///
76    /// # Arguments
77    /// * `pin` — Pin GPIO configuré en entrée.
78    pub fn new(pin: P) -> Self {
79        Self {
80            pin,
81            last_confirmed_state: false,
82            debounce_ms: DEBOUNCE_MS,
83            poll_ms: POLL_MS,
84        }
85    }
86
87    /// Initialise une nouvelle instance avec des durées personnalisées.
88    ///
89    /// # Arguments
90    /// * `pin`          Pin GPIO configuré en entrée.
91    /// * `debounce_ms`  Durée d'attente de stabilisation (ms).
92    /// * `poll_ms`     Période de polling en l'absence de changement (ms).
93    pub fn new_with_timing(pin: P, debounce_ms: u64, poll_ms: u64) -> Self {
94        Self {
95            pin,
96            last_confirmed_state: false,
97            debounce_ms,
98            poll_ms,
99        }
100    }
101
102    /// Attend un changement d'état avec confirmation temporelle (debounce).
103    ///
104    /// Cette fonction est asynchrone : elle rend la main à l'exécuteur
105    /// pendant les phases d'attente ([`Timer`]), sans jamais bloquer le CPU.
106    ///
107    /// # Retourne
108    /// * `Ok(TouchState::Pressed)`  Toucher confirmé.
109    /// * `Ok(TouchState::Released)` Relâchement confirmé.
110    /// * `Err(TouchError::ReadError)` Erreur de lecture GPIO.
111    pub async fn wait_for_change(&mut self) -> Result<TouchState, TouchError> {
112        loop {
113            let current_raw = self
114                .pin
115                .is_high()
116                .map_err(|_| TouchError::ReadError)?;
117
118            if current_raw != self.last_confirmed_state {
119                // Debounce : attendre que les rebonds électriques se calment.
120                Timer::after(Duration::from_millis(self.debounce_ms)).await;
121
122                let confirmed = self
123                    .pin
124                    .is_high()
125                    .map_err(|_| TouchError::ReadError)?;
126
127                if confirmed == current_raw {
128                    self.last_confirmed_state = confirmed;
129                    return Ok(if confirmed {
130                        TouchState::Pressed
131                    } else {
132                        TouchState::Released
133                    });
134                }
135                // Signal instable : on ignore et on reboucle.
136            }
137
138            // Pause légère pour ne pas saturer le scheduler.
139            Timer::after(Duration::from_millis(self.poll_ms)).await;
140        }
141    }
142
143    /// Renvoie l'état mémorisé sans attendre de nouveau changement.
144    ///
145    /// Utile pour consulter l'état courant dans une tâche périodique.
146    pub fn get_state(&self) -> TouchState {
147        if self.last_confirmed_state {
148            TouchState::Pressed
149        } else {
150            TouchState::Released
151        }
152    }
153
154    /// Indique si le capteur est actuellement pressé (état mémorisé).
155    pub fn is_pressed(&self) -> bool {
156        self.last_confirmed_state
157    }
158
159    /// Libère le pin GPIO et retourne la ressource matérielle.
160    pub fn release(self) -> P {
161        self.pin
162    }
163}