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}