new-sonar/firmware/src/mcp4726.rs

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,
}
}
}