embedded_devices/devices/microchip/
mcp3208.rs

1//! The Microchip Technology Inc. MCP3208 device is a successive approximation 12-bit Analog-
2//! to-Digital (A/D) Converter with on-board sample and hold circuitry. The MCP3208 is programmable
3//! to provide four pseudo-differential input pairs or eight single-ended inputs.
4//!
5//! Differential Nonlinearity (DNL) is specified at ±1 LSB, while Integral Nonlinearity (INL) is
6//! offered in ±1 LSB (MCP3208-B) and ±2 LSB (MCP3208-C) versions. Communication with the devices
7//! is accomplished using a simple serial interface compatible with the SPI protocol. The devices
8//! are capable of conversion rates of up to 100 ksps. The MCP3208 devices operate over a
9//! broad voltage range (2.7V - 5.5V). Low current design permits operation with typical standby
10//! and active currents of only 500 nA and 320 μA, respectively.
11//!
12//! ## Usage (sync)
13//!
14//! ```rust
15//! # #[cfg(feature = "sync")] mod test {
16//! # fn test<I>(mut spi: I) -> Result<(), I::Error>
17//! # where
18//! #   I: embedded_hal::spi::SpiDevice,
19//! # {
20//! use embedded_devices::devices::microchip::mcp3208::{MCP3208Sync, InputChannel};
21//! use uom::si::electric_potential::{volt, millivolt};
22//! use uom::si::f64::ElectricPotential;
23//!
24//! let mut mcp3208 = MCP3208Sync::new_spi(
25//!     spi,
26//!     // 2.5V reference
27//!     ElectricPotential::new::<volt>(2.5),
28//! );
29//!
30//! let value = mcp3208.convert(InputChannel::Single0)?;
31//! let voltage = value.get::<millivolt>();
32//! println!("V_in at channel 0: {:?}mV", value);
33//! # Ok(())
34//! # }
35//! # }
36//! ```
37//!
38//! ## Usage (async)
39//!
40//! ```rust
41//! # #[cfg(feature = "async")] mod test {
42//! # async fn test<I>(mut spi: I) -> Result<(), I::Error>
43//! # where
44//! #   I: embedded_hal_async::spi::SpiDevice,
45//! # {
46//! use embedded_devices::devices::microchip::mcp3208::{MCP3208Async, InputChannel};
47//! use uom::si::electric_potential::{volt, millivolt};
48//! use uom::si::f64::ElectricPotential;
49//!
50//! let mut mcp3208 = MCP3208Async::new_spi(
51//!     spi,
52//!     // 2.5V reference
53//!     ElectricPotential::new::<volt>(2.5),
54//! );
55//!
56//! let value = mcp3208.convert(InputChannel::Single0).await?;
57//! let voltage = value.get::<millivolt>();
58//! println!("V_in at channel 0: {:?}mV", value);
59//! # Ok(())
60//! # }
61//! # }
62//! ```
63
64use uom::si::{electric_potential::volt, f64::ElectricPotential};
65
66/// The ADC input channel
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68#[derive(Copy, Clone, PartialEq, Eq, Debug)]
69pub enum InputChannel {
70    /// Single channel 0
71    Single0 = 0b1000,
72    /// Single channel 1
73    Single1 = 0b1001,
74    /// Single channel 2
75    Single2 = 0b1010,
76    /// Single channel 3
77    Single3 = 0b1011,
78    /// Single channel 4
79    Single4 = 0b1100,
80    /// Single channel 5
81    Single5 = 0b1101,
82    /// Single channel 6
83    Single6 = 0b1110,
84    /// Single channel 7
85    Single7 = 0b1111,
86    /// Pseudo-differential channel (IN+ = CH0, IN- = CH1)
87    Diff01 = 0b0000,
88    /// Pseudo-differential channel (IN+ = CH1, IN- = CH0)
89    Diff10 = 0b0001,
90    /// Pseudo-differential channel (IN+ = CH2, IN- = CH3)
91    Diff23 = 0b0010,
92    /// Pseudo-differential channel (IN+ = CH3, IN- = CH2)
93    Diff32 = 0b0011,
94    /// Pseudo-differential channel (IN+ = CH4, IN- = CH5)
95    Diff45 = 0b0100,
96    /// Pseudo-differential channel (IN+ = CH5, IN- = CH4)
97    Diff54 = 0b0101,
98    /// Pseudo-differential channel (IN+ = CH6, IN- = CH7)
99    Diff67 = 0b0110,
100    /// Pseudo-differential channel (IN+ = CH7, IN- = CH6)
101    Diff76 = 0b0111,
102}
103
104/// The MCP3208 is a 12-bit ADC with on-board sample and hold circuitry. It is programmable
105/// to provide four pseudo-differential input pairs or eight single-ended inputs.
106///
107/// For a full description and usage examples, refer to the [module documentation](self).
108#[maybe_async_cfg::maybe(sync(feature = "sync"), async(feature = "async"))]
109pub struct MCP3208<I> {
110    /// The interface to communicate with the device
111    interface: I,
112    /// The reference voltage
113    reference_voltage: ElectricPotential,
114}
115
116#[maybe_async_cfg::maybe(
117    idents(hal(sync = "embedded_hal", async = "embedded_hal_async")),
118    sync(feature = "sync"),
119    async(feature = "async")
120)]
121impl<I> MCP3208<I>
122where
123    I: hal::spi::r#SpiDevice,
124{
125    /// Initializes a new device from the specified SPI device.
126    /// This consumes the SPI device `I`.
127    ///
128    /// The device supports SPI modes 0 and 3.
129    #[inline]
130    pub fn new_spi(interface: I, reference_voltage: ElectricPotential) -> Self {
131        Self {
132            interface,
133            reference_voltage,
134        }
135    }
136}
137
138#[maybe_async_cfg::maybe(
139    idents(hal(sync = "embedded_hal", async = "embedded_hal_async")),
140    sync(feature = "sync"),
141    async(feature = "async")
142)]
143impl<I: hal::spi::r#SpiDevice> MCP3208<I> {
144    /// Performs a conversion of the given channel and returns the raw value
145    pub async fn convert_raw(&mut self, channel: InputChannel) -> Result<u16, I::Error> {
146        // Do bitwise-or with the required start bit. We also pad one leading zero so the readout
147        // has better alignment.
148        let command: u8 = 0b01000000 | (channel as u8) << 2;
149        let mut data = [command, 0, 0];
150        self.interface.transfer_in_place(&mut data).await?;
151        Ok((data[1] as u16) << 4 | (data[2] as u16) >> 4)
152    }
153
154    /// Performs a conversion of the given channel and returns the value in volts
155    pub async fn convert(&mut self, channel: InputChannel) -> Result<ElectricPotential, I::Error> {
156        let raw_value = self.convert_raw(channel).await?;
157        let v_ref = self.reference_voltage.get::<volt>();
158        let value = ElectricPotential::new::<volt>(raw_value as f64 * v_ref / 4096.0);
159        Ok(value)
160    }
161}