ad5627/
lib.rs

1//! Driver for the AD5627R 2-output programmable reference generator (2-channel DAC).
2//!
3//! # Description
4//! This driver allows for configuring either or both DAC output voltages. It assumes that the DAC
5//! is using an internal 1.25V reference (AD5627R variant).
6#![no_std]
7#![deny(warnings)]
8
9use embedded_hal::i2c::{ErrorType, I2c};
10
11/// The maximum voltage that the DAC can output.
12pub const MAX_VOLTAGE: f32 = 2.5;
13
14/// The driver representing the programmable reference generator.
15pub struct Ad5627<I2C> {
16    i2c: I2C,
17    address: u8,
18}
19
20#[doc(hidden)]
21#[allow(dead_code)]
22/// Represents various commands that can be sent to the DAC during a write.
23enum Command {
24    WriteInput = 0b000,
25    WriteDac = 0b001,
26    WriteInputUpdate = 0b010,
27    WriteDacUpdate = 0b011,
28    PowerUpdate = 0b100,
29    Reset = 0b101,
30    LdacSetup = 0b110,
31    InternalRefSetup = 0b111,
32}
33
34/// Represents which DAC output to update.
35pub enum Dac {
36    A = 0b000,
37    B = 0b001,
38    Both = 0b111,
39}
40
41/// Represents possible errors from the DAC driver.
42pub enum Error<E> {
43    Range,
44    I2c(E),
45}
46
47impl<E> From<E> for Error<E> {
48    fn from(e: E) -> Self {
49        Error::I2c(e)
50    }
51}
52
53impl<I2C> Ad5627<I2C>
54where
55    I2C: I2c,
56{
57    /// Construct a driver for the DAC.
58    ///
59    /// # Args
60    /// * `i2c` - The I2C bus to communicate with the DAC.
61    /// * `address` - The 7-bit I2C address of the device.
62    pub fn new(i2c: I2C, address: u8) -> Result<Self, <I2C as ErrorType>::Error> {
63        let mut device = Ad5627 { i2c, address };
64
65        // Reset the DAC outputs.
66        device.write(Command::Reset, Dac::Both, [0, 0])?;
67
68        // Power up the internal reference.
69        device.write(Command::InternalRefSetup, Dac::Both, [0, 1])?;
70
71        Ok(device)
72    }
73
74    fn write(
75        &mut self,
76        command: Command,
77        dac: Dac,
78        payload: [u8; 2],
79    ) -> Result<(), <I2C as ErrorType>::Error> {
80        // Construct the command byte.
81        let write: [u8; 3] = [((command as u8) << 3) | dac as u8, payload[0], payload[1]];
82
83        self.i2c.write(self.address, &write)
84    }
85
86    /// Construct a driver for the DAC.
87    ///
88    /// # Note
89    /// This constructs a driver with a default address when the address pin is not connected.
90    ///
91    /// # Args
92    /// * `i2c` - The I2C bus to communicate with the DAC.
93    pub fn default(i2c: I2C) -> Result<Self, <I2C as ErrorType>::Error> {
94        Ad5627::new(i2c, 0b0001110)
95    }
96
97    /// Set the output voltage for a specific DAC channel.
98    ///
99    /// # Args
100    /// * `voltage` - The desired output voltage to configure the DAC to.
101    /// * `dac` - Specifies which DAC (or both) should be configured.
102    ///
103    /// # Returns
104    /// The actual voltage programmed into the DAC after digitization.
105    pub fn set_voltage(
106        &mut self,
107        voltage: f32,
108        dac: Dac,
109    ) -> Result<f32, Error<<I2C as ErrorType>::Error>> {
110        // Assuming a 1.25V internal reference with a 2x output stage gain, our full scale range is
111        // 2.5V.
112        if !(0.0..=crate::MAX_VOLTAGE).contains(&voltage) {
113            return Err(Error::Range);
114        }
115
116        // The DAC has a 12-bit DAC output. Full scale is 0xFFF.
117        let code = ((voltage / 2.5) * (0x1000 as f32)) as u16;
118
119        // Check that the DAC code has not overflown.
120        if code > 0xFFF {
121            return Err(Error::Range);
122        }
123
124        // The 12-bit code must be stored MSB-aligned.
125        let code = code << 4;
126
127        // Write the dac level to the output.
128        self.write(Command::WriteInput, dac, code.to_be_bytes())?;
129
130        let programmed_voltage = ((code >> 4) as f32) / (0x1000 as f32) * 2.5;
131        Ok(programmed_voltage)
132    }
133}