#![no_std]
use embedded_hal::blocking::i2c;
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum PowerState {
On = 0b00,
Off1kOhm = 0b01,
Off125kOhm = 0b10,
Off640kOhm = 0b11,
}
impl TryFrom<u8> for PowerState {
type Error = ();
fn try_from(mode: u8) -> Result<Self, ()> {
match mode {
0b00 => Ok(PowerState::On),
0b01 => Ok(PowerState::Off1kOhm),
0b10 => Ok(PowerState::Off125kOhm),
0b11 => Ok(PowerState::Off640kOhm),
_ => Err(()),
}
}
}
#[cfg(test)]
mod power_state_try_into {
use super::*;
#[test]
fn on() {
let result = 0b0000_0000u8.try_into();
assert_eq!(result, Ok(PowerState::On));
}
#[test]
fn off_1k() {
let result = 0b0000_0001u8.try_into();
assert_eq!(result, Ok(PowerState::Off1kOhm));
}
#[test]
fn off_125k() {
let result = 0b0000_0010u8.try_into();
assert_eq!(result, Ok(PowerState::Off125kOhm));
}
#[test]
fn off_640k() {
let result = 0b0000_0011u8.try_into();
assert_eq!(result, Ok(PowerState::Off640kOhm));
}
#[test]
fn invalid() {
let result:Result<PowerState, ()> = 0b0000_0100u8.try_into();
assert_eq!(result, Err(()));
}
}
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum Vref {
Vdd = 0b00,
VrefUnbuffered = 0b10,
VrefBuffered = 0b11,
}
impl TryFrom<u8> for Vref {
type Error = ();
fn try_from(mode: u8) -> Result<Self, ()> {
match mode {
0b00 => Ok(Vref::Vdd),
0b01 => Ok(Vref::Vdd),
0b10 => Ok(Vref::VrefUnbuffered),
0b11 => Ok(Vref::VrefBuffered),
_ => Err(()),
}
}
}
#[cfg(test)]
mod vref_try_into {
use super::*;
#[test]
fn vdd_00() {
let result = 0b0000_0000u8.try_into();
assert_eq!(result, Ok(Vref::Vdd));
}
#[test]
fn vdd_01() {
let result = 0b0000_0001u8.try_into();
assert_eq!(result, Ok(Vref::Vdd));
}
#[test]
fn vref_unbuffered() {
let result = 0b0000_0010u8.try_into();
assert_eq!(result, Ok(Vref::VrefUnbuffered));
}
#[test]
fn vref_buffered() {
let result = 0b0000_0011u8.try_into();
assert_eq!(result, Ok(Vref::VrefBuffered));
}
#[test]
fn invalid() {
let result: Result<Vref, ()> = 0b0000_0100u8.try_into();
assert_eq!(result, Err(()));
}
}
#[derive(Debug, PartialEq)]
pub struct Mcp4726Status {
bytes: [u8; 6],
}
impl From<[u8; 6]> for Mcp4726Status {
fn from(bytes: [u8; 6]) -> Self {
Self { bytes }
}
}
impl Mcp4726Status {
pub fn nv_memory_ready(&self) -> bool {
self.bytes[0] & 0b1000_0000 == 0b1000_0000
}
pub fn power_on_reset(&self) -> bool {
self.bytes[0] & 0b0100_0000 == 0b0100_0000
}
pub fn vref(&self) -> Vref {
((self.bytes[0] & 0b0001_1000) >> 3).try_into().unwrap()
}
pub fn nv_vref(&self) -> Vref {
((self.bytes[3] & 0b0001_1000) >> 3).try_into().unwrap()
}
pub fn power_state(&self) -> PowerState {
((self.bytes[0] & 0b0000_0110) >> 1).try_into().unwrap()
}
pub fn nv_power_state(&self) -> PowerState {
((self.bytes[3] & 0b0000_0110) >> 1).try_into().unwrap()
}
pub fn gain(&self) -> u8 {
match (self.bytes[0] & 0b0000_0001) == 0b0000_0001 {
false => 1,
true => 2,
}
}
pub fn nv_gain(&self) -> u8 {
match (self.bytes[3] & 0b0000_0001) == 0b0000_0001 {
false => 1,
true => 2,
}
}
pub fn output(&self) -> u16 {
(((self.bytes[1] as u16) << 8) + self.bytes[2] as u16) >> 4
}
pub fn nv_output(&self) -> u16 {
(((self.bytes[4] as u16) << 8) + self.bytes[5] as u16) >> 4
}
}
#[cfg(test)]
mod status {
use super::*;
#[test]
fn nv_memory_ready_false() {
let status: Mcp4726Status = [0b0111_1111u8, 0xff, 0xff, 0xffu8, 0xffu8, 0xffu8].into();
assert_eq!(status.nv_memory_ready(), false);
}
#[test]
fn nv_memory_ready_true() {
let status: Mcp4726Status = [0b1000_0000u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.nv_memory_ready(), true);
}
#[test]
fn power_on_reset_false() {
let status: Mcp4726Status = [0b1011_1111u8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8].into();
assert_eq!(status.power_on_reset(), false);
}
#[test]
fn power_on_reset_true() {
let status: Mcp4726Status = [0b0100_0000u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.power_on_reset(), true);
}
#[test]
fn vref_vdd_00() {
let status: Mcp4726Status = [0b0000_0000u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.vref(), Vref::Vdd);
}
#[test]
fn vref_vdd_01() {
let status: Mcp4726Status = [0b0000_1000u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.vref(), Vref::Vdd);
}
#[test]
fn vref_vref_unbuffered() {
let status: Mcp4726Status = [0b0001_0000u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.vref(), Vref::VrefUnbuffered);
}
#[test]
fn vref_vref_buffered() {
let status: Mcp4726Status = [0b0001_1000u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.vref(), Vref::VrefBuffered);
}
#[test]
fn nv_vref_vdd_00() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0000_0000u8, 0u8, 0u8].into();
assert_eq!(status.nv_vref(), Vref::Vdd);
}
#[test]
fn nv_vref_vdd_01() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0000_1000u8, 0u8, 0u8].into();
assert_eq!(status.nv_vref(), Vref::Vdd);
}
#[test]
fn nv_vref_vref_unbuffered() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0001_0000u8, 0u8, 0u8].into();
assert_eq!(status.nv_vref(), Vref::VrefUnbuffered);
}
#[test]
fn nv_vref_vref_buffered() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0001_1000u8, 0u8, 0u8].into();
assert_eq!(status.nv_vref(), Vref::VrefBuffered);
}
#[test]
fn power_state_on() {
let status: Mcp4726Status = [0b1111_1001u8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8].into();
assert_eq!(status.power_state(), PowerState::On);
}
#[test]
fn power_state_off_1k() {
let status: Mcp4726Status = [0b0000_0010u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.power_state(), PowerState::Off1kOhm);
}
#[test]
fn power_state_off_125k() {
let status: Mcp4726Status = [0b0000_0100u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.power_state(), PowerState::Off125kOhm);
}
#[test]
fn power_state_off_640k() {
let status: Mcp4726Status = [0b0000_0110u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.power_state(), PowerState::Off640kOhm);
}
#[test]
fn nv_power_state_on() {
let status: Mcp4726Status = [0xffu8, 0xffu8, 0xffu8, 0b1111_1001u8, 0xffu8, 0xffu8].into();
assert_eq!(status.nv_power_state(), PowerState::On);
}
#[test]
fn nv_power_state_off_1k() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0000_0010u8, 0u8, 0u8].into();
assert_eq!(status.nv_power_state(), PowerState::Off1kOhm);
}
#[test]
fn nv_power_state_off_125k() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0000_0100u8, 0u8, 0u8].into();
assert_eq!(status.nv_power_state(), PowerState::Off125kOhm);
}
#[test]
fn nv_power_state_off_640k() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0000_0110u8, 0u8, 0u8].into();
assert_eq!(status.nv_power_state(), PowerState::Off640kOhm);
}
#[test]
fn gain_x1() {
let status: Mcp4726Status = [0b1111_1110u8, 0xff, 0xff, 0xffu8, 0xffu8, 0xffu8].into();
assert_eq!(status.gain(), 1);
}
#[test]
fn gain_x2() {
let status: Mcp4726Status = [0b0000_0001u8, 0u8, 0u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.gain(), 2);
}
#[test]
fn nv_gain_x1() {
let status: Mcp4726Status = [0xffu8, 0xffu8, 0xffu8, 0b1111_1110u8, 0xffu8, 0xffu8].into();
assert_eq!(status.nv_gain(), 1);
}
#[test]
fn nv_gain_x2() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0b0000_0001u8, 0u8, 0u8].into();
assert_eq!(status.nv_gain(), 2);
}
#[test]
fn output_0x0000() {
let status: Mcp4726Status = [0xffu8, 0b0000_0000u8, 0b0000_1111u8, 0xffu8, 0xffu8, 0xffu8].into();
assert_eq!(status.output(), 0u16);
}
#[test]
fn output_0x0fff() {
let status: Mcp4726Status = [0u8, 0b1111_1111u8, 0b1111_0000u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.output(), 0b0000_1111_1111_1111u16);
}
#[test]
fn output_0x0ff0() {
let status: Mcp4726Status = [0u8, 0b1111_1111u8, 0b0000_0000u8, 0u8, 0u8, 0u8].into();
assert_eq!(status.output(), 0b0000_1111_1111_0000u16);
}
#[test]
fn nv_output_0x0000() {
let status: Mcp4726Status = [0xffu8, 0xffu8, 0xffu8, 0xffu8, 0b0000_0000u8, 0b0000_1111u8].into();
assert_eq!(status.nv_output(), 0u16);
}
#[test]
fn nv_output_0x0ff0() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0u8, 0b1111_1111u8, 0b1111_0000u8].into();
assert_eq!(status.nv_output(), 0b0000_1111_1111_1111u16);
}
#[test]
fn nv_output_0x0fff() {
let status: Mcp4726Status = [0u8, 0u8, 0u8, 0u8, 0b1111_1111u8, 0b0000_0000u8].into();
assert_eq!(status.nv_output(), 0b0000_1111_1111_0000u16);
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Error<I2cError> {
OutputOutOfRange,
I2cError(I2cError),
}
impl<E> From<E> for Error<E> {
fn from(error: E) -> Self {
Error::I2cError(error)
}
}
pub struct MCP4726<I2C: i2c::Read + i2c::Write>{
i2c: I2C,
address: u8,
}
impl<I2C, I2cError> MCP4726<I2C>
where
I2C: i2c::Read<Error = I2cError> + i2c::Write<Error = I2cError>,
{
const SET_VOLATILE_CMD: u8 = 0b010;
const SET_CMD: u8 = 0b011;
const SET_VOLATILE_CONFIG_CMD: u8 = 0b100;
const ADDR_COMMON_MASK: u8 = 0b1111_1000;
const ADDR_COMMON: u8 = 0b_110_0000;
const GENERAL_CALL_RESET_CMD: u8 = 0b0000_0110;
const GENERAL_CALL_WAKEUP_CMD: u8 = 0b0000_1001;
const GENERAL_CALL_ADDR: i2c::SevenBitAddress = 0b_000_0000;
pub fn new(i2c: I2C, address: i2c::SevenBitAddress) -> Option<Self> {
match (address & Self::ADDR_COMMON_MASK) == Self::ADDR_COMMON {
false => None, true => Some(MCP4726 {
i2c,
address,
}),
}
}
pub fn destroy(self) -> I2C {
self.i2c
}
pub fn read(&mut self) -> Result<Mcp4726Status, I2cError> {
let mut buffer: [u8; 6] = [0; 6];
self.i2c.read(self.address, &mut buffer)?;
Ok(buffer.into())
}
pub fn set_volatile_fast(&mut self, power_state: PowerState, output: u16) -> Result<(), Error<I2cError>> {
match output > 0b0000_1111_1111_1111u16 {
true => Err(Error::OutputOutOfRange),
false => {
self.i2c.write(self.address, &[
((power_state as u8) & 0b11 ) << 4 | ((output & 0x0f00) >> 8) as u8,
output as u8,
])?;
Ok(())
},
}
}
pub fn set_volatile(&mut self, vref: Vref, power_state: PowerState, gain: bool, output: u16) -> Result<(), Error<I2cError>> {
match output > 0b0000_1111_1111_1111u16 {
true => Err(Error::OutputOutOfRange),
false => {
self.i2c.write(self.address, &[
Self::SET_VOLATILE_CMD << 5 | (vref as u8) << 3 | (power_state as u8) << 1 | gain as u8,
((output & 0x0ff0) >> 4) as u8,
((output & 0x000f) << 4) as u8,
])?;
Ok(())
},
}
}
pub fn set_volatile_config(&mut self, vref: Vref, power_state: PowerState, gain: bool) -> Result<(), I2cError> {
self.i2c.write(self.address, &[
Self::SET_VOLATILE_CONFIG_CMD << 5 | (vref as u8) << 3 | (power_state as u8) << 1 | gain as u8,
])?;
Ok(())
}
pub fn set_persistent(&mut self, vref: Vref, power_state: PowerState, gain: bool, output: u16) -> Result<(), Error<I2cError>> {
match output > 0b0000_1111_1111_1111u16 {
true => Err(Error::OutputOutOfRange),
false => {
self.i2c.write(self.address, &[
Self::SET_CMD << 5 | (vref as u8) << 3 | (power_state as u8) << 1 | gain as u8,
((output & 0x0ff0) >> 4) as u8,
((output & 0x000f) << 4) as u8,
])?;
Ok(())
},
}
}
pub fn global_call_reset(&mut self) -> Result<(), I2cError>{
self.i2c.write(Self::GENERAL_CALL_ADDR, &[Self::GENERAL_CALL_RESET_CMD])
}
pub fn global_call_wakeup(&mut self) -> Result<(), I2cError>{
self.i2c.write(Self::GENERAL_CALL_ADDR, &[Self::GENERAL_CALL_WAKEUP_CMD])
}
}
#[cfg(test)]
mod mcp4726 {
extern crate std;
use super::*;
use embedded_hal_mock::{
i2c::{Mock, Transaction},
MockError,
};
const VALID_ADDR: i2c::SevenBitAddress = 0b0110_0000;
#[test]
fn new_ok() {
let i2c = Mock::new(&[]);
assert!(MCP4726::new(i2c, VALID_ADDR).is_some());
}
#[test]
fn new_unsupported_address() {
let i2c = Mock::new(&[]);
assert!(MCP4726::new(i2c, 0b0001_1000u8).is_none());
}
#[test]
fn new_invalid_address() {
let i2c = Mock::new(&[]);
assert!(MCP4726::new(i2c, 0b1000_0000u8).is_none());
}
#[test]
fn destroy() {
let i2c = Mock::new(&[]);
let dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
let _i2c: embedded_hal_mock::common::Generic<Transaction> = dac.destroy();
}
#[test]
fn read_ok() {
let i2c = Mock::new(&[Transaction::read(VALID_ADDR, std::vec![0, 0, 0, 0, 0, 0]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.read().is_ok());
}
#[test]
fn read_err_should_propagate() {
let i2c = Mock::new(&[Transaction::read(VALID_ADDR, std::vec![0, 0, 0, 0, 0, 0]).with_error(MockError::Io(std::io::ErrorKind::Other)),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
let status = dac.read();
assert!(status.is_err());
assert_eq!(status.unwrap_err(), MockError::Io(std::io::ErrorKind::Other));
}
#[test]
fn read_data_should_propagate_0() {
let i2c = Mock::new(&[Transaction::read(VALID_ADDR, std::vec![0, 0, 0, 0, 0, 0]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
let status = dac.read();
assert!(status.is_ok());
assert_eq!(status.unwrap(), [0, 0, 0, 0, 0, 0].into());
}
#[test]
fn read_data_should_propagate_1() {
let i2c = Mock::new(&[Transaction::read(VALID_ADDR, std::vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
let status = dac.read();
assert!(status.is_ok());
assert_eq!(status.unwrap(), [0xff, 0xff, 0xff, 0xff, 0xff, 0xff].into());
}
#[test]
fn set_volatile_fast_0(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b0000_0000, 0b0000_0000]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_volatile_fast(PowerState::On, 0x0000).is_ok());
}
#[test]
fn set_volatile_fast_1(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b0011_1111, 0b1111_1111]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_volatile_fast(PowerState::Off640kOhm, 0x0fff).is_ok());
}
#[test]
fn set_volatile_fast_invalid(){
let i2c = Mock::new(&[]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert_eq!(dac.set_volatile_fast(PowerState::On, 0xffff), Err(Error::OutputOutOfRange));
}
#[test]
fn set_volatile_0(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b0100_0000, 0b0000_0000, 0b0000_0000]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_volatile(Vref::Vdd, PowerState::On, false, 0x0000).is_ok());
}
#[test]
fn set_volatile_1(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b0101_1111, 0b1111_1111, 0b1111_0000]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_volatile(Vref::VrefBuffered, PowerState::Off640kOhm, true, 0x0fff).is_ok());
}
#[test]
fn set_volatile_invalid(){
let i2c = Mock::new(&[]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert_eq!(dac.set_volatile(Vref::Vdd, PowerState::On, false, 0xffff), Err(Error::OutputOutOfRange));
}
#[test]
fn set_persistent_0(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b0110_0000, 0b0000_0000, 0b0000_0000]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_persistent(Vref::Vdd, PowerState::On, false, 0x0000).is_ok());
}
#[test]
fn set_persistent_1(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b0111_1111, 0b1111_1111, 0b1111_0000]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_persistent(Vref::VrefBuffered, PowerState::Off640kOhm, true, 0x0fff).is_ok());
}
#[test]
fn set_persistent_invalid(){
let i2c = Mock::new(&[]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert_eq!(dac.set_persistent(Vref::Vdd, PowerState::On, false, 0xffff), Err(Error::OutputOutOfRange));
}
#[test]
fn set_volatile_conf_0(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b1000_0000]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_volatile_config(Vref::Vdd, PowerState::On, false).is_ok());
}
#[test]
fn set_volatile_conf_1(){
let i2c = Mock::new(&[Transaction::write(VALID_ADDR, std::vec![0b1001_1111]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.set_volatile_config(Vref::VrefBuffered, PowerState::Off640kOhm, true).is_ok());
}
#[test]
fn global_call_reset(){
let i2c = Mock::new(&[Transaction::write(0b_000_0000, std::vec![0b0000_0110]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.global_call_reset().is_ok());
}
#[test]
fn global_call_wakeup(){
let i2c = Mock::new(&[Transaction::write(0b_000_0000, std::vec![0b0000_1001]),]);
let mut dac = MCP4726::new(i2c, VALID_ADDR).unwrap();
assert!(dac.global_call_wakeup().is_ok());
}
}