use core::fmt; use log::{info, warn}; use stm32f4xx_hal:: { spi::Spi, pac::SPI3, gpio::{PA15, Output, PushPull}, hal::{ spi::SpiBus, digital::OutputPin, }, }; use uom::si::{ f32::ElectricPotential, electric_potential::volt, }; use super::{ checksum::{Checksum, ChecksumMode}, regs::{self, Register, RegisterData}, sinc3_fine_odr_closest, sinc3_fine_odr_output_rate, DigitalFilterOrder, FilterType, Input, Mode, PostFilter, RefSource, SingleChODR }; /// 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, } type AdcSpi = Spi; type AdcNss = PA15>; pub type AdcPhy = Adc; 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_sing_cyc(false); 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.set_sinc5_sinc1_with_50hz_60hz_rejection(index, PostFilter::F16SPS)?; 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); // Set SING_CYC = 0 to maximize sampling rate with lowest noise for single channel of temperature measurement adc_mode.set_sing_cyc(false); adc_mode.set_mode(Mode::ContinuousConversion); self.write_reg(®s::AdcMode, &mut adc_mode)?; Ok(()) } /// Rate is only valid with single channel enabled pub fn get_filter_type_and_rate(&mut self, index: u8) -> Result<(FilterType, f32), SPI::Error> { let mut filter_type: FilterType = FilterType::Sinc5Sinc1With50hz60HzRejection; let mut rate: f32 = -1.0; self.read_reg(®s::FiltCon { index }) .map(|data| { if data.sinc3_map() { filter_type = FilterType::Sinc3WithFineODR; let odr : u16 = (data.sinc3_map_fine_odr_msb() as u16) << 8 | data.sinc3_map_fine_odr_lsb() as u16; rate = sinc3_fine_odr_output_rate(odr); } else if data.enh_filt_en() { filter_type = FilterType::Sinc5Sinc1With50hz60HzRejection; match data.enh_filt().output_rate(){ Some(val) => { rate = val; } None => { rate = -1.0; } }; } else if data.order() == DigitalFilterOrder::Sinc5Sinc1 { filter_type = FilterType::Sinc5Sinc1; match data.odr().output_rate(){ Some(val) => { rate = val; } None => { rate = -1.0; } } } else { filter_type = FilterType::Sinc3; match data.odr().output_rate(){ Some(val) => { rate = val; } None => { rate = -1.0; } } } })?; Ok((filter_type, rate)) } pub fn set_sinc5_sinc1_with_50hz_60hz_rejection(&mut self, index: u8, rate: PostFilter) -> Result<(), SPI::Error> { self.update_reg(®s::FiltCon { index }, |data| { data.set_sinc3_map(false); data.set_enh_filt_en(true); data.set_order(DigitalFilterOrder::Sinc5Sinc1); data.set_enh_filt(rate); }) } pub fn set_sinc5_sinc1_filter(&mut self, index: u8, rate: SingleChODR) -> Result<(), SPI::Error> { self.update_reg(®s::FiltCon { index }, |data| { data.set_sinc3_map(false); data.set_enh_filt_en(false); data.set_order(DigitalFilterOrder::Sinc5Sinc1); data.set_odr(rate); }) } pub fn set_sinc3_filter(&mut self, index: u8, rate: SingleChODR) -> Result<(), SPI::Error> { self.update_reg(®s::FiltCon { index }, |data| { data.set_sinc3_map(false); data.set_enh_filt_en(false); data.set_order(DigitalFilterOrder::Sinc3); data.set_odr(rate); }) } pub fn set_sinc3_fine_filter(&mut self, index: u8, rate: f32) -> Result { let sinc3_fine_odr_u16 = sinc3_fine_odr_closest(rate); self.update_reg(®s::FiltCon { index }, |data| { data.set_sinc3_map(true); data.set_sinc3_map_fine_odr_msb((sinc3_fine_odr_u16 >> 8 & 0xFF) as u8); data.set_sinc3_map_fine_odr_lsb((sinc3_fine_odr_u16 & 0xFF) as u8); }).map(|_| sinc3_fine_odr_output_rate(sinc3_fine_odr_u16)) } /// 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_in_place(&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_in_place(&mut addr_buf) { Ok(_) => self.spi.transfer_in_place(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_in_place(&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 Default for ChannelCalibration { fn default() -> Self { ChannelCalibration { offset: 0, gain: 0, bipolar: false, } } } impl ChannelCalibration { pub fn convert_data(&self, data: u32) -> ElectricPotential { let data = if self.bipolar { (data as i32 - 0x80_0000) as f32 } else { data as f32 / 2.0 }; let data = data / (self.gain as f32 / (0x40_0000 as f32)); let data = data + (self.offset as i32 - 0x80_0000) as f32; let data = data / (2 << 23) as f32; const V_REF: f32 = 3.3; ElectricPotential::new::(data * V_REF / 0.75) } }