#![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()) as i32;
#[allow(clippy::useless_conversion)]
Ok(Voltage::from_millivolts((converted as i16).into()))
}
}
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 {
#[cfg(not(feature = "measurements"))]
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);
}
}