#![no_std]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::enum_glob_use)]
mod flags;
pub mod interrupts;
mod registers;
use flags::{Flags, INT_CFG, INT_EN};
pub use registers::csb;
use registers::{Register16, Register8, Registers};
use core::marker::PhantomData;
#[cfg(feature = "defmt")]
use defmt::{assert, debug, info, trace};
use embedded_hal::delay::blocking::DelayUs;
use embedded_hal::i2c::blocking::I2c;
#[allow(missing_docs)]
pub mod mode {
pub trait BarometricMeasurement {}
pub struct Altitude;
impl BarometricMeasurement for Altitude {}
pub struct Pressure;
impl BarometricMeasurement for Pressure {}
}
pub struct HP203B<I, M = mode::Altitude, C = csb::CSBLow>
where
I: I2c,
M: mode::BarometricMeasurement,
C: csb::CSB,
{
i2c: I,
waiting_reset: bool,
osr: OSR,
_c: PhantomData<(C, M)>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum OSR {
OSR4096 = 0b0_0000,
OSR2048 = 0b0_0100,
OSR1024 = 0b0_1000,
OSR512 = 0b0_1100,
OSR256 = 0b1_0000,
OSR128 = 0b1_0100,
}
use fugit::{ExtU32, MicrosDurationU32};
impl OSR {
#[must_use]
pub fn associated_delay(self) -> MicrosDurationU32 {
match self {
OSR::OSR128 => 2100,
OSR::OSR256 => 4100,
OSR::OSR512 => 8200,
OSR::OSR1024 => 16400,
OSR::OSR2048 => 32800,
OSR::OSR4096 => 65600,
}
.millis()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Channel {
SensorPressureTemperature = 0b00,
Temperature = 0b10,
}
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum Command {
SOFT_RST = 0x06,
ADC_CVT = 0x40,
ANA_CAL = 0x28,
READ_REG = 0x80,
WRITE_REG = 0xC0,
READ_PT = 0x10,
READ_AT = 0x11,
READ_P = 0x30,
READ_A = 0x31,
READ_T = 0x32,
}
impl<I, E, M, C> HP203B<I, M, C>
where
I: I2c<Error = E>,
M: mode::BarometricMeasurement,
C: csb::CSB,
{
pub fn destroy(self) -> I {
self.i2c
}
pub fn set_osr_channel(&mut self, osr: OSR, ch: Channel) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting {} and {}", osr, ch);
let command = Command::ADC_CVT as u8 + osr as u8 + ch as u8;
self.i2c.write(Self::ADDR, &[command])
}
pub fn read_delay(&self) -> MicrosDurationU32 {
self.osr.associated_delay()
}
pub fn reset(&mut self, delay: &mut impl DelayUs) -> nb::Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Resetting device");
if !self.waiting_reset {
self.command(Command::SOFT_RST)?;
delay.delay_ms(100).unwrap(); self.waiting_reset = true;
}
if !self.is_ready()? {
#[cfg(feature = "defmt")]
trace!("Device hasn't set ready flag, sending WouldBlock");
return Err(nb::Error::WouldBlock);
}
self.waiting_reset = false;
Ok(())
}
pub fn is_ready(&mut self) -> Result<bool, E> {
#[cfg(feature = "defmt")]
debug!("Checking ready flag");
Ok(self.get_interrupts()?.contains(flags::INT_SRC::DEV_RDY))
}
pub fn wait_ready(&mut self) -> nb::Result<(), E> {
if !self.is_ready()? {
#[cfg(feature = "defmt")]
trace!("Device hasn't set ready flag, sending WouldBlock");
return Err(nb::Error::WouldBlock);
}
self.waiting_reset = false;
Ok(())
}
pub fn set_temp_bounds(&mut self, lower: i8, upper: i8) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting temperature bounds ({}, {})", upper, lower);
assert!(
lower <= upper,
"Lower bound {} is larger than upper bound {}",
lower,
upper,
);
self.write_reg8(Register8::T_L_TH, lower)
.and(self.write_reg8(Register8::T_H_TH, upper))
}
pub fn set_temp_mid(&mut self, mid: i8) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting temperature mid bound {}", mid);
self.write_reg8(Register8::T_M_TH, mid)
}
pub fn recalibrate_analog(&mut self) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Recalibrating analog blocks");
self.command(Command::ANA_CAL)
}
pub fn compensate(&mut self, comp: bool) -> Result<(), E> {
let flag = if comp {
flags::PARA::CMPS_EN
} else {
flags::PARA::empty()
};
#[cfg(feature = "defmt")]
debug!("Setting compensation flag = {}", flag);
self.set_para(flag)
}
pub fn is_compensation_enabled(&mut self) -> Result<bool, E> {
#[cfg(feature = "defmt")]
debug!("Checking compensation enabled");
Ok(self.para()?.contains(flags::PARA::CMPS_EN))
}
pub fn read_temp(&mut self) -> Result<Temperature, E> {
#[cfg(feature = "defmt")]
debug!("Reading temperature");
nb::block!(self.inner_block(flags::INT_SRC::T_RDY))?;
Ok(self.read_one(Command::READ_T)?.into())
}
fn read_one(&mut self, cmd: Command) -> Result<[u8; 3], E> {
let mut raw = [0; 3];
#[cfg(feature = "defmt")]
trace!("Sending command {}", cmd);
self.i2c.write_read(Self::ADDR, &[cmd as u8], &mut raw)?;
Ok(raw)
}
fn read_two(&mut self, cmd: Command) -> Result<[u8; 6], E> {
#[cfg(feature = "defmt")]
trace!("Reading both values from device");
let mut raw = [0; 6];
#[cfg(feature = "defmt")]
trace!("Sending command {}", cmd);
self.i2c.write_read(Self::ADDR, &[cmd as u8], &mut raw)?;
Ok(raw)
}
fn command(&mut self, cmd: Command) -> Result<(), E> {
#[cfg(feature = "defmt")]
trace!("Sending command {}", cmd);
self.i2c.write(Self::ADDR, &[cmd as u8])
}
fn inner_block(&mut self, event: flags::INT_SRC) -> nb::Result<(), E> {
if self.get_interrupts()?.contains(event) {
Ok(())
} else {
Err(nb::Error::WouldBlock)
}
}
}
impl<I, E, C> HP203B<I, mode::Pressure, C>
where
I: I2c<Error = E>,
C: csb::CSB,
{
pub const PRES_MAX: f32 = (u16::MAX as f32) / 0.02;
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
fn bound_to_dev_int(bound: f32) -> u16 {
assert!(bound.is_sign_positive());
assert!(bound <= Self::PRES_MAX,);
assert!(bound.is_normal());
(bound / 0.02) as u16
}
pub fn new(i2c: I, osr: OSR, ch: Channel, delay: &mut impl DelayUs) -> Result<Self, E> {
#[cfg(feature = "defmt")]
debug!("Creating new HP203B altimeter");
let mut new = Self {
i2c,
_c: PhantomData,
waiting_reset: false,
osr,
};
nb::block!(new.reset(delay))?;
new.set_osr_channel(osr, ch)?;
new.set_interrupts_enabled(INT_EN::RDY_EN)?;
#[cfg(feature = "defmt")]
info!("HP203B altimeter object created and configured");
Ok(new)
}
pub fn to_altitude(self) -> Result<HP203B<I, mode::Altitude, C>, E> {
let mut new = HP203B::<_, mode::Altitude, _> {
_c: PhantomData,
waiting_reset: false,
osr: self.osr,
i2c: self.destroy(),
};
#[cfg(feature = "defmt")]
debug!("Converting altimeter to altitude mode");
new.set_alti_bounds(0, 0)?;
new.set_alti_mid(0)?;
new.set_offset(0)?;
let mut new_en_flags = new.get_interrupts_enabled()?;
new_en_flags.remove(INT_EN::PA_TRAV_EN);
new_en_flags.remove(INT_EN::PA_WIN_EN);
new.set_interrupts_enabled(new_en_flags)?;
let mut new_pinout_flags = new.get_interrupts_pinout()?;
new_pinout_flags.insert(INT_CFG::PA_MODE);
new.set_interrupts_pinout(new_pinout_flags)?;
#[cfg(feature = "defmt")]
info!("Altimeter succesfully set to altitude mode");
Ok(new)
}
pub fn set_pres_bounds(&mut self, lower: f32, upper: f32) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting pressure bounds: ({}mbar, {}mbar)", lower, upper);
assert!(
lower <= upper,
"Lower bound {} is larger than upper bound {}",
lower,
upper,
);
let lower = Self::bound_to_dev_int(lower);
let upper = Self::bound_to_dev_int(upper);
self.write_reg16u(Register16::PA_L_TH_LS, lower)
.and(self.write_reg16u(Register16::PA_H_TH_LS, upper))
}
pub fn set_pres_mid(&mut self, mid: f32) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting pressure midpoint: {}mbar", mid);
let mid = Self::bound_to_dev_int(mid);
self.write_reg16u(Register16::PA_M_TH_LS, mid)
}
pub fn read_pres_temp(&mut self) -> Result<(Temperature, Pressure), E> {
#[cfg(feature = "defmt")]
debug!("Reading temperature and pressure");
nb::block!(self.inner_block(flags::INT_SRC::READ_RDY))?;
let raw = self.read_two(Command::READ_PT)?;
Ok(((&raw[0..3]).into(), (&raw[3..6]).into()))
}
pub fn read_pres(&mut self) -> Result<Pressure, E> {
#[cfg(feature = "defmt")]
debug!("Reading pressure");
nb::block!(self.inner_block(flags::INT_SRC::PA_RDY))?;
Ok(self.read_one(Command::READ_P)?.into())
}
}
impl<I, E, C> HP203B<I, mode::Altitude, C>
where
I: I2c<Error = E>,
C: csb::CSB,
{
pub fn to_pressure(self) -> Result<HP203B<I, mode::Pressure, C>, E> {
let mut new = HP203B::<_, mode::Pressure, _> {
_c: PhantomData,
waiting_reset: false,
osr: self.osr,
i2c: self.destroy(),
};
#[cfg(feature = "defmt")]
debug!("Converting altimeter to pressure mode");
new.set_pres_bounds(0.0, 0.0)?;
new.set_pres_mid(0.0)?;
let mut new_en_flags = new.get_interrupts_enabled()?;
new_en_flags.remove(INT_EN::PA_TRAV_EN);
new_en_flags.remove(INT_EN::PA_WIN_EN);
new.set_interrupts_enabled(new_en_flags)?;
let mut new_pinout_flags = new.get_interrupts_pinout()?;
new_pinout_flags.remove(INT_CFG::PA_MODE);
new.set_interrupts_pinout(new_pinout_flags)?;
#[cfg(feature = "defmt")]
info!("Altimeter succesfully set to pressure mode");
Ok(new)
}
pub fn set_offset(&mut self, offset: i16) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting altitude offset to {}cm", offset);
self.write_reg16s(Register16::ALT_OFF, offset)
}
pub fn set_alti_bounds(&mut self, lower: i16, upper: i16) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting altitude outer bounds to ({}m, {}m)", lower, upper);
assert!(
lower <= upper,
"Lower bound {} is larger than upper bound {}",
lower,
upper,
);
self.write_reg16s(Register16::PA_L_TH_LS, lower)
.and(self.write_reg16s(Register16::PA_H_TH_LS, upper))
}
pub fn set_alti_mid(&mut self, mid: i16) -> Result<(), E> {
#[cfg(feature = "defmt")]
debug!("Setting altitude mid bound to {}m)", mid);
self.write_reg16s(Register16::PA_M_TH_LS, mid)
}
pub fn read_alti_temp(&mut self) -> Result<(Temperature, Altitude), E> {
#[cfg(feature = "defmt")]
debug!("Reading altitude and temperature");
nb::block!(self.inner_block(flags::INT_SRC::READ_RDY))?;
let raw = self.read_two(Command::READ_AT)?;
Ok(((&raw[0..3]).into(), (&raw[3..6]).into()))
}
pub fn read_alti(&mut self) -> Result<Altitude, E> {
#[cfg(feature = "defmt")]
debug!("Reading altitude");
nb::block!(self.inner_block(flags::INT_SRC::PA_RDY))?;
Ok(self.read_one(Command::READ_A)?.into())
}
}
fn read_signed(reading: &[u8]) -> f32 {
assert!(reading.len() == 3);
let signed: i32 = {
let base = if reading[0] & 0b1000 == 0b1000 {
i32::MIN + 0x7FF8_0000
} else {
0
};
base + (i32::from(reading[0] & 0b0000_0111) << 16)
+ (i32::from(reading[1]) << 8)
+ i32::from(reading[2])
};
signed as f32
}
fn read_unsigned(reading: &[u8]) -> f32 {
assert!(reading.len() == 3);
let signed: u32 = {
(u32::from(reading[0] & 0b0000_1111) << 16)
+ (u32::from(reading[1]) << 8)
+ u32::from(reading[2])
};
signed as f32
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Pressure(pub f32);
impl From<&[u8]> for Pressure {
fn from(reading: &[u8]) -> Self {
let res = read_unsigned(reading) / 100.0;
#[cfg(feature = "defmt")]
trace!("Converted raw output {} to pressure {}mBar", reading, res);
Self(res)
}
}
impl From<[u8; 3]> for Pressure {
fn from(reading: [u8; 3]) -> Self {
<Self as From<&[u8]>>::from(&reading)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Pressure {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{}mBar", self.0);
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Altitude(pub f32);
impl From<&[u8]> for Altitude {
fn from(reading: &[u8]) -> Self {
let res = read_signed(reading) / 100.0;
#[cfg(feature = "defmt")]
trace!("Converted raw output {} to altitude {}m", reading, res);
Self(res)
}
}
impl From<[u8; 3]> for Altitude {
fn from(reading: [u8; 3]) -> Self {
<Self as From<&[u8]>>::from(&reading)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Altitude {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{}m", self.0);
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Temperature(pub f32);
impl From<&[u8]> for Temperature {
fn from(reading: &[u8]) -> Self {
let res = read_signed(reading) / 100.0;
#[cfg(feature = "defmt")]
trace!("Converted raw output {} to temp {}°C", reading, res);
Self(res)
}
}
impl From<[u8; 3]> for Temperature {
fn from(reading: [u8; 3]) -> Self {
<Self as From<&[u8]>>::from(&reading)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Temperature {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{}°C", self.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case(&[0x00, 0x0A, 0x5C], 26.52)]
#[test_case(&[0xFF, 0xFC, 0x02], -10.22)]
fn raw_to_temp(input: &[u8], expected: f32) {
assert_eq!(
<&[u8] as Into<Temperature>>::into(input),
Temperature(expected)
);
}
#[test_case(&[0x00, 0x13, 0x88], 50.0)]
#[test_case(&[0xFF, 0xEC, 0x78], -50.0)]
fn raw_to_alti(input: &[u8], expected: f32) {
assert_eq!(<&[u8] as Into<Altitude>>::into(input), Altitude(expected));
}
#[test_case(&[0x01, 0x8a, 0x9e], 1010.22)]
fn raw_to_pres(input: &[u8], expected: f32) {
assert_eq!(<&[u8] as Into<Pressure>>::into(input), Pressure(expected));
}
}