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}