Several improvements #1
14
CHANGELOG.md
14
CHANGELOG.md
@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## [unreleased]
|
## [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.1]
|
## [v0.1.1]
|
||||||
|
|
||||||
- Added badges and some documentation and README tweaks
|
- Added badges and some documentation and README tweaks
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "max116xx-10bit"
|
name = "max116xx-10bit"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Driver crate for the MAX116xx 10-bit ADC devices"
|
description = "Driver crate for the MAX116xx 10-bit ADC devices"
|
||||||
|
@ -7,5 +7,6 @@ RUN apt-get --yes upgrade
|
|||||||
# tzdata is a dependency, won't install otherwise
|
# tzdata is a dependency, won't install otherwise
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
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
|
rustup component add rustfmt clippy
|
||||||
|
14
automation/Jenkinsfile
vendored
14
automation/Jenkinsfile
vendored
@ -34,18 +34,8 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
sh 'cargo check --target thumbv6m-none-eabi'
|
sh 'cargo check --target thumbv6m-none-eabi'
|
||||||
sh 'cargo check --target armv7-unknown-linux-gnueabihf'
|
sh 'cargo check --target armv7-unknown-linux-gnueabihf'
|
||||||
}
|
sh 'cargo check --target x86_64-unknown-linux-gnu'
|
||||||
}
|
sh 'cargo check --target thumbv7em-none-eabihf'
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
727
src/lib.rs
727
src/lib.rs
@ -1,13 +1,29 @@
|
|||||||
//! The crate uses basic type-level support to prevent using the ADC in a wrong way.
|
//! Type-Safe API to use the MAX116xx 10-bit ADC devices
|
||||||
//! The type-level support defaults to an externally clocked device with no wake-up delay.
|
//!
|
||||||
|
//! ## 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`]
|
||||||
//!
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! You can find an example application [here](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/src/max11619.rs)
|
//! You can find an example application [here](https://egit.irs.uni-stuttgart.de/rust/vorago-reb1/src/branch/main/examples/max11619-adc.rs)
|
||||||
//! and [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]
|
#![no_std]
|
||||||
use core::{marker::PhantomData, slice::IterMut};
|
use core::{marker::PhantomData, slice::IterMut};
|
||||||
use embedded_hal::{
|
use embedded_hal::{
|
||||||
|
blocking::delay::DelayUs,
|
||||||
blocking::spi::Transfer,
|
blocking::spi::Transfer,
|
||||||
digital::v2::{InputPin, OutputPin},
|
digital::v2::{InputPin, OutputPin},
|
||||||
spi::FullDuplex,
|
spi::FullDuplex,
|
||||||
@ -21,16 +37,29 @@ pub trait HasChannels: private::Sealed {
|
|||||||
const NUM: u8;
|
const NUM: u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Max11618;
|
struct Max11618;
|
||||||
pub struct Max11619;
|
struct Max11619;
|
||||||
pub struct Max11620;
|
struct Max11620;
|
||||||
pub struct Max11621;
|
struct Max11621;
|
||||||
pub struct Max11624;
|
struct Max11624;
|
||||||
pub struct Max11625;
|
struct Max11625;
|
||||||
|
|
||||||
pub struct WithWakeupDelay;
|
pub struct WithWakeupDelay;
|
||||||
pub struct WithoutWakeupDelay;
|
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 private::Sealed for Max11618 {}
|
||||||
impl HasChannels for Max11618 {
|
impl HasChannels for Max11618 {
|
||||||
const NUM: u8 = 4;
|
const NUM: u8 = 4;
|
||||||
@ -73,6 +102,7 @@ impl Clocked for InternallyClockedInternallyTimedCnvst {
|
|||||||
const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedCnvst;
|
const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedCnvst;
|
||||||
}
|
}
|
||||||
impl InternallyClocked for InternallyClockedInternallyTimedCnvst {}
|
impl InternallyClocked for InternallyClockedInternallyTimedCnvst {}
|
||||||
|
type IntClkdIntTmdCnvst = InternallyClockedInternallyTimedCnvst;
|
||||||
|
|
||||||
pub struct InternallyClockedExternallyTimedCnvst {}
|
pub struct InternallyClockedExternallyTimedCnvst {}
|
||||||
impl private::Sealed for InternallyClockedExternallyTimedCnvst {}
|
impl private::Sealed for InternallyClockedExternallyTimedCnvst {}
|
||||||
@ -80,6 +110,7 @@ impl Clocked for InternallyClockedExternallyTimedCnvst {
|
|||||||
const CLK_SEL: ClockMode = ClockMode::InternalClockExternallyTimedCnvst;
|
const CLK_SEL: ClockMode = ClockMode::InternalClockExternallyTimedCnvst;
|
||||||
}
|
}
|
||||||
impl InternallyClocked for InternallyClockedExternallyTimedCnvst {}
|
impl InternallyClocked for InternallyClockedExternallyTimedCnvst {}
|
||||||
|
type IntClkdExtTmdCnvst = InternallyClockedExternallyTimedCnvst;
|
||||||
|
|
||||||
pub struct InternallyClockedInternallyTimedSerialInterface {}
|
pub struct InternallyClockedInternallyTimedSerialInterface {}
|
||||||
impl private::Sealed for InternallyClockedInternallyTimedSerialInterface {}
|
impl private::Sealed for InternallyClockedInternallyTimedSerialInterface {}
|
||||||
@ -87,40 +118,38 @@ impl Clocked for InternallyClockedInternallyTimedSerialInterface {
|
|||||||
const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedSerialInterface;
|
const CLK_SEL: ClockMode = ClockMode::InternalClockInternallyTimedSerialInterface;
|
||||||
}
|
}
|
||||||
impl InternallyClocked for InternallyClockedInternallyTimedSerialInterface {}
|
impl InternallyClocked for InternallyClockedInternallyTimedSerialInterface {}
|
||||||
|
type IntClkdIntTmdSerIF = InternallyClockedInternallyTimedSerialInterface;
|
||||||
|
|
||||||
pub struct ExternallyClocked {}
|
pub struct ExternallyClocked {}
|
||||||
impl private::Sealed for ExternallyClocked {}
|
impl private::Sealed for ExternallyClocked {}
|
||||||
impl Clocked for ExternallyClocked {
|
impl Clocked for ExternallyClocked {
|
||||||
const CLK_SEL: ClockMode = ClockMode::ExternalClockExternallyTimedSclk;
|
const CLK_SEL: ClockMode = ClockMode::ExternalClockExternallyTimedSclk;
|
||||||
}
|
}
|
||||||
|
type ExtClkd = ExternallyClocked;
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Definitions
|
// Definitions
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum PendingOp {
|
|
||||||
None,
|
|
||||||
SingleChannel,
|
|
||||||
MultiChannel,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clock modes for the MAX116XX devices
|
/// Clock modes for the MAX116XX devices
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum ClockMode {
|
pub enum ClockMode {
|
||||||
|
/// Internally timed, CNVST only needs to be pulsed for 40ns.
|
||||||
/// CNVST Configuration: CNVST active low
|
/// CNVST Configuration: CNVST active low
|
||||||
InternalClockInternallyTimedCnvst = 0b00,
|
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,
|
InternalClockExternallyTimedCnvst = 0b01,
|
||||||
|
/// Start conversions using the serial interface instead of CNVST.
|
||||||
/// Default mode at power-up. CNVST Configuration: AIN15/AIN11/AIN7
|
/// Default mode at power-up. CNVST Configuration: AIN15/AIN11/AIN7
|
||||||
/// Start conversions using the serial interface instead of CNVST
|
|
||||||
InternalClockInternallyTimedSerialInterface = 0b10,
|
InternalClockInternallyTimedSerialInterface = 0b10,
|
||||||
|
/// Use the SPI clock as the conversion clock
|
||||||
ExternalClockExternallyTimedSclk = 0b11,
|
ExternalClockExternallyTimedSclk = 0b11,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Voltage reference modes
|
/// Voltage reference modes
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum RefMode {
|
pub enum VoltageRefMode {
|
||||||
/// Auto-Shutdown is on, wake-up delay of 65 us
|
/// Auto-Shutdown is on, wake-up delay of 65 us
|
||||||
InternalRefWithWakeupDelay = 0b00,
|
InternalRefWithWakeupDelay = 0b00,
|
||||||
ExternalSingleEndedNoWakeupDelay = 0b01,
|
ExternalSingleEndedNoWakeupDelay = 0b01,
|
||||||
@ -138,6 +167,7 @@ pub enum AveragingConversions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies the number of returned result in single scan mode
|
/// Specifies the number of returned result in single scan mode
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum AveragingResults {
|
pub enum AveragingResults {
|
||||||
FourResults = 0b00,
|
FourResults = 0b00,
|
||||||
EightResults = 0b01,
|
EightResults = 0b01,
|
||||||
@ -145,18 +175,21 @@ pub enum AveragingResults {
|
|||||||
SixteenResults = 0b11,
|
SixteenResults = 0b11,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum ScanMode {
|
pub enum ScanMode {
|
||||||
Scan0ToChannelN = 0b00,
|
Scan0ToChannelN = 0b00,
|
||||||
ScanChannelNToHighest = 0b01,
|
ScanChannelNToHighest = 0b01,
|
||||||
ScanChnanelNRepeatedly = 0b10,
|
ScanChannelNRepeatedly = 0b10,
|
||||||
ConvertChannelNOnce = 0b11,
|
ConvertChannelNOnce = 0b11,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AdcError {
|
pub enum AdcError {
|
||||||
InvalidChannel,
|
InvalidChannel,
|
||||||
|
InvalidRefMode,
|
||||||
CmdBufTooSmall,
|
CmdBufTooSmall,
|
||||||
ResulBufTooSmall,
|
ResulBufTooSmall,
|
||||||
|
/// Other pending operation (possible of other type)
|
||||||
PendingOperation,
|
PendingOperation,
|
||||||
NoPendingOperation,
|
NoPendingOperation,
|
||||||
InvalidClockMode,
|
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<
|
pub struct Max116xx10Bit<SPI, CS, CLOCKED = ExtClkd, WAKEUP = WithoutWakeupDelay> {
|
||||||
SPI,
|
|
||||||
CS,
|
|
||||||
MAX: HasChannels,
|
|
||||||
CLOCKED = ExternallyClocked,
|
|
||||||
DELAY = WithoutWakeupDelay,
|
|
||||||
> {
|
|
||||||
clk_mode: ClockMode,
|
|
||||||
ref_mode: RefMode,
|
|
||||||
spi: SPI,
|
spi: SPI,
|
||||||
cs: CS,
|
cs: CS,
|
||||||
pending_op: PendingOp,
|
cfg: InternalCfg,
|
||||||
max: PhantomData<MAX>,
|
|
||||||
clocked: PhantomData<CLOCKED>,
|
clocked: PhantomData<CLOCKED>,
|
||||||
delay: PhantomData<DELAY>,
|
delay: PhantomData<WAKEUP>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SpiE, PinE, CS, SPI, MAX: HasChannels, CLOCKED: Clocked, DELAY>
|
pub struct Max116xx10BitEocExt<SPI, CS, EOC, CLOCKED> {
|
||||||
Max116xx10Bit<SPI, CS, MAX, CLOCKED, DELAY>
|
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
|
where
|
||||||
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
||||||
CS: OutputPin<Error = PinE>,
|
CS: OutputPin<Error = PinE>,
|
||||||
{
|
{
|
||||||
/// Create a new generic MAX116xx instance.
|
pub fn max11618(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
|
||||||
pub fn new(spi: SPI, cs: CS, ref_mode: RefMode) -> Result<Self, Error<SpiE, PinE>> {
|
Self::new::<Max11618>(spi, cs)
|
||||||
let mut max_dev = Max116xx10Bit {
|
}
|
||||||
clk_mode: CLOCKED::CLK_SEL,
|
pub fn max11619(spi: SPI, cs: CS) -> Result<Self, Error<SpiE, PinE>> {
|
||||||
ref_mode,
|
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,
|
spi,
|
||||||
cs,
|
cs,
|
||||||
pending_op: PendingOp::None,
|
cfg: InternalCfg {
|
||||||
max: PhantomData,
|
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,
|
delay: PhantomData,
|
||||||
clocked: PhantomData,
|
clocked: PhantomData,
|
||||||
};
|
};
|
||||||
max_dev.setup()?;
|
|
||||||
Ok(max_dev)
|
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]
|
#[inline]
|
||||||
fn send_wrapper(&mut self, byte: u8) -> Result<(), Error<SpiE, PinE>> {
|
fn send_wrapper(&mut self, byte: u8) -> Result<(), Error<SpiE, PinE>> {
|
||||||
self.cs.set_low().map_err(Error::Pin)?;
|
self.cs.set_low().map_err(Error::Pin)?;
|
||||||
@ -226,22 +412,26 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set up the ADC depending on clock and reference configuration
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn setup(&mut self) -> Result<(), Error<SpiE, PinE>> {
|
pub fn setup(&mut self) -> Result<(), Error<SpiE, PinE>> {
|
||||||
self.send_wrapper(self.get_setup_byte())
|
self.send_wrapper(self.get_setup_byte())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set up the Averaging register. This sets the AVGON, NAVG1, NAVG0, NSCAN1 and NSCAN0
|
||||||
|
/// bits accordingly
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn averaging(
|
pub fn averaging(
|
||||||
&mut self,
|
&mut self,
|
||||||
avg_conv: AveragingConversions,
|
avg_conv: AveragingConversions,
|
||||||
avg_res: AveragingResults,
|
avg_res: AveragingResults,
|
||||||
) -> Result<(), Error<SpiE, PinE>> {
|
) -> 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))
|
self.send_wrapper(Self::get_averaging_byte(avg_conv, avg_res))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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;
|
let mut reset_byte = 0b0001_0000;
|
||||||
if fifo_only {
|
if fifo_only {
|
||||||
reset_byte |= 1 << 3;
|
reset_byte |= 1 << 3;
|
||||||
@ -251,7 +441,7 @@ where
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_setup_byte(&self) -> u8 {
|
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]
|
#[inline]
|
||||||
@ -260,75 +450,41 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_conversion_byte(scan_mode: ScanMode, channel_num: u8) -> Result<u8, AdcError> {
|
pub fn get_results_len(avg_res: AveragingResults) -> u8 {
|
||||||
if channel_num > MAX::NUM {
|
(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);
|
return Err(AdcError::InvalidChannel);
|
||||||
}
|
}
|
||||||
Ok((1 << 7) | (channel_num << 3) | ((scan_mode as u8) << 1))
|
Ok((1 << 7) | (channel_num << 3) | ((scan_mode as u8) << 1))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl<SpiE, PinE, SPI, CS, MAX: HasChannels>
|
/// Generic function which can be used a single result is available
|
||||||
Max116xx10Bit<SPI, CS, MAX, InternallyClockedInternallyTimedSerialInterface, WithoutWakeupDelay>
|
/// when EOC is low
|
||||||
where
|
fn internal_read_single_channel<EOC: InputPin<Error = PinE>>(
|
||||||
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
|
||||||
CS: OutputPin<Error = PinE>,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn request_wrapper(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_num: u8,
|
eoc: &mut EOC,
|
||||||
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,
|
|
||||||
) -> nb::Result<u16, Error<SpiE, PinE>> {
|
) -> 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)));
|
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() {
|
if eoc.is_low().map_err(Error::Pin)? {
|
||||||
Ok(low) => low,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(nb::Error::Other(Error::Pin(e)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if is_low {
|
|
||||||
let mut dummy_cmd: [u8; 2] = [0; 2];
|
let mut dummy_cmd: [u8; 2] = [0; 2];
|
||||||
self.cs.set_low().map_err(Error::Pin)?;
|
self.cs.set_low().map_err(Error::Pin)?;
|
||||||
let transfer_result = self.spi.transfer(&mut dummy_cmd);
|
let transfer_result = self.spi.transfer(&mut dummy_cmd);
|
||||||
self.cs.set_high().map_err(Error::Pin)?;
|
self.cs.set_high().map_err(Error::Pin)?;
|
||||||
match transfer_result {
|
match transfer_result {
|
||||||
Ok(reply) => {
|
Ok(reply) => {
|
||||||
self.pending_op = PendingOp::None;
|
self.cfg.pending_scan_mode = None;
|
||||||
Ok(((reply[0] as u16) << 6) | (reply[1] as u16 >> 2))
|
Ok(((reply[0] as u16) << 6) | (reply[1] as u16 >> 2))
|
||||||
}
|
}
|
||||||
Err(e) => Err(nb::Error::Other(Error::Spi(e))),
|
Err(e) => Err(nb::Error::Other(Error::Spi(e))),
|
||||||
@ -339,8 +495,62 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SpiE, PinE, SPI, CS, MAX: HasChannels>
|
macro_rules! ext_impl {
|
||||||
Max116xx10Bit<SPI, CS, MAX, ExternallyClocked, WithoutWakeupDelay>
|
() => {
|
||||||
|
/// 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
|
where
|
||||||
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
||||||
CS: OutputPin<Error = PinE>,
|
CS: OutputPin<Error = PinE>,
|
||||||
@ -353,13 +563,7 @@ where
|
|||||||
if buf.len() < 3 {
|
if buf.len() < 3 {
|
||||||
return Err(Error::Adc(AdcError::CmdBufTooSmall));
|
return Err(Error::Adc(AdcError::CmdBufTooSmall));
|
||||||
}
|
}
|
||||||
|
buf[0] = self.get_conversion_byte(ScanMode::ConvertChannelNOnce, channel_num)?;
|
||||||
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[1] = 0x00;
|
||||||
buf[2] = 0x00;
|
buf[2] = 0x00;
|
||||||
self.cs.set_low().map_err(Error::Pin)?;
|
self.cs.set_low().map_err(Error::Pin)?;
|
||||||
@ -378,7 +582,7 @@ where
|
|||||||
let mut next_byte: &mut u8;
|
let mut next_byte: &mut u8;
|
||||||
for idx in 0..n + 1 {
|
for idx in 0..n + 1 {
|
||||||
next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?;
|
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 = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?;
|
||||||
*next_byte = 0x00;
|
*next_byte = 0x00;
|
||||||
}
|
}
|
||||||
@ -411,13 +615,13 @@ where
|
|||||||
) -> Result<(), Error<SpiE, PinE>> {
|
) -> Result<(), Error<SpiE, PinE>> {
|
||||||
let mut iter = buf.iter_mut();
|
let mut iter = buf.iter_mut();
|
||||||
let mut next_byte: &mut u8;
|
let mut next_byte: &mut u8;
|
||||||
if n > MAX::NUM - 1 {
|
if n > self.cfg.max_channels - 1 {
|
||||||
return Err(Error::Adc(AdcError::InvalidChannel));
|
return Err(Error::Adc(AdcError::InvalidChannel));
|
||||||
}
|
}
|
||||||
let conversions = MAX::NUM - n;
|
let conversions = self.cfg.max_channels - n;
|
||||||
for idx in n..MAX::NUM {
|
for idx in n..self.cfg.max_channels {
|
||||||
next_byte = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?;
|
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 = iter.next().ok_or(Error::Adc(AdcError::CmdBufTooSmall))?;
|
||||||
*next_byte = 0x00;
|
*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) {
|
||||||
|
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<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 {
|
mod private {
|
||||||
pub trait Sealed {}
|
pub trait Sealed {}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user