Repo of no-std crates for my personal embedded projects
at main 34 kB view raw
1//! # Introduction 2//! 3//! This is a platform agnostic Rust driver for the Sensirion SHTC3 temperature / 4//! humidity sensor, based on the 5//! [`embedded-hal`](https://github.com/rust-embedded/embedded-hal) traits. 6//! 7//! ## Supported Devices 8//! 9//! Tested with the following sensors: 10//! - [SHTC3](https://www.sensirion.com/shtc3/) 11//! 12//! ## Blocking / Non-Blocking Modes 13//! 14//! This driver provides blocking and non-blocking calls. The blocking calls delay the execution 15//! until the measurement is done and return the results. The non-blocking ones just start the 16//! measurement and allow the application code to do other stuff and get the results afterwards. 17//! 18//! ## Clock Stretching 19//! 20//! While the sensor would provide measurement commands with clock stretching to indicate when the 21//! measurement is done, this is not implemented and probably won't be. 22//! 23//! ## Usage 24//! 25//! ### Setup 26//! 27//! Instantiate a new driver instance using a [blocking I²C HAL 28//! implementation](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/i2c/index.html) 29//! and a [blocking `Delay` 30//! instance](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/delay/index.html). 31//! For example, using `linux-embedded-hal` and an SHTC3 sensor: 32//! 33//! ```no_run 34//! use linux_embedded_hal::{Delay, I2cdev}; 35//! use sachy_shtc3::ShtC3; 36//! 37//! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 38//! let mut sht = ShtC3::new(dev, Default::default()); 39//! ``` 40//! 41//! ### Device Info 42//! 43//! Then, you can query information about the sensor: 44//! 45//! ```no_run 46//! use linux_embedded_hal::{Delay, I2cdev}; 47//! use sachy_shtc3::ShtC3; 48//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap(), Default::default()); 49//! let device_id = sht.device_identifier().unwrap(); 50//! let raw_id = sht.raw_id_register().unwrap(); 51//! ``` 52//! 53//! ### Measurements (Blocking) 54//! 55//! For measuring your environment, you can either measure just temperature, 56//! just humidity, or both: 57//! 58//! ```no_run 59//! use linux_embedded_hal::{Delay, I2cdev}; 60//! use sachy_shtc3::{ShtC3, PowerMode}; 61//! 62//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap(), Default::default()); 63//! let mut delay = Delay; 64//! 65//! let temperature = sht.measure_temperature(&mut delay).unwrap(); 66//! let humidity = sht.measure_humidity(&mut delay).unwrap(); 67//! let combined = sht.measure(&mut delay).unwrap(); 68//! 69//! println!("Temperature: {} °C", temperature.as_degrees_celsius()); 70//! println!("Humidity: {} %RH", humidity.as_percent()); 71//! println!("Combined: {} °C / {} %RH", 72//! combined.temperature.as_degrees_celsius(), 73//! combined.humidity.as_percent()); 74//! ``` 75//! 76//! You can also use the low power mode for less power consumption, at the cost 77//! of reduced repeatability and accuracy of the sensor signals. For more 78//! information, see the ["Low Power Measurement Mode" application note][low-power]. 79//! 80//! [low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf 81//! 82//! ```no_run 83//! use linux_embedded_hal::{Delay, I2cdev}; 84//! use sachy_shtc3::{ShtC3, PowerMode}; 85//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap(), PowerMode::LowPower); 86//! let mut delay = Delay; 87//! let measurement = sht.measure(&mut delay).unwrap(); 88//! ``` 89//! 90//! ### Measurements (Non-Blocking) 91//! 92//! If you want to avoid blocking measurements, you can use the non-blocking 93//! commands instead. You are, however, responsible for ensuring the correct 94//! timing of the calls. 95//! 96//! ```no_run 97//! use linux_embedded_hal::I2cdev; 98//! use sachy_shtc3::{ShtC3, PowerMode}; 99//! 100//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap(), PowerMode::NormalMode); 101//! 102//! sht.start_measurement().unwrap(); 103//! // Wait for at least `max_measurement_duration(&sht, PowerMode::NormalMode)` µs 104//! let result = sht.get_measurement_result().unwrap(); 105//! ``` 106//! 107//! In non-blocking mode, if desired, you can also read the raw 16-bit 108//! measurement results from the sensor by using the following two methods 109//! instead: 110//! 111//! - [`get_raw_measurement_result`](crate::ShtC3::get_raw_measurement_result()) 112//! - [`get_raw_partial_measurement_result`](crate::ShtC3::get_raw_partial_measurement_result()) 113//! 114//! The raw values are of type u16. They require a conversion formula for 115//! conversion to a temperature / humidity value (see datasheet). 116//! 117//! Invoking any command other than 118//! [`wakeup`](crate::ShtC3::wakeup()) while the sensor is in 119//! sleep mode will result in an error. 120//! 121//! ### Soft Reset 122//! 123//! The SHTC3 provides a soft reset mechanism that forces the system into a 124//! well-defined state without removing the power supply. If the system is in 125//! its idle state (i.e. if no measurement is in progress) the soft reset 126//! command can be sent. This triggers the sensor to reset all internal state 127//! machines and reload calibration data from the memory. 128//! 129//! ```no_run 130//! use linux_embedded_hal::{Delay, I2cdev}; 131//! use sachy_shtc3::{ShtC3, PowerMode}; 132//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap(), PowerMode::NormalMode); 133//! let mut delay = Delay; 134//! sht.reset(&mut delay).unwrap(); 135//! ``` 136#![deny(unsafe_code, missing_docs)] 137#![no_std] 138 139mod crc; 140mod types; 141 142use embedded_hal::{ 143 delay::DelayNs as BlockingDelayNs, 144 i2c::{self, I2c, SevenBitAddress}, 145}; 146 147use crc::crc8; 148use embedded_hal_async::delay::DelayNs; 149pub use types::*; 150 151/// Whether temperature or humidity is returned first when doing a measurement. 152#[derive(Debug, Copy, Clone, PartialEq, Eq)] 153#[cfg_attr(feature = "defmt", derive(defmt::Format))] 154enum MeasurementOrder { 155 TemperatureFirst, 156 HumidityFirst, 157} 158 159/// Measurement power mode: Normal mode or low power mode. 160/// 161/// The sensors provides a low power measurement mode. Using the low power mode 162/// significantly shortens the measurement duration and thus minimizes the 163/// energy consumption per measurement. The benefit of ultra-low power 164/// consumption comes at the cost of reduced repeatability of the sensor 165/// signals: while the impact on the relative humidity signal is negligible and 166/// does not affect accuracy, it has an effect on temperature accuracy. 167/// 168/// More details can be found in the ["Low Power Measurement Mode" application 169/// note][an-low-power] by Sensirion. 170/// 171/// [an-low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf 172#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] 173#[cfg_attr(feature = "defmt", derive(defmt::Format))] 174pub enum PowerMode { 175 /// Normal measurement. 176 #[default] 177 NormalMode, 178 /// Low power measurement: Less energy consumption, but repeatability and 179 /// accuracy of measurements are negatively impacted. 180 LowPower, 181} 182 183/// All possible errors in this crate 184#[derive(Debug, PartialEq, Clone)] 185#[cfg_attr(feature = "defmt", derive(defmt::Format))] 186pub enum Error<E> { 187 /// I²C bus error 188 I2c(E), 189 /// CRC checksum validation failed 190 Crc, 191} 192 193impl<E> From<CrcError> for Error<E> { 194 fn from(_value: CrcError) -> Self { 195 Self::Crc 196 } 197} 198 199#[derive(Debug, PartialEq, Clone)] 200#[cfg_attr(feature = "defmt", derive(defmt::Format))] 201struct CrcError; 202 203impl<E> From<E> for Error<E> 204where 205 E: i2c::Error, 206{ 207 fn from(e: E) -> Self { 208 Error::I2c(e) 209 } 210} 211 212/// I²C commands sent to the sensor. 213#[derive(Debug, Copy, Clone)] 214#[cfg_attr(feature = "defmt", derive(defmt::Format))] 215enum Command { 216 /// Go into sleep mode. 217 Sleep, 218 /// Wake up from sleep mode. 219 WakeUp, 220 /// Measurement commands. 221 Measure { 222 power_mode: PowerMode, 223 order: MeasurementOrder, 224 }, 225 /// Software reset. 226 SoftwareReset, 227 /// Read ID register. 228 ReadIdRegister, 229} 230 231impl Command { 232 fn as_bytes(self) -> [u8; 2] { 233 match self { 234 Command::Sleep => [0xB0, 0x98], 235 Command::WakeUp => [0x35, 0x17], 236 Command::Measure { 237 power_mode: PowerMode::NormalMode, 238 order: MeasurementOrder::TemperatureFirst, 239 } => [0x78, 0x66], 240 Command::Measure { 241 power_mode: PowerMode::NormalMode, 242 order: MeasurementOrder::HumidityFirst, 243 } => [0x58, 0xE0], 244 Command::Measure { 245 power_mode: PowerMode::LowPower, 246 order: MeasurementOrder::TemperatureFirst, 247 } => [0x60, 0x9C], 248 Command::Measure { 249 power_mode: PowerMode::LowPower, 250 order: MeasurementOrder::HumidityFirst, 251 } => [0x40, 0x1A], 252 Command::ReadIdRegister => [0xEF, 0xC8], 253 Command::SoftwareReset => [0x80, 0x5D], 254 } 255 } 256} 257 258/// Driver for the SHTC3 sensor. 259#[derive(Debug, Default)] 260#[cfg_attr(feature = "defmt", derive(defmt::Format))] 261pub struct ShtC3<I2C> { 262 /// The concrete I²C device implementation. 263 i2c: I2C, 264 /// The I²C device address. 265 address: u8, 266 mode: PowerMode, 267} 268 269impl<I2C> ShtC3<I2C> { 270 /// Create a new instance of the driver for the SHTC3. 271 #[inline] 272 pub const fn new(i2c: I2C, mode: PowerMode) -> Self { 273 Self { 274 i2c, 275 address: 0x70, 276 mode, 277 } 278 } 279 280 /// Get the device's wakeup delay duration in microseconds 281 #[inline(always)] 282 pub const fn wakeup_duration(&self) -> u32 { 283 240 284 } 285 286 /// Destroy driver instance, return I²C bus instance. 287 pub fn destroy(self) -> I2C { 288 self.i2c 289 } 290 291 /// Return the maximum measurement duration (depending on the mode) in 292 /// microseconds. 293 /// 294 /// Maximum measurement duration (SHTC3 datasheet 3.1): 295 /// - Normal mode: 12.1 ms 296 /// - Low power mode: 0.8 ms 297 #[inline(always)] 298 pub const fn max_measurement_duration(&self) -> u32 { 299 match self.mode { 300 PowerMode::NormalMode => 12100, 301 PowerMode::LowPower => 800, 302 } 303 } 304 305 /// Returns the reset duration for the SHTC3 in microseconds 306 #[inline(always)] 307 pub const fn reset_duration(&self) -> u32 { 308 240_000 309 } 310 311 /// Iterate over the provided buffer and validate the CRC8 checksum. 312 /// 313 /// If the checksum is wrong, return `CrcError`. 314 /// 315 /// Note: This method will consider every third byte a checksum byte. If 316 /// the buffer size is not a multiple of 3, then not all data will be 317 /// validated. 318 fn validate_crc(&self, buf: &[u8]) -> Result<(), CrcError> { 319 let mut chunks = buf.chunks_exact(3); 320 321 for chunk in chunks.by_ref() { 322 if crc8(&chunk[..2]) != chunk[2] { 323 return Err(CrcError); 324 } 325 } 326 327 #[cfg(feature = "defmt")] 328 if !chunks.remainder().is_empty() { 329 defmt::warn!("Remaining data in buffer was not CRC8 validated"); 330 } 331 332 Ok(()) 333 } 334} 335 336impl<I2C> ShtC3<I2C> 337where 338 I2C: embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress>, 339{ 340 /// Write an I²C command to the sensor. 341 async fn send_command_async(&mut self, command: Command) -> Result<(), Error<I2C::Error>> { 342 self.i2c 343 .write(self.address, &command.as_bytes()) 344 .await 345 .map_err(Error::I2c) 346 } 347 348 /// Read data into the provided buffer and validate the CRC8 checksum. 349 /// 350 /// If the checksum is wrong, return `Error::Crc`. 351 /// 352 /// Note: This method will consider every third byte a checksum byte. If 353 /// the buffer size is not a multiple of 3, then not all data will be 354 /// validated. 355 async fn read_with_crc_async(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> { 356 self.i2c.read(self.address, buf).await?; 357 self.validate_crc(buf)?; 358 Ok(()) 359 } 360 361 /// Return the raw ID register. 362 pub async fn raw_id_register_async(&mut self) -> Result<u16, Error<I2C::Error>> { 363 // Request serial number 364 self.send_command_async(Command::ReadIdRegister).await?; 365 366 // Read id register 367 let mut buf = [0; 3]; 368 self.read_with_crc_async(&mut buf).await?; 369 370 Ok(u16::from_be_bytes([buf[0], buf[1]])) 371 } 372 373 /// Return the 7-bit device identifier. 374 /// 375 /// Should be 0x47 (71) for the SHTC3. 376 pub async fn device_identifier_async(&mut self) -> Result<u8, Error<I2C::Error>> { 377 let ident = self.raw_id_register_async().await?; 378 let lsb = (ident & 0b0011_1111) as u8; 379 let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8; 380 Ok(lsb | msb) 381 } 382 383 /// Set sensor to sleep mode. 384 /// 385 /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires 386 /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C 387 /// communication. 388 pub async fn sleep_async(&mut self) -> Result<(), Error<I2C::Error>> { 389 self.send_command_async(Command::Sleep).await 390 } 391 392 /// Trigger a soft reset. (async) 393 /// 394 /// The SHTC3 provides a soft reset mechanism that forces the system into a 395 /// well-defined state without removing the power supply. If the system is 396 /// in its idle state (i.e. if no measurement is in progress) the soft 397 /// reset command can be sent. This triggers the sensor to reset all 398 /// internal state machines and reload calibration data from the memory. 399 pub async fn reset_async(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> { 400 self.send_command_async(Command::SoftwareReset).await?; 401 // Table 5: 180-240 µs 402 delay.delay_us(self.reset_duration()).await; 403 Ok(()) 404 } 405 406 /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. (async) 407 pub async fn wakeup_async( 408 &mut self, 409 delay: &mut impl DelayNs, 410 ) -> Result<(), Error<I2C::Error>> { 411 self.send_command_async(Command::WakeUp).await?; 412 delay.delay_us(self.wakeup_duration()).await; 413 Ok(()) 414 } 415 416 /// Run a temperature/humidity measurement and return the combined result. 417 /// 418 /// This is an async function call. 419 pub async fn measure_async( 420 &mut self, 421 delay: &mut impl DelayNs, 422 ) -> Result<Measurement, Error<I2C::Error>> { 423 self.send_command_async(Command::Measure { 424 power_mode: self.mode, 425 order: MeasurementOrder::TemperatureFirst, 426 }) 427 .await?; 428 429 delay.delay_us(self.max_measurement_duration()).await; 430 431 let mut buf = [0; 6]; 432 self.read_with_crc_async(&mut buf).await?; 433 434 Ok(RawMeasurement { 435 temperature: u16::from_be_bytes([buf[0], buf[1]]), 436 humidity: u16::from_be_bytes([buf[3], buf[4]]), 437 } 438 .into()) 439 } 440} 441 442/// General blocking functions. 443impl<I2C> ShtC3<I2C> 444where 445 I2C: I2c<SevenBitAddress>, 446{ 447 /// Write an I²C command to the sensor. 448 fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> { 449 self.i2c 450 .write(self.address, &command.as_bytes()) 451 .map_err(Error::I2c) 452 } 453 454 /// Read data into the provided buffer and validate the CRC8 checksum. 455 /// 456 /// If the checksum is wrong, return `Error::Crc`. 457 /// 458 /// Note: This method will consider every third byte a checksum byte. If 459 /// the buffer size is not a multiple of 3, then not all data will be 460 /// validated. 461 fn read_with_crc(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> { 462 self.i2c.read(self.address, buf)?; 463 self.validate_crc(buf)?; 464 Ok(()) 465 } 466 467 /// Return the raw ID register. 468 pub fn raw_id_register(&mut self) -> Result<u16, Error<I2C::Error>> { 469 // Request serial number 470 self.send_command(Command::ReadIdRegister)?; 471 472 // Read id register 473 let mut buf = [0; 3]; 474 self.read_with_crc(&mut buf)?; 475 476 Ok(u16::from_be_bytes([buf[0], buf[1]])) 477 } 478 479 /// Return the 7-bit device identifier. 480 /// 481 /// Should be 0x47 (71) for the SHTC3. 482 pub fn device_identifier(&mut self) -> Result<u8, Error<I2C::Error>> { 483 let ident = self.raw_id_register()?; 484 let lsb = (ident & 0b0011_1111) as u8; 485 let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8; 486 Ok(lsb | msb) 487 } 488 489 /// Trigger a soft reset. (blocking) 490 /// 491 /// The SHTC3 provides a soft reset mechanism that forces the system into a 492 /// well-defined state without removing the power supply. If the system is 493 /// in its idle state (i.e. if no measurement is in progress) the soft 494 /// reset command can be sent. This triggers the sensor to reset all 495 /// internal state machines and reload calibration data from the memory. 496 pub fn reset(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> { 497 self.send_command(Command::SoftwareReset)?; 498 // Table 5: 180-240 µs 499 delay.delay_us(self.reset_duration()); 500 Ok(()) 501 } 502 503 /// Trigger a soft reset. 504 /// 505 /// The SHTC3 provides a soft reset mechanism that forces the system into a 506 /// well-defined state without removing the power supply. If the system is 507 /// in its idle state (i.e. if no measurement is in progress) the soft 508 /// reset command can be sent. This triggers the sensor to reset all 509 /// internal state machines and reload calibration data from the memory. 510 pub fn start_reset(&mut self) -> Result<(), Error<I2C::Error>> { 511 self.send_command(Command::SoftwareReset) 512 } 513 514 /// Set sensor to sleep mode. 515 /// 516 /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires 517 /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C 518 /// communication. 519 pub fn sleep(&mut self) -> Result<(), Error<I2C::Error>> { 520 self.send_command(Command::Sleep) 521 } 522 523 /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. 524 pub fn wakeup(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> { 525 self.start_wakeup()?; 526 delay.delay_us(self.wakeup_duration()); 527 Ok(()) 528 } 529} 530 531/// Non-blocking functions for starting / reading measurements. 532impl<I2C> ShtC3<I2C> 533where 534 I2C: I2c<SevenBitAddress>, 535{ 536 /// Start a measurement with the specified measurement order and write the 537 /// result into the provided buffer. 538 /// 539 /// If you just need one of the two measurements, provide a 3-byte buffer 540 /// instead of a 6-byte buffer. 541 fn start_measure_partial( 542 &mut self, 543 power_mode: PowerMode, 544 order: MeasurementOrder, 545 ) -> Result<(), Error<I2C::Error>> { 546 // Request measurement 547 self.send_command(Command::Measure { power_mode, order }) 548 } 549 550 /// Start a combined temperature / humidity measurement. 551 pub fn start_measurement(&mut self) -> Result<(), Error<I2C::Error>> { 552 self.start_measure_partial(self.mode, MeasurementOrder::TemperatureFirst) 553 } 554 555 /// Start a temperature measurement. 556 pub fn start_temperature_measurement(&mut self) -> Result<(), Error<I2C::Error>> { 557 self.start_measure_partial(self.mode, MeasurementOrder::TemperatureFirst) 558 } 559 560 /// Start a humidity measurement. 561 pub fn start_humidity_measurement(&mut self) -> Result<(), Error<I2C::Error>> { 562 self.start_measure_partial(self.mode, MeasurementOrder::HumidityFirst) 563 } 564 565 /// Read the result of a temperature / humidity measurement. 566 pub fn get_measurement_result(&mut self) -> Result<Measurement, Error<I2C::Error>> { 567 let raw = self.get_raw_measurement_result()?; 568 Ok(raw.into()) 569 } 570 571 /// Read the result of a temperature measurement. 572 pub fn get_temperature_measurement_result(&mut self) -> Result<Temperature, Error<I2C::Error>> { 573 let raw = self.get_raw_partial_measurement_result()?; 574 Ok(Temperature::from_raw(raw)) 575 } 576 577 /// Read the result of a humidity measurement. 578 pub fn get_humidity_measurement_result(&mut self) -> Result<Humidity, Error<I2C::Error>> { 579 let raw = self.get_raw_partial_measurement_result()?; 580 Ok(Humidity::from_raw(raw)) 581 } 582 583 /// Read the raw result of a combined temperature / humidity measurement. 584 pub fn get_raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<I2C::Error>> { 585 let mut buf = [0; 6]; 586 self.read_with_crc(&mut buf)?; 587 Ok(RawMeasurement { 588 temperature: u16::from_be_bytes([buf[0], buf[1]]), 589 humidity: u16::from_be_bytes([buf[3], buf[4]]), 590 }) 591 } 592 593 /// Read the raw result of a partial temperature or humidity measurement. 594 /// 595 /// Return the raw 3-byte buffer (after validating CRC). 596 pub fn get_raw_partial_measurement_result(&mut self) -> Result<u16, Error<I2C::Error>> { 597 let mut buf = [0; 3]; 598 self.read_with_crc(&mut buf)?; 599 Ok(u16::from_be_bytes([buf[0], buf[1]])) 600 } 601 602 /// Wake up sensor from [sleep mode](#method.sleep). 603 pub fn start_wakeup(&mut self) -> Result<(), Error<I2C::Error>> { 604 self.send_command(Command::WakeUp) 605 } 606} 607 608/// Blocking functions for doing measurements. 609impl<I2C> ShtC3<I2C> 610where 611 I2C: I2c<SevenBitAddress>, 612{ 613 /// Wait the maximum time needed for the given measurement mode 614 pub fn wait_for_measurement(&mut self, delay: &mut impl BlockingDelayNs) { 615 delay.delay_us(self.max_measurement_duration()); 616 } 617 618 /// Run a temperature/humidity measurement and return the combined result. 619 /// 620 /// This is a blocking function call. 621 pub fn measure( 622 &mut self, 623 delay: &mut impl BlockingDelayNs, 624 ) -> Result<Measurement, Error<I2C::Error>> { 625 self.start_measurement()?; 626 self.wait_for_measurement(delay); 627 self.get_measurement_result() 628 } 629 630 /// Run a temperature measurement and return the result. 631 /// 632 /// This is a blocking function call. 633 /// 634 /// Internally, it will request a measurement in "temperature first" mode 635 /// and only read the first half of the measurement response. 636 pub fn measure_temperature( 637 &mut self, 638 delay: &mut impl BlockingDelayNs, 639 ) -> Result<Temperature, Error<I2C::Error>> { 640 self.start_temperature_measurement()?; 641 self.wait_for_measurement(delay); 642 self.get_temperature_measurement_result() 643 } 644 645 /// Run a humidity measurement and return the result. 646 /// 647 /// This is a blocking function call. 648 /// 649 /// Internally, it will request a measurement in "humidity first" mode 650 /// and only read the first half of the measurement response. 651 pub fn measure_humidity( 652 &mut self, 653 delay: &mut impl BlockingDelayNs, 654 ) -> Result<Humidity, Error<I2C::Error>> { 655 self.start_humidity_measurement()?; 656 self.wait_for_measurement(delay); 657 self.get_humidity_measurement_result() 658 } 659} 660 661#[cfg(test)] 662mod tests { 663 extern crate alloc; 664 665 use super::*; 666 667 use embedded_hal::i2c::ErrorKind; 668 use embedded_hal_mock::eh1::{ 669 delay::NoopDelay, 670 i2c::{Mock as I2cMock, Transaction}, 671 }; 672 673 const SHT_ADDR: u8 = 0x70; 674 675 mod core { 676 use super::*; 677 678 /// Test whether the `send_command` function propagates I²C errors. 679 #[test] 680 fn send_command_error() { 681 let expectations = 682 [Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]) 683 .with_error(ErrorKind::Other)]; 684 let mock = I2cMock::new(&expectations); 685 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 686 let err = sht.send_command(Command::ReadIdRegister).unwrap_err(); 687 assert_eq!(err, Error::I2c(ErrorKind::Other)); 688 sht.destroy().done(); 689 } 690 691 /// Test the `validate_crc` function. 692 #[test] 693 fn validate_crc() { 694 let mock = I2cMock::new(&[]); 695 let sht = ShtC3::new(mock, PowerMode::NormalMode); 696 697 // Not enough data 698 sht.validate_crc(&[]).unwrap(); 699 sht.validate_crc(&[0xbe]).unwrap(); 700 sht.validate_crc(&[0xbe, 0xef]).unwrap(); 701 702 // Valid CRC 703 sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap(); 704 705 // Invalid CRC 706 match sht.validate_crc(&[0xbe, 0xef, 0x91]) { 707 Err(CrcError) => {} 708 Ok(_) => panic!("CRC check did not fail"), 709 } 710 711 // Valid CRC (8 bytes) 712 sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00]) 713 .unwrap(); 714 715 // Invalid CRC (8 bytes) 716 match sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0xff, 0x00, 0x00]) { 717 Err(CrcError) => {} 718 Ok(_) => panic!("CRC check did not fail"), 719 } 720 721 sht.destroy().done(); 722 } 723 724 /// Test the `read_with_crc` function. 725 #[test] 726 fn read_with_crc() { 727 let mut buf = [0; 3]; 728 729 // Valid CRC 730 let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x92])]; 731 let mock = I2cMock::new(&expectations); 732 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 733 sht.read_with_crc(&mut buf).unwrap(); 734 assert_eq!(buf, [0xbe, 0xef, 0x92]); 735 sht.destroy().done(); 736 737 // Invalid CRC 738 let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x00])]; 739 let mock = I2cMock::new(&expectations); 740 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 741 match sht.read_with_crc(&mut buf) { 742 Err(Error::Crc) => {} 743 Err(_) => panic!("Invalid error: Must be Crc"), 744 Ok(_) => panic!("CRC check did not fail"), 745 } 746 assert_eq!(buf, [0xbe, 0xef, 0x00]); // Buf was changed 747 sht.destroy().done(); 748 } 749 } 750 751 mod factory_functions { 752 use super::*; 753 754 #[test] 755 fn new_shtc3() { 756 let mock = I2cMock::new(&[]); 757 let sht = ShtC3::new(mock, PowerMode::NormalMode); 758 assert_eq!(sht.address, 0x70); 759 sht.destroy().done(); 760 } 761 } 762 763 mod device_info { 764 use super::*; 765 766 /// Test the `raw_id_register` function. 767 #[test] 768 fn raw_id_register() { 769 let msb = 0b00001000; 770 let lsb = 0b00000111; 771 let crc = crc8(&[msb, lsb]); 772 let expectations = [ 773 Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]), 774 Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]), 775 ]; 776 let mock = I2cMock::new(&expectations); 777 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 778 let val = sht.raw_id_register().unwrap(); 779 assert_eq!(val, (msb as u16) << 8 | (lsb as u16)); 780 sht.destroy().done(); 781 } 782 783 /// Test the `device_identifier` function. 784 #[test] 785 fn device_identifier() { 786 let msb = 0b00001000; 787 let lsb = 0b00000111; 788 let crc = crc8(&[msb, lsb]); 789 let expectations = [ 790 Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]), 791 Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]), 792 ]; 793 let mock = I2cMock::new(&expectations); 794 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 795 let ident = sht.device_identifier().unwrap(); 796 assert_eq!(ident, 0b01000111); 797 sht.destroy().done(); 798 } 799 } 800 801 mod measurements { 802 use super::*; 803 804 #[test] 805 fn measure_normal() { 806 let expectations = [ 807 // Expect a write command: Normal mode measurement, temperature 808 // first, no clock stretching. 809 Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]), 810 // Return the measurement result (using example values from the 811 // datasheet, section 5.4 "Measuring and Reading the Signals") 812 Transaction::read( 813 SHT_ADDR, 814 alloc::vec![ 815 0b0110_0100, 816 0b1000_1011, 817 0b1100_0111, 818 0b1010_0001, 819 0b0011_0011, 820 0b0001_1100, 821 ], 822 ), 823 ]; 824 let mock = I2cMock::new(&expectations); 825 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 826 let mut delay = NoopDelay; 827 let measurement = sht.measure(&mut delay).unwrap(); 828 assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C 829 assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH 830 sht.destroy().done(); 831 } 832 833 #[test] 834 fn measure_low_power() { 835 let expectations = [ 836 // Expect a write command: Low power mode measurement, temperature 837 // first, no clock stretching. 838 Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]), 839 // Return the measurement result (using example values from the 840 // datasheet, section 5.4 "Measuring and Reading the Signals") 841 Transaction::read( 842 SHT_ADDR, 843 alloc::vec![ 844 0b0110_0100, 845 0b1000_1011, 846 0b1100_0111, 847 0b1010_0001, 848 0b0011_0011, 849 0b0001_1100, 850 ], 851 ), 852 ]; 853 let mock = I2cMock::new(&expectations); 854 let mut sht = ShtC3::new(mock, PowerMode::LowPower); 855 let mut delay = NoopDelay; 856 let measurement = sht.measure(&mut delay).unwrap(); 857 assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C 858 assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH 859 sht.destroy().done(); 860 } 861 862 #[test] 863 fn measure_temperature_only() { 864 let expectations = [ 865 // Expect a write command: Normal mode measurement, temperature 866 // first, no clock stretching. 867 Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]), 868 // Return the measurement result (using example values from the 869 // datasheet, section 5.4 "Measuring and Reading the Signals") 870 Transaction::read(SHT_ADDR, alloc::vec![0b0110_0100, 0b1000_1011, 0b1100_0111]), 871 ]; 872 let mock = I2cMock::new(&expectations); 873 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 874 let mut delay = NoopDelay; 875 let temperature = sht.measure_temperature(&mut delay).unwrap(); 876 assert_eq!(temperature.as_millidegrees_celsius(), 23_730); // 23.7°C 877 sht.destroy().done(); 878 } 879 880 #[test] 881 fn measure_humidity_only() { 882 let expectations = [ 883 // Expect a write command: Normal mode measurement, humidity 884 // first, no clock stretching. 885 Transaction::write(SHT_ADDR, alloc::vec![0x58, 0xE0]), 886 // Return the measurement result (using example values from the 887 // datasheet, section 5.4 "Measuring and Reading the Signals") 888 Transaction::read(SHT_ADDR, alloc::vec![0b1010_0001, 0b0011_0011, 0b0001_1100]), 889 ]; 890 let mock = I2cMock::new(&expectations); 891 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 892 let mut delay = NoopDelay; 893 let humidity = sht.measure_humidity(&mut delay).unwrap(); 894 assert_eq!(humidity.as_millipercent(), 62_968); // 62.9 %RH 895 sht.destroy().done(); 896 } 897 898 /// Ensure that I²C write errors are handled when measuring. 899 #[test] 900 fn measure_write_error() { 901 let expectations = 902 [Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]) 903 .with_error(ErrorKind::Other)]; 904 let mock = I2cMock::new(&expectations); 905 let mut sht = ShtC3::new(mock, PowerMode::LowPower); 906 let err = sht.measure(&mut NoopDelay).unwrap_err(); 907 assert_eq!(err, Error::I2c(ErrorKind::Other)); 908 sht.destroy().done(); 909 } 910 } 911 912 mod power_management { 913 use super::*; 914 915 /// Test the `sleep` function. 916 #[test] 917 fn sleep() { 918 let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0xB0, 0x98])]; 919 let mock = I2cMock::new(&expectations); 920 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 921 sht.sleep().unwrap(); 922 sht.destroy().done(); 923 } 924 925 /// Test the `wakeup` function. 926 #[test] 927 fn wakeup() { 928 let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x35, 0x17])]; 929 let mock = I2cMock::new(&expectations); 930 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 931 sht.wakeup(&mut NoopDelay).unwrap(); 932 sht.destroy().done(); 933 } 934 935 /// Test the `reset` function. 936 #[test] 937 fn reset() { 938 let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x80, 0x5D])]; 939 let mock = I2cMock::new(&expectations); 940 let mut sht = ShtC3::new(mock, PowerMode::NormalMode); 941 sht.reset(&mut NoopDelay).unwrap(); 942 sht.destroy().done(); 943 } 944 } 945 946 mod max_measurement_duration { 947 use super::*; 948 949 #[test] 950 fn shortcut_function() { 951 let c3 = ShtC3::new(I2cMock::new(&[]), PowerMode::NormalMode); 952 953 assert_eq!(c3.max_measurement_duration(), 12100); 954 955 let i2c = c3.destroy(); 956 957 let c3 = ShtC3::new(i2c, PowerMode::LowPower); 958 959 assert_eq!(c3.max_measurement_duration(), 800); 960 961 c3.destroy().done(); 962 } 963 } 964}