use crate::{AsyncTimer, Timer};
use core::num::NonZeroU32;
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU16, Ordering};
use cortex_m::interrupt::CriticalSection;
use stm32l4xx_hal::stm32::{Interrupt, LPTIM1, NVIC, RCC};
const ALARM_TICKS_PER_SECOND: u64 = 32000;
const ALARM_SEGMENT: u32 = 0xff00;
const ALARM_DIRTY_ZONE: u32 = ALARM_SEGMENT / 2;
const ALARM_ARR: u32 = ALARM_SEGMENT - 1;
pub struct Lptim1Rtc {
inner: LPTIM1,
time: AtomicU16,
dirty: AtomicBool,
uploading: AtomicBool,
deadline: Option<NonZeroU32>,
}
#[derive(Debug)]
pub struct DeadlinePassed;
impl Lptim1Rtc {
pub fn new(timer: LPTIM1, rcc: &RCC) -> Self {
rcc.apb1enr1.modify(|_, w| w.lptim1en().set_bit());
rcc.apb1rstr1.modify(|_, w| w.lptim1rst().set_bit());
rcc.apb1rstr1.modify(|_, w| w.lptim1rst().clear_bit());
rcc.apb1smenr1.modify(|_, w| w.lptim1smen().set_bit());
rcc.ccipr.modify(|_, w| unsafe { w.lptim1sel().bits(0b01) });
Self {
inner: timer,
time: AtomicU16::new(0),
dirty: AtomicBool::new(true),
uploading: AtomicBool::new(false),
deadline: None,
}
}
pub fn start_timer(&mut self) {
self.inner.cr.modify(|_, w| w.enable().clear_bit());
NVIC::mask(Interrupt::LPTIM1);
self.inner.cfgr.modify(|_, w| {
w.cksel().clear_bit(); w.enc().clear_bit(); w.preload().clear_bit(); w.countmode().clear_bit(); w.timout().set_bit();
unsafe {
w.presc().bits(0b000); w.ckpol().bits(0b00);
w.trigen().bits(0b00)
}
});
self.inner
.ier
.write(|w| w.arrmie().set_bit().cmpmie().set_bit().cmpokie().set_bit());
unsafe { NVIC::unmask(Interrupt::LPTIM1) };
self.inner.cr.modify(|_, w| w.enable().set_bit());
self.inner.arr.write(|w| unsafe { w.bits(ALARM_ARR) });
self.set_cmp(ALARM_DIRTY_ZONE);
compiler_fence(Ordering::SeqCst);
self.inner.cr.modify(|_, w| w.cntstrt().set_bit());
}
pub fn get_time(&self) -> u32 {
let get_time_unstable = || {
let frac: u32 = self.get_fraction();
let frac = if frac >= ALARM_SEGMENT {
ALARM_SEGMENT
} else if frac < ALARM_DIRTY_ZONE {
frac + ALARM_SEGMENT
} else if self.dirty.load(Ordering::Acquire) {
frac + ALARM_SEGMENT
} else {
frac
};
((self.time.load(Ordering::Acquire) as u32) * ALARM_SEGMENT)
.wrapping_add(frac)
.wrapping_sub(ALARM_SEGMENT) };
loop {
let x = get_time_unstable();
let y = get_time_unstable();
if x == y {
return x;
}
}
}
pub fn set_alarm(&mut self, deadline: u32) -> Result<(), DeadlinePassed> {
self.deadline = NonZeroU32::new(deadline);
if self.update_alarm(false) {
Err(DeadlinePassed)
} else {
Ok(())
}
}
pub fn clear_alarm(&mut self) {
self.deadline = None;
self.reset_cmp();
}
pub fn handle_interrupt(&mut self) -> bool {
let isr = self.inner.isr.read();
let arrm = isr.arrm().bit();
let cmpm = isr.cmpm().bit();
let cmpok = isr.cmpok().bit();
if cmpok {
self.uploading.store(false, Ordering::Release);
}
if arrm {
self.dirty.store(true, Ordering::Release);
}
let fraction = self.get_fraction();
if cmpm
&& fraction < ALARM_SEGMENT
&& fraction >= ALARM_DIRTY_ZONE
&& self
.dirty
.compare_exchange(true, false, Ordering::SeqCst, Ordering::Acquire)
.is_ok()
{
self.time.fetch_add(1, Ordering::SeqCst);
}
if arrm & cmpm {
panic!("Invariant ARRM and CMPM together violated");
}
let u = self.update_alarm(true);
self.inner
.icr
.write(|w| w.arrmcf().bit(arrm).cmpmcf().bit(cmpm).cmpokcf().bit(cmpok));
compiler_fence(Ordering::SeqCst);
u
}
#[inline(always)]
fn reset_cmp(&mut self) {
self.set_cmp(ALARM_DIRTY_ZONE);
}
#[inline(always)]
fn set_cmp(&mut self, mut fractions: u32) {
if fractions == ALARM_SEGMENT {
fractions = 0;
} else if fractions > ALARM_DIRTY_ZONE && self.dirty.load(Ordering::Acquire) {
fractions = ALARM_DIRTY_ZONE;
}
if fractions != self.inner.cmp.read().bits() {
if self
.uploading
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Acquire)
.is_ok()
{
self.inner.cmp.write(|w| unsafe { w.bits(fractions) });
}
}
}
fn update_alarm(&mut self, consume_alarm: bool) -> bool {
if let Some(deadline) = self.deadline {
let deadline = deadline.get();
let now = self.get_time();
if deadline <= now {
if consume_alarm {
self.deadline.take();
self.reset_cmp();
} else {
self.set_cmp((now + 2) % ALARM_SEGMENT);
}
return true;
} else if deadline < ((now / ALARM_SEGMENT) + 1) * ALARM_SEGMENT {
self.set_cmp(deadline % ALARM_SEGMENT);
} else {
self.reset_cmp();
}
} else if self.dirty.load(Ordering::Acquire) {
self.set_cmp(ALARM_DIRTY_ZONE);
return false;
}
false
}
fn teardown(&mut self) {
self.inner.cr.modify(|_, w| w.enable().clear_bit());
self.inner
.icr
.write(|w| w.arrmcf().set_bit().cmpmcf().set_bit());
}
pub fn get_fraction(&self) -> u32 {
loop {
let x = self.inner.cnt.read().bits();
let y = self.inner.cnt.read().bits();
if x == y {
return x & 0xFFFF;
}
}
}
}
impl Drop for Lptim1Rtc {
fn drop(&mut self) {
self.teardown();
}
}
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Debug)]
pub struct InstantU32(pub u32);
#[derive(Clone)]
pub struct DurationU32(pub u32);
impl core::convert::From<core::time::Duration> for DurationU32 {
fn from(duration: core::time::Duration) -> Self {
Self(
((duration.as_secs() as u64) * ALARM_TICKS_PER_SECOND
+ (duration.subsec_nanos() as u64) * ALARM_TICKS_PER_SECOND / 1_000_000_000)
as u32,
)
}
}
impl core::ops::AddAssign<DurationU32> for InstantU32 {
fn add_assign(&mut self, duration: DurationU32) {
self.0 += duration.0;
}
}
impl core::ops::Add<DurationU32> for InstantU32 {
type Output = Self;
fn add(mut self, rhs: DurationU32) -> Self::Output {
self += rhs;
self
}
}
impl Timer for Lptim1Rtc {
type Instant = InstantU32;
type Duration = DurationU32;
const DELTA: Self::Duration = DurationU32(2);
#[inline(always)]
fn reset(&mut self) {
self.start_timer()
}
#[inline(always)]
fn interrupt_free<F: FnOnce(&CriticalSection) -> R, R>(f: F) -> R {
cortex_m::interrupt::free(f)
}
#[inline(always)]
fn now(&self) -> Self::Instant {
InstantU32(self.get_time())
}
#[inline(always)]
fn disarm(&mut self) {
self.clear_alarm();
}
#[inline(always)]
fn arm(&mut self, deadline: &Self::Instant) {
let _ = self.set_alarm(deadline.0);
}
}
#[inline(always)]
pub fn handle_interrupt<'a>(get_timer: impl FnOnce() -> &'a AsyncTimer<Lptim1Rtc>) -> bool {
let timer = get_timer();
let triggered = unsafe { timer.get_inner(|t| t.handle_interrupt()) };
if triggered {
timer.awaken();
}
triggered
}