1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! *Texas Instruments DAC5578 Driver for Rust Embedded HAL*
//! This is a driver crate for embedded Rust. It's built on top of the Rust
//! [embedded HAL](https://github.com/rust-embedded/embedded-hal)
//! It supports sending commands to a TI DAC5578 over I2C.
//!
//! The driver can be initialized by calling create and passing it an I2C interface.
//! The device address (set by ADDR0) also needs to be specified.
//! It can be set by pulling the ADDR0 on the device high/low or floating.
//!
//! ```
//! # use embedded_hal_mock::i2c::Mock;
//! # use dac5578::*;
//! # let mut i2c = Mock::new(&[]);
//! let mut dac = DAC5578::new(i2c, Address::PinLow);
//! ```
//!
//! To set the dac output for channel A:
//! ```
//! # use embedded_hal_mock::i2c::{Mock, Transaction};
//! # use dac5578::*;
//! # let mut i2c = Mock::new(&[Transaction::write(98, vec![0x40, 0xff, 0xf0]),]);
//! # let mut dac = DAC5578::new(i2c, Address::PinLow);
//! dac.write_channel(Channel::A, 128);
//! ```
//!
//! ## More information
//! - [DAC5578 datasheet](https://www.ti.com/lit/ds/symlink/dac5578.pdf?ts=1621340690413&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FDAC5578)
//! - [API documentation](https://docs.rs/dac5578/)
//! - [Github repository](https://github.com/chmanie/dac5578)
//! - [Crates.io](https://crates.io/crates/dac5578)
//!
#![no_std]
#![warn(missing_debug_implementations, missing_docs)]

use core::fmt::Debug;
use embedded_hal::blocking::i2c::{Read, Write};

/// user_address can be set by pulling the ADDR0 pin high/low or leave it floating
#[derive(Debug)]
#[repr(u8)]
pub enum Address {
    /// ADDR0 is low
    PinLow = 0x48,
    /// ADDR0 is high
    PinHigh = 0x4a,
    /// ADDR0 is floating
    PinFloat = 0x4c,
}

/// Defines the output channel to set the voltage for
#[derive(Debug)]
#[repr(u8)]
pub enum Channel {
    /// DAC output channel A
    A,
    /// DAC output channel B
    B,
    /// DAC output channel C
    C,
    /// DAC output channel D
    D,
    /// DAC output channel E
    E,
    /// DAC output channel F
    F,
    /// DAC output channel G
    G,
    /// DAC output channel H
    H,
    /// Targets all DAC output channels
    All = 0xf,
}

impl From<u8> for Channel {
    fn from(index: u8) -> Self {
        match index {
            0 => Channel::A,
            1 => Channel::B,
            2 => Channel::C,
            3 => Channel::D,
            4 => Channel::E,
            5 => Channel::F,
            6 => Channel::G,
            7 => Channel::H,
            _ => panic!("Unkown channel number {}", index),
        }
    }
}

/// The type of the command to send for a Command
#[derive(Debug)]
#[repr(u8)]
pub enum CommandType {
    /// Write to the channel's DAC input register
    WriteToChannel = 0x0,
    /// Selects DAC channel to be updated
    UpdateChannel = 0x10,
    /// Write to DAC input register for a channel and update channel DAC register
    WriteToChannelAndUpdate = 0x30,
    /// Write to Selected DAC Input Register and Update All DAC Registers (Global Software LDAC)
    WriteToChannelAndUpdateAll = 0x20,
}

/// Two bit flags indicating the reset mode for the DAC5578
#[derive(Debug)]
#[repr(u8)]
pub enum ResetMode {
    /// Software reset (default). Same as power-on reset (POR).
    Por = 0b00,
    /// Software reset that sets device into High-Speed mode
    SetHighSpeed = 0b01,
    /// Software reset that maintains High-Speed mode state
    MaintainHighSpeed = 0b10,
}

/// DAC5578 driver. Wraps an I2C port to send commands to a DAC5578
#[derive(Debug)]
pub struct DAC5578<I2C>
where
    I2C: Read + Write,
{
    i2c: I2C,
    address: u8,
}

impl<I2C, E> DAC5578<I2C>
where
    I2C: Read<Error = E> + Write<Error = E>,
{
    /// Construct a new DAC5578 driver instance.
    /// i2c is the initialized i2c driver port to use, address depends on the state of the ADDR0 pin (see [`Address`])
    pub fn new(i2c: I2C, address: Address) -> Self {
        DAC5578 {
            i2c,
            address: address as u8,
        }
    }

    /// Write to the channel's DAC input register
    pub fn write(&mut self, channel: Channel, data: u8) -> Result<(), E> {
        let bytes = Self::encode_command(CommandType::WriteToChannel, channel as u8, data);
        self.i2c.write(self.address, &bytes)
    }

    /// Selects DAC channel to be updated
    pub fn update(&mut self, channel: Channel, data: u8) -> Result<(), E> {
        let bytes = Self::encode_command(CommandType::UpdateChannel, channel as u8, data);
        self.i2c.write(self.address, &bytes)
    }

    /// Write to DAC input register for a channel and update channel DAC register
    pub fn write_and_update(&mut self, channel: Channel, data: u8) -> Result<(), E> {
        let bytes = Self::encode_command(CommandType::WriteToChannelAndUpdate, channel as u8, data);
        self.i2c.write(self.address, &bytes)
    }

    /// Write to Selected DAC Input Register and Update All DAC Registers (Global Software LDAC)
    pub fn write_and_update_all(&mut self, channel: Channel, data: u8) -> Result<(), E> {
        let bytes =
            Self::encode_command(CommandType::WriteToChannelAndUpdateAll, channel as u8, data);
        self.i2c.write(self.address, &bytes)
    }

    /// Perform a software reset using the selected mode
    pub fn reset(&mut self, mode: ResetMode) -> Result<(), E> {
        let bytes = [0x70, mode as u8, 0];
        self.i2c.write(self.address, &bytes)
    }

    /// Send a wake-up command over the I2C bus.
    /// WARNING: This is a general call command and can wake-up other devices on the bus as well.
    pub fn wake_up_all(&mut self) -> Result<(), E> {
        self.i2c.write(0x00, &[0x06u8])?;
        Ok(())
    }

    /// Send a reset command on the I2C bus.
    /// WARNING: This is a general call command and can reset other devices on the bus as well.
    pub fn reset_all(&mut self) -> Result<(), E> {
        self.i2c.write(0x00, &[0x09u8])?;
        Ok(())
    }

    /// Destroy the DAC5578 driver, return the wrapped I2C
    pub fn destroy(self) -> I2C {
        self.i2c
    }

    /// Encode command type, channel and data into a three byte command
    fn encode_command(command: CommandType, access: u8, msdb: u8) -> [u8; 3] {
        [command as u8 | access, msdb, 0]
    }
}