use defmt::Format;
use enum_iterator::IntoEnumIterator;
use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
use smlang::statemachine;
use super::ad7172::{self, AdcChannel};
use super::hal::{
self, device,
gpio::{self, gpiob, gpioc, gpioe, ExtiPin},
hal::blocking::delay::DelayUs,
hal::digital::v2::PinState,
prelude::*,
rcc, spi, stm32,
};
use num_traits::float::Float;
#[derive(Copy, Clone, Debug, Format)]
pub struct AdcCode(u32);
impl AdcCode {
const GAIN: f32 = 0x555555 as _; const R_REF: f32 = 2.0 * 5000.0; const ZERO_C: f32 = 273.15; const B: f32 = 3988.0; const T_N: f32 = 25.0 + AdcCode::ZERO_C; const R_N: f32 = 10000.0;
const FS_PER_LSB: f32 = 0x400000 as f32 / (2.0 * (1 << 23) as f32 * AdcCode::GAIN * 0.75);
const R_REF_N: f32 = AdcCode::R_REF / AdcCode::R_N;
}
impl From<u32> for AdcCode {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<AdcCode> for u32 {
fn from(code: AdcCode) -> u32 {
code.0
}
}
impl From<AdcCode> for f32 {
fn from(code: AdcCode) -> f32 {
let relative_voltage = code.0 as f32 * AdcCode::FS_PER_LSB;
let relative_resistance = relative_voltage / (1.0 - relative_voltage) * AdcCode::R_REF_N;
let temperature_kelvin_inv =
1.0 / AdcCode::T_N + 1.0 / AdcCode::B * relative_resistance.ln();
1.0 / temperature_kelvin_inv - AdcCode::ZERO_C
}
}
impl From<AdcCode> for f64 {
fn from(code: AdcCode) -> f64 {
let relative_voltage = (code.0 as f32 * AdcCode::FS_PER_LSB) as f64;
let relative_resistance =
relative_voltage / (1.0 - relative_voltage) * AdcCode::R_REF_N as f64;
let temperature_kelvin_inv =
1.0 / AdcCode::T_N as f64 + 1.0 / AdcCode::B as f64 * relative_resistance.ln();
1.0 / temperature_kelvin_inv - AdcCode::ZERO_C as f64
}
}
#[derive(Clone, Copy, TryFromPrimitive, Debug, Format, PartialEq, IntoEnumIterator)]
#[repr(usize)]
pub enum InputChannel {
Zero = 0,
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
}
impl TryFrom<(AdcPhy, AdcChannel)> for InputChannel {
type Error = TryFromPrimitiveError<Self>;
fn try_from((phy, ch): (AdcPhy, AdcChannel)) -> Result<Self, Self::Error> {
Self::try_from(((phy as usize) << 1) + ch as usize)
}
}
#[derive(Clone, Copy, TryFromPrimitive, Debug, Format, IntoEnumIterator)]
#[repr(usize)]
pub enum AdcPhy {
Zero = 0,
One = 1,
Two = 2,
Three = 3,
}
impl AdcPhy {
pub fn next(&self, _ch: &AdcChannel) -> Self {
Self::try_from((*self as usize + 1) & 0x3).unwrap()
}
}
#[derive(Debug)]
pub enum Error {
Ident,
}
pub struct AdcPins {
pub spi: (
gpioe::PE2<gpio::Alternate<5>>,
gpioe::PE5<gpio::Alternate<5>>,
gpioe::PE6<gpio::Alternate<5>>,
),
pub cs: [gpio::ErasedPin<gpio::Output>; 4],
pub rdyn: gpioc::PC11<gpio::Input>,
pub sync: gpiob::PB11<gpio::Output<gpio::PushPull>>,
}
#[allow(clippy::complexity)]
pub struct Adc {
adcs: ad7172::Ad7172<hal::spi::Spi<hal::stm32::SPI4, hal::spi::Enabled>>,
cs: [gpio::ErasedPin<gpio::Output>; 4],
rdyn: gpioc::PC11<gpio::Input>,
sync: gpiob::PB11<gpio::Output<gpio::PushPull>>,
}
impl Adc {
pub fn new(
delay: &mut impl DelayUs<u16>,
clocks: &rcc::CoreClocks,
spi4_rec: rcc::rec::Spi4,
spi4: stm32::SPI4,
pins: AdcPins,
) -> Result<Self, Error> {
let rdyn_pullup = pins.rdyn.internal_pull_up(true);
let spi: spi::Spi<_, _, u8> =
spi4.spi(pins.spi, spi::MODE_3, 12500.kHz(), spi4_rec, clocks);
let mut adc = Adc {
adcs: ad7172::Ad7172::new(spi),
cs: pins.cs,
rdyn: rdyn_pullup,
sync: pins.sync,
};
adc.setup(delay)?;
Ok(adc)
}
fn setup(&mut self, delay: &mut impl DelayUs<u16>) -> Result<(), Error> {
for pin in self.cs.iter_mut() {
pin.set_state(PinState::High);
}
self.sync.set_low();
for phy in AdcPhy::into_enum_iter() {
self.selected(phy, |adc| adc.setup_adc(delay))?;
}
self.sync.set_high();
Ok(())
}
fn selected<F, R>(&mut self, phy: AdcPhy, func: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.cs[phy as usize].set_state(PinState::Low);
let res = func(self);
self.cs[phy as usize].set_state(PinState::High);
res
}
fn setup_adc(&mut self, delay: &mut impl DelayUs<u16>) -> Result<(), Error> {
self.adcs.reset();
delay.delay_us(500u16);
let id = self.adcs.read(ad7172::AdcReg::ID);
if id & 0xfff0 != 0x00d0 {
defmt::error!("invalid ID: {=u32:#x}", id);
return Err(Error::Ident);
}
self.adcs.write(
ad7172::AdcReg::ADCMODE,
ad7172::Adcmode::RefEn::ENABLED
| ad7172::Adcmode::Mode::CONTINOUS_CONVERSION
| ad7172::Adcmode::Clocksel::EXTERNAL_CLOCK,
);
self.adcs
.write(ad7172::AdcReg::IFMODE, ad7172::Ifmode::DataStat::ENABLED);
self.adcs.write(
ad7172::AdcReg::CH0,
ad7172::Channel::ChEn::ENABLED
| ad7172::Channel::SetupSel::SETUP_0
| ad7172::Channel::Ainpos::AIN0
| ad7172::Channel::Ainneg::AIN1,
);
self.adcs.write(
ad7172::AdcReg::CH1,
ad7172::Channel::ChEn::ENABLED
| ad7172::Channel::SetupSel::SETUP_0
| ad7172::Channel::Ainpos::AIN2
| ad7172::Channel::Ainneg::AIN3,
);
self.adcs.write(
ad7172::AdcReg::SETUPCON0,
ad7172::Setupcon::BiUnipolar::UNIPOLAR
| ad7172::Setupcon::Refbufn::ENABLED
| ad7172::Setupcon::Refbufp::ENABLED
| ad7172::Setupcon::Ainbufn::ENABLED
| ad7172::Setupcon::Ainbufp::ENABLED
| ad7172::Setupcon::Refsel::EXTERNAL,
);
self.adcs.write(
ad7172::AdcReg::FILTCON0,
ad7172::Filtcon::Order::SINC5SINC1 | ad7172::Filtcon::Odr::ODR_1007,
);
self.adcs
.write(ad7172::AdcReg::GPIOCON, ad7172::Gpiocon::SyncEn::ENABLED);
Ok(())
}
pub fn read_data(&mut self) -> (AdcCode, Option<ad7172::Status>) {
let (data, status) = self.adcs.read_data();
(data.into(), Some(status.into()))
}
}
pub mod sm {
use super::*;
statemachine! {
transitions: {
*Stopped + Start / start = Selected(AdcPhy),
Selected(AdcPhy) + Read(AdcChannel) / next = Selected(AdcPhy),
Selected(AdcPhy) + Stop / stop = Stopped,
}
}
}
impl sm::StateMachineContext for Adc {
fn start(&mut self) -> AdcPhy {
self.rdyn.clear_interrupt_pending_bit();
self.cs[AdcPhy::Zero as usize].set_state(PinState::Low);
AdcPhy::Zero
}
fn next(&mut self, phy: &AdcPhy, ch: &AdcChannel) -> AdcPhy {
self.cs[*phy as usize].set_state(PinState::High);
self.rdyn.clear_interrupt_pending_bit();
let next = phy.next(ch);
self.cs[next as usize].set_state(PinState::Low);
next
}
fn stop(&mut self, phy: &AdcPhy) {
self.cs[*phy as usize].set_state(PinState::High);
self.rdyn.clear_interrupt_pending_bit();
}
}
impl sm::StateMachine<Adc> {
pub fn start(&mut self, exti: &mut device::EXTI, syscfg: &mut device::SYSCFG) {
let adc = self.context_mut();
adc.rdyn.make_interrupt_source(syscfg);
adc.rdyn.trigger_on_edge(exti, gpio::Edge::Falling);
adc.rdyn.enable_interrupt(exti);
self.process_event(sm::Events::Start).unwrap();
}
pub fn handle_interrupt(&mut self) -> (InputChannel, AdcCode) {
if let sm::States::Selected(phy) = *self.state() {
let (code, status) = self.context_mut().read_data();
let adc_ch = status.unwrap().channel();
self.process_event(sm::Events::Read(adc_ch)).unwrap();
let input_ch = InputChannel::try_from((phy, adc_ch)).unwrap();
(input_ch, code)
} else {
panic!("Unexpected State")
}
}
}