diff --git a/pic32-hal/README.md b/pic32-hal/README.md index 6c50529..51fb288 100644 --- a/pic32-hal/README.md +++ b/pic32-hal/README.md @@ -18,6 +18,8 @@ This crate provides a thin low-level API on top of the register access API imple * interrupt controller * Peripheral Pin Select (PPS) * 10-bit analog-to-digital converter (ADC) +* Timer +* Output Compare Some of the modules implement the [embedded-hal](https://crates.io/crates/embedded-hal) API so that device drivers (e. g. for displays) using this API can access PIC32 peripherals. diff --git a/pic32-hal/src/lib.rs b/pic32-hal/src/lib.rs index 7c0e862..df68370 100644 --- a/pic32-hal/src/lib.rs +++ b/pic32-hal/src/lib.rs @@ -55,6 +55,8 @@ pub mod pps; pub mod spi; pub mod time; pub mod uart; +pub mod timer; +pub mod oc; #[cfg(any( feature = "pic32mx2xxfxxxb", diff --git a/pic32-hal/src/oc.rs b/pic32-hal/src/oc.rs new file mode 100644 index 0000000..49eea36 --- /dev/null +++ b/pic32-hal/src/oc.rs @@ -0,0 +1,263 @@ +//! Output Compare +//! + +use crate::pac::{OCMP1, OCMP2, OCMP3, OCMP4, OCMP5}; +use crate::pac::{TMR2, TMR3}; +use embedded_hal::PwmPin; + +use core::marker::PhantomData; + +/// Marker for configurations where the even numbered 16-bit timer (e.g. Timer 2) is used as a time base +pub struct Timebase16even; + +/// Marker for configurations where the odd numbered 16-bit timer (e.g. Timer 3) is used as a time base +pub struct Timebase16odd; + +/// Marker for configurations where two 16-bits timer form a 32-bit time base +pub struct Timebase32; + +/// Output compare configuration (excluding PWM modes) +pub enum OcConfig { + /// No operation + Off, + /// Rising slope when the timer is equal to the specified value + RisingSlope(u32), + + /// Falling slope when the timer is equal to the specified value + FallingSlope(u32), + + /// Toggle when the timer is equal to the specified value + Toggle(u32), + + /// Single pulse with values defining the rising edge and falling edge, respectively + SinglePulse(u32, u32), + + /// Continuous pulses with values defining the rising edges and falling edges, respectively + ContinuousPulses(u32, u32), +} + +impl OcConfig { + const fn ocm_bits(&self) -> u8 { + match self { + Self::Off => 0b000, + Self::RisingSlope(_) => 0b001, + Self::FallingSlope(_) => 0b010, + Self::Toggle(_) => 0b011, + Self::SinglePulse(_, _) => 0b100, + Self::ContinuousPulses(_, _) => 0b101, + } + } +} + +/// Output compare modules configured for non-PWM output compare operations +pub struct Oc { + ocmp: OCMP, + _timebase: PhantomData, +} + +macro_rules! oc_impl { + ($constructor: ident, $ocmp: ty) => { + impl Oc<$ocmp, TIMEBASE> { + pub fn $constructor(ocmp: $ocmp, stop_in_idle_mode: bool) -> Self { + ocmp.cont.write(|w| w.on().clear_bit().sidl().bit(stop_in_idle_mode)); + Self { ocmp, _timebase: PhantomData } + } + + /// Deactivate the output compare module and return the PAC object + pub fn free(self) -> $ocmp { + self.ocmp.cont.write(|w| w.on().clear_bit()); + self.ocmp + } + + /// Turn output compare module off + #[inline(never)] // forces some cycles after OCMP access (as required by data sheet) + pub fn turn_off(&mut self) { + self.ocmp.contclr.write(|w| w.on().set_bit()); + } + } + + impl Oc<$ocmp, Timebase16even> { + pub fn turn_on(&mut self, config: OcConfig) { + self.ocmp.cont.modify(|_, w| unsafe { + w.oc32().bit(false).octsel().bit(false).ocm().bits(config.ocm_bits()) + }); + let (r, rs) = match config { + OcConfig::RisingSlope(r) | OcConfig::FallingSlope(r) | OcConfig::Toggle(r) => { + (r, 0) + } + OcConfig::SinglePulse(r, rs) | OcConfig::ContinuousPulses(r, rs) => (r, rs), + OcConfig::Off => (0, 0), + }; + self.ocmp.r.write(|w| unsafe { w.r().bits(r) }); + self.ocmp.rs.write(|w| unsafe { w.rs().bits(rs) }); + self.ocmp.contset.write(|w| w.on().set_bit()); + } + } + + impl Oc<$ocmp, Timebase16odd> { + pub fn turn_on(&mut self, config: OcConfig) { + self.ocmp.cont.modify(|_, w| unsafe { + w.oc32().bit(false).octsel().bit(true).ocm().bits(config.ocm_bits()) + }); + let (r, rs) = match config { + OcConfig::RisingSlope(r) | OcConfig::FallingSlope(r) | OcConfig::Toggle(r) => { + (r, 0) + } + OcConfig::SinglePulse(r, rs) | OcConfig::ContinuousPulses(r, rs) => (r, rs), + OcConfig::Off => (0, 0), + }; + self.ocmp.r.write(|w| unsafe { w.r().bits(r) }); + self.ocmp.rs.write(|w| unsafe { w.rs().bits(rs) }); + self.ocmp.contset.write(|w| w.on().set_bit()); + } + } + impl Oc<$ocmp, Timebase32> { + pub fn turn_on(&mut self, config: OcConfig) { + self.ocmp.cont.modify(|_, w| unsafe { + w.oc32().bit(true).octsel().bit(false).ocm().bits(config.ocm_bits()) + }); + let (r, rs) = match config { + OcConfig::RisingSlope(r) | OcConfig::FallingSlope(r) | OcConfig::Toggle(r) => { + (r, 0) + } + OcConfig::SinglePulse(r, rs) | OcConfig::ContinuousPulses(r, rs) => (r, rs), + OcConfig::Off => (0, 0), + }; + self.ocmp.r.write(|w| unsafe { w.r().bits(r) }); + self.ocmp.rs.write(|w| unsafe { w.rs().bits(rs) }); + self.ocmp.contset.write(|w| w.on().set_bit()); + } + } + }; +} + +oc_impl!(oc1, OCMP1); +oc_impl!(oc2, OCMP2); +oc_impl!(oc3, OCMP3); +oc_impl!(oc4, OCMP4); +oc_impl!(oc5, OCMP5); + +/// Output compare modules configured for PWM +pub struct Pwm { + ocmp: OCMP, + _timebase: PhantomData, +} + +macro_rules! pwm_impl { + ($constructor: ident, $ocmp: ty, $timer_even: ty, $timer_odd: ty) => { + impl Pwm<$ocmp, TIMEBASE> { + /// Initialize the output compare module for 16-bit PWM + /// + /// The respetive timer must be set up previously. The HAL code for PWM + /// reads the respective timer period register to get the maximum duty cycle + /// value. + pub fn $constructor( + ocmp: $ocmp, + enable_fault_pin: bool, + stop_in_idle_mode: bool, + ) -> Self { + let ocm = match enable_fault_pin { + false => 0b110, + true => 0b111, + }; + ocmp.cont.write(|w| unsafe { w.sidl().bit(stop_in_idle_mode).ocm().bits(ocm) }); + Pwm { ocmp, _timebase: PhantomData } + } + + /// Get fault state + /// + /// Returns false if fault pin is not enabled. + pub fn fault(&self) -> bool { + self.ocmp.cont.read().ocflt().bit() + } + + /// Deactivate the output compare module and return the PAC object + pub fn free(self) -> $ocmp { + self.ocmp.cont.write(|w| w.on().clear_bit()); + self.ocmp + } + } + + impl PwmPin for Pwm<$ocmp, Timebase16even> { + type Duty = u32; + + fn enable(&mut self) { + self.ocmp.cont.modify(|_, w| w.octsel().bit(false).oc32().bit(false)); + self.ocmp.contset.write(|w| w.on().set_bit()); + } + + fn disable(&mut self) { + self.ocmp.contclr.write(|w| w.on().set_bit()); + } + + fn get_duty(&self) -> Self::Duty { + self.ocmp.rs.read().rs().bits() as Self::Duty + } + + fn set_duty(&mut self, duty: Self::Duty) { + self.ocmp.rs.write(|w| unsafe { w.rs().bits(duty) }); + } + + fn get_max_duty(&self) -> Self::Duty { + unsafe { (*<$timer_even>::ptr()).pr.read().pr().bits() + 1 } + } + } + + impl PwmPin for Pwm<$ocmp, Timebase16odd> { + type Duty = u32; + + fn enable(&mut self) { + self.ocmp.cont.modify(|_, w| w.octsel().bit(true).oc32().bit(false)); + self.ocmp.contset.write(|w| w.on().set_bit()); + } + + fn disable(&mut self) { + self.ocmp.contclr.write(|w| w.on().set_bit()); + } + + fn get_duty(&self) -> Self::Duty { + self.ocmp.rs.read().rs().bits() as Self::Duty + } + + fn set_duty(&mut self, duty: Self::Duty) { + self.ocmp.rs.write(|w| unsafe { w.rs().bits(duty) }); + } + + fn get_max_duty(&self) -> Self::Duty { + unsafe { (*<$timer_odd>::ptr()).pr.read().pr().bits() + 1 } + } + } + + impl PwmPin for Pwm<$ocmp, Timebase32> { + type Duty = u32; + + fn enable(&mut self) { + self.ocmp.cont.modify(|_, w| w.octsel().bit(false).oc32().bit(true)); + self.ocmp.contset.write(|w| w.on().set_bit()); + } + + fn disable(&mut self) { + self.ocmp.contclr.write(|w| w.on().set_bit()); + } + + fn get_duty(&self) -> Self::Duty { + self.ocmp.rs.read().rs().bits() as Self::Duty + } + + fn set_duty(&mut self, duty: Self::Duty) { + self.ocmp.rs.write(|w| unsafe { w.rs().bits(duty) }); + } + + fn get_max_duty(&self) -> Self::Duty { + let pr = unsafe { (*<$timer_even>::ptr()).pr.read().pr().bits() }; + pr.saturating_add(1) + } + } + }; +} + +pwm_impl!(oc1, OCMP1, TMR2, TMR3); +pwm_impl!(oc2, OCMP2, TMR2, TMR3); +pwm_impl!(oc3, OCMP3, TMR2, TMR3); +pwm_impl!(oc4, OCMP4, TMR2, TMR3); +pwm_impl!(oc5, OCMP5, TMR2, TMR3); diff --git a/pic32-hal/src/timer.rs b/pic32-hal/src/timer.rs new file mode 100644 index 0000000..1523dcd --- /dev/null +++ b/pic32-hal/src/timer.rs @@ -0,0 +1,18 @@ +//! Timer + +pub mod timer_a; +pub mod timer_b; + +/// Clocking Modes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Clocking { + /// Internal clock (Pbclock) + Pbclock, + + /// Internal clock, gated + PbclockGated, + + /// External clock + External, +} diff --git a/pic32-hal/src/timer/timer_a.rs b/pic32-hal/src/timer/timer_a.rs new file mode 100644 index 0000000..e84a607 --- /dev/null +++ b/pic32-hal/src/timer/timer_a.rs @@ -0,0 +1,123 @@ +//! Timer type A + +use super::Clocking; +use crate::pac::TMR1; +use core::marker::PhantomData; + +/// Clock pre scaler configuration for timer type A +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum ClockPrescale { + /// 1:1 prescale value + Prescale1 = 0, + + /// 1:8 prescale value + Prescale8 = 1, + + /// 1:64 prescale value + Prescale64 = 2, + + /// 1:256 prescale value + Prescale256 = 3, +} + +/// Marker for Synchronous operation +pub struct TimerSynchronous; + +/// Marker for Asynchronous operation +pub struct TimerAsynchronous; + +/// HAL struct for Timer1 (timer type A) +pub struct Timer { + timer: TMR1, + _marker: PhantomData, +} + +impl Timer { + /// Initalize the timer for synchronous operation + /// + /// The timer peripheral is running synchronously with the PBCLOCK and + /// cannot count external pulses when the PBCLOCK is gated. + pub fn timer1_synchronous( + timer: TMR1, + clocking: Clocking, + prescale: ClockPrescale, + period: u16, + stop_in_idle_mode: bool, + ) -> Timer { + timer.cont.write(|w| unsafe { w + .sidl().bit(stop_in_idle_mode) + .tgate().bit(clocking == Clocking::PbclockGated) + .tckps().bits(prescale as u8) + .tsync().set_bit() + .tcs().bit(clocking == Clocking::External) + }); + timer.tmr.write(|w| unsafe { w.tmr().bits(0) }); + timer.pr.write(|w| unsafe { w.pr().bits(period as u32) }); + timer.contset.write(|w| w.on().set_bit()); + + Timer { timer, _marker: PhantomData } + } + + /// Initialize the timer for asynchronous operation + /// + /// The timer will operate asynchronously with respect and independently + /// from to the PBCLOCK When operating asynchronously, the timer is always + /// external clocked, e.g. by a 32 kHz clock source or the like. + pub fn timer1_asynchronous( + timer: TMR1, + prescale: ClockPrescale, + period: u16, + stop_in_idle_mode: bool, + ) -> Timer { + timer.tmr.write(|w| unsafe { w.tmr().bits(0) }); + timer.pr.write(|w| unsafe { w.pr().bits(period as u32) }); + timer.cont.write(|w| unsafe { w + .sidl().bit(stop_in_idle_mode) + .twdis().clear_bit() + .tgate().clear_bit() + .tckps().bits(prescale as u8) + .tsync().clear_bit() + .tcs().set_bit() + }); + timer.contset.write(|w| w.on().set_bit()); + + Timer { timer, _marker: PhantomData } + } + + /// Turn the timer off + pub fn free(self) -> TMR1 { + self.timer.contclr.write(|w| w.on().set_bit()); + self.timer + } + + /// Read the current timer count value + pub fn tmr(&self) -> u16 { + self.timer.tmr.read().tmr().bits() as u16 + } + + /// Read the period (PR register) + pub fn period(&self) -> u16 { + self.timer.pr.read().pr().bits() as u16 + } + + /// Write to the period (PR register) + pub fn set_period(&mut self, period: u16) { + self.timer.pr.write(|w| unsafe { w.pr().bits(period as u32) }); + } +} + +impl Timer { + /// Set the current timer count value + pub fn set_tmr(&mut self, tmr: u16) { + self.timer.tmr.write(|w| unsafe { w.tmr().bits(tmr as u32) }); + } +} + +impl Timer { + /// Set the current timer count value + pub fn set_tmr(&mut self, tmr: u16) { + while self.timer.cont.read().twip().bit_is_set() {} + self.timer.tmr.write(|w| unsafe { w.tmr().bits(tmr as u32) }); + } +} diff --git a/pic32-hal/src/timer/timer_b.rs b/pic32-hal/src/timer/timer_b.rs new file mode 100644 index 0000000..de6fa81 --- /dev/null +++ b/pic32-hal/src/timer/timer_b.rs @@ -0,0 +1,163 @@ +//! Timer type B + +use super::Clocking; +use crate::pac::{TMR2, TMR3, TMR4, TMR5}; + +/// Clock pre scaler configuration for timer type B +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum ClockPrescale { + /// 1:1 prescale value + Prescale1 = 0, + + /// 1:2 prescale value + Prescale2 = 1, + + /// 1:4 prescale value + Prescale4 = 2, + + /// 1:8 prescale value + Prescale8 = 3, + + /// 1:16 prescale value + Prescale16 = 4, + + /// 1:32 prescale value + Prescale32 = 5, + + /// 1:64 prescale value + Prescale64 = 6, + + /// 1:256 prescale value + Prescale256 = 7, +} + +/// HAL struct of timer type B +pub struct Timer { + timer: TIMER, +} + +macro_rules! timerb_impl { + ($constructor: ident, $timer: ty) => { + impl Timer<$timer> { + /// Initialize the timer + pub fn $constructor( + timer: $timer, + clocking: Clocking, + prescale: ClockPrescale, + period: u16, + stop_in_idle_mode: bool, + ) -> Self { + timer.cont.write(|w| unsafe { w + .sidl().bit(stop_in_idle_mode) + .tgate().bit(clocking == Clocking::PbclockGated) + .tckps().bits(prescale as u8) + .tcs().bit(clocking == Clocking::External) + }); + timer.tmr.write(|w| unsafe { w.tmr().bits(0) }); + timer.pr.write(|w| unsafe { w.pr().bits(period as u32) }); + timer.contset.write(|w| w.on().set_bit()); + + Self { timer } + } + + /// Turn the timer off + pub fn free(self) -> $timer { + self.timer.contclr.write(|w| w.on().set_bit()); + self.timer + } + + /// Read the current timer count value (TMR register) + pub fn tmr(&self) -> u16 { + self.timer.tmr.read().tmr().bits() as u16 + } + + /// Set the current timer count value (TMR register) + pub fn set_tmr(&mut self, tmr: u16) { + self.timer.tmr.write(|w| unsafe { w.tmr().bits(tmr as u32) }); + } + + /// Read the maximum value (PR register) + pub fn pr(&self) -> u16 { + self.timer.pr.read().pr().bits() as u16 + } + + /// Set the maximum value (PR register) + pub fn set_pr(&mut self, period: u16) { + self.timer.pr.write(|w| unsafe { w.pr().bits(period as u32) }); + } + } + }; +} + +timerb_impl!(timer2, TMR2); +timerb_impl!(timer3, TMR3); +timerb_impl!(timer4, TMR4); +timerb_impl!(timer5, TMR5); + +/// HAL struct for a pair of timers of type B (32-bit mode) +pub struct Timer32 { + timer_low: TIMERL, + timer_high: TIMERH, +} + +macro_rules! timer32_impl { + ($constructor: ident, $timer_low: ty, $timer_high: ty) => { + impl Timer32<$timer_low, $timer_high> { + /// Initialize the timer + pub fn $constructor( + timer_low: $timer_low, + timer_high: $timer_high, + clocking: Clocking, + prescale: ClockPrescale, + period: u32, + stop_in_idle_mode: bool, + ) -> Self { + timer_low.cont.write(|w| unsafe { w + .sidl().bit(stop_in_idle_mode) + .tgate().bit(clocking == Clocking::PbclockGated) + .tckps().bits(prescale as u8) + .t32().set_bit() + .tcs().bit(clocking == Clocking::External) + }); + timer_high.cont.write(|w| w.sidl().bit(stop_in_idle_mode)); + + timer_low.tmr.write(|w| unsafe { w.tmr().bits(0) }); + timer_low.pr.write(|w| unsafe { w.pr().bits(period) }); + timer_low.contset.write(|w| w.on().set_bit()); + + Self { timer_low, timer_high } + } + + /// Turn the timer off + pub fn free(self) -> ($timer_low, $timer_high) { + self.timer_low.contclr.write(|w| w.on().set_bit()); + self.timer_high.contclr.write(|w| w.on().set_bit()); + (self.timer_low, self.timer_high) + } + + /// Read the current timer count value (TMR register) + pub fn tmr(&self) -> u32 { + self.timer_low.tmr.read().tmr().bits() + } + + /// Set the current timer count value (TMR register) + pub fn set_tmr(&mut self, tmr: u32) { + self.timer_low.tmr.write(|w| unsafe { w.tmr().bits(tmr) }); + } + + /// Read the maximum value (PR register) + pub fn pr(&self) -> u32 { + self.timer_low.pr.read().pr().bits() + } + + /// Set the maximum value (PR register) + pub fn set_pr(&mut self, period: u32) { + self.timer_low.pr.write(|w| unsafe { w.pr().bits(period) }); + } + } + }; +} + +timer32_impl!(timer2_3, TMR2, TMR3); +timer32_impl!(timer4_5, TMR4, TMR5);