use super::*;
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32>
Channel<M, I, Generate<C, FREQ_HZ>>
{
/// Transform the [`Channel`] into one driven (chained) by another
/// [`Channel`], using the output clock of `driver` an an input
/// clock.
#[allow(clippy::complexity)]
pub fn chain<J: ChannelId, Co: ChannelClock, const DRIVER_FREQ_HZ: u32>(
#[allow(unused_mut)] mut self,
mut driver: Channel<M, J, Generate<Co, DRIVER_FREQ_HZ>>,
) -> Channel<M, I, Generate<Channel<M, J, Generate<Co, DRIVER_FREQ_HZ>>, FREQ_HZ>>
where
Channel<M, J, Generate<Co, DRIVER_FREQ_HZ>>: ChannelClock,
Self: ChannelClock,
{
// Self is generated by an "internal clock", which is defined
// within itself. Specifying &driver here, as one would
// perhaps except, would instead generate the clock from that
// channel's "external clock": HostClock. In the next
// statement, the two channels are connected.
unsafe {
self.generate_inner(&self);
}
// Connect driver TIOA output to self's input
self.reg().bmr.modify(|_, w| {
let tcxcs = I::DYN.external_feedback_field(&J::DYN);
match I::DYN {
DynChannelId::Ch0 => unsafe { w.tc0xc0s().bits(tcxcs) },
DynChannelId::Ch1 => unsafe { w.tc1xc1s().bits(tcxcs) },
DynChannelId::Ch2 => unsafe { w.tc2xc2s().bits(tcxcs) },
};
w
});
// Configure driving channel
driver.channel().cmr_waveform_mode().modify(|_, w| {
// noop effects on TIOB, TIOA toggles on RC compare match
//
// NOTE: RA and RB compare match affects TIOA and
// TIOB, respectively. RC compare match affects TIOA
// and/or TIOB.
//
// noop TIOB on software trigger
w.bswtrg().none();
// XXX clear TIOA on software trigger
w.aswtrg().clear();
// noop on external event
w.beevt().none();
w.aeevt().none();
// no TIOB effect on RC compare match
w.bcpc().none();
// XXX TIOA toggle on RC compare match
w.acpc().toggle();
// no effect on RA/RB match
w.bcpb().none();
w.acpa().none();
// Use TIOB as external event, but disable triggers:
// does not reset counter or start its clock.
//
// As a side-effect, TIOB is now an input and does not
// generate a waveform and subsequently no IRQs.
w.eevt().tiob();
w.eevtedg().none();
w.enetrg().clear_bit();
// Increment counter to RC, then reset it
w.wavsel().up_rc();
// Input clock configuration (c.f. Fig. 50-3):
//
// Do not disable/stop clock when counter reaches RC.
w.cpcdis().clear_bit();
w.cpcstop().clear_bit();
// Do not gate the clock by an external signal.
w.burst().none();
// Do not invert the clock
w.clki().clear_bit();
w
});
// disable any IRQs
driver.channel().idr.write(|w| unsafe { w.bits(u32::MAX) });
driver.enable();
// Safe: both channels are consumed
Channel::transform(self)
}
}
/// A monotonically increasing clock that is [`Channel::chain`]ed with
/// a driving [`Channel`]. Implemented using the [`Channel`]s 16-bit
/// counter and extended to 32-bits.
pub struct Monotonic<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> {
channel: Channel<M, I, Generate<C, FREQ_HZ>>,
/// Software extension of the channel's internal 16-bit counter,
/// for a total of 32 bits.
msb: u16,
/// The last IRQ status of the channel. Used to track overflow
/// events.
status: Option<StatusRegister>,
}
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> SyncChannels<M>
for Monotonic<M, I, C, FREQ_HZ>
{
}
/// A [`ehal::timer::CountDown`] implementation that is
/// [`Channel::chain`]ed with a driving [`Channel`].
///
/// [`ehal::timer::CountDown`]: crate::ehal::timer::CountDown
pub struct Timer<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> {
channel: Channel<M, I, Generate<C, FREQ_HZ>>,
}
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> SyncChannels<M>
for Timer<M, I, C, FREQ_HZ>
{
}
impl<M: TcMeta, I, J, C: ChannelClock, const DRIVER_FREQ_HZ: u32, const FREQ_HZ: u32>
Channel<M, I, Generate<Channel<M, J, Generate<C, DRIVER_FREQ_HZ>>, FREQ_HZ>>
where
I: ChannelId,
J: ChannelId,
Channel<M, J, Generate<C, DRIVER_FREQ_HZ>>: ChannelClock,
{
/// Transform the [`Channel`] into a [`rtic_monotonic::Monotonic`]
/// implementation with a frequency of `MONO_FREQ_HZ`Hz.
#[allow(clippy::complexity)]
pub fn into_monotonic<const MONO_FREQ_HZ: u32>(
self,
) -> Result<Monotonic<M, I, Channel<M, J, Generate<C, DRIVER_FREQ_HZ>>, MONO_FREQ_HZ>, TcError>
{
let freq = Hertz::from_raw(MONO_FREQ_HZ);
// Configure driver prescaler, rounding to the closest
// integer.
//
// Safe: driver was consumed in Channel::chain.
let mut driver = unsafe { Channel::<M, J, _>::new::<Generate<C, DRIVER_FREQ_HZ>>() };
// The 16-bit counter is incremented only at each positive
// input clock edge. When chaining channels, this then results
// in a static /2 prescaler. Refer to §50.6.2.
const FEEDBACK_PRESCALER: u32 = 2;
let input_freq = ChannelClock::freq(&driver).raw() as f32 / FEEDBACK_PRESCALER as f32;
let pres: u16 = ((input_freq / freq.raw() as f32 + 0.5) as u32)
.try_into()
.map_err(|_| TcError::PrescalerOverflow {
input: Hertz::from_raw(input_freq as u32),
wanted: freq,
})?;
// From experimentation: a prescaler of 0 would noop the
// clock, but a prescaler of 1 would yield a clock that is
// half of what is requested. With a minimum prescaler of 2
// the clock has an expected frequency.
const MINIMUM_ACCURATE_PRESCALER: u16 = 2;
if pres < MINIMUM_ACCURATE_PRESCALER {
return Err(TcError::PrescalerInvalid {
input: Hertz::from_raw(input_freq as u32),
wanted: freq,
});
}
driver.set_compare(CompareRegister::Rc(pres));
// Configure self
self.channel().cmr_waveform_mode().modify(|_, w| {
// noop effects on TIOA/TIOB
//
// NOTE: RA and RB compare match affects TIOA and
// TIOB, respectively. RC compare match affects TIOA
// and/or TIOB.
//
// noop on software trigger
w.bswtrg().none();
w.aswtrg().none();
// noop on external event
w.beevt().none();
w.aeevt().none();
// no effect on RC compare match
w.bcpc().none();
w.acpc().none();
// no effect on RA/RB match
w.bcpb().none();
w.acpa().none();
// Use TIOB as external event, but disable triggers:
// does not reset counter or start its clock.
//
// As a side-effect, TIOB is now an input and does not
// generate a waveform and subsequently no IRQs.
w.eevt().tiob();
w.eevtedg().none();
w.enetrg().clear_bit();
// Increment the counter from 0 to u16::MAX. Do not
// reset when RC is reached, and disallow counting
// down.
w.wavsel().up();
// Input clock configuration (c.f. Fig. 50-3):
//
// Do not disable/stop clock when counter reaches RC.
w.cpcdis().clear_bit();
w.cpcstop().clear_bit();
// Do not gate the clock by an external signal.
w.burst().none();
// Do not invert the clock
w.clki().clear_bit();
w
});
// Enable interrupt on RC compare match
self.channel().idr.write(|w| unsafe { w.bits(u32::MAX) });
self.channel().ier.write(|w| w.cpcs().set_bit());
Ok(Monotonic {
channel: Channel::transform(self),
msb: 0,
status: None,
})
}
/// Transform the [`Channel`] into a [`timer::CountDown`]
/// implementation with a frequency of `TIMER_FREQ_HZ`Hz.
#[allow(clippy::complexity)]
pub fn into_timer<const TIMER_FREQ_HZ: u32>(
self,
) -> Result<Timer<M, I, Channel<M, J, Generate<C, DRIVER_FREQ_HZ>>, TIMER_FREQ_HZ>, TcError>
{
let mono = self.into_monotonic::<TIMER_FREQ_HZ>()?;
// Disable the input clock on an RC compare. This gives us a
// oneshot timer.
mono.channel
.channel()
.cmr_waveform_mode()
.modify(|_, w| w.cpcdis().set_bit());
// XXX: we let IER.CPCS be set which allows the user to bind a
// task for when the timer reaches "zero" (RC).
Ok(Timer {
channel: mono.channel,
})
}
}
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> timer::CountDown
for Timer<M, I, C, FREQ_HZ>
{
type Time = Duration<FREQ_HZ>;
/// Starts a new count-down.
///
/// # Panics
///
/// This function will panic if `duration` cannot be represented
/// by a [`u16`].
fn start<T>(&mut self, duration: T)
where
T: Into<Self::Time>,
{
let ticks: u16 = duration.into().ticks().try_into().unwrap_or_else(|_| {
// We cannot compare values outside of the 16-bit
// range. We also cannot extend the counter, nor can
// we report an error to the user in this trait.
panic!()
});
self.channel.set_compare(CompareRegister::Rc(ticks));
self.channel.enable();
unsafe {
Self::sync_start_channels();
}
// Wait until the clock is enabled.
while self.channel.read_status().clksta().bit_is_clear() {}
}
fn wait(&mut self) -> nb::Result<(), void::Void> {
if self.channel.read_status().cpcs().bit_is_clear() {
Err(nb::Error::WouldBlock)
} else {
Ok(())
}
}
}
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> timer::Cancel
for Timer<M, I, C, FREQ_HZ>
{
type Error = CountDownError;
fn cancel(&mut self) -> Result<(), Self::Error> {
let status = self.channel.read_status();
if status.clksta().bit_is_clear() {
return Err(Self::Error::Disabled);
} else if status.cpcs().bit_is_set() {
return Err(Self::Error::Expired);
}
self.channel.disable();
Ok(())
}
}
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> delay::DelayUs<u32>
for Timer<M, I, C, FREQ_HZ>
{
fn delay_us(&mut self, us: u32) {
use timer::CountDown;
self.start(MicrosDuration::from_ticks(us).convert());
nb::block!(self.wait()).unwrap()
}
}
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> delay::DelayMs<u32>
for Timer<M, I, C, FREQ_HZ>
{
fn delay_ms(&mut self, ms: u32) {
use timer::CountDown;
self.start(MillisDuration::from_ticks(ms).convert());
nb::block!(self.wait()).unwrap()
}
}
impl<M: TcMeta, I: ChannelId, C: ChannelClock, const FREQ_HZ: u32> rtic_monotonic::Monotonic
for Monotonic<M, I, C, FREQ_HZ>
{
type Instant = Instant<FREQ_HZ>;
type Duration = Duration<FREQ_HZ>;
const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false;
/// Resets the counter to zero and start the clock.
unsafe fn reset(&mut self) {
self.channel.enable();
Self::sync_start_channels();
// Wait until the clock is enabled.
while self.channel.read_status().clksta().bit_is_clear() {}
}
#[inline(always)]
fn now(&mut self) -> Self::Instant {
Self::Instant::from_ticks(
((self.msb as u32) << u16::BITS) | self.channel.channel().cv.read().cv().bits(),
)
}
#[inline(always)]
fn set_compare(&mut self, instant: Self::Instant) {
// Mask out the 16 LSBs.
let ticks: u16 = (instant.duration_since_epoch().ticks() & u16::MAX as u32)
.try_into()
.unwrap();
self.channel.set_compare(CompareRegister::Rc(ticks));
}
#[inline(always)]
fn clear_compare_flag(&mut self) {
self.status = Some(self.channel.read_status());
}
#[inline(always)]
fn zero() -> Self::Instant {
Self::Instant::from_ticks(0)
}
#[inline(always)]
fn on_interrupt(&mut self) {
if let Some(status) = &self.status {
if status.covfs().bit_is_set() {
self.msb = self.msb.wrapping_add(1);
// Reset the channel counter such that set comparisons
// are not missed, causing a catastrophic clock drift
// of the full 16-bit life-time.
//
// XXX clock drift is still induced here, but is
// significantly lower (exact drift depends on clock
// frequency; not yet calculated).
//
// TODO null clock drift by compensating RC value?
// Refer to
// <https://git.grepit.se/embedded-rust/atsamx7x-hal/-/issues/39>.
self.channel.channel().ccr.write(|w| w.swtrg().set_bit());
}
}
}
}