use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{Mode, Phase, Polarity, SpiBus};
extern crate alloc;
#[derive(Debug, Clone, Copy)]
pub enum RTDLeads {
Two = 2,
Three = 3,
Four = 4,
}
#[derive(Debug, Clone, Copy)]
pub enum FilterHz {
Fifty = 1,
Sixty = 0,
}
#[derive(Debug)]
pub enum RtdError {
InvalidChipSelect, Init(String), Read(String), Fault(u8), }
#[derive(Debug, Clone, Copy)]
pub enum MaxFault {
RtdInMinusUndervoltage, RtdInPlusOvervoltage, RtdInMinusOvervoltage, RtdInPlusOpen, RtdInMinusOpen, RtdUnderOrOvertemp, RtdOverOrUnderBiasVoltage, AutoConversionFault, }
impl MaxFault {
pub fn bit(self) -> u8 {
match self {
MaxFault::RtdInMinusUndervoltage => 0b00000001,
MaxFault::RtdInPlusOvervoltage => 0b00000010,
MaxFault::RtdInMinusOvervoltage => 0b00000100,
MaxFault::RtdInPlusOpen => 0b00001000,
MaxFault::RtdInMinusOpen => 0b00010000,
MaxFault::RtdUnderOrOvertemp => 0b00100000,
MaxFault::RtdOverOrUnderBiasVoltage => 0b01000000,
MaxFault::AutoConversionFault => 0b10000000,
}
}
pub fn description(self) -> &'static str {
match self {
MaxFault::RtdInMinusUndervoltage => "RTD IN- Undervoltage",
MaxFault::RtdInPlusOvervoltage => "RTD IN+ Overvoltage",
MaxFault::RtdInMinusOvervoltage => "RTD IN- Overvoltage",
MaxFault::RtdInPlusOpen => "RTD IN+ Open Circuit",
MaxFault::RtdInMinusOpen => "RTD IN- Open Circuit",
MaxFault::RtdUnderOrOvertemp => "RTD Under/Over Temperature",
MaxFault::RtdOverOrUnderBiasVoltage => "RTD Over/Under Bias Voltage",
MaxFault::AutoConversionFault => "Auto-Conversion Fault",
}
}
}
pub fn decode_fault_status(status: u8) -> Vec<&'static str> {
let mut faults = Vec::new();
let all_faults = [
(MaxFault::RtdInMinusUndervoltage, 0b00000001),
(MaxFault::RtdInPlusOvervoltage, 0b00000010),
(MaxFault::RtdInMinusOvervoltage, 0b00000100),
(MaxFault::RtdInPlusOpen, 0b00001000),
(MaxFault::RtdInMinusOpen, 0b00010000),
(MaxFault::RtdUnderOrOvertemp, 0b00100000),
(MaxFault::RtdOverOrUnderBiasVoltage, 0b01000000),
(MaxFault::AutoConversionFault, 0b10000000),
];
for (fault, bit) in all_faults {
if status & bit != 0 {
faults.push(fault.description());
}
}
faults
}
pub const MODE: Mode = Mode {
phase: Phase::CaptureOnSecondTransition,
polarity: Polarity::IdleHigh,
};
pub mod temp_conversion;
pub mod rtd_reader {
use crate::private::{Error as InternalError, Max31865};
use crate::{FilterHz, RTDLeads, RtdError};
use rppal::gpio::{Gpio, OutputPin as GpioOutputPin};
use rppal::spi::{Bus, Mode as SpiMode, SlaveSelect, Spi};
pub struct RTDReader {
inner: Max31865<Spi, GpioOutputPin>,
}
impl RTDReader {
pub fn new(cs_pin: u8, leads: RTDLeads, filter: FilterHz) -> Result<Self, RtdError> {
let gpio =
Gpio::new().map_err(|e| RtdError::Init(format!("GPIO init failed: {}", e)))?;
let ncs = gpio
.get(cs_pin)
.map_err(|e| RtdError::Init(format!("NCS pin {} invalid: {}", cs_pin, e)))?
.into_output_high();
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 1_000_000, SpiMode::Mode3)
.map_err(|e| RtdError::Init(format!("SPI init failed: {}", e)))?;
let mut inner = Max31865::new(spi, ncs).map_err(|e| {
RtdError::Init(match e {
InternalError::GpioFault => "NCS pin setup failed".to_string(),
_ => "MAX31865 init failed".to_string(),
})
})?;
inner
.configure(leads, filter)
.map_err(|e| RtdError::Init(format!("Configure failed: {:?}", e)))?;
Ok(RTDReader { inner })
}
pub fn get_temperature(&mut self) -> Result<f64, RtdError> {
self.inner.read_temperature().map_err(map_internal_error)
}
pub fn get_resistance(&mut self) -> Result<f64, RtdError> {
self.inner.read_resistance().map_err(map_internal_error)
}
pub fn read_temp_100(&mut self) -> Result<i32, RtdError> {
self.inner
.read_default_conversion()
.map_err(map_internal_error)
}
pub fn get_ohms_100(&mut self) -> Result<u32, RtdError> {
self.inner.read_ohms().map_err(map_internal_error)
}
pub fn get_raw_data(&mut self) -> Result<u16, RtdError> {
self.inner.read_raw().map_err(map_internal_error)
}
pub fn is_max_fault(&self, e: &RtdError) -> bool {
matches!(e, RtdError::Fault(_))
}
pub fn read_fault_status(&mut self) -> Result<u8, RtdError> {
self.inner.read_fault_status().map_err(map_internal_error)
}
pub fn clear_fault(&mut self) -> Result<(), RtdError> {
self.inner.clear_fault().map_err(|e| {
RtdError::Read(match e {
InternalError::SpiErrorTransfer => "Clear fault SPI write failed".to_string(),
_ => "Clear fault failed".to_string(),
})
})
}
pub fn set_calibration(&mut self, calibration: u32) {
self.inner.set_calibration(calibration);
}
}
fn map_internal_error(e: InternalError) -> RtdError {
match e {
InternalError::SpiErrorTransfer | InternalError::GpioFault => {
RtdError::Read("SPI/GPIO transfer failed".to_string())
}
InternalError::MAXFault => RtdError::Fault(0), }
}
}
pub use rtd_reader::RTDReader;
mod private {
use super::*;
#[derive(Debug)]
pub enum Error {
SpiErrorTransfer,
GpioFault,
MAXFault,
}
pub struct Max31865<SPI, NCS> {
spi: SPI,
ncs: NCS,
calibration: u32,
base_config: u8, }
impl<SPI, NCS> Max31865<SPI, NCS>
where
SPI: SpiBus<u8>,
NCS: OutputPin,
{
pub fn new(spi: SPI, mut ncs: NCS) -> Result<Max31865<SPI, NCS>, Error> {
let default_calibration = 40000;
ncs.set_high().map_err(|_| Error::GpioFault)?;
let max31865 = Max31865 {
spi,
ncs,
calibration: default_calibration,
base_config: 0, };
Ok(max31865)
}
pub fn configure(
&mut self,
sensor_type_enum: RTDLeads, filter_mode_enum: FilterHz, ) -> Result<(), Error> {
let sensor_type = match sensor_type_enum {
RTDLeads::Three => 1u8,
RTDLeads::Two | RTDLeads::Four => 0u8, };
let filter_mode = match filter_mode_enum {
FilterHz::Fifty => 1u8, FilterHz::Sixty => 0u8, };
self.base_config = (1u8 << 7) | (1u8 << 6) | (sensor_type << 4) | filter_mode; self.write(Register::CONFIG, self.base_config)?; self.clear_fault()?;
Ok(())
}
pub fn clear_fault(&mut self) -> Result<(), Error> {
self.write(Register::CONFIG, 0x02)
}
pub fn read_fault_status(&mut self) -> Result<u8, Error> {
let status = self.read(Register::FAULT_STATUS)?;
self.clear_fault()?; Ok(status)
}
pub fn set_calibration(&mut self, calibration: u32) {
self.calibration = calibration;
}
pub fn read_ohms(&mut self) -> Result<u32, Error> {
let raw = self.read_raw()?;
let ohms = ((raw >> 1) as u32 * self.calibration) >> 15;
Ok(ohms)
}
pub fn read_resistance(&mut self) -> Result<f64, Error> {
let ohms_raw = self.read_ohms()?; Ok(ohms_raw as f64 / 100.0)
}
pub fn read_temperature(&mut self) -> Result<f64, Error> {
let temp_raw = self.read_default_conversion()?; Ok(temp_raw as f64 / 100.0)
}
pub fn read_default_conversion(&mut self) -> Result<i32, Error> {
let ohms = self.read_ohms()?;
let temp = temp_conversion::LOOKUP_VEC_PT100.lookup_temperature(ohms as i32);
Ok(temp)
}
pub fn read_raw(&mut self) -> Result<u16, Error> {
self.write(Register::CONFIG, self.base_config)?;
std::thread::sleep(std::time::Duration::from_millis(100));
let buffer = self.read_two(Register::RTD_MSB)?;
let raw = ((buffer[0] as u16) << 8) | (buffer[1] as u16);
if raw & 1 != 0 {
let _ = self.read_fault_status(); self.write(Register::CONFIG, self.base_config)?;
std::thread::sleep(std::time::Duration::from_millis(100));
let retry_buffer = self.read_two(Register::RTD_MSB)?;
let retry_raw = ((retry_buffer[0] as u16) << 8) | (retry_buffer[1] as u16);
if retry_raw & 1 != 0 {
return Err(Error::MAXFault); }
return Ok(retry_raw);
}
Ok(raw)
}
fn read(&mut self, reg: Register) -> Result<u8, Error> {
let mut read_buffer = [0u8; 2]; let mut write_buffer = [0u8; 2];
write_buffer[0] = reg.read_address(); write_buffer[1] = 0; self.ncs.set_low().map_err(|_| Error::GpioFault)?;
self.spi
.transfer(&mut read_buffer, &write_buffer)
.map_err(|_| Error::SpiErrorTransfer)?;
self.ncs.set_high().map_err(|_| Error::GpioFault)?;
Ok(read_buffer[1]) }
fn read_two(&mut self, reg: Register) -> Result<[u8; 2], Error> {
let mut read_buffer = [0u8; 3]; let mut write_buffer = [0u8; 3];
write_buffer[0] = reg.read_address(); write_buffer[1] = 0; write_buffer[2] = 0; self.ncs.set_low().map_err(|_| Error::GpioFault)?;
self.spi
.transfer(&mut read_buffer, &write_buffer)
.map_err(|_| Error::SpiErrorTransfer)?;
self.ncs.set_high().map_err(|_| Error::GpioFault)?;
Ok([read_buffer[1], read_buffer[2]]) }
fn write(&mut self, reg: Register, val: u8) -> Result<(), Error> {
self.ncs.set_low().map_err(|_| Error::GpioFault)?;
self.spi
.write(&[reg.write_address(), val])
.map_err(|_| Error::SpiErrorTransfer)?;
self.ncs.set_high().map_err(|_| Error::GpioFault)?;
Ok(())
}
}
#[allow(non_camel_case_types)]
#[allow(dead_code)]
#[derive(Clone, Copy)]
enum Register {
CONFIG = 0x00,
RTD_MSB = 0x01,
RTD_LSB = 0x02,
HIGH_FAULT_THRESHOLD_MSB = 0x03,
HIGH_FAULT_THRESHOLD_LSB = 0x04,
LOW_FAULT_THRESHOLD_MSB = 0x05,
LOW_FAULT_THRESHOLD_LSB = 0x06,
FAULT_STATUS = 0x07,
}
const R: u8 = 0 << 7;
const W: u8 = 1 << 7;
impl Register {
fn read_address(&self) -> u8 {
*self as u8 | R
}
fn write_address(&self) -> u8 {
*self as u8 | W
}
}
}