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}