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}