# embassy-piezo
[](https://crates.io/crates/embassy-piezo)
[](https://docs.rs/embassy-piezo)
[](LICENSE)
**Driver asynchrone `no_std` pour capteur de vibration piézoélectrique (sortie numérique DO), pour cibles ESP32 via `esp-hal`.**
Même philosophie que [`embassy-gy-bmi160`](https://crates.io/crates/embassy-gy-bmi160) et [`embassy-bmp280`](https://crates.io/crates/embassy-bmp280) :
async natif, zéro allocation, zéro `unsafe`, détection sans polling.
---
## ⚡ Caractéristiques
- `#![forbid(unsafe_code)]` : sécurité garantie à la compilation
- Détection front montant **sans polling** : zéro CPU en attente
- Debounce configurable pour filtrer les rebonds électriques
- Comptage d'événements avec `saturating_add` (pas de panic après overflow)
- Timeout configurable sur attente vibration et silence
- Snapshot d'état typé `VibrationEvent { count, active }`
- Signal global `VIBRATION_SIGNAL` inter-tâches (interrupt-safe)
- Zéro allocation, bare-metal pur
---
## 📦 Installation
```toml
[dependencies]
embassy-piezo = { version = "0.1.0", features = ["esp32c3"] } # adapter la feature à ta puce
esp-hal = { version = "1.1", features = ["unstable"] }
esp-rtos = { version = "0.3", features = ["embassy"] } # fournit l'executor + le time-driver embassy (esp-hal >= 1.x)
embassy-executor = "0.10"
embassy-time = "0.5"
embassy-sync = "0.8"
```
> Une seule feature de puce doit être active à la fois : `esp32`, `esp32c2`,
> `esp32c3`, `esp32c6`, `esp32h2`, `esp32s2` ou `esp32s3`. Par défaut, le
> crate cible l'ESP32 d'origine.
> ⚠ **`esp-hal-embassy` est obsolète depuis `esp-hal 1.x`** : il a été
> remplacé par `esp-rtos` (feature `embassy`). Si tu pars d'un projet
> récent, n'installe **pas** `esp-hal-embassy` — utilise `esp-rtos` comme
> indiqué ci-dessous, sinon Cargo échouera à résoudre les versions
> (`esp-hal-embassy` reste figé sur `esp-hal ^1.0.0-rc.0`).
---
## 🔴 Câblage (ESP32 DevKit)
| VCC | 3.3V | |
| GND | Masse | |
| DO | GPIOx | Sortie numérique : haut = vibration |
| AO | — | Sortie analogique (non utilisée ici) |
> **Sensibilité** : réglable via le potentiomètre bleu sur le module.
> Si le pin reste haut en permanence, tourner le potentiomètre dans le sens horaire.
>
> ⚠ Évite les broches de strapping au boot (ex. GPIO2, GPIO8, GPIO9 sur
> ESP32-C3 — vérifie le datasheet de ta puce). Choisis une broche libre
> standard (ex. GPIO4, GPIO5, GPIO7, GPIO18…).
---
## 🚀 Utilisation
### Initialisation du runtime Embassy (esp-hal 1.x / esp-rtos)
```rust
use esp_hal::timer::timg::TimerGroup;
use esp_hal::interrupt::software::SoftwareInterruptControl;
let peripherals = esp_hal::init(esp_hal::Config::default());
let timg0 = TimerGroup::new(peripherals.TIMG0);
let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
// Remplace l'ancien esp_hal_embassy::init(...) — requis avant tout
// Timer::after / with_timeout / spawn de tâche async.
esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0);
```
Le point d'entrée `main` doit être marqué `#[esp_rtos::main]` (et non
`#[embassy_executor::main]`) :
```rust
#[esp_rtos::main]
async fn main(spawner: embassy_executor::Spawner) -> ! {
// ...
}
```
### Initialisation du driver
```rust
use esp_hal::gpio::{Input, InputConfig, Pull};
use embassy_piezo::PiezoVibration;
// Sans debounce : tout front montant est compté immédiatement
let pin = Input::new(peripherals.GPIO7, InputConfig::default().with_pull(Pull::Down));
let mut piezo = PiezoVibration::new(pin);
// Avec debounce : chocs humains (frappe, impact mécanique)
// 12ms : valeur validée empiriquement sur module piézo DO générique
// (1 tap physique = 1 comptage exact, sans rebond ni perte)
let pin = Input::new(peripherals.GPIO7, InputConfig::default().with_pull(Pull::Down));
let mut piezo = PiezoVibration::new_with_debounce(pin, 12);
```
### Debounce — guide pratique
Après chaque front montant, le driver attend `debounce_ms` millisecondes puis
vérifie que le pin est toujours haut. Si le signal est retombé entre-temps
(rebond électrique), l'événement est ignoré et l'attente recommence sans
incrémenter le compteur.
| `0` | Désactivé — tout front montant est compté |
| `5–10` | Filtrage électrique léger (risque de double-comptage) |
| **`12`** | **Valeur de référence validée sur module piézo DO générique — 1 tap = 1 comptage exact** |
| `15–30` | Plage acceptable selon le module, à recalibrer si besoin |
| `100` | Chocs lents / mécaniques amortis |
> ⚠ **Chaque module piézo a une durée d'impulsion différente** — 12ms est la
> valeur validée pour le module de référence utilisé pendant le développement
> de ce crate, mais reste un point de départ à recalibrer pour ton propre
> module si besoin.
>
> **Méthode de calibration recommandée** (par dichotomie) :
> 1. Commence à `5ms`. Si chaque tap compte +2 ou plus → le debounce est trop court.
> 2. Monte à `30ms`. Si plus aucune vibration n'est détectée → trop long.
> 3. Resserre l'intervalle jusqu'à obtenir exactement **+1 par tap physique**.
> 4. **12ms** a donné un résultat exact (1 tap = +1) sur le matériel de test —
> commence par cette valeur avant de recalibrer si nécessaire.
### Attente d'une vibration (bloquant async)
```rust
// Suspend la tâche jusqu'au prochain front montant validé : zéro CPU pendant l'attente
let event = piezo.wait_for_vibration().await;
// event.count : u32 : nombre total de vibrations depuis le démarrage
// event.active : bool : pin encore haut à cet instant
```
### ⚠️ Éviter le double-comptage en boucle continue
Si tu enchaînes directement `wait_for_vibration()` dans une boucle sans
réarmement, et que le pin reste électriquement haut un peu plus longtemps que
prévu (résonance mécanique du capteur, bruit), le driver peut redéclencher
plusieurs fois pour un seul choc physique. **Attends que le signal redescende**
avant de réécouter :
```rust
use embassy_piezo::signals::VIBRATION_SIGNAL;
use embassy_time::Duration;
#[embassy_executor::task]
async fn piezo_monitor_task(mut piezo: PiezoVibration<'static>) {
loop {
let event = piezo.wait_for_vibration().await;
VIBRATION_SIGNAL.signal(event.count);
// Réarme la détection seulement une fois le pin redescendu,
// avec un timeout de sécurité pour ne jamais rester bloqué
// si le capteur reste électriquement haut anormalement longtemps.
let _ = piezo.wait_for_silence_timeout(Duration::from_millis(200)).await;
}
}
```
### Avec timeout
```rust
use embassy_time::Duration;
use embassy_piezo::PiezoError;
match piezo.wait_for_vibration_timeout(Duration::from_secs(5)).await {
Ok(event) => {
// Vibration détectée dans les 5 secondes
// event.count, event.active disponibles
}
Err(PiezoError::Timeout) => {
// Aucune vibration pendant 5 secondes — capteur peut-être débranché
}
Err(PiezoError::PinUnavailable) => {
// Pin mal configuré
}
}
```
> Le timeout englobe l'intégralité de la détection, délai de debounce inclus.
> Exemple : avec `timeout = 200 ms` et `debounce_ms = 100`, un choc survenu à
> 150 ms laisse 50 ms pour la confirmation : si ce délai expire, `Timeout` est retourné.
### Attente de fin de vibration
```rust
// Attend que le pin repasse bas (fin de l'impulsion)
piezo.wait_for_silence().await;
// Avec timeout (recommandé en pratique, voir section anti-double-comptage ci-dessus)
match piezo.wait_for_silence_timeout(Duration::from_millis(200)).await {
Ok(()) => { /* silence atteint */ }
Err(PiezoError::Timeout) => { /* vibration toujours active */ }
_ => {}
}
```
### Lecture instantanée
```rust
if piezo.is_vibrating() {
// front haut à cet instant
}
let state = piezo.state();
// state.count : u32
// state.active : bool
let n = piezo.count();
piezo.reset_count();
```
> 💡 **Astuce debug matériel** : si tu n'es pas sûr que le câblage/capteur
> fonctionne avant même de tester la détection de front, ajoute une tâche
> de polling temporaire qui logue `is_vibrating()` toutes les 500ms — ça
> confirme que le signal physique change bien quand tu tapes, indépendamment
> de toute la logique de debounce/edge-detection.
### Comptage sur fenêtre temporelle
```rust
use embassy_time::Duration;
// Combien de chocs détectés en 2 secondes ?
// À utiliser avec une tâche de comptage parallèle (voir exemple complet)
let hits = piezo.count_during(Duration::from_secs(2)).await;
```
---
## 📡 Signaux globaux
`VIBRATION_SIGNAL` publie le compteur `u32` à chaque vibration.
Sûr depuis les interruptions (`CriticalSectionRawMutex`).
```rust
use embassy_piezo::signals::VIBRATION_SIGNAL;
// Tâche productrice
let event = piezo.wait_for_vibration().await;
VIBRATION_SIGNAL.signal(event.count);
// Tâche consommatrice (logger, LED, radio, OLED…)
#[embassy_executor::task]
async fn alert_task() {
loop {
let count = VIBRATION_SIGNAL.wait().await;
// Réagir à la vibration numéro `count`
}
}
```
> Le signal publie un `u32` (compteur) et non un `bool` : la tâche
> consommatrice peut détecter des événements manqués entre deux `wait()`.
---
## 🏗️ Architecture du projet
embassy-piezo/
├── Cargo.toml
└── src/
├── lib.rs ← Driver principal, VibrationEvent, PiezoVibration
├── error.rs ← PiezoError (Timeout, PinUnavailable)
└── signals.rs ← VIBRATION_SIGNAL (CriticalSectionRawMutex)
---
## 📋 API publique : référence rapide
### `PiezoVibration`
| `PiezoVibration::new(pin)` | — | Crée le driver sans debounce |
| `PiezoVibration::new_with_debounce(pin, ms)` | — | Crée le driver avec debounce |
| `wait_for_vibration()` | ✅ | Attend le prochain front montant validé, incrémente le compteur |
| `wait_for_vibration_timeout(d)` | ✅ | Idem avec timeout → `Result<VibrationEvent, PiezoError>` |
| `wait_for_silence()` | ✅ | Attend le front descendant |
| `wait_for_silence_timeout(d)` | ✅ | Idem avec timeout → `Result<(), PiezoError>` |
| `count_during(window)` | ✅ | Compte les vibrations sur une fenêtre temporelle |
| `is_vibrating()` | — | Lecture instantanée du pin |
| `count()` | — | Compteur cumulé depuis le démarrage |
| `reset_count()` | — | Remet le compteur à zéro |
| `state()` | — | Snapshot `VibrationEvent { count, active }` |
### `VibrationEvent`
| `count` | `u32` | Vibrations cumulées (jamais de panic : `saturating_add`) |
| `active` | `bool` | Pin haut à l'instant de la lecture |
### `PiezoError`
| `Timeout` | Délai expiré sans événement |
| `PinUnavailable` | Pin mal configuré |
---
## 🐞 Dépannage rapide
| Tu pars de zéro et ne sais pas par où commencer | — | Essaie `12ms` en premier (valeur de référence validée), puis ajuste si besoin |
| Rien ne se passe jamais, même en tapant fort | Câblage DO/GPIO incorrect, ou alim manquante | Vérifie VCC/GND/DO, teste avec `is_vibrating()` en polling |
| `is_vibrating()` reste toujours `false` en tapant | Sensibilité du potentiomètre trop basse | Tourne le potentiomètre bleu dans le sens horaire |
| Chaque tap compte +2 ou plus | Debounce trop court pour ce module | Augmente `debounce_ms` (essaie 15-30ms) |
| Plus aucune détection après avoir augmenté le debounce | Debounce trop long, impulsion filtrée | Redescends progressivement (dichotomie) |
| Compteur explose en continu sans qu'on touche au capteur | Boucle sans réarmement + pin resté haut | Ajoute `wait_for_silence_timeout()` après chaque détection |
---
## 📜 Licence
GPL-2.0-or-later — voir [LICENSE](LICENSE).
Copyright (C) 2026 Jorge Andre Castro