embedded_devices/devices/microchip/
mcp3204.rs

1//! The Microchip Technology Inc. MCP3204 device is a successive approximation 12-bit Analog-
2//! to-Digital (A/D) Converter with on-board sample and hold circuitry. The MCP3204 is programmable
3//! to provide two pseudo-differential input pairs or four single-ended inputs.
4//!
5//! Differential Nonlinearity (DNL) is specified at ±1 LSB, while Integral Nonlinearity (INL) is
6//! offered in ±1 LSB (MCP3204-B) and ±2 LSB (MCP3204-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 MCP3204 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::mcp3204::{MCP3204Sync, InputChannel};
21//! use uom::si::electric_potential::{volt, millivolt};
22//! use uom::si::f64::ElectricPotential;
23//!
24//! // 2.5V reference
25//! let mut mcp3204 = MCP3204Sync::new_spi(spi, ElectricPotential::new::<volt>(2.5));
26//!
27//! let value = mcp3204.convert(InputChannel::Single0)?;
28//! let voltage = value.get::<millivolt>();
29//! println!("V_in at channel 0: {:?}mV", value);
30//! # Ok(())
31//! # }
32//! # }
33//! ```
34//!
35//! ## Usage (async)
36//!
37//! ```rust
38//! # #[cfg(feature = "async")] mod test {
39//! # async fn test<I>(mut spi: I) -> Result<(), I::Error>
40//! # where
41//! #   I: embedded_hal_async::spi::SpiDevice,
42//! # {
43//! use embedded_devices::devices::microchip::mcp3204::{MCP3204Async, InputChannel};
44//! use uom::si::electric_potential::{volt, millivolt};
45//! use uom::si::f64::ElectricPotential;
46//!
47//! // 2.5V reference
48//! let mut mcp3204 = MCP3204Async::new_spi(spi, ElectricPotential::new::<volt>(2.5));
49//!
50//! let value = mcp3204.convert(InputChannel::Single0).await?;
51//! let voltage = value.get::<millivolt>();
52//! println!("V_in at channel 0: {:?}mV", value);
53//! # Ok(())
54//! # }
55//! # }
56//! ```
57
58use uom::si::{electric_potential::volt, f64::ElectricPotential};
59
60/// The ADC input channel
61#[cfg_attr(feature = "defmt", derive(defmt::Format))]
62#[derive(Copy, Clone, PartialEq, Eq, Debug)]
63pub enum InputChannel {
64    /// Single channel 0
65    Single0 = 0b1000,
66    /// Single channel 1
67    Single1 = 0b1001,
68    /// Single channel 2
69    Single2 = 0b1010,
70    /// Single channel 3
71    Single3 = 0b1011,
72    /// Pseudo-differential channel (IN+ = CH0, IN- = CH1)
73    Diff01 = 0b0000,
74    /// Pseudo-differential channel (IN+ = CH1, IN- = CH0)
75    Diff10 = 0b0001,
76    /// Pseudo-differential channel (IN+ = CH2, IN- = CH3)
77    Diff23 = 0b0010,
78    /// Pseudo-differential channel (IN+ = CH3, IN- = CH2)
79    Diff32 = 0b0011,
80}
81
82/// The MCP3204 is a 12-bit ADC with on-board sample and hold circuitry. It is programmable
83/// to provide two pseudo-differential input pairs or four single-ended inputs.
84///
85/// For a full description and usage examples, refer to the [module documentation](self).
86#[maybe_async_cfg::maybe(sync(feature = "sync"), async(feature = "async"))]
87pub struct MCP3204<I> {
88    /// The interface to communicate with the device
89    interface: I,
90    /// The reference voltage
91    reference_voltage: ElectricPotential,
92}
93
94#[maybe_async_cfg::maybe(
95    idents(hal(sync = "embedded_hal", async = "embedded_hal_async")),
96    sync(feature = "sync"),
97    async(feature = "async")
98)]
99impl<I> MCP3204<I>
100where
101    I: hal::spi::r#SpiDevice,
102{
103    /// Initializes a new device from the specified SPI device.
104    /// This consumes the SPI device `I`.
105    ///
106    /// The device supports SPI modes 0 and 3.
107    #[inline]
108    pub fn new_spi(interface: I, reference_voltage: ElectricPotential) -> Self {
109        Self {
110            interface,
111            reference_voltage,
112        }
113    }
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: hal::spi::r#SpiDevice> MCP3204<I> {
122    /// Performs a conversion of the given channel and returns the raw value
123    pub async fn convert_raw(&mut self, channel: InputChannel) -> Result<u16, I::Error> {
124        // Do bitwise-or with the required start bit. We also pad one leading zero so the readout
125        // has better alignment.
126        let command: u8 = 0b01000000 | (channel as u8) << 2;
127        let mut data = [command, 0, 0];
128        self.interface.transfer_in_place(&mut data).await?;
129        Ok((data[1] as u16) << 4 | (data[2] as u16) >> 4)
130    }
131
132    /// Performs a conversion of the given channel and returns the value in volts
133    pub async fn convert(&mut self, channel: InputChannel) -> Result<ElectricPotential, I::Error> {
134        let raw_value = self.convert_raw(channel).await?;
135        let v_ref = self.reference_voltage.get::<volt>();
136        let value = ElectricPotential::new::<volt>(raw_value as f64 * v_ref / 4096.0);
137        Ok(value)
138    }
139}