#![cfg_attr(not(test), no_std)]
#![deny(missing_docs)]
#[macro_use]
extern crate bitflags;
use byteorder::{BigEndian, ByteOrder};
use embedded_hal::blocking::{
delay::DelayMs,
i2c::{Read, Write, WriteRead},
};
#[cfg(feature = "measurements")]
extern crate measurements;
#[cfg(feature = "measurements")]
use measurements::voltage::Voltage;
#[derive(Debug)]
pub enum Error<E> {
I2c(E),
VoltageTooHigh,
VoltageTooLow,
NotInitialized,
NotReady,
}
bitflags! {
struct ConfigRegister: u8 {
const NOT_READY = 0b10000000;
const MODE = 0b00010000;
const SAMPLE_RATE_H = 0b00001000;
const SAMPLE_RATE_L = 0b00000100;
const GAIN_H = 0b00000010;
const GAIN_L = 0b00000001;
}
}
impl ConfigRegister {
fn is_ready(&self) -> bool {
!self.contains(ConfigRegister::NOT_READY)
}
}
const REF_MILLIVOLTS: i16 = 2048;
pub trait ConversionMode {
fn bits(&self) -> u8;
}
pub struct OneShotMode;
impl ConversionMode for OneShotMode {
fn bits(&self) -> u8 {
0b00000000
}
}
pub struct ContinuousMode;
impl ConversionMode for ContinuousMode {
fn bits(&self) -> u8 {
0b00010000
}
}
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
pub enum Resolution {
Bits16Sps15 = 0b00001000,
Bits14Sps60 = 0b00000100,
Bits12Sps240 = 0b00000000,
}
impl Resolution {
pub fn bits(&self) -> u8 {
*self as u8
}
pub fn res_bits(&self) -> u8 {
match *self {
Resolution::Bits16Sps15 => 16,
Resolution::Bits14Sps60 => 14,
Resolution::Bits12Sps240 => 12,
}
}
pub fn max(&self) -> i16 {
match *self {
Resolution::Bits16Sps15 => 32767,
Resolution::Bits14Sps60 => 8191,
Resolution::Bits12Sps240 => 2047,
}
}
pub fn min(&self) -> i16 {
match *self {
Resolution::Bits16Sps15 => -32768,
Resolution::Bits14Sps60 => -8192,
Resolution::Bits12Sps240 => -2048,
}
}
}
impl Default for Resolution {
fn default() -> Self {
Resolution::Bits12Sps240
}
}
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
pub enum Gain {
Gain1 = 0b00000000,
Gain2 = 0b00000001,
Gain4 = 0b00000010,
Gain8 = 0b00000011,
}
impl Gain {
pub fn bits(&self) -> u8 {
*self as u8
}
}
impl Default for Gain {
fn default() -> Self {
Gain::Gain1
}
}
#[derive(Copy, Clone, Debug)]
pub enum Channel {
Channel1 = 0b0000_0000,
#[cfg(any(feature = "dual_channel", feature = "quad_channel", doc))]
Channel2 = 0b0010_0000,
#[cfg(any(feature = "quad_channel", doc))]
Channel3 = 0b0100_0000,
#[cfg(any(feature = "quad_channel", doc))]
Channel4 = 0b0110_0000,
}
impl Default for Channel {
fn default() -> Self {
Self::Channel1
}
}
impl Channel {
pub fn bits(&self) -> u8 {
*self as u8
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct Config {
pub resolution: Resolution,
pub gain: Gain,
pub channel: Channel,
}
impl Config {
pub fn with_resolution(&self, resolution: Resolution) -> Self {
Config {
resolution,
gain: self.gain,
channel: self.channel,
}
}
pub fn with_gain(&self, gain: Gain) -> Self {
Config {
resolution: self.resolution,
gain,
channel: self.channel,
}
}
#[cfg(any(feature = "dual_channel", feature = "quad_channel", doc))]
pub fn with_channel(&self, channel: Channel) -> Self {
Config {
resolution: self.resolution,
gain: self.gain,
channel,
}
}
fn bits(&self) -> u8 {
self.channel.bits() | self.resolution.bits() | self.gain.bits()
}
}
#[cfg(not(feature = "measurements"))]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Voltage {
millivolts: i16,
}
#[cfg(not(feature = "measurements"))]
impl Voltage {
pub fn from_millivolts(millivolts: i16) -> Self {
Self { millivolts }
}
pub fn as_millivolts(&self) -> i16 {
self.millivolts
}
pub fn as_volts(&self) -> f32 {
self.millivolts as f32 / 1000.0
}
}
#[derive(Debug, Default)]
pub struct MCP3425<I2C, D, M> {
i2c: I2C,
address: u8,
delay: D,
mode: M,
config: Option<Config>,
}
impl<I2C, D, E, M> MCP3425<I2C, D, M>
where
I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
D: DelayMs<u8>,
M: ConversionMode,
{
pub fn new(i2c: I2C, address: u8, delay: D, mode: M) -> Self {
MCP3425 {
i2c,
address,
delay,
mode,
config: None,
}
}
fn read_i16_and_config(&mut self) -> Result<(i16, ConfigRegister), Error<E>> {
let mut buf = [0, 0, 0];
self.i2c.read(self.address, &mut buf).map_err(Error::I2c)?;
let measurement = BigEndian::read_i16(&buf[0..2]);
let config_reg = ConfigRegister::from_bits_truncate(buf[2]);
Ok((measurement, config_reg))
}
fn calculate_voltage(
&self,
measurement: i16,
resolution: &Resolution,
) -> Result<Voltage, Error<E>> {
if measurement == resolution.max() {
return Err(Error::VoltageTooHigh);
} else if measurement == resolution.min() {
return Err(Error::VoltageTooLow);
}
let converted =
measurement as i32 * (REF_MILLIVOLTS * 2) as i32 / (1 << resolution.res_bits());
#[allow(clippy::useless_conversion)]
Ok(Voltage::from_millivolts((converted as i16).into()))
}
pub fn destroy(self) -> I2C {
self.i2c
}
}
impl<I2C, D, E> MCP3425<I2C, D, OneShotMode>
where
I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
D: DelayMs<u8>,
{
pub fn oneshot(i2c: I2C, address: u8, delay: D) -> Self {
MCP3425 {
i2c,
address,
delay,
mode: OneShotMode,
config: None,
}
}
pub fn into_continuous(self) -> MCP3425<I2C, D, ContinuousMode> {
MCP3425::continuous(self.i2c, self.address, self.delay)
}
pub fn measure(&mut self, config: &Config) -> Result<Voltage, Error<E>> {
let command = ConfigRegister::NOT_READY.bits() | self.mode.bits() | config.bits();
self.i2c
.write(self.address, &[command])
.map_err(Error::I2c)?;
let sleep_ms = match config.resolution {
Resolution::Bits12Sps240 => 4,
Resolution::Bits14Sps60 => 15,
Resolution::Bits16Sps15 => 57,
};
self.delay.delay_ms(sleep_ms + 2);
let (measurement, config_reg) = self.read_i16_and_config()?;
if !config_reg.is_ready() {
return Err(Error::NotReady);
}
let voltage = self.calculate_voltage(measurement, &config.resolution)?;
Ok(voltage)
}
}
impl<I2C, D, E> MCP3425<I2C, D, ContinuousMode>
where
I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
D: DelayMs<u8>,
{
pub fn continuous(i2c: I2C, address: u8, delay: D) -> Self {
MCP3425 {
i2c,
address,
delay,
mode: ContinuousMode,
config: None,
}
}
pub fn into_oneshot(self) -> MCP3425<I2C, D, OneShotMode> {
MCP3425::oneshot(self.i2c, self.address, self.delay)
}
pub fn set_config(&mut self, config: &Config) -> Result<(), Error<E>> {
let command = self.mode.bits() | config.bits();
self.i2c
.write(self.address, &[command])
.map(|()| self.config = Some(*config))
.map_err(Error::I2c)?;
let sleep_ms = match config.resolution {
Resolution::Bits12Sps240 => 4,
Resolution::Bits14Sps60 => 15,
Resolution::Bits16Sps15 => 57,
};
self.delay.delay_ms(sleep_ms);
let mut buf = [0, 0, 0];
loop {
self.i2c.read(self.address, &mut buf).map_err(Error::I2c)?;
if (buf[2] & ConfigRegister::NOT_READY.bits()) == ConfigRegister::NOT_READY.bits() {
self.delay.delay_ms(1);
} else {
break;
}
}
Ok(())
}
pub fn read_measurement(&mut self) -> Result<Voltage, Error<E>> {
let config = self.config.ok_or(Error::NotInitialized)?;
let (measurement, config_reg) = self.read_i16_and_config()?;
let voltage = self.calculate_voltage(measurement, &config.resolution)?;
if config_reg.is_ready() {
Ok(voltage)
} else {
Err(Error::NotReady)
}
}
}
#[cfg(test)]
mod tests {
use embedded_hal_mock::eh0::{
delay::NoopDelay,
i2c::{Mock as I2cMock, Transaction},
};
use rstest::rstest;
use super::*;
#[test]
#[cfg(not(feature = "measurements"))]
fn test_voltage_wrapper() {
let a = Voltage::from_millivolts(2500);
assert_eq!(a.as_millivolts(), 2500i16);
assert_eq!(a.as_volts(), 2.5f32);
let b = Voltage::from_millivolts(-100);
assert_eq!(b.as_millivolts(), -100i16);
assert_eq!(b.as_volts(), -0.1f32);
}
#[test]
fn test_instantiation_oneshot() {
let expectations = [];
let dev = I2cMock::new(&expectations);
let adc = MCP3425::oneshot(dev, 0x42, NoopDelay);
adc.destroy().done();
}
#[test]
fn test_instantiation_continuous() {
let expectations = [];
let dev = I2cMock::new(&expectations);
let adc = MCP3425::continuous(dev, 0x42, NoopDelay);
adc.destroy().done();
}
#[rstest]
#[case(0b00000111, 0b11111110, 2046)] #[case(0b00000000, 0b00000111, 7)]
#[case(0b00000000, 0b00000000, 0)]
#[case(0b11111111, 0b11111111, -1)]
#[case(0b11111000, 0b00000001, -2047)] #[cfg(not(feature = "measurements"))]
fn test_read_voltage_oneshot(
#[case] byte0: u8,
#[case] byte1: u8,
#[case] expected_millivolts: i16,
) {
let addr = 0x42;
let expectations = [
Transaction::write(addr, vec![0b10000000]),
Transaction::read(addr, vec![byte0, byte1, 0b00000000]),
];
let dev = I2cMock::new(&expectations);
let mut adc = MCP3425::oneshot(dev, addr, NoopDelay);
let voltage = adc.measure(&Config::default()).expect("Measuring failed");
assert_eq!(voltage.as_millivolts(), expected_millivolts);
adc.destroy().done();
}
#[rstest]
#[case(Resolution::Bits12Sps240, 0b10000000, 0b11111000, 0b00000111)] #[case(Resolution::Bits14Sps60, 0b10000100, 0b11100000, 0b00011111)] #[case(Resolution::Bits16Sps15, 0b10001000, 0b10000000, 0b01111111)] fn test_saturation(
#[case] resolution: Resolution,
#[case] config: u8,
#[case] upper_byte_negative: u8,
#[case] upper_byte_positive: u8,
) {
let addr = 0x42;
let expectations = [
Transaction::write(addr, vec![config]),
Transaction::read(addr, vec![upper_byte_negative, 0b00000000, 0b00000000]),
Transaction::write(addr, vec![config]),
Transaction::read(addr, vec![upper_byte_positive, 0b11111111, 0b00000000]),
];
let dev = I2cMock::new(&expectations);
let mut adc = MCP3425::oneshot(dev, addr, NoopDelay);
let err_negative = adc
.measure(&Config::default().with_resolution(resolution))
.unwrap_err();
assert!(
matches!(err_negative, Error::VoltageTooLow),
"{:?}",
err_negative
);
let err_positive = adc
.measure(&Config::default().with_resolution(resolution))
.unwrap_err();
assert!(
matches!(err_positive, Error::VoltageTooHigh),
"{:?}",
err_positive
);
adc.destroy().done();
}
#[rstest]
fn test_not_ready() {
let addr = 0x42;
let default_config = 0b10000000;
let expectations = [
Transaction::write(addr, vec![default_config]),
Transaction::read(addr, vec![0b00000000, 0b00000000, 0b10000000]),
];
let dev = I2cMock::new(&expectations);
let mut adc = MCP3425::oneshot(dev, addr, NoopDelay);
let err = adc.measure(&Config::default()).unwrap_err();
assert!(matches!(err, Error::NotReady), "{:?}", err);
adc.destroy().done();
}
#[rstest]
#[case(Resolution::Bits14Sps60, Gain::Gain8, 0b10000111)]
#[cfg(not(feature = "measurements"))]
fn test_config(#[case] resolution: Resolution, #[case] gain: Gain, #[case] expected: u8) {
let addr = 0x42;
let expectations = [
Transaction::write(addr, vec![expected]),
Transaction::read(addr, vec![0b00000000, 0b00000000, 0b00000000]),
];
let dev = I2cMock::new(&expectations);
let mut adc = MCP3425::oneshot(dev, addr, NoopDelay);
let voltage = adc
.measure(
&Config::default()
.with_resolution(resolution)
.with_gain(gain),
)
.expect("Measuring failed");
assert_eq!(voltage.as_volts(), 0.0);
assert_eq!(voltage.as_millivolts(), 0);
adc.destroy().done();
}
}