embassy-piezo 0.1.0

Driver async no_std pour capteur de vibration piézoélectrique, basé sur Embassy et esp-hal.
docs.rs failed to build embassy-piezo-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

embassy-piezo

Crates.io Documentation 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 et 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

[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)

Broche module Connexion Note
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)

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]) :

#[esp_rtos::main]
async fn main(spawner: embassy_executor::Spawner) -> ! {
    // ...
}

Initialisation du driver

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.

debounce_ms Cas d'usage
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)

// 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 :

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

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

// 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

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

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).

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

Méthode Async Description
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

Champ Type Description
count u32 Vibrations cumulées (jamais de panic : saturating_add)
active bool Pin haut à l'instant de la lecture

PiezoError

Variant Cause
Timeout Délai expiré sans événement
PinUnavailable Pin mal configuré

🐞 Dépannage rapide

Symptôme Cause probable Solution
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.

Copyright (C) 2026 Jorge Andre Castro