use crate::gpio::{Analog, Pin};
use crate::pac;
use crate::rcu::{Clocks, Enable, Rcu, Reset};
const VREFINT_CAL_ADDR: *const u16 = 0x1FFFF7C0 as *const u16;
const VREFINT_CAL_BLANK: u16 = 0xFFFF;
const VREFINT_TYP_MV: i32 = 1200;
const NOMINAL_VDDA_MV: i32 = 3300;
const ADC_MAX_CODE: i32 = 4095;
const V25_MV: i32 = 1450;
const AVG_SLOPE_X10: i32 = 43;
const TEMP_CHANNEL: u8 = 16;
const VREF_CHANNEL: u8 = 17;
const CALIBRATION_DELAY_CYCLES: u32 = 14;
const TEMP_MIN_SAMPTIME_US_X10: u64 = 171;
const MAX_SAMPTIME_CYCLES_X10: u64 = 2395;
const US_PER_S: u64 = 1_000_000;
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)]
pub enum SampTime {
Cycles1_5 = 0b000,
Cycles7_5 = 0b001,
Cycles13_5 = 0b010,
Cycles28_5 = 0b011,
Cycles41_5 = 0b100,
Cycles55_5 = 0b101,
Cycles71_5 = 0b110,
Cycles239_5 = 0b111,
}
pub trait Channel {
const CHANNEL: u8;
}
macro_rules! channel {
($($port:literal $pin:literal => $channel:literal),+ $(,)?) => {
$(impl Channel for Pin<$port, $pin, Analog> { const CHANNEL: u8 = $channel; })+
};
}
channel!(
'A' 0 => 0,
'A' 1 => 1,
'A' 2 => 2,
'A' 3 => 3,
'A' 4 => 4,
'A' 5 => 5,
'A' 6 => 6,
'A' 7 => 7,
'B' 0 => 8,
'B' 1 => 9,
);
pub struct Adc {
adc: pac::Adc,
clocks: Clocks,
}
impl Adc {
pub fn new(rcu: &mut Rcu, adc: pac::Adc, clocks: Clocks) -> Self {
<pac::Adc as Enable>::enable(rcu);
<pac::Adc as Reset>::reset(rcu);
adc.ctl1().modify(|_, w| w.adcon().enabled());
cortex_m::asm::delay(
(CALIBRATION_DELAY_CYCLES * clocks.hclk().to_Hz()).div_ceil(clocks.ck_adc().to_Hz()),
);
adc.ctl1().modify(|_, w| w.rstclb().start());
adc.ctl1().modify(|_, w| w.clb().start());
while adc.ctl1().read().clb().is_not_complete() {}
adc.ctl1()
.modify(|_, w| w.eterc().enabled().etsrc().swrcst());
Self { adc, clocks }
}
fn set_channel(&self, channel: u8) {
self.adc.rsq0().modify(|_, w| w.rl().bits(0b0));
self.adc
.rsq2()
.modify(|_, w| unsafe { w.rsq0().bits(channel) });
}
fn set_sample_time(&self, channel: u8, time: SampTime) {
self.adc.sampt1().modify(|_, w| match channel {
0 => w.spt0().bits(time as u8),
1 => w.spt1().bits(time as u8),
2 => w.spt2().bits(time as u8),
3 => w.spt3().bits(time as u8),
4 => w.spt4().bits(time as u8),
5 => w.spt5().bits(time as u8),
6 => w.spt6().bits(time as u8),
7 => w.spt7().bits(time as u8),
8 => w.spt8().bits(time as u8),
9 => w.spt9().bits(time as u8),
_ => unreachable!(),
});
}
fn set_internal_channel(&self, channel: u8) {
self.set_channel(channel);
}
fn sample_time_sufficient(&self) -> bool {
TEMP_MIN_SAMPTIME_US_X10 * self.clocks.ck_adc().to_Hz() as u64
<= MAX_SAMPTIME_CYCLES_X10 * US_PER_S
}
fn set_internal_sample_time(&self, channel: u8, time: SampTime) {
self.adc.sampt0().modify(|_, w| match channel {
TEMP_CHANNEL => w.spt16().bits(time as u8),
VREF_CHANNEL => w.spt17().bits(time as u8),
_ => unreachable!(),
});
}
fn convert(&self) -> u16 {
self.adc.ctl1().modify(|_, w| w.swrcst().start());
while self.adc.ctl1().read().swrcst().is_not_started() {}
while self.adc.stat().read().eoc().is_not_complete() {}
self.adc.rdata().read().rdata().bits()
}
fn with_internal<R>(&self, f: impl FnOnce(&Self) -> R) -> R {
let was_enabled = self.adc.ctl1().read().tsvren().is_enabled();
if !was_enabled {
self.adc.ctl1().modify(|_, w| w.tsvren().enabled());
}
let result = f(self);
if !was_enabled {
self.adc.ctl1().modify(|_, w| w.tsvren().disabled());
}
result
}
pub fn read<PIN: Channel>(&self, _pin: &PIN, time: SampTime) -> u16 {
self.set_channel(PIN::CHANNEL);
self.set_sample_time(PIN::CHANNEL, time);
self.convert()
}
pub fn read_temperature(&self) -> Option<i32> {
if !self.sample_time_sufficient() {
return None;
}
let vdda_mv = self.read_vref();
let raw = self.with_internal(|s| {
s.set_internal_channel(TEMP_CHANNEL);
s.set_internal_sample_time(TEMP_CHANNEL, SampTime::Cycles239_5);
s.convert()
});
let v_temperature_mv = raw as i32 * vdda_mv / ADC_MAX_CODE;
let temperature_x10 = 100 * (V25_MV - v_temperature_mv) / AVG_SLOPE_X10 + 250;
Some(temperature_x10)
}
pub fn read_vref(&self) -> i32 {
let vrefint_cal = unsafe { core::ptr::read_volatile(VREFINT_CAL_ADDR) };
let raw = self.with_internal(|s| {
s.set_internal_channel(VREF_CHANNEL);
s.set_internal_sample_time(VREF_CHANNEL, SampTime::Cycles239_5);
s.convert()
}) as i32;
if vrefint_cal == VREFINT_CAL_BLANK {
VREFINT_TYP_MV * ADC_MAX_CODE / raw
} else {
NOMINAL_VDDA_MV * vrefint_cal as i32 / raw
}
}
}
pub trait AdcExt {
fn constrain(self, rcu: &mut Rcu, clocks: Clocks) -> Adc;
}
impl AdcExt for pac::Adc {
fn constrain(self, rcu: &mut Rcu, clocks: Clocks) -> Adc {
Adc::new(rcu, self, clocks)
}
}