AD7172: Add AD7172 drivers fns
- Port from Thermostat Firmware
This commit is contained in:
parent
e355e83d28
commit
af8d361b95
|
@ -366,6 +366,8 @@ name = "kirdy"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 1.0.0",
|
"bare-metal 1.0.0",
|
||||||
|
"bit_field",
|
||||||
|
"byteorder",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-log",
|
"cortex-m-log",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
|
|
|
@ -34,6 +34,8 @@ rtt-target = { version = "0.3.1", features = ["cortex-m"] }
|
||||||
miniconf = "0.6.3"
|
miniconf = "0.6.3"
|
||||||
serde = { version = "1.0.158", features = ["derive"], default-features = false }
|
serde = { version = "1.0.158", features = ["derive"], default-features = false }
|
||||||
sfkv = "0.1"
|
sfkv = "0.1"
|
||||||
|
bit_field = "0.10"
|
||||||
|
byteorder = { version = "1", default-features = false }
|
||||||
[features]
|
[features]
|
||||||
semihosting = ["cortex-m-log/semihosting"]
|
semihosting = ["cortex-m-log/semihosting"]
|
||||||
RTT = []
|
RTT = []
|
||||||
|
|
|
@ -0,0 +1,276 @@
|
||||||
|
use core::fmt;
|
||||||
|
use log::{info, warn};
|
||||||
|
use stm32f4xx_hal::hal::{
|
||||||
|
blocking::spi::Transfer,
|
||||||
|
digital::v2::OutputPin,
|
||||||
|
};
|
||||||
|
use uom::si::{
|
||||||
|
f64::ElectricPotential,
|
||||||
|
electric_potential::volt,
|
||||||
|
};
|
||||||
|
use super::{
|
||||||
|
regs::{self, Register, RegisterData},
|
||||||
|
checksum::{ChecksumMode, Checksum},
|
||||||
|
Mode, Input, RefSource, PostFilter, DigitalFilterOrder,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// AD7172-2 implementation
|
||||||
|
///
|
||||||
|
/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf)
|
||||||
|
pub struct Adc<SPI: Transfer<u8>, NSS: OutputPin> {
|
||||||
|
spi: SPI,
|
||||||
|
nss: NSS,
|
||||||
|
checksum_mode: ChecksumMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS> {
|
||||||
|
pub fn new(spi: SPI, mut nss: NSS) -> Result<Self, SPI::Error> {
|
||||||
|
let _ = nss.set_high();
|
||||||
|
let mut adc = Adc {
|
||||||
|
spi, nss,
|
||||||
|
checksum_mode: ChecksumMode::Off,
|
||||||
|
};
|
||||||
|
adc.reset()?;
|
||||||
|
adc.set_checksum_mode(ChecksumMode::Crc).unwrap();
|
||||||
|
|
||||||
|
let mut retries = 0;
|
||||||
|
let mut adc_id;
|
||||||
|
loop {
|
||||||
|
adc_id = adc.identify()?;
|
||||||
|
if adc_id & 0xFFF0 == 0x00D0 {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("ADC id: {:04X} ({} retries)", adc_id, retries);
|
||||||
|
|
||||||
|
let mut adc_mode = <regs::AdcMode as Register>::Data::empty();
|
||||||
|
adc_mode.set_ref_en(true);
|
||||||
|
adc_mode.set_mode(Mode::Standby);
|
||||||
|
adc.write_reg(®s::AdcMode, &mut adc_mode)?;
|
||||||
|
|
||||||
|
Ok(adc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `0x00DX` for AD7172-2
|
||||||
|
pub fn identify(&mut self) -> Result<u16, SPI::Error> {
|
||||||
|
self.read_reg(®s::Id)
|
||||||
|
.map(|id| id.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), SPI::Error> {
|
||||||
|
// Cannot use update_reg() here because checksum_mode is
|
||||||
|
// updated between read_reg() and write_reg().
|
||||||
|
let mut ifmode = self.read_reg(®s::IfMode)?;
|
||||||
|
ifmode.set_crc(mode);
|
||||||
|
self.checksum_mode = mode;
|
||||||
|
self.write_reg(®s::IfMode, &mut ifmode)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sync_enable(&mut self, enable: bool) -> Result<(), SPI::Error> {
|
||||||
|
self.update_reg(®s::GpioCon, |data| {
|
||||||
|
data.set_sync_en(enable);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_channel(
|
||||||
|
&mut self, index: u8, in_pos: Input, in_neg: Input
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_reg(®s::SetupCon { index }, |data| {
|
||||||
|
data.set_bipolar(false);
|
||||||
|
data.set_refbuf_pos(true);
|
||||||
|
data.set_refbuf_neg(true);
|
||||||
|
data.set_ainbuf_pos(true);
|
||||||
|
data.set_ainbuf_neg(true);
|
||||||
|
data.set_ref_sel(RefSource::External);
|
||||||
|
})?;
|
||||||
|
self.update_reg(®s::FiltCon { index }, |data| {
|
||||||
|
data.set_enh_filt_en(true);
|
||||||
|
data.set_enh_filt(PostFilter::F16SPS);
|
||||||
|
data.set_order(DigitalFilterOrder::Sinc5Sinc1);
|
||||||
|
// output data rate: 10 Hz
|
||||||
|
data.set_odr(0b10011);
|
||||||
|
})?;
|
||||||
|
self.update_reg(®s::Channel { index }, |data| {
|
||||||
|
data.set_setup(index);
|
||||||
|
data.set_enabled(true);
|
||||||
|
data.set_a_in_pos(in_pos);
|
||||||
|
data.set_a_in_neg(in_neg);
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_calibration(&mut self, index: u8) -> Result<ChannelCalibration, SPI::Error> {
|
||||||
|
let offset = self.read_reg(®s::Offset { index })?.offset();
|
||||||
|
let gain = self.read_reg(®s::Gain { index })?.gain();
|
||||||
|
let bipolar = self.read_reg(®s::SetupCon { index })?.bipolar();
|
||||||
|
Ok(ChannelCalibration { offset, gain, bipolar })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_continuous_conversion(&mut self) -> Result<(), SPI::Error> {
|
||||||
|
let mut adc_mode = <regs::AdcMode as Register>::Data::empty();
|
||||||
|
adc_mode.set_ref_en(true);
|
||||||
|
adc_mode.set_mode(Mode::ContinuousConversion);
|
||||||
|
self.write_reg(®s::AdcMode, &mut adc_mode)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_postfilter(&mut self, index: u8) -> Result<Option<PostFilter>, SPI::Error> {
|
||||||
|
self.read_reg(®s::FiltCon { index })
|
||||||
|
.map(|data| {
|
||||||
|
if data.enh_filt_en() {
|
||||||
|
Some(data.enh_filt())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_postfilter(&mut self, index: u8, filter: Option<PostFilter>) -> Result<(), SPI::Error> {
|
||||||
|
self.update_reg(®s::FiltCon { index }, |data| {
|
||||||
|
match filter {
|
||||||
|
None => data.set_enh_filt_en(false),
|
||||||
|
Some(filter) => {
|
||||||
|
data.set_enh_filt_en(true);
|
||||||
|
data.set_enh_filt(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the channel the data is from
|
||||||
|
pub fn data_ready(&mut self) -> Result<Option<u8>, SPI::Error> {
|
||||||
|
self.read_reg(®s::Status)
|
||||||
|
.map(|status| {
|
||||||
|
if status.ready() {
|
||||||
|
Some(status.channel())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get data
|
||||||
|
pub fn read_data(&mut self) -> Result<u32, SPI::Error> {
|
||||||
|
self.read_reg(®s::Data)
|
||||||
|
.map(|data| data.data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_reg<R: regs::Register>(&mut self, reg: &R) -> Result<R::Data, SPI::Error> {
|
||||||
|
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();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?;
|
||||||
|
|
||||||
|
checksum.feed(®_data);
|
||||||
|
let checksum_expected = checksum.result();
|
||||||
|
if checksum_expected == checksum_in {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Retry
|
||||||
|
warn!("read_reg {:02X}: checksum error: {:?}!={:?}, retrying", reg.address(), checksum_expected, checksum_in);
|
||||||
|
}
|
||||||
|
Ok(reg_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_reg<R: regs::Register>(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), SPI::Error> {
|
||||||
|
loop {
|
||||||
|
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]);
|
||||||
|
checksum.feed(®_data);
|
||||||
|
let checksum_out = checksum.result();
|
||||||
|
|
||||||
|
let mut data = reg_data.clone();
|
||||||
|
self.transfer(address, data.as_mut(), checksum_out)?;
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
let readback_data = self.read_reg(reg)?;
|
||||||
|
if *readback_data == **reg_data {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
warn!("write_reg {:02X}: readback error, {:?}!={:?}, retrying", address, &*readback_data, &**reg_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_reg<R, F, A>(&mut self, reg: &R, f: F) -> Result<A, SPI::Error>
|
||||||
|
where
|
||||||
|
R: regs::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<u8>) -> Result<Option<u8>, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChannelCalibration {
|
||||||
|
offset: u32,
|
||||||
|
gain: u32,
|
||||||
|
bipolar: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChannelCalibration {
|
||||||
|
pub fn convert_data(&self, data: u32) -> ElectricPotential {
|
||||||
|
let data = if self.bipolar {
|
||||||
|
(data as i32 - 0x80_0000) as f64
|
||||||
|
} else {
|
||||||
|
data as f64 / 2.0
|
||||||
|
};
|
||||||
|
let data = data / (self.gain as f64 / (0x40_0000 as f64));
|
||||||
|
let data = data + (self.offset as i32 - 0x80_0000) as f64;
|
||||||
|
let data = data / (2 << 23) as f64;
|
||||||
|
|
||||||
|
const V_REF: f64 = 3.3;
|
||||||
|
ElectricPotential::new::<volt>(data * V_REF / 0.75)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum ChecksumMode {
|
||||||
|
Off = 0b00,
|
||||||
|
/// Seems much less reliable than `Crc`
|
||||||
|
Xor = 0b01,
|
||||||
|
Crc = 0b10,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for ChecksumMode {
|
||||||
|
fn from(x: u8) -> Self {
|
||||||
|
match x {
|
||||||
|
0 => ChecksumMode::Off,
|
||||||
|
1 => ChecksumMode::Xor,
|
||||||
|
_ => ChecksumMode::Crc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Checksum {
|
||||||
|
mode: ChecksumMode,
|
||||||
|
state: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Checksum {
|
||||||
|
pub fn new(mode: ChecksumMode) -> Self {
|
||||||
|
Checksum { mode, state: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn feed_byte(&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 feed(&mut self, input: &[u8]) {
|
||||||
|
for &b in input {
|
||||||
|
self.feed_byte(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn result(&self) -> Option<u8> {
|
||||||
|
match self.mode {
|
||||||
|
ChecksumMode::Off => None,
|
||||||
|
_ => Some(self.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
use core::fmt;
|
||||||
|
use num_traits::float::Float;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use fugit::MegahertzU32;
|
||||||
|
use stm32f4xx_hal::spi;
|
||||||
|
|
||||||
|
pub mod regs;
|
||||||
|
mod checksum;
|
||||||
|
pub use checksum::ChecksumMode;
|
||||||
|
mod adc;
|
||||||
|
pub use adc::*;
|
||||||
|
|
||||||
|
/// SPI Mode 3
|
||||||
|
pub const SPI_MODE: spi::Mode = spi::Mode {
|
||||||
|
polarity: spi::Polarity::IdleHigh,
|
||||||
|
phase: spi::Phase::CaptureOnSecondTransition,
|
||||||
|
};
|
||||||
|
/// 2 MHz
|
||||||
|
pub const SPI_CLOCK_MHZ: MegahertzU32 = MegahertzU32::from_raw(2);
|
||||||
|
|
||||||
|
pub const MAX_VALUE: u32 = 0xFF_FFFF;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Mode {
|
||||||
|
ContinuousConversion = 0b000,
|
||||||
|
SingleConversion = 0b001,
|
||||||
|
Standby = 0b010,
|
||||||
|
PowerDown = 0b011,
|
||||||
|
InternalOffsetCalibration = 0b100,
|
||||||
|
Invalid,
|
||||||
|
SystemOffsetCalibration = 0b110,
|
||||||
|
SystemGainCalibration = 0b111,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Mode {
|
||||||
|
fn from(x: u8) -> Self {
|
||||||
|
use Mode::*;
|
||||||
|
match x {
|
||||||
|
0b000 => ContinuousConversion,
|
||||||
|
0b001 => SingleConversion,
|
||||||
|
0b010 => Standby,
|
||||||
|
0b011 => PowerDown,
|
||||||
|
0b100 => InternalOffsetCalibration,
|
||||||
|
0b110 => SystemOffsetCalibration,
|
||||||
|
0b111 => SystemGainCalibration,
|
||||||
|
_ => Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[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,
|
||||||
|
Invalid = 0b11111,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Input {
|
||||||
|
fn from(x: u8) -> Self {
|
||||||
|
match x {
|
||||||
|
0 => Input::Ain0,
|
||||||
|
1 => Input::Ain1,
|
||||||
|
2 => Input::Ain2,
|
||||||
|
3 => Input::Ain3,
|
||||||
|
4 => Input::Ain4,
|
||||||
|
17 => Input::TemperaturePos,
|
||||||
|
18 => Input::TemperatureNeg,
|
||||||
|
19 => Input::AnalogSupplyPos,
|
||||||
|
20 => Input::AnalogSupplyNeg,
|
||||||
|
21 => Input::RefPos,
|
||||||
|
22 => Input::RefNeg,
|
||||||
|
_ => Input::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Input {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
use Input::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Ain0 => "ain0",
|
||||||
|
Ain1 => "ain1",
|
||||||
|
Ain2 => "ain2",
|
||||||
|
Ain3 => "ain3",
|
||||||
|
Ain4 => "ain4",
|
||||||
|
TemperaturePos => "temperature+",
|
||||||
|
TemperatureNeg => "temperature-",
|
||||||
|
AnalogSupplyPos => "analogsupply+",
|
||||||
|
AnalogSupplyNeg => "analogsupply-",
|
||||||
|
RefPos => "ref+",
|
||||||
|
RefNeg => "ref-",
|
||||||
|
_ => "<INVALID>",
|
||||||
|
}.fmt(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reference source for ADC conversion
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum RefSource {
|
||||||
|
/// External reference
|
||||||
|
External = 0b00,
|
||||||
|
/// Internal 2.5V reference
|
||||||
|
Internal = 0b10,
|
||||||
|
/// AVDD1 − AVSS
|
||||||
|
Avdd1MinusAvss = 0b11,
|
||||||
|
Invalid = 0b01,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for RefSource {
|
||||||
|
fn from(x: u8) -> Self {
|
||||||
|
match x {
|
||||||
|
0 => RefSource::External,
|
||||||
|
1 => RefSource::Internal,
|
||||||
|
2 => RefSource::Avdd1MinusAvss,
|
||||||
|
_ => RefSource::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RefSource {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
use RefSource::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
External => "external",
|
||||||
|
Internal => "internal",
|
||||||
|
Avdd1MinusAvss => "avdd1-avss",
|
||||||
|
_ => "<INVALID>",
|
||||||
|
}.fmt(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum PostFilter {
|
||||||
|
/// 27 SPS, 47 dB rejection, 36.7 ms settling
|
||||||
|
F27SPS = 0b010,
|
||||||
|
/// 21.25 SPS, 62 dB rejection, 40 ms settling
|
||||||
|
F21SPS = 0b011,
|
||||||
|
/// 20 SPS, 86 dB rejection, 50 ms settling
|
||||||
|
F20SPS = 0b101,
|
||||||
|
/// 16.67 SPS, 92 dB rejection, 60 ms settling
|
||||||
|
F16SPS = 0b110,
|
||||||
|
Invalid = 0b111,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostFilter {
|
||||||
|
pub const VALID_VALUES: &'static [Self] = &[
|
||||||
|
PostFilter::F27SPS,
|
||||||
|
PostFilter::F21SPS,
|
||||||
|
PostFilter::F20SPS,
|
||||||
|
PostFilter::F16SPS,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn closest(rate: f32) -> Option<Self> {
|
||||||
|
let mut best: Option<(f32, Self)> = None;
|
||||||
|
for value in Self::VALID_VALUES {
|
||||||
|
let error = (rate - value.output_rate().unwrap()).abs();
|
||||||
|
let better = best
|
||||||
|
.map(|(best_error, _)| error < best_error)
|
||||||
|
.unwrap_or(true);
|
||||||
|
if better {
|
||||||
|
best = Some((error, *value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best.map(|(_, best)| best)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Samples per Second
|
||||||
|
pub fn output_rate(&self) -> Option<f32> {
|
||||||
|
match self {
|
||||||
|
PostFilter::F27SPS => Some(27.0),
|
||||||
|
PostFilter::F21SPS => Some(21.25),
|
||||||
|
PostFilter::F20SPS => Some(20.0),
|
||||||
|
PostFilter::F16SPS => Some(16.67),
|
||||||
|
PostFilter::Invalid => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for PostFilter {
|
||||||
|
fn from(x: u8) -> Self {
|
||||||
|
match x {
|
||||||
|
0b010 => PostFilter::F27SPS,
|
||||||
|
0b011 => PostFilter::F21SPS,
|
||||||
|
0b101 => PostFilter::F20SPS,
|
||||||
|
0b110 => PostFilter::F16SPS,
|
||||||
|
_ => PostFilter::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum DigitalFilterOrder {
|
||||||
|
Sinc5Sinc1 = 0b00,
|
||||||
|
Sinc3 = 0b11,
|
||||||
|
Invalid = 0b10,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for DigitalFilterOrder {
|
||||||
|
fn from(x: u8) -> Self {
|
||||||
|
match x {
|
||||||
|
0b00 => DigitalFilterOrder::Sinc5Sinc1,
|
||||||
|
0b11 => DigitalFilterOrder::Sinc3,
|
||||||
|
_ => DigitalFilterOrder::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,267 @@
|
||||||
|
use core::ops::{Deref, DerefMut};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use bit_field::BitField;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub trait Register {
|
||||||
|
type Data: RegisterData;
|
||||||
|
fn address(&self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RegisterData: Clone + Deref<Target=[u8]> + DerefMut {
|
||||||
|
fn empty() -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! def_reg {
|
||||||
|
($Reg: ident, $reg: ident, $addr: expr, $size: expr) => {
|
||||||
|
/// AD7172 register
|
||||||
|
pub struct $Reg;
|
||||||
|
impl Register for $Reg {
|
||||||
|
/// Register contents
|
||||||
|
type Data = $reg::Data;
|
||||||
|
/// Register address
|
||||||
|
fn address(&self) -> u8 {
|
||||||
|
$addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod $reg {
|
||||||
|
/// Register contents
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Data(pub [u8; $size]);
|
||||||
|
impl super::RegisterData for Data {
|
||||||
|
/// Generate zeroed register contents
|
||||||
|
fn empty() -> Self {
|
||||||
|
Data([0; $size])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::ops::Deref for Data {
|
||||||
|
type Target = [u8];
|
||||||
|
fn deref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::ops::DerefMut for Data {
|
||||||
|
fn deref_mut(&mut self) -> &mut [u8] {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($Reg: ident, u8, $reg: ident, $addr: expr, $size: expr) => {
|
||||||
|
pub struct $Reg { pub index: u8, }
|
||||||
|
impl Register for $Reg {
|
||||||
|
type Data = $reg::Data;
|
||||||
|
fn address(&self) -> u8 {
|
||||||
|
$addr + self.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod $reg {
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Data(pub [u8; $size]);
|
||||||
|
impl super::RegisterData for Data {
|
||||||
|
fn empty() -> Self {
|
||||||
|
Data([0; $size])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::ops::Deref for Data {
|
||||||
|
type Target = [u8];
|
||||||
|
fn deref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::ops::DerefMut for Data {
|
||||||
|
fn deref_mut(&mut self) -> &mut [u8] {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! reg_bit {
|
||||||
|
($getter: ident, $byte: expr, $bit: expr, $doc: expr) => {
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $getter(&self) -> bool {
|
||||||
|
self.0[$byte].get_bit($bit)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($getter: ident, $setter: ident, $byte: expr, $bit: expr, $doc: expr) => {
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $getter(&self) -> bool {
|
||||||
|
self.0[$byte].get_bit($bit)
|
||||||
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $setter(&mut self, value: bool) {
|
||||||
|
self.0[$byte].set_bit($bit, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! reg_bits {
|
||||||
|
($getter: ident, $byte: expr, $bits: expr, $doc: expr) => {
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $getter(&self) -> u8 {
|
||||||
|
self.0[$byte].get_bits($bits)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($getter: ident, $setter: ident, $byte: expr, $bits: expr, $doc: expr) => {
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $getter(&self) -> u8 {
|
||||||
|
self.0[$byte].get_bits($bits)
|
||||||
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $setter(&mut self, value: u8) {
|
||||||
|
self.0[$byte].set_bits($bits, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($getter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => {
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $getter(&self) -> $ty {
|
||||||
|
self.0[$byte].get_bits($bits) as $ty
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($getter: ident, $setter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => {
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $getter(&self) -> $ty {
|
||||||
|
self.0[$byte].get_bits($bits).into()
|
||||||
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
#[doc = $doc]
|
||||||
|
pub fn $setter(&mut self, value: $ty) {
|
||||||
|
self.0[$byte].set_bits($bits, value as u8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
def_reg!(Status, status, 0x00, 1);
|
||||||
|
impl status::Data {
|
||||||
|
/// Is there new data to read?
|
||||||
|
pub fn ready(&self) -> bool {
|
||||||
|
! self.not_ready()
|
||||||
|
}
|
||||||
|
|
||||||
|
reg_bit!(not_ready, 0, 7, "No data ready indicator");
|
||||||
|
reg_bits!(channel, 0, 0..=1, "Channel for which data is ready");
|
||||||
|
reg_bit!(adc_error, 0, 6, "ADC error");
|
||||||
|
reg_bit!(crc_error, 0, 5, "SPI CRC error");
|
||||||
|
reg_bit!(reg_error, 0, 4, "Register error");
|
||||||
|
}
|
||||||
|
|
||||||
|
def_reg!(AdcMode, adc_mode, 0x01, 2);
|
||||||
|
impl adc_mode::Data {
|
||||||
|
reg_bits!(delay, set_delay, 0, 0..=2, "Delay after channel switch");
|
||||||
|
reg_bit!(sing_cyc, set_sing_cyc, 0, 5, "Can only used with single channel");
|
||||||
|
reg_bit!(hide_delay, set_hide_delay, 0, 6, "Hide delay");
|
||||||
|
reg_bit!(ref_en, set_ref_en, 0, 7, "Enable internal reference, output buffered 2.5 V to REFOUT");
|
||||||
|
reg_bits!(clockset, set_clocksel, 1, 2..=3, "Clock source");
|
||||||
|
reg_bits!(mode, set_mode, 1, 4..=6, Mode, "Operating mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
def_reg!(IfMode, if_mode, 0x02, 2);
|
||||||
|
impl if_mode::Data {
|
||||||
|
reg_bits!(crc, set_crc, 1, 2..=3, ChecksumMode, "SPI checksum mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
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!(GpioCon, gpio_con, 0x06, 2);
|
||||||
|
impl gpio_con::Data {
|
||||||
|
reg_bit!(sync_en, set_sync_en, 0, 3, "Enables the SYNC/ERROR pin as a sync input");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
reg_bit!(enabled, set_enabled, 0, 7, "Channel enabled");
|
||||||
|
reg_bits!(setup, set_setup, 0, 4..=5, "Setup number");
|
||||||
|
|
||||||
|
/// Which input is connected to positive input of this channel
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn a_in_pos(&self) -> Input {
|
||||||
|
((self.0[0].get_bits(0..=1) << 3) |
|
||||||
|
self.0[1].get_bits(5..=7)).into()
|
||||||
|
}
|
||||||
|
/// Set which input is connected to positive input of this channel
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn set_a_in_pos(&mut self, value: Input) {
|
||||||
|
let value = value as u8;
|
||||||
|
self.0[0].set_bits(0..=1, value >> 3);
|
||||||
|
self.0[1].set_bits(5..=7, value & 0x7);
|
||||||
|
}
|
||||||
|
reg_bits!(a_in_neg, set_a_in_neg, 1, 0..=4, Input,
|
||||||
|
"Which input is connected to negative input of this channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
def_reg!(SetupCon, u8, setup_con, 0x20, 2);
|
||||||
|
impl setup_con::Data {
|
||||||
|
reg_bit!(bipolar, set_bipolar, 0, 4, "Unipolar (`false`) or bipolar (`true`) coded output");
|
||||||
|
reg_bit!(refbuf_pos, set_refbuf_pos, 0, 3, "Enable REF+ input buffer");
|
||||||
|
reg_bit!(refbuf_neg, set_refbuf_neg, 0, 2, "Enable REF- input buffer");
|
||||||
|
reg_bit!(ainbuf_pos, set_ainbuf_pos, 0, 1, "Enable AIN+ input buffer");
|
||||||
|
reg_bit!(ainbuf_neg, set_ainbuf_neg, 0, 0, "Enable AIN- input buffer");
|
||||||
|
reg_bit!(burnout_en, 1, 7, "enables a 10 µA current source on the positive analog input selected and a 10 µA current sink on the negative analog input selected");
|
||||||
|
reg_bits!(ref_sel, set_ref_sel, 1, 4..=5, RefSource, "Select reference source for conversion");
|
||||||
|
}
|
||||||
|
|
||||||
|
def_reg!(FiltCon, u8, filt_con, 0x28, 2);
|
||||||
|
impl filt_con::Data {
|
||||||
|
reg_bit!(sinc3_map, 0, 7, "If set, mapping of filter register changes to directly program the decimation rate of the sinc3 filter");
|
||||||
|
reg_bit!(enh_filt_en, set_enh_filt_en, 0, 3, "Enable postfilters for enhanced 50Hz and 60Hz rejection");
|
||||||
|
reg_bits!(enh_filt, set_enh_filt, 0, 0..=2, PostFilter, "Select postfilters for enhanced 50Hz and 60Hz rejection");
|
||||||
|
reg_bits!(order, set_order, 1, 5..=6, DigitalFilterOrder, "order of the digital filter that processes the modulator data");
|
||||||
|
reg_bits!(odr, set_odr, 1, 0..=4, "Output data rate");
|
||||||
|
}
|
||||||
|
|
||||||
|
def_reg!(Offset, u8, offset, 0x30, 3);
|
||||||
|
impl offset::Data {
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn offset(&self) -> u32 {
|
||||||
|
(u32::from(self.0[0]) << 16) |
|
||||||
|
(u32::from(self.0[1]) << 8) |
|
||||||
|
u32::from(self.0[2])
|
||||||
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn set_offset(&mut self, value: u32) {
|
||||||
|
self.0[0] = (value >> 16) as u8;
|
||||||
|
self.0[1] = (value >> 8) as u8;
|
||||||
|
self.0[2] = value as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def_reg!(Gain, u8, gain, 0x38, 3);
|
||||||
|
impl gain::Data {
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn gain(&self) -> u32 {
|
||||||
|
(u32::from(self.0[0]) << 16) |
|
||||||
|
(u32::from(self.0[1]) << 8) |
|
||||||
|
u32::from(self.0[2])
|
||||||
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn set_gain(&mut self, value: u32) {
|
||||||
|
self.0[0] = (value >> 16) as u8;
|
||||||
|
self.0[1] = (value >> 8) as u8;
|
||||||
|
self.0[2] = value as u8;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod ad5680;
|
pub mod ad5680;
|
||||||
pub mod max1968;
|
pub mod max1968;
|
||||||
pub mod thermostat;
|
pub mod thermostat;
|
||||||
|
pub mod ad7172;
|
Loading…
Reference in New Issue