diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7011473 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +Change Log +======= + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [unreleased] + +## [v0.1.0] + +- Initial release diff --git a/Cargo.toml b/Cargo.toml index aabb462..233f078 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,16 @@ [package] name = "max116xx-10bit" version = "0.1.0" +authors = ["Robin Mueller "] edition = "2021" - +description = "Driver crate for the MAX116xx 10-bit ADC devices" +homepage = "https://egit.irs.uni-stuttgart.de/rust/max116xx-10bit" +repository = "https://egit.irs.uni-stuttgart.de/rust/max116xx-10bit" +license = "Apache-2.0" +keywords = ["maxim", "adc", "sensor", "embedded", "no-std"] +categories = ["embedded", "no-std", "hardware-support"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +embedded-hal = "0.2.6" +nb = "1.0.0" diff --git a/NOTICE b/NOTICE index 7d20247..72519ee 100644 --- a/NOTICE +++ b/NOTICE @@ -1,3 +1,3 @@ -Driver crate for the MAX116xx 10 bits devices +Driver crate for the MAX116xx 10-bit ADC devices This software contains code developed at the University of Stuttgart. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7eb6d3 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +Rust Maxim 116xx 10-bit ADC device driver crate +======== + +This is a platform agnostic Rust driver for the MAX11618-MAX11621, MAX11624 and MAX11625 10-bit +[ADC devices](https://www.maximintegrated.com/en/products/analog/data-converters/analog-to-digital-converters/MAX11619.html) +which uses the `embedded-hal`(https://github.com/rust-embedded/embedded-hal) traits. + +This driver implements basic operations to read raw ADC values: + +- Read ADC values using the SPI clock as an external clock +- Read ADC values using the End-Of-Conversion (EOC) pin + +Currently, the driver only supports operation without a wake-up delay and the EOC read +functionality is still limited. Pull requests to improve this are welcome. + +# Usage + +To use this driver, import this crate and an `embedded-hal` implementation and then instantiate +the appropriate device. + +The crate uses basic type-level support to prevent using the ADC in a wrong way.https://github.com/rust-embedded/embedded-hal +The type-level support defaults to an externally clocked device with no wake-up delay. + +This crate was tested using the Vorago REB1 development board. You can find an example application +[here](). diff --git a/src/lib.rs b/src/lib.rs index 1b4a90c..3a10f2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,428 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); +#![no_std] +use core::{marker::PhantomData, slice::IterMut}; +use embedded_hal::{blocking::spi::Transfer, digital::v2::InputPin, spi::FullDuplex}; + +//================================================================================================== +// Type-level support +//================================================================================================== + +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; + +pub struct WithWakeupDelay; +pub struct WithoutWakeupDelay; + +impl private::Sealed for Max11618 {} +impl HasChannels for Max11618 { + const NUM: u8 = 4; +} + +impl private::Sealed for Max11619 {} +impl HasChannels for Max11619 { + const NUM: u8 = 4; +} + +impl private::Sealed for Max11620 {} +impl HasChannels for Max11620 { + const NUM: u8 = 8; +} + +impl private::Sealed for Max11621 {} +impl HasChannels for Max11621 { + const NUM: u8 = 8; +} + +impl private::Sealed for Max11624 {} +impl HasChannels for Max11624 { + const NUM: u8 = 16; +} + +impl private::Sealed for Max11625 {} +impl HasChannels for Max11625 { + const NUM: u8 = 16; +} + +pub trait Clocked: private::Sealed { + const CLK_SEL: ClockMode; +} + +pub trait InternallyClocked: Clocked {} + +pub struct InternallyClockedInternallyTimedCnvst {} +impl private::Sealed for InternallyClockedInternallyTimedCnvst {} +impl Clocked for InternallyClockedInternallyTimedCnvst { + const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedCnvst; +} +impl InternallyClocked for InternallyClockedInternallyTimedCnvst {} + +pub struct InternallyClockedExternallyTimedCnvst {} +impl private::Sealed for InternallyClockedExternallyTimedCnvst {} +impl Clocked for InternallyClockedExternallyTimedCnvst { + const CLK_SEL: ClockMode = ClockMode::InternalClockExternallyTimedCnvst; +} +impl InternallyClocked for InternallyClockedExternallyTimedCnvst {} + +pub struct InternallyClockedInternallyTimedSerialInterface {} +impl private::Sealed for InternallyClockedInternallyTimedSerialInterface {} +impl Clocked for InternallyClockedInternallyTimedSerialInterface { + const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedSerialInterface; +} +impl InternallyClocked for InternallyClockedInternallyTimedSerialInterface {} + +pub struct ExternallyClocked {} +impl private::Sealed for ExternallyClocked {} +impl Clocked for ExternallyClocked { + const CLK_SEL: ClockMode = ClockMode::ExternalClockExternallyTimedSclk; +} + +//================================================================================================== +// Definitions +//================================================================================================== + +#[derive(Debug, PartialEq)] +pub enum PendingOp { + None, + SingleChannel, + MultiChannel, +} + +/// Clock modes for the MAX116XX devices +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum ClockMode { + /// CNVST Configuration: CNVST active low + InternalClockInternallyTimedCnvst = 0b00, + /// Externally timed through CNVST. CNVST Configuration: CNVST active low + InternalClockExternallyTimedCnvst = 0b01, + /// Default mode at power-up. CNVST Configuration: AIN15/AIN11/AIN7 + /// Start conversions using the serial interface instead of CNVST + InternalClockInternallyTimedSerialInterface = 0b10, + ExternalClockExternallyTimedSclk = 0b11, +} + +/// Voltage reference modes +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum RefMode { + /// Auto-Shutdown is on, wake-up delay of 65 us + InternalRefWithWakeupDelay = 0b00, + ExternalSingleEndedNoWakeupDelay = 0b01, + InternalRefWithoutWakeupDelay = 0b10, +} + +/// Specifies how many conversions are performed and then averaged for each +/// requested result +pub enum AveragingConversions { + OneConversion = 0b000, + FourConversions = 0b100, + EightConversions = 0b101, + SixteenConversions = 0b110, + ThirtytwoConversions = 0b111, +} + +/// Specifies the number of returned result in single scan mode +pub enum AveragingResults { + FourResults = 0b00, + EightResults = 0b01, + TwelveResults = 0b10, + SixteenResults = 0b11, +} + +pub enum ScanMode { + Scan0ToChannelN = 0b00, + ScanChannelNToHighest = 0b01, + ScanChnanelNRepeatedly = 0b10, + ConvertChannelNOnce = 0b11, +} + +#[derive(Debug)] +pub enum AdcError { + InvalidChannel, + CmdBufTooSmall, + ResulBufTooSmall, + PendingOperation, + NoPendingOperation, + InvalidClockMode, +} + +#[derive(Debug)] +pub enum Error { + Adc(AdcError), + Spi(SpiE), +} + +pub enum ErrorWithEoc { + Error(Error), + Pin(PinE), +} + +impl From for ErrorWithEoc { + fn from(other: AdcError) -> Self { + ErrorWithEoc::Error(Error::Adc(other)) } } + +impl From for Error { + fn from(other: AdcError) -> Self { + Error::Adc(other) + } +} + +//================================================================================================== +// ADc implementation +//================================================================================================== + +pub struct Max116xx10Bit< + SPI, + MAX: HasChannels, + CLOCKED = ExternallyClocked, + DELAY = WithoutWakeupDelay, +> { + pub clk_mode: ClockMode, + pub ref_mode: RefMode, + spi: SPI, + pending_op: PendingOp, + max: PhantomData, + clocked: PhantomData, + delay: PhantomData, +} + +impl Max116xx10Bit +where + SPI: Transfer + FullDuplex, +{ + /// Create a new generic MAX116xx instance. + pub fn new(spi: SPI, ref_mode: RefMode) -> Result> { + let mut max_dev: Max116xx10Bit = Max116xx10Bit { + clk_mode: CLOCKED::CLK_SEL, + ref_mode, + spi, + pending_op: PendingOp::None, + max: PhantomData, + delay: PhantomData, + clocked: PhantomData, + }; + max_dev.setup()?; + Ok(max_dev) + } + + #[inline] + fn send_wrapper(&mut self, byte: u8) -> Result<(), Error> { + nb::block!(self.spi.send(byte)).map_err(|e| Error::Spi(e)) + } + + #[inline] + pub fn setup(&mut self) -> Result<(), Error> { + self.send_wrapper(self.get_setup_byte()) + } + + #[inline] + pub fn averaging( + &mut self, + avg_conv: AveragingConversions, + avg_res: AveragingResults, + ) -> Result<(), Error> { + self.send_wrapper(Self::get_averaging_byte(avg_conv, avg_res)) + } + + #[inline] + pub fn reset_adc(&mut self, fifo_only: bool) -> Result<(), Error> { + let mut reset_byte = 0b0001_0000; + if fifo_only { + reset_byte |= 1 << 3; + } + self.send_wrapper(reset_byte) + } + + #[inline] + pub fn get_setup_byte(&self) -> u8 { + ((1 << 6) as u8) | ((self.clk_mode as u8) << 4) | ((self.ref_mode as u8) << 2) + } + + #[inline] + pub fn get_averaging_byte(avg_conv: AveragingConversions, avg_res: AveragingResults) -> u8 { + ((1 << 5) as u8) | ((avg_conv as u8) << 2) | avg_res as u8 + } + + #[inline] + pub fn get_conversion_byte(scan_mode: ScanMode, channel_num: u8) -> Result { + if channel_num > MAX::NUM { + return Err(AdcError::InvalidChannel); + } + Ok((1 << 7) | (channel_num << 3) | ((scan_mode as u8) << 1)) + } +} +impl + Max116xx10Bit +where + SPI: Transfer + FullDuplex, +{ + #[inline] + fn request_wrapper( + &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(|e| Error::Adc(e))?; + 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, + ) -> nb::Result> { + if self.pending_op != PendingOp::SingleChannel { + return Err(nb::Error::Other(ErrorWithEoc::Error(Error::Adc( + AdcError::NoPendingOperation, + )))); + } + let is_low = match eoc_pin.is_low() { + Ok(low) => low, + Err(e) => { + return Err(nb::Error::Other(ErrorWithEoc::Pin(e))); + } + }; + if is_low { + let mut dummy_cmd: [u8; 2] = [0; 2]; + match self.spi.transfer(&mut dummy_cmd) { + Ok(reply) => { + self.pending_op = PendingOp::None; + Ok(((reply[0] as u16) << 6) | (reply[1] as u16 >> 2)) + } + Err(e) => Err(nb::Error::Other(ErrorWithEoc::Error(Error::Spi(e)))), + } + } else { + Err(nb::Error::WouldBlock) + } + } +} + +impl Max116xx10Bit +where + SPI: Transfer + FullDuplex, +{ + pub fn read_single_channel( + &mut self, + buf: &mut [u8], + channel_num: u8, + ) -> Result> { + 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[1] = 0x00; + buf[2] = 0x00; + let reply = self.spi.transfer(&mut buf[0..3]).ok().unwrap(); + Ok(((reply[1] as u16) << 6) | (reply[2] as u16 >> 2)) + } + + pub fn read_multiple_channels_0_to_n( + &mut self, + buf: &mut [u8], + result_iter: &mut IterMut, + n: u8, + ) -> 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; + let reply = self + .spi + .transfer(&mut buf[0..((n + 1) * 2 + 1) as usize]) + .map_err(|e| Error::Spi(e))?; + let mut reply_iter = reply.iter(); + // Skip first reply byte + reply_iter.next().unwrap(); + 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, + ) -> Result<(), Error> { + let mut iter = buf.iter_mut(); + let mut next_byte: &mut u8; + if n > MAX::NUM - 1 { + return Err(Error::Adc(AdcError::InvalidChannel)); + } + let conversions = MAX::NUM - n; + for idx in n..MAX::NUM { + 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; + let reply = self + .spi + .transfer(&mut buf[0..(conversions * 2 + 1) as usize]) + .ok() + .unwrap(); + let mut reply_iter = reply.iter(); + // Skip first reply byte + reply_iter.next().unwrap(); + 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(()) + } +} + +mod private { + pub trait Sealed {} +}