diff options
author | cs2dsb <cs2dsb@gmail.com> | 2020-01-08 22:25:31 +0000 |
---|---|---|
committer | cs2dsb <cs2dsb@gmail.com> | 2020-01-08 22:25:31 +0000 |
commit | c0c2b922b272f2b87b1a907e49499260a430ce23 (patch) | |
tree | fd71d9d120034c2d2d32e4d49afa82f9a21d2ca6 /src | |
download | max31855-async-c0c2b922b272f2b87b1a907e49499260a430ce23.tar.gz max31855-async-c0c2b922b272f2b87b1a907e49499260a430ce23.tar.xz max31855-async-c0c2b922b272f2b87b1a907e49499260a430ce23.zip |
Initial commit, cargo version 0.1
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d88d273 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,282 @@ +//! # max31855 +//! +//! Driver for [MAX31855 thermocouple converter](https://www.maximintegrated.com/en/products/sensors/MAX31855.html) using traits from `embedded-hal`. +//! +//! ## Features +//! * Read thermocouple temperature +//! * Read internal reference junction temperature +//! * Read fault data (missing thermocouple, short to ground or short to vcc) +//! * Supports 16-bit (thermocouple + fault only) or 32-bit (thermocouple, internal and full fault details) +//! * Supports Celsius, Fahrenheit or Kelvin units +//! * Supports returning raw (ADC count) readings +//! +//! ## Example: +//! ``` +//! let freq: Hertz = 4.mhz().into(); +//! let mode = Mode { +//! polarity: Polarity::IdleLow, +//! phase: Phase::CaptureOnFirstTransition +//! }; +//! +//! let mut spi = Spi::spi2( +//! device.SPI2, +//! (sck_pin, miso_pin, mosi_pin) +//! mode, +//! freq, +//! clocks, +//! &mut rcc.apb1 +//! ); +//! +//! // Full 32-bit read, result contains both thermocouple and internal temperatures +//! match spi.read_all(&mut cs_pin, Unit::Celsius) { +//! Ok(v) => info!("Ok: {:?}", v), +//! Err(e) => info!("Err: {:?}", e), +//! } +//! +//! // Just thermocouple 16-bit read +//! match spi.read_thermocouple(&mut cs_pin, Unit::Celsius) { +//! Ok(v) => info!("Ok: {:?}", v), +//! Err(e) => info!("Err: {:?}", e), +//! } +//! ``` + +#![no_std] +#![deny(warnings, missing_docs)] + +use embedded_hal::{ + blocking::spi::Transfer, + digital::v2::OutputPin, +}; +use bit_field::BitField; +use core::ops::RangeInclusive; + +/// The bits that represent the thermocouple value when reading the first u16 from the sensor +const THERMOCOUPLE_BITS: RangeInclusive<usize> = 2..=15; +/// The bit that indicates some kind of fault when reading the first u16 from the sensor +const FAULT_BIT: usize = 0; +/// The bits that represent the internal value when reading the second u16 from the sensor +const INTERNAL_BITS: RangeInclusive<usize> = 4..=15; +/// The bit that indicates a short-to-vcc fault when reading the second u16 from the sensor +const FAULT_VCC_SHORT_BIT: usize = 2; +/// The bit that indicates a short-to-gnd fault when reading the second u16 from the sensor +const FAULT_GROUND_SHORT_BIT: usize = 1; +/// The bit that indicates a missing thermocouple fault when reading the second u16 from the sensor +const FAULT_NO_THERMOCOUPLE_BIT: usize = 0; + +/// Possible errors returned by this crate +#[derive(Debug)] +pub enum Error<SpiE, CsE> { + /// An error returned by a call to Transfer::transfer + SpiError(SpiE), + /// An error returned by a call to OutputPin::{set_high, set_low} + ChipSelectError(CsE), + /// The fault bit (16) was set in the response from the MAX31855 + Fault, + /// The SCV fault bit (2) was set in the response from the MAX31855 + VccShortFault, + /// The SCG fault bit (1) was set in the response from the MAX31855 + GroundShortFault, + /// The OC fault bit (0) was set in the response from the MAX31855 + MissingThermocoupleFault, +} + +/// The temperature unit to use +#[derive(Clone, Copy, Debug)] +pub enum Unit { + /// Degrees Celsius + Celsius, + /// Degrees Fahrenheit + Fahrenheit, + /// Degrees Kelvin + Kelvin, +} + +impl Unit { + /// Converts degrees celsius into this unit + pub fn convert(&self, celsius: f32) -> f32 { + match self { + Unit::Celsius => celsius, + Unit::Fahrenheit => celsius * 1.8 + 32., + Unit::Kelvin => celsius + 273.15, + } + } +} + +/// Possible MAX31855 readings +pub enum Reading { + /// The attached thermocouple + Thermocouple, + /// The internal reference junction + Internal, +} + +impl Reading { + /// Convert the raw ADC count into degrees celsius + pub fn convert(self, count: i16) -> f32 { + let count = count as f32; + match self { + Reading::Thermocouple => count * 0.25, + Reading::Internal => count * 0.0625, + } + } +} + +enum CsState { + High, + Low, +} +use CsState::*; + +fn set_cs<CS, SpiE, CsE>(cs: &mut CS, state: CsState) -> Result<(), Error<SpiE, CsE>> +where + CS: OutputPin<Error = CsE>, +{ + let result = match state { + CsState::High => cs.set_high(), + CsState::Low => cs.set_low(), + }; + + result.map_err(|e| Error::ChipSelectError(e)) +} + +fn transfer<CS, SPI, SpiE, CsE>(spi: &mut SPI, chip_select: &mut CS, buffer: &mut [u8]) -> Result<(), Error<SpiE, CsE>> +where + CS: OutputPin<Error = CsE>, + SPI: Transfer<u8, Error = SpiE>, +{ + set_cs(chip_select, Low)?; + + spi.transfer(buffer).map_err(|e| Error::SpiError(e))?; + + set_cs(chip_select, High) +} + +fn bits_to_i16(bits: u16, len: usize, divisor: i16, shift: usize) -> i16 { + let negative = bits.get_bit(len - 1); + if negative { + (bits << shift) as i16 / divisor + } else { + bits as i16 + } +} + +/// Represents the data contained in a full 32-bit read from the MAX31855 as raw ADC counts +#[derive(Debug)] +pub struct FullResultRaw { + /// The temperature of the thermocouple as raw ADC counts + pub thermocouple: i16, + /// The temperature of the MAX31855 reference junction as raw ADC counts + pub internal: i16, +} + +impl FullResultRaw { + /// Convert the raw ADC counts into degrees in the provided Unit + pub fn convert(self, unit: Unit) -> FullResult { + let thermocouple = unit.convert(Reading::Thermocouple.convert(self.thermocouple)); + let internal = unit.convert(Reading::Internal.convert(self.internal)); + + FullResult { + thermocouple, + internal, + unit, + } + } +} + +/// Represents the data contained in a full 32-bit read from the MAX31855 as degrees in the included Unit +#[derive(Debug)] +pub struct FullResult { + /// The temperature of the thermocouple + pub thermocouple: f32, + /// The temperature of the MAX31855 reference junction + pub internal: f32, + /// The unit that the temperatures are in + pub unit: Unit, +} + +/// Trait enabling using the MAX31855 +pub trait Max31855<SpiE, CsE, CS> { + /// Reads the thermocouple temperature and leave it as a raw ADC count. Checks if there is a fault but doesn't detect what kind of fault it is + fn read_thermocouple_raw(&mut self, chip_select: &mut CS) -> Result<i16, Error<SpiE, CsE>>; + /// Reads the thermocouple temperature and converts it into degrees in the provided unit. Checks if there is a fault but doesn't detect what kind of fault it is + fn read_thermocouple(&mut self, chip_select: &mut CS, unit: Unit) -> Result<f32, Error<SpiE, CsE>>; + /// Reads both the thermocouple and the internal temperatures, leaving them as raw ADC counts and resolves faults to one of vcc short, ground short or missing thermocouple + fn read_all_raw(&mut self, chip_select: &mut CS) -> Result<FullResultRaw, Error<SpiE, CsE>>; + /// Reads both the thermocouple and the internal temperatures, converts them into degrees in the provided unit and resolves faults to one of vcc short, ground short or missing thermocouple + fn read_all(&mut self, chip_select: &mut CS, unit: Unit) -> Result<FullResult, Error<SpiE, CsE>>; +} + +impl<CS, SPI, SpiE, CsE> Max31855<SpiE, CsE, CS> for SPI +where + CS: OutputPin<Error = CsE>, + SPI: Transfer<u8, Error = SpiE>, +{ + /// Reads the thermocouple temperature and leave it as a raw ADC count. Checks if there is a fault but doesn't detect what kind of fault it is + fn read_thermocouple_raw(&mut self, chip_select: &mut CS) -> Result<i16, Error<SpiE, CsE>> { + let mut buffer = [0; 2]; + transfer(self, chip_select, &mut buffer)?; + + if buffer[1].get_bit(FAULT_BIT) { + Err(Error::Fault)? + } + + let raw = (buffer[0] as u16) << 8 | + (buffer[1] as u16) << 0; + + let thermocouple = bits_to_i16(raw.get_bits(THERMOCOUPLE_BITS), 14, 4, 2); + + Ok(thermocouple) + } + + /// Reads the thermocouple temperature and converts it into degrees in the provided unit. Checks if there is a fault but doesn't detect what kind of fault it is + fn read_thermocouple(&mut self, chip_select: &mut CS, unit: Unit) -> Result<f32, Error<SpiE, CsE>> { + self + .read_thermocouple_raw(chip_select) + .map(|r| unit.convert(Reading::Thermocouple.convert(r))) + } + + /// Reads both the thermocouple and the internal temperatures, leaving them as raw ADC counts and resolves faults to one of vcc short, ground short or missing thermocouple + fn read_all_raw(&mut self, chip_select: &mut CS) -> Result<FullResultRaw, Error<SpiE, CsE>> { + let mut buffer = [0; 4]; + transfer(self, chip_select, &mut buffer)?; + + let fault = buffer[1].get_bit(0); + + if fault { + let raw = (buffer[2] as u16) << 8 | + (buffer[3] as u16) << 0; + + if raw.get_bit(FAULT_NO_THERMOCOUPLE_BIT) { + Err(Error::MissingThermocoupleFault)? + } else if raw.get_bit(FAULT_GROUND_SHORT_BIT) { + Err(Error::GroundShortFault)? + } else if raw.get_bit(FAULT_VCC_SHORT_BIT) { + Err(Error::VccShortFault)? + } else { + // This should impossible, one of the other fields should be set as well + // but handled here just-in-case + Err(Error::Fault)? + } + } + + let first_u16 = (buffer[0] as u16) << 8 | + (buffer[1] as u16) << 0; + let second_u16 = (buffer[2] as u16) << 8 | + (buffer[3] as u16) << 0; + + let thermocouple = bits_to_i16(first_u16.get_bits(THERMOCOUPLE_BITS), 14, 4, 2); + let internal = bits_to_i16(second_u16.get_bits(INTERNAL_BITS), 12, 16, 4); + + Ok(FullResultRaw { + thermocouple, + internal, + }) + } + + /// Reads both the thermocouple and the internal temperatures, converts them into degrees in the provided unit and resolves faults to one of vcc short, ground short or missing thermocouple + fn read_all(&mut self, chip_select: &mut CS, unit: Unit) -> Result<FullResult, Error<SpiE, CsE>> { + self + .read_all_raw(chip_select) + .map(|r| r.convert(unit)) + } +}
\ No newline at end of file |