From 061cc6e9ee82f9f3297d44230d7c6d2d312e3607 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 14 Dec 2021 00:38:48 +0100 Subject: [PATCH 1/4] Several improvements Added - Extended and improved type-level support significantly - Added implementation for externally clocked mode with wakeup delay - Added simple implementation for using CNVST pin. This is untested because board did not have CNVST connected Changed - Improved documentation - Made library easier to use --- CHANGELOG.md | 14 + src/lib.rs | 721 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 624 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7011473..9d0b8c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +## [v0.2.0] + +### Added + +- Extended and improved type-level support significantly +- Added implementation for externally clocked mode with wakeup delay +- Added simple implementation for using CNVST pin. This is untested because board did not have + CNVST connected + +### Changed + +- Improved documentation +- Made library easier to use + ## [v0.1.0] - Initial release diff --git a/src/lib.rs b/src/lib.rs index 09a07c2..bfe59b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,24 @@ +//! Type-Safe API to use the MAX116xx 10-bit ADC devices +//! +//! ## Usage +//! +//! You can create an initial ADC struct by using the [`Max116xx10Bit::max11618`], +//! [`Max116xx10Bit::max11619`], [`Max116xx10Bit::max11620`], [`Max116xx10Bit::max11621`], +//! [`Max116xx10Bit::max11624`] and [`Max116xx10Bit::max11625`] functionsdepending on which device +//! you are using. This automatically sets the highest channel number accordingly. +//! +//! The default structs use the externally clocked mode with an external voltage reference. +//! You can modify the operation mode of the ADC by converting the default struct using the +//! following functions +//! +//! - [`Max116xx10Bit::into_ext_clkd_with_int_ref_no_wakeup_delay`] +//! - [`Max116xx10Bit::into_ext_clkd_with_int_ref_wakeup_delay`] +//! - [`Max116xx10Bit::into_int_clkd_int_timed_through_ser_if_with_wakeup`] +//! - [`Max116xx10Bit::into_int_clkd_int_timed_through_ser_if_without_wakeup`] #![no_std] use core::{marker::PhantomData, slice::IterMut}; use embedded_hal::{ + blocking::delay::DelayUs, blocking::spi::Transfer, digital::v2::{InputPin, OutputPin}, spi::FullDuplex, @@ -14,16 +32,29 @@ pub trait HasChannels: private::Sealed { const NUM: u8; } -pub struct Max11618; -pub struct Max11619; -pub struct Max11620; -pub struct Max11621; -pub struct Max11624; -pub struct Max11625; +struct Max11618; +struct Max11619; +struct Max11620; +struct Max11621; +struct Max11624; +struct Max11625; pub struct WithWakeupDelay; pub struct WithoutWakeupDelay; +pub trait WakeupDelay: private::Sealed { + const ON: bool; +} + +impl WakeupDelay for WithWakeupDelay { + const ON: bool = true; +} +impl private::Sealed for WithWakeupDelay {} +impl WakeupDelay for WithoutWakeupDelay { + const ON: bool = false; +} +impl private::Sealed for WithoutWakeupDelay {} + impl private::Sealed for Max11618 {} impl HasChannels for Max11618 { const NUM: u8 = 4; @@ -66,6 +97,7 @@ impl Clocked for InternallyClockedInternallyTimedCnvst { const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedCnvst; } impl InternallyClocked for InternallyClockedInternallyTimedCnvst {} +type IntClkdIntTmdCnvst = InternallyClockedInternallyTimedCnvst; pub struct InternallyClockedExternallyTimedCnvst {} impl private::Sealed for InternallyClockedExternallyTimedCnvst {} @@ -73,6 +105,7 @@ impl Clocked for InternallyClockedExternallyTimedCnvst { const CLK_SEL: ClockMode = ClockMode::InternalClockExternallyTimedCnvst; } impl InternallyClocked for InternallyClockedExternallyTimedCnvst {} +type IntClkdExtTmdCnvst = InternallyClockedExternallyTimedCnvst; pub struct InternallyClockedInternallyTimedSerialInterface {} impl private::Sealed for InternallyClockedInternallyTimedSerialInterface {} @@ -80,40 +113,38 @@ impl Clocked for InternallyClockedInternallyTimedSerialInterface { const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedSerialInterface; } impl InternallyClocked for InternallyClockedInternallyTimedSerialInterface {} +type IntClkdIntTmdSerIF = InternallyClockedInternallyTimedSerialInterface; pub struct ExternallyClocked {} impl private::Sealed for ExternallyClocked {} impl Clocked for ExternallyClocked { const CLK_SEL: ClockMode = ClockMode::ExternalClockExternallyTimedSclk; } +type ExtClkd = ExternallyClocked; //================================================================================================== // Definitions //================================================================================================== -#[derive(Debug, PartialEq)] -pub enum PendingOp { - None, - SingleChannel, - MultiChannel, -} - /// Clock modes for the MAX116XX devices #[derive(Debug, PartialEq, Clone, Copy)] pub enum ClockMode { + /// Internally timed, CNVST only needs to be pulsed for 40ns. /// CNVST Configuration: CNVST active low InternalClockInternallyTimedCnvst = 0b00, - /// Externally timed through CNVST. CNVST Configuration: CNVST active low + /// Externally timed through CNVST. CNVST needs to be held low for the conversion duration, + /// whigh might include the wake-up delay. CNVST Configuration: CNVST active low InternalClockExternallyTimedCnvst = 0b01, + /// Start conversions using the serial interface instead of CNVST. /// Default mode at power-up. CNVST Configuration: AIN15/AIN11/AIN7 - /// Start conversions using the serial interface instead of CNVST InternalClockInternallyTimedSerialInterface = 0b10, + /// Use the SPI clock as the conversion clock ExternalClockExternallyTimedSclk = 0b11, } /// Voltage reference modes #[derive(Debug, PartialEq, Clone, Copy)] -pub enum RefMode { +pub enum VoltageRefMode { /// Auto-Shutdown is on, wake-up delay of 65 us InternalRefWithWakeupDelay = 0b00, ExternalSingleEndedNoWakeupDelay = 0b01, @@ -131,6 +162,7 @@ pub enum AveragingConversions { } /// Specifies the number of returned result in single scan mode +#[derive(Debug, PartialEq, Clone, Copy)] pub enum AveragingResults { FourResults = 0b00, EightResults = 0b01, @@ -138,18 +170,21 @@ pub enum AveragingResults { SixteenResults = 0b11, } +#[derive(Debug, PartialEq, Clone, Copy)] pub enum ScanMode { Scan0ToChannelN = 0b00, ScanChannelNToHighest = 0b01, - ScanChnanelNRepeatedly = 0b10, + ScanChannelNRepeatedly = 0b10, ConvertChannelNOnce = 0b11, } #[derive(Debug)] pub enum AdcError { InvalidChannel, + InvalidRefMode, CmdBufTooSmall, ResulBufTooSmall, + /// Other pending operation (possible of other type) PendingOperation, NoPendingOperation, InvalidClockMode, @@ -168,49 +203,202 @@ impl From for Error { } } +struct InternalCfg { + clk_mode: ClockMode, + ref_mode: VoltageRefMode, + pending_scan_mode: Option, + max_channels: u8, + results_len: u8, + requested_conversions: usize, +} //================================================================================================== -// ADc implementation +// ADC implementation //================================================================================================== -pub struct Max116xx10Bit< - SPI, - CS, - MAX: HasChannels, - CLOCKED = ExternallyClocked, - DELAY = WithoutWakeupDelay, -> { - clk_mode: ClockMode, - ref_mode: RefMode, +pub struct Max116xx10Bit { spi: SPI, cs: CS, - pending_op: PendingOp, - max: PhantomData, + cfg: InternalCfg, clocked: PhantomData, - delay: PhantomData, + delay: PhantomData, } -impl - Max116xx10Bit +pub struct Max116xx10BitEocExt { + base: Max116xx10Bit, + eoc: EOC, +} + +pub struct Max116xx10BitCnvstEocExt { + base: Max116xx10Bit, + eoc: EOC, + cnvst: CNVST, +} + +//================================================================================================== +// Generic +//================================================================================================== + +impl Max116xx10Bit where SPI: Transfer + FullDuplex, CS: OutputPin, { - /// Create a new generic MAX116xx instance. - pub fn new(spi: SPI, cs: CS, ref_mode: RefMode) -> Result> { - let mut max_dev = Max116xx10Bit { - clk_mode: CLOCKED::CLK_SEL, - ref_mode, + pub fn max11618(spi: SPI, cs: CS) -> Result> { + Self::new::(spi, cs) + } + pub fn max11619(spi: SPI, cs: CS) -> Result> { + Self::new::(spi, cs) + } + pub fn max11620(spi: SPI, cs: CS) -> Result> { + Self::new::(spi, cs) + } + pub fn max11621(spi: SPI, cs: CS) -> Result> { + Self::new::(spi, cs) + } + pub fn max11624(spi: SPI, cs: CS) -> Result> { + Self::new::(spi, cs) + } + pub fn max11625(spi: SPI, cs: CS) -> Result> { + Self::new::(spi, cs) + } + + /// Create a new generic MAX116xx instance. By default the generated ADC struct is configured + /// for externally clocked mode with conversions timed through the serial interface and + /// an external voltage reference. You can convert the ADC to use other SETUP register + /// configurations using the `into*` functions. + /// + /// The corresponding SETUP register is `0b0111_0100` + /// Please note that you still might have to reset and setup the ADC. + pub fn new(spi: SPI, cs: CS) -> Result> { + let max_dev = Max116xx10Bit { spi, cs, - pending_op: PendingOp::None, - max: PhantomData, + cfg: InternalCfg { + clk_mode: ExtClkd::CLK_SEL, + ref_mode: VoltageRefMode::ExternalSingleEndedNoWakeupDelay, + pending_scan_mode: None, + max_channels: MAX::NUM, + results_len: Self::get_results_len(AveragingResults::FourResults), + requested_conversions: 0, + }, delay: PhantomData, clocked: PhantomData, }; - max_dev.setup()?; Ok(max_dev) } + /// Use internal reference which is off after scan. This means that the device needs a wakeup + /// delay + /// + /// The corresponding SETUP register is `0b0111_0000` + pub fn into_ext_clkd_with_int_ref_wakeup_delay( + self, + ) -> Max116xx10Bit { + Max116xx10Bit { + spi: self.spi, + cs: self.cs, + cfg: InternalCfg { + clk_mode: ExtClkd::CLK_SEL, + ref_mode: VoltageRefMode::InternalRefWithWakeupDelay, + pending_scan_mode: None, + max_channels: self.cfg.max_channels, + results_len: Self::get_results_len(AveragingResults::FourResults), + requested_conversions: 0, + }, + clocked: PhantomData, + delay: PhantomData, + } + } + + /// Use SPI clock as conversion clock and use the internal voltage reference without a wakeup + /// delay + /// + /// The corresponding SETUP register is `0b0111_1000` + pub fn into_ext_clkd_with_int_ref_no_wakeup_delay( + self, + ) -> Max116xx10Bit { + Max116xx10Bit { + spi: self.spi, + cs: self.cs, + cfg: InternalCfg { + clk_mode: ExtClkd::CLK_SEL, + ref_mode: VoltageRefMode::InternalRefWithoutWakeupDelay, + pending_scan_mode: None, + max_channels: self.cfg.max_channels, + results_len: Self::get_results_len(AveragingResults::FourResults), + requested_conversions: 0, + }, + clocked: PhantomData, + delay: PhantomData, + } + } + + /// Convert into interally clocked mode with internal timing initiated by the serial interface + /// and a wakeup delay. This can be used to reduce power consumption + /// + /// The corresponding SETUP register is `0b0110_1100` + pub fn into_int_clkd_int_timed_through_ser_if_with_wakeup>( + self, + eoc: EOC, + ) -> Max116xx10BitEocExt { + Max116xx10BitEocExt { + base: Max116xx10Bit { + spi: self.spi, + cs: self.cs, + cfg: InternalCfg { + clk_mode: IntClkdIntTmdSerIF::CLK_SEL, + ref_mode: VoltageRefMode::InternalRefWithWakeupDelay, + pending_scan_mode: None, + max_channels: self.cfg.max_channels, + results_len: Self::get_results_len(AveragingResults::FourResults), + requested_conversions: 0, + }, + clocked: PhantomData, + delay: PhantomData, + }, + eoc, + } + } + + /// Convert into interally clocked mode with internal timing initiated by the serial interface + /// and no wakeup delay. + /// + /// The corresponding SETUP register can be one of the two + /// - External Voltage reference: `0b0110_0100` + /// - Internal Voltage reference always on: `0b0110_1000` + pub fn into_int_clkd_int_timed_through_ser_if_without_wakeup>( + self, + v_ref: VoltageRefMode, + eoc: EOC, + ) -> Result, AdcError> { + if v_ref == VoltageRefMode::InternalRefWithWakeupDelay { + return Err(AdcError::InvalidRefMode); + } + Ok(Max116xx10BitEocExt { + base: Max116xx10Bit { + spi: self.spi, + cs: self.cs, + cfg: InternalCfg { + clk_mode: IntClkdIntTmdSerIF::CLK_SEL, + ref_mode: VoltageRefMode::InternalRefWithWakeupDelay, + pending_scan_mode: None, + max_channels: self.cfg.max_channels, + results_len: Self::get_results_len(AveragingResults::FourResults), + requested_conversions: 0, + }, + clocked: PhantomData, + delay: PhantomData, + }, + eoc, + }) + } +} + +impl Max116xx10Bit +where + SPI: Transfer + FullDuplex, + CS: OutputPin, +{ #[inline] fn send_wrapper(&mut self, byte: u8) -> Result<(), Error> { self.cs.set_low().map_err(Error::Pin)?; @@ -219,22 +407,26 @@ where Ok(()) } + /// Set up the ADC depending on clock and reference configuration #[inline] pub fn setup(&mut self) -> Result<(), Error> { self.send_wrapper(self.get_setup_byte()) } + /// Set up the Averaging register. This sets the AVGON, NAVG1, NAVG0, NSCAN1 and NSCAN0 + /// bits accordingly #[inline] pub fn averaging( &mut self, avg_conv: AveragingConversions, avg_res: AveragingResults, ) -> Result<(), Error> { + self.cfg.results_len = Self::get_results_len(avg_res); self.send_wrapper(Self::get_averaging_byte(avg_conv, avg_res)) } #[inline] - pub fn reset_adc(&mut self, fifo_only: bool) -> Result<(), Error> { + pub fn reset(&mut self, fifo_only: bool) -> Result<(), Error> { let mut reset_byte = 0b0001_0000; if fifo_only { reset_byte |= 1 << 3; @@ -244,7 +436,7 @@ where #[inline] pub fn get_setup_byte(&self) -> u8 { - ((1 << 6) as u8) | ((self.clk_mode as u8) << 4) | ((self.ref_mode as u8) << 2) + ((1 << 6) as u8) | ((self.cfg.clk_mode as u8) << 4) | ((self.cfg.ref_mode as u8) << 2) } #[inline] @@ -253,75 +445,41 @@ where } #[inline] - pub fn get_conversion_byte(scan_mode: ScanMode, channel_num: u8) -> Result { - if channel_num > MAX::NUM { + pub fn get_results_len(avg_res: AveragingResults) -> u8 { + (avg_res as u8 + 1) * 4 + } + + #[inline] + pub fn get_conversion_byte( + &self, + scan_mode: ScanMode, + channel_num: u8, + ) -> Result { + if channel_num > self.cfg.max_channels { return Err(AdcError::InvalidChannel); } Ok((1 << 7) | (channel_num << 3) | ((scan_mode as u8) << 1)) } -} -impl - Max116xx10Bit -where - SPI: Transfer + FullDuplex, - CS: OutputPin, -{ - #[inline] - fn request_wrapper( + + /// Generic function which can be used a single result is available + /// when EOC is low + fn internal_read_single_channel>( &mut self, - channel_num: u8, - scan_mode: ScanMode, - op_type: PendingOp, - ) -> Result<(), Error> { - if self.pending_op != PendingOp::None { - return Err(Error::Adc(AdcError::PendingOperation)); - } - let conv_byte = Self::get_conversion_byte(scan_mode, channel_num).map_err(Error::Adc)?; - self.send_wrapper(conv_byte)?; - self.pending_op = op_type; - Ok(()) - } - - pub fn request_single_channel(&mut self, channel_num: u8) -> Result<(), Error> { - self.request_wrapper( - channel_num, - ScanMode::ConvertChannelNOnce, - PendingOp::SingleChannel, - ) - } - - pub fn request_multiple_channels_0_to_n(&mut self, n: u8) -> Result<(), Error> { - self.request_wrapper(n, ScanMode::Scan0ToChannelN, PendingOp::MultiChannel) - } - - pub fn request_multiple_channels_n_to_highest( - &mut self, - n: u8, - ) -> Result<(), Error> { - self.request_wrapper(n, ScanMode::ScanChannelNToHighest, PendingOp::MultiChannel) - } - - pub fn get_single_channel>( - &mut self, - eoc_pin: &mut I, + eoc: &mut EOC, ) -> nb::Result> { - if self.pending_op != PendingOp::SingleChannel { + if self.cfg.pending_scan_mode.is_none() { return Err(nb::Error::Other(Error::Adc(AdcError::NoPendingOperation))); + } else if self.cfg.pending_scan_mode != Some(ScanMode::ConvertChannelNOnce) { + return Err(nb::Error::Other(Error::Adc(AdcError::PendingOperation))); } - let is_low = match eoc_pin.is_low() { - Ok(low) => low, - Err(e) => { - return Err(nb::Error::Other(Error::Pin(e))); - } - }; - if is_low { + if eoc.is_low().map_err(Error::Pin)? { let mut dummy_cmd: [u8; 2] = [0; 2]; self.cs.set_low().map_err(Error::Pin)?; let transfer_result = self.spi.transfer(&mut dummy_cmd); self.cs.set_high().map_err(Error::Pin)?; match transfer_result { Ok(reply) => { - self.pending_op = PendingOp::None; + self.cfg.pending_scan_mode = None; Ok(((reply[0] as u16) << 6) | (reply[1] as u16 >> 2)) } Err(e) => Err(nb::Error::Other(Error::Spi(e))), @@ -332,8 +490,62 @@ where } } -impl - Max116xx10Bit +macro_rules! ext_impl { + () => { + /// Set up the ADC depending on clock and reference configuration + #[inline] + pub fn setup(&mut self) -> Result<(), Error> { + self.base.send_wrapper(self.base.get_setup_byte()) + } + + /// Set up the Averaging register. This sets the AVGON, NAVG1, NAVG0, NSCAN1 and NSCAN0 + /// bits accordingly + #[inline] + pub fn averaging( + &mut self, + avg_conv: AveragingConversions, + avg_res: AveragingResults, + ) -> Result<(), Error> { + self.base.cfg.results_len = Max116xx10Bit::::get_results_len(avg_res); + self.base + .send_wrapper(Max116xx10Bit::::get_averaging_byte( + avg_conv, avg_res, + )) + } + + #[inline] + pub fn reset(&mut self, fifo_only: bool) -> Result<(), Error> { + let mut reset_byte = 0b0001_0000; + if fifo_only { + reset_byte |= 1 << 3; + } + self.base.send_wrapper(reset_byte) + } + }; +} +impl Max116xx10BitEocExt +where + SPI: Transfer + FullDuplex, + CS: OutputPin, +{ + ext_impl!(); +} + +impl + Max116xx10BitCnvstEocExt +where + SPI: Transfer + FullDuplex, + CS: OutputPin, +{ + ext_impl!(); +} + +//================================================================================================== +// External SPI clock used +//================================================================================================== + +/// Implementations when using the external SPI clock to time the conversions +impl Max116xx10Bit where SPI: Transfer + FullDuplex, CS: OutputPin, @@ -346,13 +558,7 @@ where if buf.len() < 3 { return Err(Error::Adc(AdcError::CmdBufTooSmall)); } - - match Self::get_conversion_byte(ScanMode::ConvertChannelNOnce, channel_num) { - Ok(byte) => buf[0] = byte, - Err(e) => { - return Err(Error::Adc(e)); - } - }; + buf[0] = self.get_conversion_byte(ScanMode::ConvertChannelNOnce, channel_num)?; buf[1] = 0x00; buf[2] = 0x00; self.cs.set_low().map_err(Error::Pin)?; @@ -371,7 +577,7 @@ where let mut next_byte: &mut u8; for idx in 0..n + 1 { next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; - *next_byte = Self::get_conversion_byte(ScanMode::ConvertChannelNOnce, idx)?; + *next_byte = self.get_conversion_byte(ScanMode::ConvertChannelNOnce, idx)?; next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; *next_byte = 0x00; } @@ -404,13 +610,13 @@ where ) -> Result<(), Error> { let mut iter = buf.iter_mut(); let mut next_byte: &mut u8; - if n > MAX::NUM - 1 { + if n > self.cfg.max_channels - 1 { return Err(Error::Adc(AdcError::InvalidChannel)); } - let conversions = MAX::NUM - n; - for idx in n..MAX::NUM { + let conversions = self.cfg.max_channels - n; + for idx in n..self.cfg.max_channels { next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; - *next_byte = Self::get_conversion_byte(ScanMode::ConvertChannelNOnce, idx)?; + *next_byte = self.get_conversion_byte(ScanMode::ConvertChannelNOnce, idx)?; next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; *next_byte = 0x00; } @@ -436,6 +642,299 @@ where } } +/// Implementations when using the external SPI clock to time the conversions but also requiring +/// a wakeup delay +impl Max116xx10Bit +where + SPI: Transfer + FullDuplex, + CS: OutputPin, +{ + pub fn read_single_channel>( + &mut self, + buf: &mut [u8], + channel_num: u8, + delay: &mut DELAY, + ) -> Result> { + if buf.len() < 3 { + return Err(Error::Adc(AdcError::CmdBufTooSmall)); + } + buf[0] = self + .get_conversion_byte(ScanMode::ConvertChannelNOnce, channel_num) + .map_err(Error::Adc)?; + self.send_wrapper(buf[0])?; + delay.delay_us(65); + buf[0] = 0x00; + buf[1] = 0x00; + self.cs.set_low().map_err(Error::Pin)?; + let reply = self.spi.transfer(&mut buf[0..2]).map_err(Error::Spi)?; + self.cs.set_high().map_err(Error::Pin)?; + Ok(((reply[0] as u16) << 6) | (reply[1] as u16 >> 2)) + } + + pub fn read_multiple_channels_0_to_n>( + &mut self, + buf: &mut [u8], + result_iter: &mut IterMut, + n: u8, + delay: &mut DELAY, + ) -> Result<(), Error> { + let mut iter = buf.iter_mut(); + let mut next_byte: &mut u8; + for idx in 0..n + 1 { + next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; + *next_byte = self.get_conversion_byte(ScanMode::ConvertChannelNOnce, idx)?; + next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; + *next_byte = 0x00; + } + next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; + *next_byte = 0x00; + // Write first conversion byte and then wait 65 us to allow internal reference to power up + self.send_wrapper(buf[0])?; + delay.delay_us(65); + + self.cs.set_low().map_err(Error::Pin)?; + let reply = self + .spi + .transfer(&mut buf[1..((n + 1) * 2 + 1) as usize]) + .map_err(Error::Spi)?; + self.cs.set_high().map_err(Error::Pin)?; + let mut reply_iter = reply.iter(); + for _ in 0..n + 1 { + let next_res = result_iter + .next() + .ok_or(Error::Adc(AdcError::ResulBufTooSmall))?; + *next_res = ((*reply_iter.next().unwrap() as u16) << 6) + | (*reply_iter.next().unwrap() as u16 >> 2); + } + Ok(()) + } + + pub fn read_multiple_channels_n_to_highest>( + &mut self, + buf: &mut [u8], + result_iter: &mut IterMut, + n: u8, + delay: &mut DELAY, + ) -> Result<(), Error> { + let mut iter = buf.iter_mut(); + let mut next_byte: &mut u8; + if n > self.cfg.max_channels - 1 { + return Err(Error::Adc(AdcError::InvalidChannel)); + } + let conversions = self.cfg.max_channels - n; + for idx in n..self.cfg.max_channels { + next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; + *next_byte = self.get_conversion_byte(ScanMode::ConvertChannelNOnce, idx)?; + next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; + *next_byte = 0x00; + } + next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?; + *next_byte = 0x00; + // Write first conversion byte and then wait 65 us with CS high to allow internal + // reference to power up + self.send_wrapper(buf[0])?; + delay.delay_us(65); + self.cs.set_low().map_err(Error::Pin)?; + let reply = self + .spi + .transfer(&mut buf[1..(conversions * 2 + 1) as usize]) + .map_err(Error::Spi)?; + self.cs.set_high().map_err(Error::Pin)?; + let mut reply_iter = reply.iter(); + for _ in 0..conversions { + let next_res = result_iter + .next() + .ok_or(Error::Adc(AdcError::ResulBufTooSmall))?; + *next_res = ((*reply_iter.next().unwrap() as u16) << 6) + | (*reply_iter.next().unwrap() as u16 >> 2); + } + Ok(()) + } +} + +//================================================================================================== +// Internal clock, EOC pin used +//================================================================================================== + +/// Implementations when using the internal clock with a conversion started +/// through the serial interface +impl Max116xx10BitEocExt +where + SPI: Transfer + FullDuplex, + CS: OutputPin, + EOC: InputPin, +{ + #[inline] + fn request_wrapper( + &mut self, + channel_num: u8, + scan_mode: ScanMode, + ) -> Result<(), Error> { + if self.base.cfg.pending_scan_mode.is_some() { + return Err(Error::Adc(AdcError::PendingOperation)); + } + let conv_byte = self + .base + .get_conversion_byte(scan_mode, channel_num) + .map_err(Error::Adc)?; + self.base.send_wrapper(conv_byte)?; + self.base.cfg.pending_scan_mode = Some(scan_mode); + Ok(()) + } + + pub fn request_single_channel(&mut self, channel_num: u8) -> Result<(), Error> { + self.request_wrapper(channel_num, ScanMode::ConvertChannelNOnce) + } + + /// Request a channel repeatedly, using scan mode 10. The number of scans is determined + /// by the averaging register NSCAN0 and NSCAN1 configuration which can be configured + /// with the [`averaging`](Max116xx10Bit::averaging) function + pub fn request_channel_n_repeatedly( + &mut self, + channel_num: u8, + ) -> Result<(), Error> { + self.request_wrapper(channel_num, ScanMode::ScanChannelNRepeatedly) + } + + pub fn request_multiple_channels_0_to_n(&mut self, n: u8) -> Result<(), Error> { + self.base.cfg.requested_conversions = n as usize + 1; + self.request_wrapper(n, ScanMode::Scan0ToChannelN) + } + + pub fn request_multiple_channels_n_to_highest( + &mut self, + n: u8, + ) -> Result<(), Error> { + self.base.cfg.requested_conversions = self.base.cfg.max_channels as usize + 1 - n as usize; + self.request_wrapper(n, ScanMode::ScanChannelNToHighest) + } + + /// This function is used to retrieve the results for a single byte request. The EOC pin + /// needs to be passed explicitely here. + /// If no request was made, [AdcError::NoPendingOperation] is returned. + /// If a request was made for multipel results, [AdcError::PendingOperation] will be returned. + pub fn get_single_channel(&mut self) -> nb::Result> { + self.base.internal_read_single_channel(&mut self.eoc) + } + + /// This function is used to retrieve the results for all functions requesting multiple + /// bytes. If no request was made, [AdcError::NoPendingOperation] is returned. + /// If a request was made for a single channel, [AdcError::PendingOperation] will be returned. + pub fn get_multi_channel( + &mut self, + result_iter: &mut IterMut, + ) -> nb::Result<(), Error> { + if self.base.cfg.pending_scan_mode.is_none() { + return Err(nb::Error::Other(Error::Adc(AdcError::NoPendingOperation))); + } else if self.base.cfg.pending_scan_mode == Some(ScanMode::ConvertChannelNOnce) { + return Err(nb::Error::Other(Error::Adc(AdcError::PendingOperation))); + } + if self.eoc.is_low().map_err(Error::Pin)? { + // maximum length of reply is 32 for 16 channels + let mut dummy_cmd: [u8; 32] = [0; 32]; + let num_conv: usize; + if self.base.cfg.pending_scan_mode == Some(ScanMode::ScanChannelNRepeatedly) { + num_conv = self.base.cfg.results_len as usize; + } else { + num_conv = self.base.cfg.requested_conversions; + } + self.base.cfg.pending_scan_mode = None; + self.base.cfg.requested_conversions = 0; + self.base.cs.set_low().map_err(Error::Pin)?; + let transfer_result = self.base.spi.transfer(&mut dummy_cmd[0..(num_conv * 2)]); + self.base.cs.set_high().map_err(Error::Pin)?; + match transfer_result { + Ok(reply) => { + let mut reply_iter = reply.iter(); + for _ in 0..num_conv { + let next_res = result_iter + .next() + .ok_or(Error::Adc(AdcError::ResulBufTooSmall))?; + *next_res = ((*reply_iter.next().unwrap() as u16) << 6) + | (*reply_iter.next().unwrap() as u16 >> 2); + } + Ok(()) + } + Err(e) => Err(nb::Error::Other(Error::Spi(e))), + } + } else { + Err(nb::Error::WouldBlock) + } + } +} + +//================================================================================================== +// Internal clock, CNVST and EOC pin used +//================================================================================================== + +/// Implementations when using the internal clock where CNVST is held low for the duration +/// of the conversion +/// +/// TODO: Implement. Unfortunately, the test board used to verify this library did not have +/// the CNVST connected, so I wouldn't be able to test an implementation easily. +impl + Max116xx10BitCnvstEocExt +where + SPI: Transfer + FullDuplex, + CS: OutputPin, + EOC: InputPin, + CNVST: OutputPin, +{ + pub fn dummy() { + todo!("Implement this") + } +} + +/// Implementations when using the internal clock where CNVST is only pulsed to start acquisition +/// and conversion +/// +/// TODO: Test. Unfortunately, the test board used to verify this library did not have +/// the CNVST connected, so I wouldn't be able to test an implementation easily. +impl + Max116xx10BitCnvstEocExt +where + SPI: Transfer + FullDuplex, + CS: OutputPin, + EOC: InputPin, + CNVST: OutputPin, +{ + /// The pulse needs to be at least 40ns. A pulse cycle value can be used to increase + /// the width of the pulse + pub fn request_single_channel( + &mut self, + channel_num: u8, + pulse_cycles: u8, + ) -> Result<(), Error> { + self.request_wrapper(channel_num, ScanMode::ConvertChannelNOnce, pulse_cycles) + } + + #[inline] + fn request_wrapper( + &mut self, + channel_num: u8, + scan_mode: ScanMode, + pulse_cycles: u8, + ) -> Result<(), Error> { + if self.base.cfg.pending_scan_mode.is_some() { + return Err(Error::Adc(AdcError::PendingOperation)); + } + let conv_byte = self + .base + .get_conversion_byte(scan_mode, channel_num) + .map_err(Error::Adc)?; + self.base.send_wrapper(conv_byte)?; + self.cnvst.set_low().map_err(Error::Pin)?; + for _ in 0..pulse_cycles {} + self.cnvst.set_high().map_err(Error::Pin)?; + self.base.cfg.pending_scan_mode = Some(scan_mode); + Ok(()) + } + + pub fn get_single_channel(&mut self) -> nb::Result> { + self.base.internal_read_single_channel(&mut self.eoc) + } +} + mod private { pub trait Sealed {} } From c2255f2da9a55c194b38c6ecbcfcc30ceabd9315 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 14 Dec 2021 14:07:13 +0100 Subject: [PATCH 2/4] bump subversion --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 091d581..ecbf2fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "max116xx-10bit" -version = "0.1.1" +version = "0.2.0" authors = ["Robin Mueller "] edition = "2021" description = "Driver crate for the MAX116xx 10-bit ADC devices" From cc0680ea632b87766bd339e814f7803c00a29f08 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 14 Dec 2021 14:12:10 +0100 Subject: [PATCH 3/4] update jenkinsfile there are now examples --- automation/Jenkinsfile | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile index e8ae0e3..df13c69 100644 --- a/automation/Jenkinsfile +++ b/automation/Jenkinsfile @@ -36,17 +36,5 @@ pipeline { sh 'cargo check --target armv7-unknown-linux-gnueabihf' } } - stage('Check Examples') { - agent { - dockerfile { - dir 'automation' - reuseNode true - } - } - steps { - sh 'cargo check --target thumbv6m-none-eabi --examples' - sh 'cargo check --target armv7-unknown-linux-gnueabihf' - } - } } } \ No newline at end of file From 0cd4d7369b09fb49283c35e956bb16ad2706ec73 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 14 Dec 2021 14:13:45 +0100 Subject: [PATCH 4/4] add two more toolchains in jenkinsfile --- automation/Dockerfile | 3 ++- automation/Jenkinsfile | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/automation/Dockerfile b/automation/Dockerfile index 84da5d0..9834d56 100644 --- a/automation/Dockerfile +++ b/automation/Dockerfile @@ -7,5 +7,6 @@ RUN apt-get --yes upgrade # tzdata is a dependency, won't install otherwise ARG DEBIAN_FRONTEND=noninteractive -RUN rustup target add thumbv6m-none-eabi armv7-unknown-linux-gnueabihf && \ +RUN rustup target add thumbv6m-none-eabi armv7-unknown-linux-gnueabihf \ + thumbv7em-none-eabihf x86_64-unknown-linux-gnu && \ rustup component add rustfmt clippy diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile index df13c69..16c2c78 100644 --- a/automation/Jenkinsfile +++ b/automation/Jenkinsfile @@ -34,6 +34,8 @@ pipeline { steps { sh 'cargo check --target thumbv6m-none-eabi' sh 'cargo check --target armv7-unknown-linux-gnueabihf' + sh 'cargo check --target x86_64-unknown-linux-gnu' + sh 'cargo check --target thumbv7em-none-eabihf' } } }