embassy_am2302/lib.rs
1// Copyright (C) 2026 Jorge Andre Castro
2// GPL-2.0-or-later
3
4#![no_std]
5
6//! Driver async `no_std` pour le capteur AM2302 (DHT22).
7//! Compatible avec toutes les cartes Embassy via `embedded-hal` (pins)
8//! et `embassy-time` (délais précis).
9//!
10//! ## Calibration du seuil
11//!
12//! Le DHT22 encode ses bits par la durée relative du signal haut :
13//! ~28 µs → bit `0`, ~70 µs → bit `1`. Cette crate mesure cette durée
14//! par **comptage d'itérations de boucle**. Le seuil dépend de la
15//! fréquence du MCU :
16//!
17//! | Carte | Fréquence | Constante |
18//! |------------------------------|-----------|-------------------------|
19//! | Raspberry Pi Pico 2 (RP2350) | 150 MHz | [`PICO2_BIT_THRESHOLD`] |
20//! | Raspberry Pi Pico (RP2040) | 125 MHz | [`PICO_BIT_THRESHOLD`] |
21//!
22//! ## Exemple — Embassy RP2350
23//!
24//! ```rust,ignore
25//! use embassy_rp::gpio::Flex;
26//! use embassy_am2302::{am2302_read, PICO2_BIT_THRESHOLD};
27//!
28//! #[embassy_executor::task]
29//! async fn sensor_task(mut pin: Flex<'static>) {
30//! loop {
31//! match am2302_read(&mut pin, PICO2_BIT_THRESHOLD).await {
32//! Ok(data) => defmt::info!("{}°C {}%", data.temp, data.hum),
33//! Err(_) => {}
34//! }
35//! embassy_time::Timer::after_secs(3).await;
36//! }
37//! }
38//! ```
39
40pub mod signals;
41
42use embassy_time::{Duration, Timer};
43use embedded_hal::digital::{InputPin, OutputPin};
44
45/// Seuil calibré pour le **Raspberry Pi Pico 2 (RP2350)** à 150 MHz.
46pub const PICO2_BIT_THRESHOLD: u32 = 40;
47
48/// Seuil calibré pour le **Raspberry Pi Pico (RP2040)** à 125 MHz.
49pub const PICO_BIT_THRESHOLD: u32 = 33;
50
51/// Données environnementales lues depuis le capteur AM2302.
52#[derive(Clone, Copy, Debug)]
53pub struct EnvData {
54 /// Température en °C. Peut être négative (bit de signe DHT22).
55 pub temp: f32,
56 /// Humidité relative en %, dans la plage `[0.0, 100.0]`.
57 pub hum: f32,
58}
59
60/// Erreurs possibles lors de la lecture du capteur AM2302.
61#[derive(Debug, PartialEq)]
62pub enum Am2302Error<E> {
63 /// Timeout pendant le handshake ou la lecture des bits.
64 Timeout,
65 /// Checksum invalide — données corrompues ou transmission incomplète.
66 ChecksumMismatch,
67 /// Erreur matérielle retournée par le HAL GPIO.
68 Gpio(E),
69}
70
71/// Lit une mesure depuis le capteur AM2302.
72///
73/// # Arguments
74///
75/// * `pin` — broche GPIO implémentant [`InputPin`] + [`OutputPin`]
76/// (ex : `embassy_rp::gpio::Flex`, `embassy_stm32::gpio::Flex`, etc.)
77/// * `bit_threshold` — seuil de comptage pour distinguer bit `0` et bit `1`
78///
79/// # Retour
80///
81/// `Ok(EnvData)` si la lecture et le checksum sont valides,
82/// `Err(Am2302Error)` sinon.
83pub async fn am2302_read<P, E>(
84 pin: &mut P,
85 bit_threshold: u32,
86) -> Result<EnvData, Am2302Error<E>>
87where
88 P: InputPin<Error = E> + OutputPin<Error = E>,
89{
90 // 1. SIGNAL DE START — 20 ms à l'état bas
91 pin.set_low().map_err(Am2302Error::Gpio)?;
92 Timer::after(Duration::from_millis(20)).await;
93 pin.set_high().map_err(Am2302Error::Gpio)?;
94
95 // 2. HANDSHAKE
96 let mut timeout = 0u32;
97 while pin.is_high().map_err(Am2302Error::Gpio)? {
98 timeout += 1;
99 if timeout > 10_000 { return Err(Am2302Error::Timeout); }
100 }
101 timeout = 0;
102 while pin.is_low().map_err(Am2302Error::Gpio)? {
103 timeout += 1;
104 if timeout > 10_000 { return Err(Am2302Error::Timeout); }
105 }
106 timeout = 0;
107 while pin.is_high().map_err(Am2302Error::Gpio)? {
108 timeout += 1;
109 if timeout > 10_000 { return Err(Am2302Error::Timeout); }
110 }
111
112 // 3. LECTURE DES 40 BITS
113 let mut data = [0u8; 5];
114
115 for i in 0..40usize {
116 timeout = 0;
117 while pin.is_low().map_err(Am2302Error::Gpio)? {
118 timeout += 1;
119 if timeout > 10_000 { return Err(Am2302Error::Timeout); }
120 }
121
122 let mut high_count = 0u32;
123 while pin.is_high().map_err(Am2302Error::Gpio)? {
124 high_count += 1;
125 if high_count > bit_threshold * 5 { break; }
126 }
127
128 if high_count > bit_threshold {
129 data[i / 8] |= 1 << (7 - (i % 8));
130 }
131 }
132
133 // 4. VALIDATION DU CHECKSUM
134 let checksum = data[0]
135 .wrapping_add(data[1])
136 .wrapping_add(data[2])
137 .wrapping_add(data[3]);
138
139 if data[4] != checksum {
140 return Err(Am2302Error::ChecksumMismatch);
141 }
142
143 // 5. DÉCODAGE
144 let hum = (((data[0] as u16) << 8) | data[1] as u16) as f32 / 10.0;
145 let mut temp = ((((data[2] & 0x7F) as u16) << 8) | data[3] as u16) as f32 / 10.0;
146 if data[2] & 0x80 != 0 { temp = -temp; }
147
148 Ok(EnvData { temp, hum })
149}