First basic device crate done
This commit is contained in:
parent
7e987f3407
commit
54286e67c2
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
@ -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
|
10
Cargo.toml
10
Cargo.toml
@ -1,8 +1,16 @@
|
||||
[package]
|
||||
name = "max116xx-10bit"
|
||||
version = "0.1.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
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"
|
||||
|
2
NOTICE
2
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.
|
25
README.md
Normal file
25
README.md
Normal file
@ -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]().
|
432
src/lib.rs
432
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<SpiE> {
|
||||
Adc(AdcError),
|
||||
Spi(SpiE),
|
||||
}
|
||||
|
||||
pub enum ErrorWithEoc<SpiE, PinE> {
|
||||
Error(Error<SpiE>),
|
||||
Pin(PinE),
|
||||
}
|
||||
|
||||
impl<SpiE, PinE> From<AdcError> for ErrorWithEoc<SpiE, PinE> {
|
||||
fn from(other: AdcError) -> Self {
|
||||
ErrorWithEoc::Error(Error::Adc(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<SpiE> From<AdcError> for Error<SpiE> {
|
||||
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<MAX>,
|
||||
clocked: PhantomData<CLOCKED>,
|
||||
delay: PhantomData<DELAY>,
|
||||
}
|
||||
|
||||
impl<SpiE, SPI, MAX: HasChannels, CLOCKED: Clocked, DELAY> Max116xx10Bit<SPI, MAX, CLOCKED, DELAY>
|
||||
where
|
||||
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
||||
{
|
||||
/// Create a new generic MAX116xx instance.
|
||||
pub fn new(spi: SPI, ref_mode: RefMode) -> Result<Self, Error<SpiE>> {
|
||||
let mut max_dev: Max116xx10Bit<SPI, MAX, CLOCKED, DELAY> = 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<SpiE>> {
|
||||
nb::block!(self.spi.send(byte)).map_err(|e| Error::Spi(e))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn setup(&mut self) -> Result<(), Error<SpiE>> {
|
||||
self.send_wrapper(self.get_setup_byte())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn averaging(
|
||||
&mut self,
|
||||
avg_conv: AveragingConversions,
|
||||
avg_res: AveragingResults,
|
||||
) -> Result<(), Error<SpiE>> {
|
||||
self.send_wrapper(Self::get_averaging_byte(avg_conv, avg_res))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_adc(&mut self, fifo_only: bool) -> Result<(), Error<SpiE>> {
|
||||
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<u8, AdcError> {
|
||||
if channel_num > MAX::NUM {
|
||||
return Err(AdcError::InvalidChannel);
|
||||
}
|
||||
Ok((1 << 7) | (channel_num << 3) | ((scan_mode as u8) << 1))
|
||||
}
|
||||
}
|
||||
impl<SpiE, SPI, MAX: HasChannels>
|
||||
Max116xx10Bit<SPI, MAX, InternallyClockedInternallyTimedSerialInterface, WithoutWakeupDelay>
|
||||
where
|
||||
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
||||
{
|
||||
#[inline]
|
||||
fn request_wrapper(
|
||||
&mut self,
|
||||
channel_num: u8,
|
||||
scan_mode: ScanMode,
|
||||
op_type: PendingOp,
|
||||
) -> Result<(), Error<SpiE>> {
|
||||
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<SpiE>> {
|
||||
self.request_wrapper(
|
||||
channel_num,
|
||||
ScanMode::ConvertChannelNOnce,
|
||||
PendingOp::SingleChannel,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn request_multiple_channels_0_to_n(&mut self, n: u8) -> Result<(), Error<SpiE>> {
|
||||
self.request_wrapper(n, ScanMode::Scan0ToChannelN, PendingOp::MultiChannel)
|
||||
}
|
||||
|
||||
pub fn request_multiple_channels_n_to_highest(&mut self, n: u8) -> Result<(), Error<SpiE>> {
|
||||
self.request_wrapper(n, ScanMode::ScanChannelNToHighest, PendingOp::MultiChannel)
|
||||
}
|
||||
|
||||
pub fn get_single_channel<I: InputPin>(
|
||||
&mut self,
|
||||
eoc_pin: &mut I,
|
||||
) -> nb::Result<u16, ErrorWithEoc<SpiE, I::Error>> {
|
||||
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<SpiE, SPI, MAX: HasChannels> Max116xx10Bit<SPI, MAX, ExternallyClocked, WithoutWakeupDelay>
|
||||
where
|
||||
SPI: Transfer<u8, Error = SpiE> + FullDuplex<u8, Error = SpiE>,
|
||||
{
|
||||
pub fn read_single_channel(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
channel_num: u8,
|
||||
) -> Result<u16, Error<SpiE>> {
|
||||
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<u16>,
|
||||
n: u8,
|
||||
) -> Result<(), Error<SpiE>> {
|
||||
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<u16>,
|
||||
n: u8,
|
||||
) -> Result<(), Error<SpiE>> {
|
||||
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 {}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user