diff --git a/Cargo.lock b/Cargo.lock index 635c290..5d87484 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,8 @@ name = "kirdy" version = "0.0.0" dependencies = [ "bare-metal 1.0.0", + "bit_field", + "byteorder", "cortex-m", "cortex-m-log", "cortex-m-rt", diff --git a/Cargo.toml b/Cargo.toml index d337d31..2ff4f8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ rtt-target = { version = "0.3.1", features = ["cortex-m"] } miniconf = "0.6.3" serde = { version = "1.0.158", features = ["derive"], default-features = false } sfkv = "0.1" +bit_field = "0.10" +byteorder = { version = "1", default-features = false } [features] semihosting = ["cortex-m-log/semihosting"] RTT = [] diff --git a/src/thermostat/ad7172/adc.rs b/src/thermostat/ad7172/adc.rs new file mode 100644 index 0000000..68ebab3 --- /dev/null +++ b/src/thermostat/ad7172/adc.rs @@ -0,0 +1,276 @@ +use core::fmt; +use log::{info, warn}; +use stm32f4xx_hal::hal::{ + blocking::spi::Transfer, + digital::v2::OutputPin, +}; +use uom::si::{ + f64::ElectricPotential, + electric_potential::volt, +}; +use super::{ + regs::{self, Register, RegisterData}, + checksum::{ChecksumMode, Checksum}, + Mode, Input, RefSource, PostFilter, DigitalFilterOrder, +}; + +/// AD7172-2 implementation +/// +/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf) +pub struct Adc, NSS: OutputPin> { + spi: SPI, + nss: NSS, + checksum_mode: ChecksumMode, +} + +impl, NSS: OutputPin, E: fmt::Debug> Adc { + pub fn new(spi: SPI, mut nss: NSS) -> Result { + let _ = nss.set_high(); + let mut adc = Adc { + spi, nss, + checksum_mode: ChecksumMode::Off, + }; + adc.reset()?; + adc.set_checksum_mode(ChecksumMode::Crc).unwrap(); + + let mut retries = 0; + let mut adc_id; + loop { + adc_id = adc.identify()?; + if adc_id & 0xFFF0 == 0x00D0 { + break; + } else { + retries += 1; + } + } + info!("ADC id: {:04X} ({} retries)", adc_id, retries); + + let mut adc_mode = ::Data::empty(); + adc_mode.set_ref_en(true); + adc_mode.set_mode(Mode::Standby); + adc.write_reg(®s::AdcMode, &mut adc_mode)?; + + Ok(adc) + } + + /// `0x00DX` for AD7172-2 + pub fn identify(&mut self) -> Result { + self.read_reg(®s::Id) + .map(|id| id.id()) + } + + pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), SPI::Error> { + // Cannot use update_reg() here because checksum_mode is + // updated between read_reg() and write_reg(). + let mut ifmode = self.read_reg(®s::IfMode)?; + ifmode.set_crc(mode); + self.checksum_mode = mode; + self.write_reg(®s::IfMode, &mut ifmode)?; + Ok(()) + } + + pub fn set_sync_enable(&mut self, enable: bool) -> Result<(), SPI::Error> { + self.update_reg(®s::GpioCon, |data| { + data.set_sync_en(enable); + }) + } + + pub fn setup_channel( + &mut self, index: u8, in_pos: Input, in_neg: Input + ) -> Result<(), SPI::Error> { + self.update_reg(®s::SetupCon { index }, |data| { + data.set_bipolar(false); + data.set_refbuf_pos(true); + data.set_refbuf_neg(true); + data.set_ainbuf_pos(true); + data.set_ainbuf_neg(true); + data.set_ref_sel(RefSource::External); + })?; + self.update_reg(®s::FiltCon { index }, |data| { + data.set_enh_filt_en(true); + data.set_enh_filt(PostFilter::F16SPS); + data.set_order(DigitalFilterOrder::Sinc5Sinc1); + // output data rate: 10 Hz + data.set_odr(0b10011); + })?; + self.update_reg(®s::Channel { index }, |data| { + data.set_setup(index); + data.set_enabled(true); + data.set_a_in_pos(in_pos); + data.set_a_in_neg(in_neg); + })?; + Ok(()) + } + + pub fn get_calibration(&mut self, index: u8) -> Result { + let offset = self.read_reg(®s::Offset { index })?.offset(); + let gain = self.read_reg(®s::Gain { index })?.gain(); + let bipolar = self.read_reg(®s::SetupCon { index })?.bipolar(); + Ok(ChannelCalibration { offset, gain, bipolar }) + } + + pub fn start_continuous_conversion(&mut self) -> Result<(), SPI::Error> { + let mut adc_mode = ::Data::empty(); + adc_mode.set_ref_en(true); + adc_mode.set_mode(Mode::ContinuousConversion); + self.write_reg(®s::AdcMode, &mut adc_mode)?; + + Ok(()) + } + + pub fn get_postfilter(&mut self, index: u8) -> Result, SPI::Error> { + self.read_reg(®s::FiltCon { index }) + .map(|data| { + if data.enh_filt_en() { + Some(data.enh_filt()) + } else { + None + } + }) + } + + pub fn set_postfilter(&mut self, index: u8, filter: Option) -> Result<(), SPI::Error> { + self.update_reg(®s::FiltCon { index }, |data| { + match filter { + None => data.set_enh_filt_en(false), + Some(filter) => { + data.set_enh_filt_en(true); + data.set_enh_filt(filter); + } + } + }) + } + + /// Returns the channel the data is from + pub fn data_ready(&mut self) -> Result, SPI::Error> { + self.read_reg(®s::Status) + .map(|status| { + if status.ready() { + Some(status.channel()) + } else { + None + } + }) + } + + /// Get data + pub fn read_data(&mut self) -> Result { + self.read_reg(®s::Data) + .map(|data| data.data()) + } + + fn read_reg(&mut self, reg: &R) -> Result { + let mut reg_data = R::Data::empty(); + let address = 0x40 | reg.address(); + let mut checksum = Checksum::new(self.checksum_mode); + checksum.feed(&[address]); + let checksum_out = checksum.result(); + + loop { + let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?; + + checksum.feed(®_data); + let checksum_expected = checksum.result(); + if checksum_expected == checksum_in { + break; + } + // Retry + warn!("read_reg {:02X}: checksum error: {:?}!={:?}, retrying", reg.address(), checksum_expected, checksum_in); + } + Ok(reg_data) + } + + fn write_reg(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), SPI::Error> { + loop { + let address = reg.address(); + let mut checksum = Checksum::new(match self.checksum_mode { + ChecksumMode::Off => ChecksumMode::Off, + // write checksums are always crc + ChecksumMode::Xor => ChecksumMode::Crc, + ChecksumMode::Crc => ChecksumMode::Crc, + }); + checksum.feed(&[address]); + checksum.feed(®_data); + let checksum_out = checksum.result(); + + let mut data = reg_data.clone(); + self.transfer(address, data.as_mut(), checksum_out)?; + + // Verification + let readback_data = self.read_reg(reg)?; + if *readback_data == **reg_data { + return Ok(()); + } + warn!("write_reg {:02X}: readback error, {:?}!={:?}, retrying", address, &*readback_data, &**reg_data); + } + } + + fn update_reg(&mut self, reg: &R, f: F) -> Result + where + R: regs::Register, + F: FnOnce(&mut R::Data) -> A, + { + let mut reg_data = self.read_reg(reg)?; + let result = f(&mut reg_data); + self.write_reg(reg, &mut reg_data)?; + Ok(result) + } + + pub fn reset(&mut self) -> Result<(), SPI::Error> { + let mut buf = [0xFFu8; 8]; + let _ = self.nss.set_low(); + let result = self.spi.transfer(&mut buf); + let _ = self.nss.set_high(); + result?; + Ok(()) + } + + fn transfer<'w>(&mut self, addr: u8, reg_data: &'w mut [u8], checksum: Option) -> Result, SPI::Error> { + let mut addr_buf = [addr]; + + let _ = self.nss.set_low(); + let result = match self.spi.transfer(&mut addr_buf) { + Ok(_) => self.spi.transfer(reg_data), + Err(e) => Err(e), + }; + let result = match (result, checksum) { + (Ok(_), None) => + Ok(None), + (Ok(_), Some(checksum_out)) => { + let mut checksum_buf = [checksum_out; 1]; + match self.spi.transfer(&mut checksum_buf) { + Ok(_) => Ok(Some(checksum_buf[0])), + Err(e) => Err(e), + } + } + (Err(e), _) => + Err(e), + }; + let _ = self.nss.set_high(); + + result + } +} + +#[derive(Debug, Clone)] +pub struct ChannelCalibration { + offset: u32, + gain: u32, + bipolar: bool, +} + +impl ChannelCalibration { + pub fn convert_data(&self, data: u32) -> ElectricPotential { + let data = if self.bipolar { + (data as i32 - 0x80_0000) as f64 + } else { + data as f64 / 2.0 + }; + let data = data / (self.gain as f64 / (0x40_0000 as f64)); + let data = data + (self.offset as i32 - 0x80_0000) as f64; + let data = data / (2 << 23) as f64; + + const V_REF: f64 = 3.3; + ElectricPotential::new::(data * V_REF / 0.75) + } +} diff --git a/src/thermostat/ad7172/checksum.rs b/src/thermostat/ad7172/checksum.rs new file mode 100644 index 0000000..496eb7c --- /dev/null +++ b/src/thermostat/ad7172/checksum.rs @@ -0,0 +1,60 @@ +#[derive(Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum ChecksumMode { + Off = 0b00, + /// Seems much less reliable than `Crc` + Xor = 0b01, + Crc = 0b10, +} + +impl From for ChecksumMode { + fn from(x: u8) -> Self { + match x { + 0 => ChecksumMode::Off, + 1 => ChecksumMode::Xor, + _ => ChecksumMode::Crc, + } + } +} + +pub struct Checksum { + mode: ChecksumMode, + state: u8, +} + +impl Checksum { + pub fn new(mode: ChecksumMode) -> Self { + Checksum { mode, state: 0 } + } + + fn feed_byte(&mut self, input: u8) { + match self.mode { + ChecksumMode::Off => {}, + ChecksumMode::Xor => self.state ^= input, + ChecksumMode::Crc => { + for i in 0..8 { + let input_mask = 0x80 >> i; + self.state = (self.state << 1) ^ + if ((self.state & 0x80) != 0) != ((input & input_mask) != 0) { + 0x07 /* x8 + x2 + x + 1 */ + } else { + 0 + }; + } + } + } + } + + pub fn feed(&mut self, input: &[u8]) { + for &b in input { + self.feed_byte(b); + } + } + + pub fn result(&self) -> Option { + match self.mode { + ChecksumMode::Off => None, + _ => Some(self.state) + } + } +} diff --git a/src/thermostat/ad7172/mod.rs b/src/thermostat/ad7172/mod.rs new file mode 100644 index 0000000..173dadb --- /dev/null +++ b/src/thermostat/ad7172/mod.rs @@ -0,0 +1,221 @@ +use core::fmt; +use num_traits::float::Float; +use serde::{Serialize, Deserialize}; +use fugit::MegahertzU32; +use stm32f4xx_hal::spi; + +pub mod regs; +mod checksum; +pub use checksum::ChecksumMode; +mod adc; +pub use adc::*; + +/// SPI Mode 3 +pub const SPI_MODE: spi::Mode = spi::Mode { + polarity: spi::Polarity::IdleHigh, + phase: spi::Phase::CaptureOnSecondTransition, +}; +/// 2 MHz +pub const SPI_CLOCK_MHZ: MegahertzU32 = MegahertzU32::from_raw(2); + +pub const MAX_VALUE: u32 = 0xFF_FFFF; + + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum Mode { + ContinuousConversion = 0b000, + SingleConversion = 0b001, + Standby = 0b010, + PowerDown = 0b011, + InternalOffsetCalibration = 0b100, + Invalid, + SystemOffsetCalibration = 0b110, + SystemGainCalibration = 0b111, +} + +impl From for Mode { + fn from(x: u8) -> Self { + use Mode::*; + match x { + 0b000 => ContinuousConversion, + 0b001 => SingleConversion, + 0b010 => Standby, + 0b011 => PowerDown, + 0b100 => InternalOffsetCalibration, + 0b110 => SystemOffsetCalibration, + 0b111 => SystemGainCalibration, + _ => Invalid, + } + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum Input { + Ain0 = 0, + Ain1 = 1, + Ain2 = 2, + Ain3 = 3, + Ain4 = 4, + TemperaturePos = 17, + TemperatureNeg = 18, + AnalogSupplyPos = 19, + AnalogSupplyNeg = 20, + RefPos = 21, + RefNeg = 22, + Invalid = 0b11111, +} + +impl From for Input { + fn from(x: u8) -> Self { + match x { + 0 => Input::Ain0, + 1 => Input::Ain1, + 2 => Input::Ain2, + 3 => Input::Ain3, + 4 => Input::Ain4, + 17 => Input::TemperaturePos, + 18 => Input::TemperatureNeg, + 19 => Input::AnalogSupplyPos, + 20 => Input::AnalogSupplyNeg, + 21 => Input::RefPos, + 22 => Input::RefNeg, + _ => Input::Invalid, + } + } +} + +impl fmt::Display for Input { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use Input::*; + + match self { + Ain0 => "ain0", + Ain1 => "ain1", + Ain2 => "ain2", + Ain3 => "ain3", + Ain4 => "ain4", + TemperaturePos => "temperature+", + TemperatureNeg => "temperature-", + AnalogSupplyPos => "analogsupply+", + AnalogSupplyNeg => "analogsupply-", + RefPos => "ref+", + RefNeg => "ref-", + _ => "", + }.fmt(fmt) + } +} + +/// Reference source for ADC conversion +#[repr(u8)] +pub enum RefSource { + /// External reference + External = 0b00, + /// Internal 2.5V reference + Internal = 0b10, + /// AVDD1 − AVSS + Avdd1MinusAvss = 0b11, + Invalid = 0b01, +} + +impl From for RefSource { + fn from(x: u8) -> Self { + match x { + 0 => RefSource::External, + 1 => RefSource::Internal, + 2 => RefSource::Avdd1MinusAvss, + _ => RefSource::Invalid, + } + } +} + +impl fmt::Display for RefSource { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use RefSource::*; + + match self { + External => "external", + Internal => "internal", + Avdd1MinusAvss => "avdd1-avss", + _ => "", + }.fmt(fmt) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +#[repr(u8)] +pub enum PostFilter { + /// 27 SPS, 47 dB rejection, 36.7 ms settling + F27SPS = 0b010, + /// 21.25 SPS, 62 dB rejection, 40 ms settling + F21SPS = 0b011, + /// 20 SPS, 86 dB rejection, 50 ms settling + F20SPS = 0b101, + /// 16.67 SPS, 92 dB rejection, 60 ms settling + F16SPS = 0b110, + Invalid = 0b111, +} + +impl PostFilter { + pub const VALID_VALUES: &'static [Self] = &[ + PostFilter::F27SPS, + PostFilter::F21SPS, + PostFilter::F20SPS, + PostFilter::F16SPS, + ]; + + pub fn closest(rate: f32) -> Option { + let mut best: Option<(f32, Self)> = None; + for value in Self::VALID_VALUES { + let error = (rate - value.output_rate().unwrap()).abs(); + let better = best + .map(|(best_error, _)| error < best_error) + .unwrap_or(true); + if better { + best = Some((error, *value)); + } + } + best.map(|(_, best)| best) + } + + /// Samples per Second + pub fn output_rate(&self) -> Option { + match self { + PostFilter::F27SPS => Some(27.0), + PostFilter::F21SPS => Some(21.25), + PostFilter::F20SPS => Some(20.0), + PostFilter::F16SPS => Some(16.67), + PostFilter::Invalid => None, + } + } +} + +impl From for PostFilter { + fn from(x: u8) -> Self { + match x { + 0b010 => PostFilter::F27SPS, + 0b011 => PostFilter::F21SPS, + 0b101 => PostFilter::F20SPS, + 0b110 => PostFilter::F16SPS, + _ => PostFilter::Invalid, + } + } +} + +#[repr(u8)] +pub enum DigitalFilterOrder { + Sinc5Sinc1 = 0b00, + Sinc3 = 0b11, + Invalid = 0b10, +} + +impl From for DigitalFilterOrder { + fn from(x: u8) -> Self { + match x { + 0b00 => DigitalFilterOrder::Sinc5Sinc1, + 0b11 => DigitalFilterOrder::Sinc3, + _ => DigitalFilterOrder::Invalid, + } + } +} diff --git a/src/thermostat/ad7172/regs.rs b/src/thermostat/ad7172/regs.rs new file mode 100644 index 0000000..5b8c9d5 --- /dev/null +++ b/src/thermostat/ad7172/regs.rs @@ -0,0 +1,267 @@ +use core::ops::{Deref, DerefMut}; +use byteorder::{BigEndian, ByteOrder}; +use bit_field::BitField; + +use super::*; + +pub trait Register { + type Data: RegisterData; + fn address(&self) -> u8; +} + +pub trait RegisterData: Clone + Deref + DerefMut { + fn empty() -> Self; +} + +macro_rules! def_reg { + ($Reg: ident, $reg: ident, $addr: expr, $size: expr) => { + /// AD7172 register + pub struct $Reg; + impl Register for $Reg { + /// Register contents + type Data = $reg::Data; + /// Register address + fn address(&self) -> u8 { + $addr + } + } + mod $reg { + /// Register contents + #[derive(Clone)] + pub struct Data(pub [u8; $size]); + impl super::RegisterData for Data { + /// Generate zeroed register contents + fn empty() -> Self { + Data([0; $size]) + } + } + impl core::ops::Deref for Data { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + impl core::ops::DerefMut for Data { + fn deref_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + } + }; + ($Reg: ident, u8, $reg: ident, $addr: expr, $size: expr) => { + pub struct $Reg { pub index: u8, } + impl Register for $Reg { + type Data = $reg::Data; + fn address(&self) -> u8 { + $addr + self.index + } + } + mod $reg { + #[derive(Clone)] + pub struct Data(pub [u8; $size]); + impl super::RegisterData for Data { + fn empty() -> Self { + Data([0; $size]) + } + } + impl core::ops::Deref for Data { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + impl core::ops::DerefMut for Data { + fn deref_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + } + } +} + +macro_rules! reg_bit { + ($getter: ident, $byte: expr, $bit: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> bool { + self.0[$byte].get_bit($bit) + } + }; + ($getter: ident, $setter: ident, $byte: expr, $bit: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> bool { + self.0[$byte].get_bit($bit) + } + #[allow(unused)] + #[doc = $doc] + pub fn $setter(&mut self, value: bool) { + self.0[$byte].set_bit($bit, value); + } + }; +} + +macro_rules! reg_bits { + ($getter: ident, $byte: expr, $bits: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> u8 { + self.0[$byte].get_bits($bits) + } + }; + ($getter: ident, $setter: ident, $byte: expr, $bits: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> u8 { + self.0[$byte].get_bits($bits) + } + #[allow(unused)] + #[doc = $doc] + pub fn $setter(&mut self, value: u8) { + self.0[$byte].set_bits($bits, value); + } + }; + ($getter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> $ty { + self.0[$byte].get_bits($bits) as $ty + } + }; + ($getter: ident, $setter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> $ty { + self.0[$byte].get_bits($bits).into() + } + #[allow(unused)] + #[doc = $doc] + pub fn $setter(&mut self, value: $ty) { + self.0[$byte].set_bits($bits, value as u8); + } + }; +} + +def_reg!(Status, status, 0x00, 1); +impl status::Data { + /// Is there new data to read? + pub fn ready(&self) -> bool { + ! self.not_ready() + } + + reg_bit!(not_ready, 0, 7, "No data ready indicator"); + reg_bits!(channel, 0, 0..=1, "Channel for which data is ready"); + reg_bit!(adc_error, 0, 6, "ADC error"); + reg_bit!(crc_error, 0, 5, "SPI CRC error"); + reg_bit!(reg_error, 0, 4, "Register error"); +} + +def_reg!(AdcMode, adc_mode, 0x01, 2); +impl adc_mode::Data { + reg_bits!(delay, set_delay, 0, 0..=2, "Delay after channel switch"); + reg_bit!(sing_cyc, set_sing_cyc, 0, 5, "Can only used with single channel"); + reg_bit!(hide_delay, set_hide_delay, 0, 6, "Hide delay"); + reg_bit!(ref_en, set_ref_en, 0, 7, "Enable internal reference, output buffered 2.5 V to REFOUT"); + reg_bits!(clockset, set_clocksel, 1, 2..=3, "Clock source"); + reg_bits!(mode, set_mode, 1, 4..=6, Mode, "Operating mode"); +} + +def_reg!(IfMode, if_mode, 0x02, 2); +impl if_mode::Data { + reg_bits!(crc, set_crc, 1, 2..=3, ChecksumMode, "SPI checksum mode"); +} + +def_reg!(Data, data, 0x04, 3); +impl data::Data { + pub fn data(&self) -> u32 { + (u32::from(self.0[0]) << 16) | + (u32::from(self.0[1]) << 8) | + u32::from(self.0[2]) + } +} + +def_reg!(GpioCon, gpio_con, 0x06, 2); +impl gpio_con::Data { + reg_bit!(sync_en, set_sync_en, 0, 3, "Enables the SYNC/ERROR pin as a sync input"); +} + +def_reg!(Id, id, 0x07, 2); +impl id::Data { + pub fn id(&self) -> u16 { + BigEndian::read_u16(&self.0) + } +} + +def_reg!(Channel, u8, channel, 0x10, 2); +impl channel::Data { + reg_bit!(enabled, set_enabled, 0, 7, "Channel enabled"); + reg_bits!(setup, set_setup, 0, 4..=5, "Setup number"); + + /// Which input is connected to positive input of this channel + #[allow(unused)] + pub fn a_in_pos(&self) -> Input { + ((self.0[0].get_bits(0..=1) << 3) | + self.0[1].get_bits(5..=7)).into() + } + /// Set which input is connected to positive input of this channel + #[allow(unused)] + pub fn set_a_in_pos(&mut self, value: Input) { + let value = value as u8; + self.0[0].set_bits(0..=1, value >> 3); + self.0[1].set_bits(5..=7, value & 0x7); + } + reg_bits!(a_in_neg, set_a_in_neg, 1, 0..=4, Input, + "Which input is connected to negative input of this channel"); +} + +def_reg!(SetupCon, u8, setup_con, 0x20, 2); +impl setup_con::Data { + reg_bit!(bipolar, set_bipolar, 0, 4, "Unipolar (`false`) or bipolar (`true`) coded output"); + reg_bit!(refbuf_pos, set_refbuf_pos, 0, 3, "Enable REF+ input buffer"); + reg_bit!(refbuf_neg, set_refbuf_neg, 0, 2, "Enable REF- input buffer"); + reg_bit!(ainbuf_pos, set_ainbuf_pos, 0, 1, "Enable AIN+ input buffer"); + reg_bit!(ainbuf_neg, set_ainbuf_neg, 0, 0, "Enable AIN- input buffer"); + reg_bit!(burnout_en, 1, 7, "enables a 10 µA current source on the positive analog input selected and a 10 µA current sink on the negative analog input selected"); + reg_bits!(ref_sel, set_ref_sel, 1, 4..=5, RefSource, "Select reference source for conversion"); +} + +def_reg!(FiltCon, u8, filt_con, 0x28, 2); +impl filt_con::Data { + reg_bit!(sinc3_map, 0, 7, "If set, mapping of filter register changes to directly program the decimation rate of the sinc3 filter"); + reg_bit!(enh_filt_en, set_enh_filt_en, 0, 3, "Enable postfilters for enhanced 50Hz and 60Hz rejection"); + reg_bits!(enh_filt, set_enh_filt, 0, 0..=2, PostFilter, "Select postfilters for enhanced 50Hz and 60Hz rejection"); + reg_bits!(order, set_order, 1, 5..=6, DigitalFilterOrder, "order of the digital filter that processes the modulator data"); + reg_bits!(odr, set_odr, 1, 0..=4, "Output data rate"); +} + +def_reg!(Offset, u8, offset, 0x30, 3); +impl offset::Data { + #[allow(unused)] + pub fn offset(&self) -> u32 { + (u32::from(self.0[0]) << 16) | + (u32::from(self.0[1]) << 8) | + u32::from(self.0[2]) + } + #[allow(unused)] + pub fn set_offset(&mut self, value: u32) { + self.0[0] = (value >> 16) as u8; + self.0[1] = (value >> 8) as u8; + self.0[2] = value as u8; + } +} + +def_reg!(Gain, u8, gain, 0x38, 3); +impl gain::Data { + #[allow(unused)] + pub fn gain(&self) -> u32 { + (u32::from(self.0[0]) << 16) | + (u32::from(self.0[1]) << 8) | + u32::from(self.0[2]) + } + #[allow(unused)] + pub fn set_gain(&mut self, value: u32) { + self.0[0] = (value >> 16) as u8; + self.0[1] = (value >> 8) as u8; + self.0[2] = value as u8; + } +} diff --git a/src/thermostat/mod.rs b/src/thermostat/mod.rs index 50cbd16..e066de0 100644 --- a/src/thermostat/mod.rs +++ b/src/thermostat/mod.rs @@ -1,3 +1,4 @@ pub mod ad5680; pub mod max1968; -pub mod thermostat; \ No newline at end of file +pub mod thermostat; +pub mod ad7172; \ No newline at end of file