134 lines
3.7 KiB
Rust
134 lines
3.7 KiB
Rust
//! Blocking driver for MCP2746
|
|
|
|
use embedded_hal::blocking::i2c::{Write, Read};
|
|
|
|
const COMMAND_WRITE_VOLATILE_DAC: u8 = 0x00;
|
|
const COMMAND_WRITE_VOLATILE_MEM: u8 = 0x40;
|
|
const COMMAND_WRITE_ALL_MEM: u8 = 0x60;
|
|
const COMMAND_WRITE_VOLATILE_CONFIG: u8 = 0x80;
|
|
|
|
const BASE_ADDRESS: u8 = 0b110_0000;
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum VRef {
|
|
UnbufferedVDD,
|
|
UnbufferedVRef,
|
|
BufferedVRef,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum PowerDown {
|
|
NormalOperation,
|
|
PoweredDownVout1k,
|
|
PoweredDownVout100k,
|
|
PoweredDownVout500k,
|
|
}
|
|
|
|
/// Configuration to apply
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct Config {
|
|
pub vref_source: VRef,
|
|
/// Set device operation
|
|
pub operation: PowerDown,
|
|
/// True to set gain 2x, false for 1x
|
|
pub use_2x_gain: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct Status {
|
|
/// Is the device ready to use? Only false in an EEPROM programming cycle
|
|
pub ready: bool,
|
|
|
|
/// Power-on reset status indicator. Low if VDD is below the minimum operational
|
|
/// voltage.
|
|
pub device_powered: bool,
|
|
}
|
|
|
|
pub struct MCP4726 {
|
|
address: u8,
|
|
power_status: PowerDown,
|
|
}
|
|
|
|
impl MCP4726 {
|
|
pub fn new(sub_address: u8) -> Self {
|
|
Self {
|
|
address: BASE_ADDRESS + (sub_address & 0x07),
|
|
// TODO maybe come up with better semantics for setting this on init
|
|
power_status: PowerDown::NormalOperation,
|
|
}
|
|
}
|
|
|
|
pub fn read_status<Error>(&self, i2c: &mut dyn Read<Error = Error>) -> Result<Status, Error> {
|
|
let mut buf = [0u8; 1];
|
|
|
|
self.read_all_mem(i2c, &mut buf)?;
|
|
|
|
Ok(Status {
|
|
ready: buf[0] & 0x80 != 0,
|
|
device_powered: buf[0] & 0x40 != 0,
|
|
})
|
|
}
|
|
|
|
pub fn write_config<Error>(&mut self, i2c: &mut dyn Write<Error = Error>, config: Config) -> Result<(), Error> {
|
|
let mut buf = [0u8; 1];
|
|
buf[0] |= COMMAND_WRITE_VOLATILE_CONFIG;
|
|
buf[0] |= MCP4726::vref_config(config.vref_source) << 3;
|
|
buf[0] |= MCP4726::power_config(config.operation) << 1;
|
|
if config.use_2x_gain {
|
|
buf[0] |= 1;
|
|
}
|
|
|
|
// Make sure power config is set properly
|
|
self.power_status = config.operation;
|
|
|
|
i2c.write(self.address, &buf)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_dac<Error>(&self, i2c: &mut dyn Write<Error = Error>, dac: u16) -> Result<(), Error> {
|
|
let mut buf = [0u8; 2];
|
|
buf[0] |= COMMAND_WRITE_VOLATILE_DAC;
|
|
// Note this "overlaps" the command bits, the lowest command bit is not used for this one
|
|
buf[0] |= MCP4726::power_config(self.power_status) << 4;
|
|
|
|
buf[0] |= (dac >> 8) as u8 & 0x0F;
|
|
buf[1] = (dac & 0xFF) as u8;
|
|
|
|
i2c.write(self.address, &buf)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_nonvolatile<Error>(&mut self, i2c: &mut dyn Write<Error = Error>, config: Config, dac: u16) -> Result<(), Error> {
|
|
todo!()
|
|
}
|
|
|
|
/// Reads all memory, up to 6 bytes into the out buffer.
|
|
fn read_all_mem<Error>(&self, i2c: &mut dyn Read<Error = Error>, out: &mut [u8]) -> Result<u8, Error> {
|
|
let buf = if out.len() <= 6 {
|
|
out
|
|
} else {
|
|
&mut out[0..6]
|
|
};
|
|
|
|
i2c.read(self.address, buf)?;
|
|
Ok(buf.len() as u8)
|
|
}
|
|
|
|
fn vref_config(vref: VRef) -> u8 {
|
|
match vref {
|
|
VRef::UnbufferedVDD => 0b00,
|
|
VRef::UnbufferedVRef => 0b10,
|
|
VRef::BufferedVRef => 0b11,
|
|
}
|
|
}
|
|
|
|
fn power_config(pd: PowerDown) -> u8 {
|
|
match pd {
|
|
PowerDown::NormalOperation => 0b00,
|
|
PowerDown::PoweredDownVout1k => 0b01,
|
|
PowerDown::PoweredDownVout100k => 0b10,
|
|
PowerDown::PoweredDownVout500k => 0b11,
|
|
}
|
|
}
|
|
}
|