Skip to main content

embassy_bmp280/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2// GPL-2.0-or-later
3
4//! # embassy-bmp280
5//!
6//! Driver asynchrone `no_std` pour le capteur pression/température Bosch BMP280.
7//!
8//! ## Caractéristiques
9//! - Async natif via `embedded-hal-async`
10//! - Lecture calibration OTP au démarrage
11//! - Compensation température et pression (algorithme Bosch officiel)
12//! - Compatible bus I2C partagé (`embassy-embedded-hal`)
13//! - zéro `unsafe`
14//!
15//! ## Exemple minimal
16//! ```rust,ignore
17//! let mut bmp = Bmp280::new(i2c_device, Bmp280Address::Default).await?;
18//! let data = bmp.read().await?;
19//! // data.temperature_cdeg : i32 (°C × 100, ex: 2315 = 23.15 °C)
20//! // data.pressure_pa256   : u32 (Pa × 256, format Q24.8)
21//! ```
22
23#![no_std]
24#![forbid(unsafe_code)]
25
26pub mod calibration;
27pub mod error;
28pub mod signals;
29
30use calibration::CalibrationData;
31pub use error::Bmp280Error;
32
33use embassy_time::Timer;
34use embedded_hal_async::i2c::I2c;
35
36// Registres 
37const REG_CALIB_START:  u8 = 0x88;
38const REG_CHIP_ID:      u8 = 0xD0;
39const REG_RESET:        u8 = 0xE0;
40const REG_CTRL_MEAS:    u8 = 0xF4;
41const REG_CONFIG:       u8 = 0xF5;
42const REG_PRESS_MSB:    u8 = 0xF7;
43
44const CHIP_ID_BMP280:   u8 = 0x58;
45const CHIP_ID_BME280:   u8 = 0x60; // variante compatible
46const RESET_WORD:       u8 = 0xB6;
47
48// Adresse I2C
49
50/// Adresse I2C du BMP280.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Bmp280Address {
53    /// SDO relié au GND → 0x76 (défaut usine).
54    Default,
55    /// SDO relié au VCC → 0x77.
56    Secondary,
57    /// Adresse personnalisée.
58    Custom(u8),
59}
60
61impl From<Bmp280Address> for u8 {
62    fn from(a: Bmp280Address) -> u8 {
63        match a {
64            Bmp280Address::Default    => 0x76,
65            Bmp280Address::Secondary  => 0x77,
66            Bmp280Address::Custom(v)  => v,
67        }
68    }
69}
70
71// Configuration
72/// Oversampling pour la température.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74#[repr(u8)]
75pub enum OversamplingTemp {
76    Skip  = 0b000,
77    X1    = 0b001,
78    X2    = 0b010,
79    X4    = 0b011,
80    X8    = 0b100,
81    X16   = 0b101,
82}
83
84/// Oversampling pour la pression.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[repr(u8)]
87pub enum OversamplingPress {
88    Skip  = 0b000,
89    X1    = 0b001,
90    X2    = 0b010,
91    X4    = 0b011,
92    X8    = 0b100,
93    X16   = 0b101,
94}
95
96/// Mode de fonctionnement.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[repr(u8)]
99pub enum PowerMode {
100    /// Pas de mesure. Utile pour économiser l'énergie.
101    Sleep  = 0b00,
102    /// Une mesure puis retour en sleep.
103    Forced = 0b01,
104    /// Mesures continues (recommandé avec signaux).
105    Normal = 0b11,
106}
107
108/// Configuration complète du capteur.
109#[derive(Debug, Clone, Copy)]
110pub struct Bmp280Config {
111    pub temp_os:  OversamplingTemp,
112    pub press_os: OversamplingPress,
113    pub mode:     PowerMode,
114}
115
116impl Default for Bmp280Config {
117    /// Équilibre performances/consommation : oversampling ×1, mode Normal.
118    fn default() -> Self {
119        Self {
120            temp_os:  OversamplingTemp::X1,
121            press_os: OversamplingPress::X1,
122            mode:     PowerMode::Normal,
123        }
124    }
125}
126
127// Données de sortie 
128
129/// Mesure compensée provenant du BMP280.
130#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
131pub struct Bmp280Data {
132    /// Température en centidegrés Celsius (ex: `2315` = 23.15 °C).
133    pub temperature_cdeg: i32,
134    /// Pression en Pascals × 256 : format Q24.8.
135    /// Diviser par 256 pour obtenir des Pascals.
136    pub pressure_pa256: u32,
137}
138
139impl Bmp280Data {
140    /// Température en degrés Celsius (f32).
141    /// 
142    /// Note : Nécessite le support matériel FPU de la cible. 
143    /// Aucune dépendance externe (libm) n'est requise pour ces divisions simples.
144    #[cfg(feature = "float")]
145    pub fn temperature_celsius(&self) -> f32 {
146        self.temperature_cdeg as f32 / 100.0
147    }
148
149    /// Pression en hPa (f32). 1 hPa = 100 Pa.
150    #[cfg(feature = "float")]
151    pub fn pressure_hpa(&self) -> f32 {
152        (self.pressure_pa256 as f32) / (256.0 * 100.0)
153    }
154}
155
156//  Driver 
157pub struct Bmp280<I2C> {
158    i2c:   I2C,
159    addr:  u8,
160    calib: CalibrationData,
161}
162
163impl<I2C: I2c> Bmp280<I2C> {
164    /// Crée et initialise le driver.
165    ///
166    /// Effectue dans l'ordre :
167    /// 1. Reset logiciel du capteur
168    /// 2. Vérification du Chip ID (0x58 ou 0x60)
169    /// 3. Lecture des 24 octets de calibration OTP
170    /// 4. Application de la configuration (oversampling + mode)
171    ///
172    /// # Erreurs
173    /// - [`Bmp280Error::I2c`] : échec de communication
174    /// - [`Bmp280Error::InvalidChipId`] : mauvais composant sur le bus
175    /// - [`Bmp280Error::InvalidCalibration`] :OTP corrompue (dig_T1 == 0)
176    pub async fn new(
177        i2c:    I2C,
178        addr:   Bmp280Address,
179        config: Bmp280Config,
180    ) -> Result<Self, Bmp280Error<I2C::Error>> {
181        let mut driver = Self {
182            i2c,
183            addr: addr.into(),
184            calib: CalibrationData::default(),
185        };
186
187        driver.soft_reset().await?;
188        driver.verify_chip_id().await?;
189        driver.read_calibration().await?;
190        driver.apply_config(config).await?;
191
192        Ok(driver)
193    }
194
195    // Privé 
196    async fn write_reg(
197        &mut self,
198        reg: u8,
199        val: u8,
200    ) -> Result<(), Bmp280Error<I2C::Error>> {
201        self.i2c
202            .write(self.addr, &[reg, val])
203            .await
204            .map_err(Bmp280Error::I2c)
205    }
206
207    async fn read_reg(
208        &mut self,
209        reg: u8,
210    ) -> Result<u8, Bmp280Error<I2C::Error>> {
211        let mut buf = [0u8; 1];
212        self.i2c
213            .write_read(self.addr, &[reg], &mut buf)
214            .await
215            .map_err(Bmp280Error::I2c)?;
216        Ok(buf[0])
217    }
218
219    async fn read_bytes<const N: usize>(
220        &mut self,
221        reg: u8,
222    ) -> Result<[u8; N], Bmp280Error<I2C::Error>> {
223        let mut buf = [0u8; N];
224        self.i2c
225            .write_read(self.addr, &[reg], &mut buf)
226            .await
227            .map_err(Bmp280Error::I2c)?;
228        Ok(buf)
229    }
230
231    async fn soft_reset(&mut self) -> Result<(), Bmp280Error<I2C::Error>> {
232        self.write_reg(REG_RESET, RESET_WORD).await?;
233        // Le BMP280 a besoin de ~2 ms après un reset pour être prêt à communiquer.
234        Timer::after_millis(3).await;
235        Ok(())
236    }
237
238    async fn verify_chip_id(&mut self) -> Result<(), Bmp280Error<I2C::Error>> {
239        let id = self.read_reg(REG_CHIP_ID).await?;
240        if id != CHIP_ID_BMP280 && id != CHIP_ID_BME280 {
241            return Err(Bmp280Error::InvalidChipId(id));
242        }
243        Ok(())
244    }
245
246    async fn read_calibration(&mut self) -> Result<(), Bmp280Error<I2C::Error>> {
247        let raw: [u8; 24] = self.read_bytes::<24>(REG_CALIB_START).await?;
248        let calib = CalibrationData::from_raw(&raw);
249
250        // Sanity check : dig_T1 ne peut pas être 0 sur un capteur valide
251        if calib.dig_t1 == 0 {
252            return Err(Bmp280Error::InvalidCalibration);
253        }
254
255        self.calib = calib;
256        Ok(())
257    }
258
259    async fn apply_config(
260        &mut self,
261        cfg: Bmp280Config,
262    ) -> Result<(), Bmp280Error<I2C::Error>> {
263        // ctrl_meas : [7:5] osrs_t | [4:2] osrs_p | [1:0] mode
264        let ctrl = ((cfg.temp_os  as u8) << 5)
265                 | ((cfg.press_os as u8) << 2)
266                 |  (cfg.mode     as u8);
267        self.write_reg(REG_CTRL_MEAS, ctrl).await?;
268
269        // config : filtre IIR désactivé, standby 0.5 ms
270        self.write_reg(REG_CONFIG, 0x00).await?;
271        Ok(())
272    }
273
274    // Public
275
276    /// Lit et retourne une mesure compensée (température + pression).
277    ///
278    /// Lit 6 octets en burst depuis 0xF7, applique l'algorithme de
279    /// compensation Bosch et retourne [`Bmp280Data`].
280    ///
281    /// # Erreurs
282    /// - [`Bmp280Error::I2c`] échec de lecture
283    pub async fn read(&mut self) -> Result<Bmp280Data, Bmp280Error<I2C::Error>> {
284        // Burst read : press[2:0] puis temp[2:0] (ordre datasheet)
285        let raw: [u8; 6] = self.read_bytes::<6>(REG_PRESS_MSB).await?;
286
287        let raw_p = ((raw[0] as u32) << 12)
288                  | ((raw[1] as u32) <<  4)
289                  | ((raw[2] as u32) >>  4);
290
291        let raw_t = ((raw[3] as u32) << 12)
292                  | ((raw[4] as u32) <<  4)
293                  | ((raw[5] as u32) >>  4);
294
295        let (temperature_cdeg, t_fine) = self.calib.compensate_temperature(raw_t);
296        let pressure_pa256 = self.calib
297            .compensate_pressure(raw_p, t_fine)
298            .unwrap_or(0);
299
300        Ok(Bmp280Data { temperature_cdeg, pressure_pa256 })
301    }
302
303    /// Change l'adresse I2C dynamiquement (ex: après une redétection).
304    pub fn set_address(&mut self, addr: Bmp280Address) {
305        self.addr = addr.into();
306    }
307
308    /// Change le mode de fonctionnement sans reconfigurer l'oversampling.
309    ///
310    /// Utile pour passer en `Sleep` pendant les phases basse consommation.
311    pub async fn set_mode(
312        &mut self,
313        mode: PowerMode,
314    ) -> Result<(), Bmp280Error<I2C::Error>> {
315        let current = self.read_reg(REG_CTRL_MEAS).await?;
316        let updated = (current & 0b1111_1100) | (mode as u8);
317        self.write_reg(REG_CTRL_MEAS, updated).await
318    }
319}