diff --git a/firmware/src/ad7172.rs b/firmware/src/ad7172.rs deleted file mode 100644 index dc32a0c..0000000 --- a/firmware/src/ad7172.rs +++ /dev/null @@ -1,367 +0,0 @@ -use embedded_hal::digital::v2::OutputPin; -use embedded_hal::blocking::spi::Transfer; -use byteorder::{BigEndian, ByteOrder}; -use bit_field::BitField; - -trait Register { - type Data: RegisterData; - fn address(&self) -> u8; -} -trait RegisterData { - fn empty() -> Self; - fn as_mut(&mut self) -> &mut [u8]; -} - -macro_rules! def_reg { - ($Reg: ident, $reg: ident, $addr: expr, $size: expr) => { - struct $Reg; - impl Register for $Reg { - type Data = $reg::Data; - fn address(&self) -> u8 { - $addr - } - } - mod $reg { - pub struct Data(pub [u8; $size]); - impl super::RegisterData for Data { - fn empty() -> Self { - Data([0; $size]) - } - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } - } - } - }; - ($Reg: ident, $index: ty, $reg: ident, $addr: expr, $size: expr) => { - struct $Reg { pub index: $index, } - impl Register for $Reg { - type Data = $reg::Data; - fn address(&self) -> u8 { - $addr + (self.index as u8) - } - } - mod $reg { - pub struct Data(pub [u8; $size]); - impl super::RegisterData for Data { - fn empty() -> Self { - Data([0; $size]) - } - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } - } - } - } -} - -def_reg!(Status, status, 0x00, 1); -impl status::Data { - /// Is there new data to read? - fn ready(&self) -> bool { - ! self.0[0].get_bit(7) - } - - /// Channel for which data is ready - fn channel(&self) -> u8 { - self.0[0].get_bits(0..=1) - } - - fn adc_error(&self) -> bool { - self.0[0].get_bit(6) - } - - fn crc_error(&self) -> bool { - self.0[0].get_bit(5) - } - - fn reg_error(&self) -> bool { - self.0[0].get_bit(4) - } -} - -def_reg!(IfMode, if_mode, 0x02, 2); -impl if_mode::Data { - fn set_crc(&mut self, mode: ChecksumMode) { - self.0[1].set_bits(2..=3, mode as u8); - } -} - -def_reg!(Data, data, 0x04, 3); -impl data::Data { - fn data(&self) -> u32 { - (u32::from(self.0[0]) << 16) | - (u32::from(self.0[1]) << 8) | - u32::from(self.0[2]) - } -} - -def_reg!(Id, id, 0x07, 2); -impl id::Data { - fn id(&self) -> u16 { - BigEndian::read_u16(&self.0) - } -} - -def_reg!(Channel, u8, channel, 0x10, 2); -impl channel::Data { - fn enabled(&self) -> bool { - self.0[0].get_bit(7) - } - - fn set_enabled(&mut self, value: bool) { - self.0[0].set_bit(7, value); - } -} - -def_reg!(SetupCon, u8, setup_con, 0x20, 2); - -def_reg!(FiltCon, u8, filt_con, 0x80, 2); - -// #[allow(unused)] -// #[derive(Clone, Copy)] -// #[repr(u8)] -// pub enum Register { -// Status = 0x00, -// AdcMode = 0x01, -// IfMode = 0x02, -// RegCheck = 0x03, -// Data = 0x04, -// GpioCon = 0x06, -// Id = 0x07, -// Ch0 = 0x10, -// Ch1 = 0x11, -// Ch2 = 0x12, -// Ch3 = 0x13, -// SetupCon0 = 0x20, -// SetupCon1 = 0x21, -// SetupCon2 = 0x22, -// SetupCon3 = 0x23, -// FiltCon0 = 0x28, -// FiltCon1 = 0x29, -// FiltCon2 = 0x2A, -// FiltCon3 = 0x2B, -// Offset0 = 0x30, -// Offset1 = 0x31, -// Offset2 = 0x32, -// Offset3 = 0x33, -// Gain0 = 0x38, -// Gain1 = 0x39, -// Gain2 = 0x3A, -// Gain3 = 0x3B, -// } - -#[repr(u8)] -pub enum Input { - Ain0 = 0, - Ain1 = 1, - Ain2 = 2, - Ain3 = 3, - Ain4 = 4, - TemperaturePos = 17, - TemperatureNeg = 18, - AnalogSupplyPos = 19, - AnalogSupplyNeg = 20, - RefPos = 21, - RefNeg = 22, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum AdcError { - SPI(SPI), - ChecksumMismatch(Option, Option), -} - -impl From for AdcError { - fn from(e: SPI) -> Self { - AdcError::SPI(e) - } -} - -#[derive(Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum ChecksumMode { - Off = 0b00, - /// Seems much less reliable than `Crc` - Xor = 0b01, - Crc = 0b10, -} - -struct Checksum { - mode: ChecksumMode, - state: u8, -} - -impl Checksum { - pub fn new(mode: ChecksumMode) -> Self { - Checksum { mode, state: 0 } - } - pub fn feed(&mut self, input: u8) { - match self.mode { - ChecksumMode::Off => {}, - ChecksumMode::Xor => self.state ^= input, - ChecksumMode::Crc => { - for i in 0..8 { - let input_mask = 0x80 >> i; - self.state = (self.state << 1) ^ - if ((self.state & 0x80) != 0) != ((input & input_mask) != 0) { - 0x07 /* x8 + x2 + x + 1 */ - } else { - 0 - }; - } - } - } - } - pub fn result(&self) -> Option { - match self.mode { - ChecksumMode::Off => None, - _ => Some(self.state) - } - } -} - -/// AD7172-2 implementation -/// -/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf) -pub struct Adc, NSS: OutputPin> { - spi: SPI, - nss: NSS, - checksum_mode: ChecksumMode, -} - -impl, NSS: OutputPin> Adc { - pub fn new(spi: SPI, mut nss: NSS) -> Result { - let _ = nss.set_high(); - let mut adc = Adc { - spi, nss, - checksum_mode: ChecksumMode::Off, - }; - adc.reset()?; - - Ok(adc) - } - - /// `0x00DX` for AD7271-2 - pub fn identify(&mut self) -> Result> { - self.read_reg(&Id) - .map(|id| id.id()) - } - - pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), AdcError> { - let mut ifmode = self.read_reg(&IfMode)?; - ifmode.set_crc(mode); - self.checksum_mode = mode; - self.write_reg(&IfMode, &mut ifmode)?; - Ok(()) - } - - // pub fn setup(&mut self) -> Result<(), SPI::Error> { - // let mut buf = [0, 0, 0]; - // adc.write_reg(Register::AdcMode, &mut buf)?; - // let mut buf = [0, 1, 0]; - // adc.write_reg(Register::IfMode, &mut buf)?; - // let mut buf = [0, 0, 0]; - // adc.write_reg(Register::GpioCon, &mut buf)?; - - // Ok(()) - // } - - /// Returns the channel the data is from - pub fn data_ready(&mut self) -> Result, AdcError> { - self.read_reg(&Status) - .map(|status| { - if status.ready() { - Some(status.channel()) - } else { - None - } - }) - } - - /// Get data - pub fn read_data(&mut self) -> Result> { - self.read_reg(&Data) - .map(|data| data.data()) - } - - fn read_reg(&mut self, reg: &R) -> Result> { - let mut reg_data = R::Data::empty(); - let address = 0x40 | reg.address(); - let mut checksum = Checksum::new(self.checksum_mode); - checksum.feed(address); - let checksum_out = checksum.result(); - let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?; - for &mut b in reg_data.as_mut() { - checksum.feed(b); - } - let checksum_expected = checksum.result(); - if checksum_expected != checksum_in { - return Err(AdcError::ChecksumMismatch(checksum_expected, checksum_in)); - } - Ok(reg_data) - } - - fn write_reg(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), AdcError> { - let address = reg.address(); - let mut checksum = Checksum::new(match self.checksum_mode { - ChecksumMode::Off => ChecksumMode::Off, - // write checksums are always crc - ChecksumMode::Xor => ChecksumMode::Crc, - ChecksumMode::Crc => ChecksumMode::Crc, - }); - checksum.feed(address); - for &mut b in reg_data.as_mut() { - checksum.feed(b); - } - let checksum_out = checksum.result(); - self.transfer(address, reg_data.as_mut(), checksum_out)?; - Ok(()) - } - - fn update_reg(&mut self, reg: &R, f: F) -> Result> - where - R: Register, - F: FnOnce(&mut R::Data) -> A, - { - let mut reg_data = self.read_reg(reg)?; - let result = f(&mut reg_data); - self.write_reg(reg, &mut reg_data)?; - Ok(result) - } - - pub fn reset(&mut self) -> Result<(), SPI::Error> { - let mut buf = [0xFFu8; 8]; - let _ = self.nss.set_low(); - let result = self.spi.transfer(&mut buf); - let _ = self.nss.set_high(); - result?; - Ok(()) - } - - fn transfer<'w>(&mut self, addr: u8, reg_data: &'w mut [u8], checksum: Option) -> Result, SPI::Error> { - let mut addr_buf = [addr]; - - let _ = self.nss.set_low(); - let result = match self.spi.transfer(&mut addr_buf) { - Ok(_) => self.spi.transfer(reg_data), - Err(e) => Err(e), - }; - let result = match (result, checksum) { - (Ok(_),None) => - Ok(None), - (Ok(_), Some(checksum_out)) => { - let mut checksum_buf = [checksum_out; 1]; - match self.spi.transfer(&mut checksum_buf) { - Ok(_) => Ok(Some(checksum_buf[0])), - Err(e) => Err(e), - } - } - (Err(e), _) => - Err(e), - }; - let _ = self.nss.set_high(); - - result - } -} diff --git a/firmware/src/ad7172/adc.rs b/firmware/src/ad7172/adc.rs new file mode 100644 index 0000000..ea1d4de --- /dev/null +++ b/firmware/src/ad7172/adc.rs @@ -0,0 +1,150 @@ +use embedded_hal::digital::v2::OutputPin; +use embedded_hal::blocking::spi::Transfer; +use super::checksum::{ChecksumMode, Checksum}; +use super::AdcError; +use super::regs::*; + +/// AD7172-2 implementation +/// +/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf) +pub struct Adc, NSS: OutputPin> { + spi: SPI, + nss: NSS, + checksum_mode: ChecksumMode, +} + +impl, NSS: OutputPin> Adc { + pub fn new(spi: SPI, mut nss: NSS) -> Result { + let _ = nss.set_high(); + let mut adc = Adc { + spi, nss, + checksum_mode: ChecksumMode::Off, + }; + adc.reset()?; + + Ok(adc) + } + + /// `0x00DX` for AD7271-2 + pub fn identify(&mut self) -> Result> { + self.read_reg(&Id) + .map(|id| id.id()) + } + + pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), AdcError> { + let mut ifmode = self.read_reg(&IfMode)?; + ifmode.set_crc(mode); + self.checksum_mode = mode; + self.write_reg(&IfMode, &mut ifmode)?; + Ok(()) + } + + // pub fn setup(&mut self) -> Result<(), SPI::Error> { + // let mut buf = [0, 0, 0]; + // adc.write_reg(Register::AdcMode, &mut buf)?; + // let mut buf = [0, 1, 0]; + // adc.write_reg(Register::IfMode, &mut buf)?; + // let mut buf = [0, 0, 0]; + // adc.write_reg(Register::GpioCon, &mut buf)?; + + // Ok(()) + // } + + /// Returns the channel the data is from + pub fn data_ready(&mut self) -> Result, AdcError> { + self.read_reg(&Status) + .map(|status| { + if status.ready() { + Some(status.channel()) + } else { + None + } + }) + } + + /// Get data + pub fn read_data(&mut self) -> Result> { + self.read_reg(&Data) + .map(|data| data.data()) + } + + fn read_reg(&mut self, reg: &R) -> Result> { + let mut reg_data = R::Data::empty(); + let address = 0x40 | reg.address(); + let mut checksum = Checksum::new(self.checksum_mode); + checksum.feed(address); + let checksum_out = checksum.result(); + let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?; + for &mut b in reg_data.as_mut() { + checksum.feed(b); + } + let checksum_expected = checksum.result(); + if checksum_expected != checksum_in { + return Err(AdcError::ChecksumMismatch(checksum_expected, checksum_in)); + } + Ok(reg_data) + } + + fn write_reg(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), AdcError> { + let address = reg.address(); + let mut checksum = Checksum::new(match self.checksum_mode { + ChecksumMode::Off => ChecksumMode::Off, + // write checksums are always crc + ChecksumMode::Xor => ChecksumMode::Crc, + ChecksumMode::Crc => ChecksumMode::Crc, + }); + checksum.feed(address); + for &mut b in reg_data.as_mut() { + checksum.feed(b); + } + let checksum_out = checksum.result(); + self.transfer(address, reg_data.as_mut(), checksum_out)?; + Ok(()) + } + + fn update_reg(&mut self, reg: &R, f: F) -> Result> + where + R: Register, + F: FnOnce(&mut R::Data) -> A, + { + let mut reg_data = self.read_reg(reg)?; + let result = f(&mut reg_data); + self.write_reg(reg, &mut reg_data)?; + Ok(result) + } + + pub fn reset(&mut self) -> Result<(), SPI::Error> { + let mut buf = [0xFFu8; 8]; + let _ = self.nss.set_low(); + let result = self.spi.transfer(&mut buf); + let _ = self.nss.set_high(); + result?; + Ok(()) + } + + fn transfer<'w>(&mut self, addr: u8, reg_data: &'w mut [u8], checksum: Option) -> Result, SPI::Error> { + let mut addr_buf = [addr]; + + let _ = self.nss.set_low(); + let result = match self.spi.transfer(&mut addr_buf) { + Ok(_) => self.spi.transfer(reg_data), + Err(e) => Err(e), + }; + let result = match (result, checksum) { + (Ok(_),None) => + Ok(None), + (Ok(_), Some(checksum_out)) => { + let mut checksum_buf = [checksum_out; 1]; + match self.spi.transfer(&mut checksum_buf) { + Ok(_) => Ok(Some(checksum_buf[0])), + Err(e) => Err(e), + } + } + (Err(e), _) => + Err(e), + }; + let _ = self.nss.set_high(); + + result + } +} diff --git a/firmware/src/ad7172/checksum.rs b/firmware/src/ad7172/checksum.rs new file mode 100644 index 0000000..eea239b --- /dev/null +++ b/firmware/src/ad7172/checksum.rs @@ -0,0 +1,44 @@ +#[derive(Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum ChecksumMode { + Off = 0b00, + /// Seems much less reliable than `Crc` + Xor = 0b01, + Crc = 0b10, +} + +pub struct Checksum { + mode: ChecksumMode, + state: u8, +} + +impl Checksum { + pub fn new(mode: ChecksumMode) -> Self { + Checksum { mode, state: 0 } + } + + pub fn feed(&mut self, input: u8) { + match self.mode { + ChecksumMode::Off => {}, + ChecksumMode::Xor => self.state ^= input, + ChecksumMode::Crc => { + for i in 0..8 { + let input_mask = 0x80 >> i; + self.state = (self.state << 1) ^ + if ((self.state & 0x80) != 0) != ((input & input_mask) != 0) { + 0x07 /* x8 + x2 + x + 1 */ + } else { + 0 + }; + } + } + } + } + + pub fn result(&self) -> Option { + match self.mode { + ChecksumMode::Off => None, + _ => Some(self.state) + } + } +} diff --git a/firmware/src/ad7172/mod.rs b/firmware/src/ad7172/mod.rs new file mode 100644 index 0000000..b57f70c --- /dev/null +++ b/firmware/src/ad7172/mod.rs @@ -0,0 +1,32 @@ +mod regs; +mod checksum; +pub use checksum::ChecksumMode; +mod adc; +pub use adc::*; + +#[repr(u8)] +pub enum Input { + Ain0 = 0, + Ain1 = 1, + Ain2 = 2, + Ain3 = 3, + Ain4 = 4, + TemperaturePos = 17, + TemperatureNeg = 18, + AnalogSupplyPos = 19, + AnalogSupplyNeg = 20, + RefPos = 21, + RefNeg = 22, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum AdcError { + SPI(SPI), + ChecksumMismatch(Option, Option), +} + +impl From for AdcError { + fn from(e: SPI) -> Self { + AdcError::SPI(e) + } +} diff --git a/firmware/src/ad7172/regs.rs b/firmware/src/ad7172/regs.rs new file mode 100644 index 0000000..c93dcb0 --- /dev/null +++ b/firmware/src/ad7172/regs.rs @@ -0,0 +1,152 @@ +use byteorder::{BigEndian, ByteOrder}; +use bit_field::BitField; + +use super::*; + +pub trait Register { + type Data: RegisterData; + fn address(&self) -> u8; +} +pub trait RegisterData { + fn empty() -> Self; + fn as_mut(&mut self) -> &mut [u8]; +} + +macro_rules! def_reg { + ($Reg: ident, $reg: ident, $addr: expr, $size: expr) => { + pub struct $Reg; + impl Register for $Reg { + type Data = $reg::Data; + fn address(&self) -> u8 { + $addr + } + } + mod $reg { + pub struct Data(pub [u8; $size]); + impl super::RegisterData for Data { + fn empty() -> Self { + Data([0; $size]) + } + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + } + }; + ($Reg: ident, $index: ty, $reg: ident, $addr: expr, $size: expr) => { + struct $Reg { pub index: $index, } + impl Register for $Reg { + type Data = $reg::Data; + fn address(&self) -> u8 { + $addr + (self.index as u8) + } + } + mod $reg { + pub struct Data(pub [u8; $size]); + impl super::RegisterData for Data { + fn empty() -> Self { + Data([0; $size]) + } + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + } + } +} + +def_reg!(Status, status, 0x00, 1); +impl status::Data { + /// Is there new data to read? + pub fn ready(&self) -> bool { + ! self.0[0].get_bit(7) + } + + /// Channel for which data is ready + pub fn channel(&self) -> u8 { + self.0[0].get_bits(0..=1) + } + + pub fn adc_error(&self) -> bool { + self.0[0].get_bit(6) + } + + pub fn crc_error(&self) -> bool { + self.0[0].get_bit(5) + } + + pub fn reg_error(&self) -> bool { + self.0[0].get_bit(4) + } +} + +def_reg!(IfMode, if_mode, 0x02, 2); +impl if_mode::Data { + pub fn set_crc(&mut self, mode: ChecksumMode) { + self.0[1].set_bits(2..=3, mode as u8); + } +} + +def_reg!(Data, data, 0x04, 3); +impl data::Data { + pub fn data(&self) -> u32 { + (u32::from(self.0[0]) << 16) | + (u32::from(self.0[1]) << 8) | + u32::from(self.0[2]) + } +} + +def_reg!(Id, id, 0x07, 2); +impl id::Data { + pub fn id(&self) -> u16 { + BigEndian::read_u16(&self.0) + } +} + +def_reg!(Channel, u8, channel, 0x10, 2); +impl channel::Data { + pub fn enabled(&self) -> bool { + self.0[0].get_bit(7) + } + + pub fn set_enabled(&mut self, value: bool) { + self.0[0].set_bit(7, value); + } +} + +def_reg!(SetupCon, u8, setup_con, 0x20, 2); + +def_reg!(FiltCon, u8, filt_con, 0x80, 2); + +// #[allow(unused)] +// #[derive(Clone, Copy)] +// #[repr(u8)] +// pub enum Register { +// Status = 0x00, +// AdcMode = 0x01, +// IfMode = 0x02, +// RegCheck = 0x03, +// Data = 0x04, +// GpioCon = 0x06, +// Id = 0x07, +// Ch0 = 0x10, +// Ch1 = 0x11, +// Ch2 = 0x12, +// Ch3 = 0x13, +// SetupCon0 = 0x20, +// SetupCon1 = 0x21, +// SetupCon2 = 0x22, +// SetupCon3 = 0x23, +// FiltCon0 = 0x28, +// FiltCon1 = 0x29, +// FiltCon2 = 0x2A, +// FiltCon3 = 0x2B, +// Offset0 = 0x30, +// Offset1 = 0x31, +// Offset2 = 0x32, +// Offset3 = 0x33, +// Gain0 = 0x38, +// Gain1 = 0x39, +// Gain2 = 0x3A, +// Gain3 = 0x3B, +// }