Compare commits

...

10 Commits
v0.1.1 ... main

Author SHA1 Message Date
Robin Müller c9b5f6a4e9
add Eq derives
Rust/max116xx-10bit/pipeline/head This commit looks good Details
2022-09-13 10:46:30 +02:00
Robin Müller 3422801850
small clippy improvement
Rust/max116xx-10bit/pipeline/head This commit looks good Details
2022-06-18 22:40:05 +02:00
Robin Müller 8a81b3a721
typo
Rust/max116xx-10bit/pipeline/head This commit looks good Details
2021-12-14 14:36:58 +01:00
Robin Müller 0532c1b94e
v0.2.1
Rust/max116xx-10bit/pipeline/head This commit looks good Details
2021-12-14 14:23:10 +01:00
Robin Müller 8eb5b556db Merge pull request 'Several improvements' (#1) from simplified-api into main
Rust/max116xx-10bit/pipeline/head This commit looks good Details
Reviewed-on: #1
2021-12-14 14:20:04 +01:00
Robin Müller 0cd4d7369b
add two more toolchains in jenkinsfile
Rust/max116xx-10bit/pipeline/pr-main This commit looks good Details
Rust/max116xx-10bit/pipeline/head This commit looks good Details
2021-12-14 14:13:45 +01:00
Robin Müller cc0680ea63
update jenkinsfile
Rust/max116xx-10bit/pipeline/head This commit looks good Details
Rust/max116xx-10bit/pipeline/pr-main This commit looks good Details
there are now examples
2021-12-14 14:12:10 +01:00
Robin Müller c2255f2da9
bump subversion
Rust/max116xx-10bit/pipeline/head This commit looks good Details
Rust/max116xx-10bit/pipeline/pr-main This commit looks good Details
2021-12-14 14:07:13 +01:00
Robin Müller 6b416f5684
Merge remote-tracking branch 'origin/main' into simplified-api
Rust/max116xx-10bit/pipeline/head This commit looks good Details
Rust/max116xx-10bit/pipeline/pr-main This commit looks good Details
2021-12-14 14:06:07 +01:00
Robin Müller 061cc6e9ee Several improvements
Rust/max116xx-10bit/pipeline/pr-main There was a failure building this commit Details
Rust/max116xx-10bit/pipeline/head This commit looks good Details
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
2021-12-14 14:01:31 +01:00
6 changed files with 642 additions and 141 deletions

View File

@ -8,6 +8,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased]
## [v0.2.1]
- README tweaks
## [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.1]
- Added badges and some documentation and README tweaks

View File

@ -1,6 +1,6 @@
[package]
name = "max116xx-10bit"
version = "0.1.1"
version = "0.2.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021"
description = "Driver crate for the MAX116xx 10-bit ADC devices"

View File

@ -9,13 +9,8 @@ This is a platform agnostic Rust driver for the MAX11618-MAX11621, MAX11624 and
[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.
This driver supports most required features but the CNVST pin support is still limited because
the test development board did not have the pin connected. Pull requests to improve this are welcome.
# Usage
@ -25,6 +20,6 @@ the appropriate device.
The crate uses basic type-level support to prevent using the ADC in a wrong way.
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](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/src/max11619.rs)
and [here](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/examples/max11619-adc.rs).
This crate was tested using the Vorago REB1 development board. You can find the example
application [here](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/examples/max11619-adc.rs)
using a [thin abstraction layer](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/src/max11619.rs)

View File

@ -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

View File

@ -34,18 +34,8 @@ pipeline {
steps {
sh 'cargo check --target thumbv6m-none-eabi'
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'
sh 'cargo check --target x86_64-unknown-linux-gnu'
sh 'cargo check --target thumbv7em-none-eabihf'
}
}
}

View File

@ -1,13 +1,29 @@
//! The crate uses basic type-level support to prevent using the ADC in a wrong way.
//! The type-level support defaults to an externally clocked device with no wake-up delay.
//! 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`] functions depending 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`]
//!
//! ## Examples
//!
//! You can find an example application [here](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/src/max11619.rs)
//! and [here](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/examples/max11619-adc.rs)
//! You can find an example application [here](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/examples/max11619-adc.rs)
//! using a [thin abstraction layer](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/src/max11619.rs)
#![no_std]
use core::{marker::PhantomData, slice::IterMut};
use embedded_hal::{
blocking::delay::DelayUs,
blocking::spi::Transfer,
digital::v2::{InputPin, OutputPin},
spi::FullDuplex,
@ -21,16 +37,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;
@ -73,6 +102,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 {}
@ -80,6 +110,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 {}
@ -87,40 +118,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)]
#[derive(Debug, PartialEq, Eq, 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 {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum VoltageRefMode {
/// Auto-Shutdown is on, wake-up delay of 65 us
InternalRefWithWakeupDelay = 0b00,
ExternalSingleEndedNoWakeupDelay = 0b01,
@ -138,6 +167,7 @@ pub enum AveragingConversions {
}
/// Specifies the number of returned result in single scan mode
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum AveragingResults {
FourResults = 0b00,
EightResults = 0b01,
@ -145,18 +175,21 @@ pub enum AveragingResults {
SixteenResults = 0b11,
}
#[derive(Debug, PartialEq, Eq, 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,
@ -175,49 +208,202 @@ impl<SpiE, PinE> From<AdcError> for Error<SpiE, PinE> {
}
}
struct InternalCfg {
clk_mode: ClockMode,
ref_mode: VoltageRefMode,
pending_scan_mode: Option<ScanMode>,
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, CS, CLOCKED = ExtClkd, WAKEUP = WithoutWakeupDelay> {
spi: SPI,
cs: CS,
pending_op: PendingOp,
max: PhantomData<MAX>,
cfg: InternalCfg,
clocked: PhantomData<CLOCKED>,
delay: PhantomData<DELAY>,
delay: PhantomData<WAKEUP>,
}
impl<SpiE, PinE, CS, SPI, MAX: HasChannels, CLOCKED: Clocked, DELAY>
Max116xx10Bit<SPI, CS, MAX, CLOCKED, DELAY>
pub struct Max116xx10BitEocExt<SPI, CS, EOC, CLOCKED> {
base: Max116xx10Bit<SPI, CS, CLOCKED, WithoutWakeupDelay>,
eoc: EOC,
}
pub struct Max116xx10BitCnvstEocExt<SPI, CS, EOC, CNVST, CLOCKED, WAKEUP = WithoutWakeupDelay> {
base: Max116xx10Bit<SPI, CS, CLOCKED, WAKEUP>,
eoc: EOC,
cnvst: CNVST,
}
//==================================================================================================
// Generic
//==================================================================================================
impl<SpiE, PinE, CS, SPI> Max116xx10Bit<SPI, CS, ExtClkd, WithoutWakeupDelay>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
{
/// Create a new generic MAX116xx instance.
pub fn new(spi: SPI, cs: CS, ref_mode: RefMode) -> Result<Self, Error<SpiE, PinE>> {
let mut max_dev = Max116xx10Bit {
clk_mode: CLOCKED::CLK_SEL,
ref_mode,
pub fn max11618(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
Self::new::<Max11618>(spi, cs)
}
pub fn max11619(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
Self::new::<Max11619>(spi, cs)
}
pub fn max11620(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
Self::new::<Max11620>(spi, cs)
}
pub fn max11621(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
Self::new::<Max11621>(spi, cs)
}
pub fn max11624(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
Self::new::<Max11624>(spi, cs)
}
pub fn max11625(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
Self::new::<Max11625>(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<MAX: HasChannels>(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
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<SPI, CS, ExtClkd, WithWakeupDelay> {
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<SPI, CS, ExtClkd, WithoutWakeupDelay> {
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<EOC: InputPin<Error = PinE>>(
self,
eoc: EOC,
) -> Max116xx10BitEocExt<SPI, CS, EOC, IntClkdIntTmdSerIF> {
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<EOC: InputPin<Error = PinE>>(
self,
v_ref: VoltageRefMode,
eoc: EOC,
) -> Result<Max116xx10BitEocExt<SPI, CS, EOC, IntClkdIntTmdSerIF>, 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<SpiE, PinE, CS, SPI, CLOCKED: Clocked, WAKEUP> Max116xx10Bit<SPI, CS, CLOCKED, WAKEUP>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
{
#[inline]
fn send_wrapper(&mut self, byte: u8) -> Result<(), Error<SpiE, PinE>> {
self.cs.set_low().map_err(Error::Pin)?;
@ -226,22 +412,26 @@ where
Ok(())
}
/// Set up the ADC depending on clock and reference configuration
#[inline]
pub fn setup(&mut self) -> Result<(), Error<SpiE, PinE>> {
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<SpiE, PinE>> {
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<SpiE, PinE>> {
pub fn reset(&mut self, fifo_only: bool) -> Result<(), Error<SpiE, PinE>> {
let mut reset_byte = 0b0001_0000;
if fifo_only {
reset_byte |= 1 << 3;
@ -251,7 +441,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]
@ -260,75 +450,41 @@ where
}
#[inline]
pub fn get_conversion_byte(scan_mode: ScanMode, channel_num: u8) -> Result<u8, AdcError> {
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<u8, AdcError> {
if channel_num > self.cfg.max_channels {
return Err(AdcError::InvalidChannel);
}
Ok((1 << 7) | (channel_num << 3) | ((scan_mode as u8) << 1))
}
}
impl<SpiE, PinE, SPI, CS, MAX: HasChannels>
Max116xx10Bit<SPI, CS, MAX, InternallyClockedInternallyTimedSerialInterface, WithoutWakeupDelay>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
{
#[inline]
fn request_wrapper(
/// Generic function which can be used a single result is available
/// when EOC is low
fn internal_read_single_channel<EOC: InputPin<Error = PinE>>(
&mut self,
channel_num: u8,
scan_mode: ScanMode,
op_type: PendingOp,
) -> Result<(), Error<SpiE, PinE>> {
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<SpiE, PinE>> {
self.request_wrapper(
channel_num,
ScanMode::ConvertChannelNOnce,
PendingOp::SingleChannel,
)
}
pub fn request_multiple_channels_0_to_n(&mut self, n: u8) -> Result<(), Error<SpiE, PinE>> {
self.request_wrapper(n, ScanMode::Scan0ToChannelN, PendingOp::MultiChannel)
}
pub fn request_multiple_channels_n_to_highest(
&mut self,
n: u8,
) -> Result<(), Error<SpiE, PinE>> {
self.request_wrapper(n, ScanMode::ScanChannelNToHighest, PendingOp::MultiChannel)
}
pub fn get_single_channel<I: InputPin<Error = PinE>>(
&mut self,
eoc_pin: &mut I,
eoc: &mut EOC,
) -> nb::Result<u16, Error<SpiE, PinE>> {
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))),
@ -339,8 +495,62 @@ where
}
}
impl<SpiE, PinE, SPI, CS, MAX: HasChannels>
Max116xx10Bit<SPI, CS, MAX, ExternallyClocked, WithoutWakeupDelay>
macro_rules! ext_impl {
() => {
/// Set up the ADC depending on clock and reference configuration
#[inline]
pub fn setup(&mut self) -> Result<(), Error<SpiE, PinE>> {
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<SpiE, PinE>> {
self.base.cfg.results_len = Max116xx10Bit::<SPI, CS, CLOCKED>::get_results_len(avg_res);
self.base
.send_wrapper(Max116xx10Bit::<SPI, CS, CLOCKED>::get_averaging_byte(
avg_conv, avg_res,
))
}
#[inline]
pub fn reset(&mut self, fifo_only: bool) -> Result<(), Error<SpiE, PinE>> {
let mut reset_byte = 0b0001_0000;
if fifo_only {
reset_byte |= 1 << 3;
}
self.base.send_wrapper(reset_byte)
}
};
}
impl<SpiE, PinE, CS, SPI, EOC, CLOCKED: Clocked> Max116xx10BitEocExt<SPI, CS, EOC, CLOCKED>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
{
ext_impl!();
}
impl<SpiE, PinE, CS, SPI, EOC, CNVST, CLOCKED: Clocked, DELAY>
Max116xx10BitCnvstEocExt<SPI, CS, EOC, CNVST, CLOCKED, DELAY>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
{
ext_impl!();
}
//==================================================================================================
// External SPI clock used
//==================================================================================================
/// Implementations when using the external SPI clock to time the conversions
impl<SpiE, PinE, SPI, CS> Max116xx10Bit<SPI, CS, ExtClkd, WithoutWakeupDelay>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
@ -353,13 +563,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)?;
@ -378,7 +582,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;
}
@ -411,13 +615,13 @@ where
) -> Result<(), Error<SpiE, PinE>> {
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;
}
@ -443,6 +647,299 @@ where
}
}
/// Implementations when using the external SPI clock to time the conversions but also requiring
/// a wakeup delay
impl<SpiE, PinE, SPI, CS> Max116xx10Bit<SPI, CS, ExtClkd, WithWakeupDelay>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
{
pub fn read_single_channel<DELAY: DelayUs<u8>>(
&mut self,
buf: &mut [u8],
channel_num: u8,
delay: &mut DELAY,
) -> Result<u16, Error<SpiE, PinE>> {
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<DELAY: DelayUs<u8>>(
&mut self,
buf: &mut [u8],
result_iter: &mut IterMut<u16>,
n: u8,
delay: &mut DELAY,
) -> Result<(), Error<SpiE, PinE>> {
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<DELAY: DelayUs<u8>>(
&mut self,
buf: &mut [u8],
result_iter: &mut IterMut<u16>,
n: u8,
delay: &mut DELAY,
) -> Result<(), Error<SpiE, PinE>> {
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<SpiE, PinE, SPI, CS, EOC> Max116xx10BitEocExt<SPI, CS, EOC, IntClkdIntTmdSerIF>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
EOC: InputPin<Error = PinE>,
{
#[inline]
fn request_wrapper(
&mut self,
channel_num: u8,
scan_mode: ScanMode,
) -> Result<(), Error<SpiE, PinE>> {
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<SpiE, PinE>> {
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<SpiE, PinE>> {
self.request_wrapper(channel_num, ScanMode::ScanChannelNRepeatedly)
}
pub fn request_multiple_channels_0_to_n(&mut self, n: u8) -> Result<(), Error<SpiE, PinE>> {
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<SpiE, PinE>> {
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<u16, Error<SpiE, PinE>> {
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<u16>,
) -> nb::Result<(), Error<SpiE, PinE>> {
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) {
self.base.cfg.results_len as usize
} else {
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<SpiE, PinE, SPI, CS, EOC, CNVST>
Max116xx10BitCnvstEocExt<SPI, CS, EOC, CNVST, IntClkdExtTmdCnvst, WithoutWakeupDelay>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
EOC: InputPin<Error = PinE>,
CNVST: OutputPin<Error = PinE>,
{
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<SpiE, PinE, SPI, CS, EOC, CNVST>
Max116xx10BitCnvstEocExt<SPI, CS, EOC, CNVST, IntClkdIntTmdCnvst, WithWakeupDelay>
where
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
EOC: InputPin<Error = PinE>,
CNVST: OutputPin<Error = PinE>,
{
/// 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<SpiE, PinE>> {
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<SpiE, PinE>> {
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<u16, Error<SpiE, PinE>> {
self.base.internal_read_single_channel(&mut self.eoc)
}
}
mod private {
pub trait Sealed {}
}