diff options
| author | unitexe <unitexe70@gmail.com> | 2026-04-08 23:53:17 -0500 |
|---|---|---|
| committer | unitexe <unitexe70@gmail.com> | 2026-04-08 23:54:28 -0500 |
| commit | de3f7ed45185f3a678ba0de04d98cc9ac92de0c8 (patch) | |
| tree | 3db0159c1370f240d42c1e8b89a67247538f43d1 /tmp117/src | |
Initial commit
Diffstat (limited to 'tmp117/src')
| -rw-r--r-- | tmp117/src/client.rs | 199 | ||||
| -rw-r--r-- | tmp117/src/conf.rs | 547 | ||||
| -rw-r--r-- | tmp117/src/eeprom.rs | 7 | ||||
| -rw-r--r-- | tmp117/src/id.rs | 52 | ||||
| -rw-r--r-- | tmp117/src/lib.rs | 5 | ||||
| -rw-r--r-- | tmp117/src/reg.rs | 74 |
6 files changed, 884 insertions, 0 deletions
diff --git a/tmp117/src/client.rs b/tmp117/src/client.rs new file mode 100644 index 0000000..6618d72 --- /dev/null +++ b/tmp117/src/client.rs @@ -0,0 +1,199 @@ +use crate::conf::Configuration; +use crate::eeprom::EEPROM; +use crate::id::Identity; +use crate::reg::Registers; +use embedded_hal::{delay::DelayNs, i2c::I2c}; + +pub struct TMP117<I2C, Delay> { + addr: u8, + i2c: I2C, + delay: Delay, +} + +impl<I2C: I2c, Delay: DelayNs> TMP117<I2C, Delay> { + const CELSIUS_PER_LSB: f32 = 7.8125e-3; + + pub fn new(addr: u8, i2c: I2C, delay: Delay) -> Self { + Self { addr, i2c, delay } + } + + pub fn configure(&mut self, configuration: u16) -> Result<(), I2C::Error> { + let bytes = configuration.to_be_bytes(); + self.i2c.write( + self.addr, + &[Registers::Configuration as u8, bytes[0], bytes[1]], + ) + } + + pub fn configure_typed(&mut self, configuration: Configuration) -> Result<(), I2C::Error> { + let configuration = u16::from(&configuration); + self.configure(configuration) + } + + pub fn get_configuration(&mut self) -> Result<u16, I2C::Error> { + let mut bytes = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Configuration as u8], &mut bytes)?; + let configuration = u16::from_be_bytes(bytes); + Ok(configuration) + } + + pub fn get_configuration_typed(&mut self) -> Result<Configuration, I2C::Error> { + let configuration = self.get_configuration()?; + let configuration = Configuration::from(configuration); + Ok(configuration) + } + + pub fn configure_low_limit(&mut self, limit: i16) -> Result<(), I2C::Error> { + let bytes = limit.to_be_bytes(); + self.i2c + .write(self.addr, &[Registers::LowLimit as u8, bytes[0], bytes[1]]) + } + + pub fn configure_low_limit_celsius(&mut self, limit: f32) -> Result<(), I2C::Error> { + let limit = limit / Self::CELSIUS_PER_LSB; + let limit = limit.round() as i16; + self.configure_low_limit(limit) + } + + pub fn get_low_limit(&mut self) -> Result<i16, I2C::Error> { + let mut limit = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::LowLimit as u8], &mut limit)?; + let limit = i16::from_be_bytes(limit); + Ok(limit) + } + + pub fn get_low_limit_celsius(&mut self) -> Result<f32, I2C::Error> { + let limit = self.get_low_limit()?; + let limit = limit as f32 * Self::CELSIUS_PER_LSB; + Ok(limit) + } + + pub fn configure_high_limit(&mut self, limit: i16) -> Result<(), I2C::Error> { + let limit = limit.to_be_bytes(); + self.i2c + .write(self.addr, &[Registers::HighLimit as u8, limit[0], limit[1]]) + } + + pub fn configure_high_limit_celsius(&mut self, limit: f32) -> Result<(), I2C::Error> { + let limit = limit / Self::CELSIUS_PER_LSB; + let limit = limit.round() as i16; + self.configure_high_limit(limit) + } + + pub fn get_high_limit(&mut self) -> Result<i16, I2C::Error> { + let mut limit = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::HighLimit as u8], &mut limit)?; + let limit = i16::from_be_bytes(limit); + Ok(limit) + } + + pub fn get_high_limit_celsius(&mut self) -> Result<f32, I2C::Error> { + let high_limit = self.get_high_limit()?; + let high_limit = high_limit as f32 * Self::CELSIUS_PER_LSB; + Ok(high_limit) + } + + pub fn configure_offset_temperature(&mut self, offset: i16) -> Result<(), I2C::Error> { + let offset = offset.to_be_bytes(); + self.i2c.write( + self.addr, + &[Registers::TemperatureOffset as u8, offset[0], offset[1]], + ) + } + + pub fn configure_offset_temperature_celsius(&mut self, offset: f32) -> Result<(), I2C::Error> { + let offset = offset / Self::CELSIUS_PER_LSB; + let offset = offset.round() as i16; + self.configure_offset_temperature(offset) + } + + pub fn get_offset_temperature(&mut self) -> Result<i16, I2C::Error> { + let mut offset = [0u8; 2]; + self.i2c.write_read( + self.addr, + &[Registers::TemperatureOffset as u8], + &mut offset, + )?; + let offset = i16::from_be_bytes(offset); + Ok(offset) + } + + pub fn get_offset_temperature_celsius(&mut self) -> Result<f32, I2C::Error> { + let offset = self.get_offset_temperature()?; + let offset = offset as f32 * Self::CELSIUS_PER_LSB; + Ok(offset) + } + + pub fn get_eeprom_1(&mut self) -> Result<u16, I2C::Error> { + let mut eeprom = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Eeprom1 as u8], &mut eeprom)?; + let eeprom = u16::from_be_bytes(eeprom); + Ok(eeprom) + } + + pub fn get_eeprom_2(&mut self) -> Result<u16, I2C::Error> { + let mut eeprom = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Eeprom2 as u8], &mut eeprom)?; + let eeprom = u16::from_be_bytes(eeprom); + Ok(eeprom) + } + + pub fn get_eeprom_3(&mut self) -> Result<u16, I2C::Error> { + let mut eeprom = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Eeprom3 as u8], &mut eeprom)?; + let eeprom = u16::from_be_bytes(eeprom); + Ok(eeprom) + } + + pub fn get_eeprom_all(&mut self) -> Result<EEPROM, I2C::Error> { + let eeprom1 = self.get_eeprom_1()?; + let eeprom2 = self.get_eeprom_2()?; + let eeprom3 = self.get_eeprom_3()?; + let eeprom = EEPROM { + eeprom1, + eeprom2, + eeprom3, + }; + Ok(eeprom) + } + + pub fn get_identity(&mut self) -> Result<Identity, I2C::Error> { + let mut identity = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::DeviceId as u8], &mut identity)?; + let identity = u16::from_be_bytes(identity); + let identity = Identity::from(identity); + Ok(identity) + } + + pub fn get_temperature(&mut self) -> Result<i16, I2C::Error> { + let mut temperature = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Temperature as u8], &mut temperature)?; + let temperature = i16::from_be_bytes(temperature); + Ok(temperature) + } + + pub fn get_temperature_celsius(&mut self) -> Result<f32, I2C::Error> { + let temperature = self.get_temperature()?; + let temperature = temperature as f32 * Self::CELSIUS_PER_LSB; + Ok(temperature) + } + + pub fn software_reset(&mut self) -> Result<(), I2C::Error> { + let mask: u16 = 0x0002; + let mask = mask.to_be_bytes(); + self.i2c.write( + self.addr, + &[Registers::Configuration as u8, mask[0], mask[1]], + )?; + self.delay.delay_ms(2); + Ok(()) + } +} diff --git a/tmp117/src/conf.rs b/tmp117/src/conf.rs new file mode 100644 index 0000000..1ea275b --- /dev/null +++ b/tmp117/src/conf.rs @@ -0,0 +1,547 @@ +use core::fmt; + +#[derive(Debug, PartialEq)] +pub enum AlertPinSelect { + /// ALERT pin reflects the status of the alert flags + Alert, + + /// ALERT pin reflects the status of the data ready flag + DataReady, +} + +impl fmt::Display for AlertPinSelect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Alert => write!(f, "Alert"), + Self::DataReady => write!(f, "Data Ready"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum PinPolarity { + /// ALERT pin is active low + ActiveLow, + + /// ALERT pin is active high + ActiveHigh, +} + +impl fmt::Display for PinPolarity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ActiveLow => write!(f, "Active Low"), + Self::ActiveHigh => write!(f, "Active High"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Mode { + /// Alert mode + Alert, + + /// Thermal mode + Thermal, +} + +impl fmt::Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Alert => write!(f, "Alert"), + Self::Thermal => write!(f, "Thermal"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum ConversionMode { + /// Continous conversion + Continuous, + + /// Shutdown + Shutdown, + + /// One-shot conversion + Oneshot, +} + +impl fmt::Display for ConversionMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Continuous => write!(f, "Continuous"), + Self::Shutdown => write!(f, "Shutdown"), + Self::Oneshot => write!(f, "One-shot"), + } + } +} + +/// Determines the number of conversion results that are collected and averaged before updating the temperature register. The average is an accumulated average, not a running average. +#[derive(Debug, PartialEq)] +pub enum ConversionAveragingMode { + /// No averaging + None, + + /// 8 averaged conversions + Avg8, + + /// 32 averaged conversions + Avg32, + + /// 64 averaged conversions + Avg64, +} + +impl ConversionAveragingMode { + fn minimum_conversion_cycle_time(&self) -> ConversionCycle { + match self { + ConversionAveragingMode::None => ConversionCycle::Microseconds15500, + ConversionAveragingMode::Avg8 => ConversionCycle::Milliseconds125, + ConversionAveragingMode::Avg32 => ConversionCycle::Milliseconds500, + ConversionAveragingMode::Avg64 => ConversionCycle::Seconds1, + } + } +} + +impl fmt::Display for ConversionAveragingMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "None"), + Self::Avg8 => write!(f, "8 conversions"), + Self::Avg32 => write!(f, "32 conversions"), + Self::Avg64 => write!(f, "64 conversions"), + } + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ConversionCycle { + Microseconds15500, + Milliseconds125, + Milliseconds250, + Milliseconds500, + Seconds1, + Seconds4, + Seconds8, + Seconds16, +} + +impl fmt::Display for ConversionCycle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Microseconds15500 => write!(f, "15.5ms"), + Self::Milliseconds125 => write!(f, "125ms"), + Self::Milliseconds250 => write!(f, "250ms"), + Self::Milliseconds500 => write!(f, "500ms"), + Self::Seconds1 => write!(f, "1s"), + Self::Seconds4 => write!(f, "4s"), + Self::Seconds8 => write!(f, "8s"), + Self::Seconds16 => write!(f, "16s"), + } + } +} + +pub enum ConfigurationError { + CycleTimeAndAveragingModeAreIncompatible, +} + +pub struct Configuration { + /// ALERT pin select bit + pub alert_pin_select: AlertPinSelect, + + /// ALERT pin polarity bit + pub alert_pin_polarity: PinPolarity, + + /// Therm/alert mode select + pub mode: Mode, + + /// Conversion averaging modes + pub conversion_averaging_mode: ConversionAveragingMode, + + /// Conversion cycle bit + pub conversion_cycle: ConversionCycle, + + /// Conversion mode + pub conversion_mode: ConversionMode, +} + +impl Configuration { + pub fn validate(&self) -> Result<(), ConfigurationError> { + if self.conversion_cycle + < self + .conversion_averaging_mode + .minimum_conversion_cycle_time() + { + Err(ConfigurationError::CycleTimeAndAveragingModeAreIncompatible) + } else { + Ok(()) + } + } +} + +impl fmt::Display for Configuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Alert Pin Select: {}", self.alert_pin_select)?; + writeln!(f, "Alert Pin Polarity: {}", self.alert_pin_polarity)?; + writeln!(f, "Mode: {}", self.mode)?; + writeln!( + f, + "Conversion Averaging: {}", + self.conversion_averaging_mode + )?; + writeln!(f, "Conversion Cycle: {}", self.conversion_cycle)?; + write!(f, "Conversion Mode: {}", self.conversion_mode) + } +} + +impl From<&Configuration> for u16 { + fn from(configuration: &Configuration) -> u16 { + let mut bits: u16 = 0; + + bits |= match configuration.alert_pin_select { + AlertPinSelect::Alert => 0 << 2, + AlertPinSelect::DataReady => 1 << 2, + }; + + bits |= match configuration.alert_pin_polarity { + PinPolarity::ActiveLow => 0 << 3, + PinPolarity::ActiveHigh => 1 << 3, + }; + + bits |= match configuration.mode { + Mode::Alert => 0 << 4, + Mode::Thermal => 1 << 4, + }; + + bits |= match configuration.conversion_averaging_mode { + ConversionAveragingMode::None => 0b00 << 5, + ConversionAveragingMode::Avg8 => 0b01 << 5, + ConversionAveragingMode::Avg32 => 0b10 << 5, + ConversionAveragingMode::Avg64 => 0b11 << 5, + }; + + bits |= match configuration.conversion_cycle { + ConversionCycle::Microseconds15500 => 0b000 << 7, + ConversionCycle::Milliseconds125 => 0b001 << 7, + ConversionCycle::Milliseconds250 => 0b010 << 7, + ConversionCycle::Milliseconds500 => 0b011 << 7, + ConversionCycle::Seconds1 => 0b100 << 7, + ConversionCycle::Seconds4 => 0b101 << 7, + ConversionCycle::Seconds8 => 0b110 << 7, + ConversionCycle::Seconds16 => 0b111 << 7, + }; + + bits |= match configuration.conversion_mode { + ConversionMode::Continuous => 0b00 << 10, + ConversionMode::Shutdown => 0b01 << 10, + ConversionMode::Oneshot => 0b11 << 10, + }; + + bits + } +} + +impl From<u16> for Configuration { + fn from(register: u16) -> Self { + let alert_pin_select = (register >> 2) & 0x1; + let alert_pin_polarity = (register >> 3) & 0x1; + let mode = (register >> 4) & 0x1; + let conversion_averaging_mode = (register >> 5) & 0x3; + let conversion_cycle = (register >> 7) & 0x7; + let conversion_mode = (register >> 10) & 0x3; + + let alert_pin_select = match alert_pin_select { + 0 => AlertPinSelect::Alert, + 1 => AlertPinSelect::DataReady, + _ => todo!("Handle unsupported alert pin select"), + }; + let alert_pin_polarity = match alert_pin_polarity { + 0 => PinPolarity::ActiveLow, + 1 => PinPolarity::ActiveHigh, + _ => todo!("Handle unsupported alert pin polarity"), + }; + let mode = match mode { + 0 => Mode::Alert, + 1 => Mode::Thermal, + _ => todo!("Handle unsupported mode"), + }; + let conversion_averaging_mode = match conversion_averaging_mode { + 0b00 => ConversionAveragingMode::None, + 0b01 => ConversionAveragingMode::Avg8, + 0b10 => ConversionAveragingMode::Avg32, + 0b11 => ConversionAveragingMode::Avg64, + _ => todo!("Handle unsupported conversion averaging mode"), + }; + let conversion_cycle = match conversion_cycle { + 0b000 => ConversionCycle::Microseconds15500, + 0b001 => ConversionCycle::Milliseconds125, + 0b010 => ConversionCycle::Milliseconds250, + 0b011 => ConversionCycle::Milliseconds500, + 0b100 => ConversionCycle::Seconds1, + 0b101 => ConversionCycle::Seconds4, + 0b110 => ConversionCycle::Seconds8, + 0b111 => ConversionCycle::Seconds16, + _ => todo!("Handle unsupported conversion cycle"), + }; + let conversion_mode = match conversion_mode { + 0b00 | 0b10 => ConversionMode::Continuous, + 0b01 => ConversionMode::Shutdown, + 0b11 => ConversionMode::Oneshot, + _ => todo!("Handle unsupported conversion mode"), + }; + + Self { + alert_pin_select, + alert_pin_polarity, + mode, + conversion_averaging_mode, + conversion_cycle, + conversion_mode, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // helpers to construct common configurations + fn default_configuration() -> Configuration { + Configuration { + alert_pin_select: AlertPinSelect::Alert, + alert_pin_polarity: PinPolarity::ActiveLow, + mode: Mode::Alert, + conversion_averaging_mode: ConversionAveragingMode::Avg8, + conversion_cycle: ConversionCycle::Seconds1, + conversion_mode: ConversionMode::Continuous, + } + } + + // --- From<&Configuration> for u16 --- + + #[test] + fn test_alert_pin_select_alert_bit() { + let mut config = default_configuration(); + config.alert_pin_select = AlertPinSelect::Alert; + assert_eq!(u16::from(&config) & (1 << 2), 0); + } + + #[test] + fn test_alert_pin_select_data_ready_bit() { + let mut config = default_configuration(); + config.alert_pin_select = AlertPinSelect::DataReady; + assert_ne!(u16::from(&config) & (1 << 2), 0); + } + + #[test] + fn test_pin_polarity_active_low_bit() { + let mut config = default_configuration(); + config.alert_pin_polarity = PinPolarity::ActiveLow; + assert_eq!(u16::from(&config) & (1 << 3), 0); + } + + #[test] + fn test_pin_polarity_active_high_bit() { + let mut config = default_configuration(); + config.alert_pin_polarity = PinPolarity::ActiveHigh; + assert_ne!(u16::from(&config) & (1 << 3), 0); + } + + #[test] + fn test_mode_alert_bit() { + let mut config = default_configuration(); + config.mode = Mode::Alert; + assert_eq!(u16::from(&config) & (1 << 4), 0); + } + + #[test] + fn test_mode_thermal_bit() { + let mut config = default_configuration(); + config.mode = Mode::Thermal; + assert_ne!(u16::from(&config) & (1 << 4), 0); + } + + #[test] + fn test_conversion_averaging_mode_bits() { + let mut config = default_configuration(); + + config.conversion_averaging_mode = ConversionAveragingMode::None; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b00); + + config.conversion_averaging_mode = ConversionAveragingMode::Avg8; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b01); + + config.conversion_averaging_mode = ConversionAveragingMode::Avg32; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b10); + + config.conversion_averaging_mode = ConversionAveragingMode::Avg64; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b11); + } + + #[test] + fn test_conversion_cycle_bits() { + let mut config = default_configuration(); + + config.conversion_cycle = ConversionCycle::Microseconds15500; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b000); + + config.conversion_cycle = ConversionCycle::Milliseconds125; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b001); + + config.conversion_cycle = ConversionCycle::Milliseconds250; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b010); + + config.conversion_cycle = ConversionCycle::Milliseconds500; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b011); + + config.conversion_cycle = ConversionCycle::Seconds1; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b100); + + config.conversion_cycle = ConversionCycle::Seconds4; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b101); + + config.conversion_cycle = ConversionCycle::Seconds8; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b110); + + config.conversion_cycle = ConversionCycle::Seconds16; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b111); + } + + #[test] + fn test_conversion_cycle_ordering() { + assert!(ConversionCycle::Microseconds15500 < ConversionCycle::Milliseconds125); + assert!(ConversionCycle::Milliseconds125 < ConversionCycle::Milliseconds250); + assert!(ConversionCycle::Milliseconds250 < ConversionCycle::Milliseconds500); + assert!(ConversionCycle::Milliseconds500 < ConversionCycle::Seconds1); + assert!(ConversionCycle::Seconds1 < ConversionCycle::Seconds4); + assert!(ConversionCycle::Seconds4 < ConversionCycle::Seconds8); + assert!(ConversionCycle::Seconds8 < ConversionCycle::Seconds16); + } + + #[test] + fn test_conversion_mode_bits() { + let mut config = default_configuration(); + + config.conversion_mode = ConversionMode::Continuous; + assert_eq!((u16::from(&config) >> 10) & 0x3, 0b00); + + config.conversion_mode = ConversionMode::Shutdown; + assert_eq!((u16::from(&config) >> 10) & 0x3, 0b01); + + config.conversion_mode = ConversionMode::Oneshot; + assert_eq!((u16::from(&config) >> 10) & 0x3, 0b11); + } + + // --- From<u16> for Configuration --- + + #[test] + fn test_configuration_from_factory_reset_value() { + // 0x0220 = 0b0000_0010_0010_0000 + // bits 6:5 = 0b01 -> Avg8 + // bits 11:10 = 0b00 -> Continuous + let config = Configuration::from(0x0220); + assert_eq!(config.alert_pin_select, AlertPinSelect::Alert); + assert_eq!(config.alert_pin_polarity, PinPolarity::ActiveLow); + assert_eq!(config.mode, Mode::Alert); + assert_eq!( + config.conversion_averaging_mode, + ConversionAveragingMode::Avg8 + ); + assert_eq!(config.conversion_mode, ConversionMode::Continuous); + } + + #[test] + fn test_configuration_from_thermal_mode() { + let register: u16 = 1 << 4; + let config = Configuration::from(register); + assert_eq!(config.mode, Mode::Thermal); + } + + #[test] + fn test_configuration_from_shutdown_mode() { + let register: u16 = 0b01 << 10; + let config = Configuration::from(register); + assert_eq!(config.conversion_mode, ConversionMode::Shutdown); + } + + // --- Round-trip --- + + #[test] + fn test_round_trip_factory_reset() { + let original: u16 = 0x0220; + let config = Configuration::from(original); + let restored = u16::from(&config); + assert_eq!(original, restored); + } + + #[test] + fn test_round_trip_all_fields_set() { + let config = Configuration { + alert_pin_select: AlertPinSelect::DataReady, + alert_pin_polarity: PinPolarity::ActiveHigh, + mode: Mode::Thermal, + conversion_averaging_mode: ConversionAveragingMode::Avg64, + conversion_cycle: ConversionCycle::Seconds1, + conversion_mode: ConversionMode::Oneshot, + }; + let bits = u16::from(&config); + let restored = Configuration::from(bits); + assert_eq!(restored.alert_pin_select, AlertPinSelect::DataReady); + assert_eq!(restored.alert_pin_polarity, PinPolarity::ActiveHigh); + assert_eq!(restored.mode, Mode::Thermal); + assert_eq!( + restored.conversion_averaging_mode, + ConversionAveragingMode::Avg64 + ); + assert_eq!(restored.conversion_cycle, ConversionCycle::Seconds1); + assert_eq!(restored.conversion_mode, ConversionMode::Oneshot); + } + + #[test] + fn test_validate_valid_combinations() { + let mut config = default_configuration(); + + // None allows any cycle time + config.conversion_averaging_mode = ConversionAveragingMode::None; + config.conversion_cycle = ConversionCycle::Microseconds15500; + assert!(config.validate().is_ok()); + + // Avg8 allows 125ms and above + config.conversion_averaging_mode = ConversionAveragingMode::Avg8; + config.conversion_cycle = ConversionCycle::Milliseconds125; + assert!(config.validate().is_ok()); + + // Avg32 allows 500ms and above + config.conversion_averaging_mode = ConversionAveragingMode::Avg32; + config.conversion_cycle = ConversionCycle::Milliseconds500; + assert!(config.validate().is_ok()); + + // Avg64 allows 1s and above + config.conversion_averaging_mode = ConversionAveragingMode::Avg64; + config.conversion_cycle = ConversionCycle::Seconds1; + assert!(config.validate().is_ok()); + } + + #[test] + fn test_validate_invalid_combinations() { + let mut config = default_configuration(); + + // Avg8 does not allow 15.5ms + config.conversion_averaging_mode = ConversionAveragingMode::Avg8; + config.conversion_cycle = ConversionCycle::Microseconds15500; + assert!(config.validate().is_err()); + + // Avg32 does not allow 125ms or 250ms + config.conversion_averaging_mode = ConversionAveragingMode::Avg32; + config.conversion_cycle = ConversionCycle::Milliseconds125; + assert!(config.validate().is_err()); + + config.conversion_cycle = ConversionCycle::Milliseconds250; + assert!(config.validate().is_err()); + + // Avg64 does not allow anything below 1s + config.conversion_averaging_mode = ConversionAveragingMode::Avg64; + config.conversion_cycle = ConversionCycle::Milliseconds500; + assert!(config.validate().is_err()); + } +} diff --git a/tmp117/src/eeprom.rs b/tmp117/src/eeprom.rs new file mode 100644 index 0000000..293c74f --- /dev/null +++ b/tmp117/src/eeprom.rs @@ -0,0 +1,7 @@ +pub struct EEPROM { + pub eeprom1: u16, + + pub eeprom2: u16, + + pub eeprom3: u16, +} diff --git a/tmp117/src/id.rs b/tmp117/src/id.rs new file mode 100644 index 0000000..e1fcc8b --- /dev/null +++ b/tmp117/src/id.rs @@ -0,0 +1,52 @@ +pub struct Identity { + pub device_id: u16, + + pub revision_number: u8, +} + +impl From<u16> for Identity { + fn from(register: u16) -> Self { + let device_id = register & 0x0FFF; + let revision_number = ((register >> 12) & 0xF) as u8; + Self { + device_id, + revision_number, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_identity_from_known_device_id() { + let identity = Identity::from(0x0117); + assert_eq!(identity.device_id, 0x117); + assert_eq!(identity.revision_number, 0); + } + + #[test] + fn test_identity_device_id_masked() { + // bits 11:0 are device ID + let identity = Identity::from(0x0FFF); + assert_eq!(identity.device_id, 0x0FFF); + assert_eq!(identity.revision_number, 0); + } + + #[test] + fn test_identity_revision_number_extracted() { + // bits 15:12 are revision + let identity = Identity::from(0x1000); + assert_eq!(identity.device_id, 0x000); + assert_eq!(identity.revision_number, 1); + } + + #[test] + fn test_identity_both_fields() { + // revision = 0xA, device_id = 0x117 + let identity = Identity::from(0xA117); + assert_eq!(identity.device_id, 0x117); + assert_eq!(identity.revision_number, 0xA); + } +} diff --git a/tmp117/src/lib.rs b/tmp117/src/lib.rs new file mode 100644 index 0000000..4c26e67 --- /dev/null +++ b/tmp117/src/lib.rs @@ -0,0 +1,5 @@ +pub mod client; +pub mod conf; +pub mod eeprom; +pub mod id; +pub mod reg; diff --git a/tmp117/src/reg.rs b/tmp117/src/reg.rs new file mode 100644 index 0000000..20ac429 --- /dev/null +++ b/tmp117/src/reg.rs @@ -0,0 +1,74 @@ +#[repr(u8)] +pub enum Registers { + Temperature = 0x00, + Configuration = 0x01, + HighLimit = 0x02, + LowLimit = 0x03, + Eeprom1 = 0x05, + Eeprom2 = 0x06, + TemperatureOffset = 0x07, + Eeprom3 = 0x08, + DeviceId = 0x0F, +} + +impl Registers { + pub const TEMPERATURE_RESET: u16 = 0x8000; + pub const CONFIGURATION_RESET: u16 = 0x0220; + pub const HIGH_LIMIT_RESET: u16 = 0x6000; + pub const LOW_LIMIT_RESET: u16 = 0x8000; + pub const TEMPERATURE_OFFSET_RESET: u16 = 0x0000; + pub const DEVICE_ID_RESET: u16 = 0x0117; + + pub fn factory_default_reset(&self) -> Option<u16> { + match self { + Registers::Temperature => Some(Registers::TEMPERATURE_RESET), + Registers::Configuration => Some(Registers::CONFIGURATION_RESET), + Registers::HighLimit => Some(Registers::HIGH_LIMIT_RESET), + Registers::LowLimit => Some(Registers::LOW_LIMIT_RESET), + Registers::TemperatureOffset => Some(Registers::TEMPERATURE_OFFSET_RESET), + Registers::DeviceId => Some(Registers::DEVICE_ID_RESET), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_register_addresses() { + assert_eq!(Registers::Temperature as u8, 0x00); + assert_eq!(Registers::Configuration as u8, 0x01); + assert_eq!(Registers::HighLimit as u8, 0x02); + assert_eq!(Registers::LowLimit as u8, 0x03); + assert_eq!(Registers::Eeprom1 as u8, 0x05); + assert_eq!(Registers::Eeprom2 as u8, 0x06); + assert_eq!(Registers::TemperatureOffset as u8, 0x07); + assert_eq!(Registers::Eeprom3 as u8, 0x08); + assert_eq!(Registers::DeviceId as u8, 0x0F); + } + + #[test] + fn test_factory_default_reset_known() { + assert_eq!(Registers::Temperature.factory_default_reset(), Some(0x8000)); + assert_eq!( + Registers::Configuration.factory_default_reset(), + Some(0x0220) + ); + assert_eq!(Registers::HighLimit.factory_default_reset(), Some(0x6000)); + assert_eq!(Registers::LowLimit.factory_default_reset(), Some(0x8000)); + assert_eq!( + Registers::TemperatureOffset.factory_default_reset(), + Some(0x0000) + ); + assert_eq!(Registers::DeviceId.factory_default_reset(), Some(0x0117)); + } + + #[test] + fn test_factory_default_reset_unknown() { + assert_eq!(Registers::Eeprom1.factory_default_reset(), None); + assert_eq!(Registers::Eeprom2.factory_default_reset(), None); + assert_eq!(Registers::Eeprom3.factory_default_reset(), None); + } +} |
