From afcb5bf3fb0b6bfbf5cc92836504536b58262b11 Mon Sep 17 00:00:00 2001 From: Justace Clutter Date: Mon, 30 Mar 2020 21:28:45 -0400 Subject: [PATCH] Implement full embedded_hal::Pwm (#176) --- CHANGELOG.md | 1 + examples/pwm.rs | 51 +++++++++++--- examples/pwm_custom.rs | 21 +++--- src/pwm.rs | 152 +++++++++++++++++++++++++++++++++++++---- src/time.rs | 22 ++++++ 5 files changed, 217 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cf6bbd7..fb8df6f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Extend the Pwm implementation to cover the full embedded_hal::Pwm API - Replace default blocking spi Write implementation with an optimized one - Use `Deref` for SPI generic implementations instead of macros - Make traits `rcc::Enable` and `rcc::Reset` public, but `RccBus` sealed diff --git a/examples/pwm.rs b/examples/pwm.rs index 6638964c..f224033e 100644 --- a/examples/pwm.rs +++ b/examples/pwm.rs @@ -11,6 +11,8 @@ use stm32f1xx_hal::{ prelude::*, pac, timer::{Tim2NoRemap, Timer}, + time::U32Ext, + pwm::Channel }; use cortex_m_rt::entry; @@ -49,25 +51,56 @@ fn main() -> ! { // let c4 = gpiob.pb9.into_alternate_push_pull(&mut gpiob.crh); let mut pwm = Timer::tim2(p.TIM2, &clocks, &mut rcc.apb1) - .pwm::(pins, &mut afio.mapr, 1.khz()) - .2; + .pwm::(pins, &mut afio.mapr, 1.khz()); + + //// Operations affecting all defined channels on the Timer + + // Adjust period to 0.5 seconds + pwm.set_period(500.ms()); + + asm::bkpt(); + + // Return to the original frequency + pwm.set_period(1.khz()); + + asm::bkpt(); let max = pwm.get_max_duty(); - pwm.enable(); + //// Operations affecting single channels can be accessed through + //// the Pwm object or via dereferencing to the pin. + + // Use the Pwm object to set C3 to full strength + pwm.set_duty(Channel::C3, max); + + asm::bkpt(); + + // Use the Pwm object to set C3 to be dim + pwm.set_duty(Channel::C3, max / 4); + + asm::bkpt(); + + // Use the Pwm object to set C3 to be zero + pwm.set_duty(Channel::C3, 0); + + asm::bkpt(); + + + // Extract the PwmChannel for C3 + let mut pwm_channel = pwm.split().2; - // full - pwm.set_duty(max); + // Use the PwmChannel object to set C3 to be full strength + pwm_channel.set_duty(max); asm::bkpt(); - // dim - pwm.set_duty(max / 4); + // Use the PwmChannel object to set C3 to be dim + pwm_channel.set_duty(max / 4); asm::bkpt(); - // zero - pwm.set_duty(0); + // Use the PwmChannel object to set C3 to be zero + pwm_channel.set_duty(0); asm::bkpt(); diff --git a/examples/pwm_custom.rs b/examples/pwm_custom.rs index e11abf1a..46b13a54 100644 --- a/examples/pwm_custom.rs +++ b/examples/pwm_custom.rs @@ -33,28 +33,31 @@ fn main() -> ! { let p0 = pb4.into_alternate_push_pull(&mut gpiob.crl); let p1 = gpiob.pb5.into_alternate_push_pull(&mut gpiob.crl); - let mut pwm = Timer::tim3(p.TIM3, &clocks, &mut rcc.apb1) + let pwm = Timer::tim3(p.TIM3, &clocks, &mut rcc.apb1) .pwm((p0, p1), &mut afio.mapr, 1.khz()); - let max = pwm.0.get_max_duty(); + let max = pwm.get_max_duty(); - pwm.0.enable(); - pwm.1.enable(); + let mut pwm_channels = pwm.split(); + + // Enable the individual channels + pwm_channels.0.enable(); + pwm_channels.1.enable(); // full - pwm.0.set_duty(max); - pwm.1.set_duty(max); + pwm_channels.0.set_duty(max); + pwm_channels.1.set_duty(max); asm::bkpt(); // dim - pwm.1.set_duty(max / 4); + pwm_channels.1.set_duty(max / 4); asm::bkpt(); // zero - pwm.0.set_duty(0); - pwm.1.set_duty(0); + pwm_channels.0.set_duty(0); + pwm_channels.1.set_duty(0); asm::bkpt(); diff --git a/src/pwm.rs b/src/pwm.rs index 7bdded1c..a3c93f13 100644 --- a/src/pwm.rs +++ b/src/pwm.rs @@ -54,6 +54,7 @@ */ use core::marker::PhantomData; +use core::marker::{Copy}; use core::mem; use crate::hal; @@ -68,6 +69,7 @@ use crate::afio::MAPR; use crate::bb; use crate::gpio::{self, Alternate, PushPull}; use crate::time::Hertz; +use crate::time::U32Ext; use crate::timer::Timer; pub trait Pins { @@ -76,6 +78,26 @@ pub trait Pins { const C3: bool = false; const C4: bool = false; type Channels; + + fn check_used(c: Channel) -> Channel { + if (c == Channel::C1 && Self::C1) + || (c == Channel::C2 && Self::C2) + || (c == Channel::C3 && Self::C3) + || (c == Channel::C4 && Self::C4) + { + c + } else { + panic!("Unused channel") + } + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Channel { + C1, + C2, + C3, + C4, } use crate::timer::sealed::{Ch1, Ch2, Ch3, Ch4, Remap}; @@ -89,7 +111,7 @@ macro_rules! pins_impl { $($PINX: $TRAIT + gpio::Mode>,)+ { $(const $ENCHX: bool = true;)+ - type Channels = ($(Pwm),+); + type Channels = ($(PwmChannel),+); } )+ }; @@ -115,7 +137,7 @@ pins_impl!( #[cfg(any(feature = "stm32f100", feature = "stm32f103", feature = "stm32f105",))] impl Timer { - pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels + pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm where REMAP: Remap, PINS: Pins, @@ -133,7 +155,7 @@ impl Timer { } impl Timer { - pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels + pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm where REMAP: Remap, PINS: Pins, @@ -147,7 +169,7 @@ impl Timer { } impl Timer { - pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels + pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm where REMAP: Remap, PINS: Pins, @@ -162,7 +184,7 @@ impl Timer { #[cfg(feature = "medium")] impl Timer { - pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels + pub fn pwm(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm where REMAP: Remap, PINS: Pins, @@ -175,7 +197,26 @@ impl Timer { } } -pub struct Pwm { +pub struct Pwm +where + REMAP: Remap, + PINS: Pins +{ + clk: Hertz, + _pins: PhantomData<(TIM, REMAP, P, PINS)>, +} + +impl Pwm +where + REMAP: Remap, + PINS: Pins +{ + pub fn split(self) -> PINS::Channels { + unsafe { mem::MaybeUninit::uninit().assume_init() } + } +} + +pub struct PwmChannel { _channel: PhantomData, _tim: PhantomData, } @@ -193,7 +234,7 @@ macro_rules! hal { _pins: PINS, freq: Hertz, clk: Hertz, - ) -> PINS::Channels + ) -> Pwm<$TIMX, REMAP, P, PINS> where REMAP: Remap, PINS: Pins, @@ -240,10 +281,97 @@ macro_rules! hal { .set_bit() ); - unsafe { mem::MaybeUninit::uninit().assume_init() } + Pwm { + clk: clk, + _pins: PhantomData + } + } + + /* + The following implemention of the embedded_hal::Pwm uses Hertz as a time type. This was choosen + because of the timescales of operations being on the order of nanoseconds and not being able to + efficently represent a float on the hardware. It might be possible to change the time type to + a different time based using such as the nanosecond. The issue with doing so is that the max + delay would then be at just a little over 2 seconds because of the 32 bit depth of the number. + Using milliseconds is also an option, however, using this as a base unit means that only there + could be resolution issues when trying to get a specific value, because of the integer nature. + + To find a middle ground, the Hertz type is used as a base here and the Into trait has been + defined for several base time units. This will allow for calling the set_period method with + something that is natural to both the MCU and the end user. + */ + impl hal::Pwm for Pwm<$TIMX, REMAP, P, PINS> where + REMAP: Remap, + PINS: Pins, + { + type Channel = Channel; + type Duty = u16; + type Time = Hertz; + + fn enable(&mut self, channel: Self::Channel) { + match PINS::check_used(channel) { + Channel::C1 => unsafe { bb::set(&(*$TIMX::ptr()).ccer, 0) }, + Channel::C2 => unsafe { bb::set(&(*$TIMX::ptr()).ccer, 4) }, + Channel::C3 => unsafe { bb::set(&(*$TIMX::ptr()).ccer, 8) }, + Channel::C4 => unsafe { bb::set(&(*$TIMX::ptr()).ccer, 12) } + } + } + + fn disable(&mut self, channel: Self::Channel) { + match PINS::check_used(channel) { + Channel::C1 => unsafe { bb::clear(&(*$TIMX::ptr()).ccer, 0) }, + Channel::C2 => unsafe { bb::clear(&(*$TIMX::ptr()).ccer, 4) }, + Channel::C3 => unsafe { bb::clear(&(*$TIMX::ptr()).ccer, 8) }, + Channel::C4 => unsafe { bb::clear(&(*$TIMX::ptr()).ccer, 12) }, + } + } + + fn get_duty(&self, channel: Self::Channel) -> Self::Duty { + match PINS::check_used(channel) { + Channel::C1 => unsafe { (*$TIMX::ptr()).ccr1.read().ccr().bits() }, + Channel::C2 => unsafe { (*$TIMX::ptr()).ccr2.read().ccr().bits() }, + Channel::C3 => unsafe { (*$TIMX::ptr()).ccr3.read().ccr().bits() }, + Channel::C4 => unsafe { (*$TIMX::ptr()).ccr4.read().ccr().bits() }, + } + } + + fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { + match PINS::check_used(channel) { + Channel::C1 => unsafe { (*$TIMX::ptr()).ccr1.write(|w| w.ccr().bits(duty)) }, + Channel::C2 => unsafe { (*$TIMX::ptr()).ccr2.write(|w| w.ccr().bits(duty)) }, + Channel::C3 => unsafe { (*$TIMX::ptr()).ccr3.write(|w| w.ccr().bits(duty)) }, + Channel::C4 => unsafe { (*$TIMX::ptr()).ccr4.write(|w| w.ccr().bits(duty)) }, + } + } + + fn get_max_duty(&self) -> Self::Duty { + unsafe { (*$TIMX::ptr()).arr.read().arr().bits() } + } + + fn get_period(&self) -> Self::Time { + let clk = self.clk; + let psc: u16 = unsafe{(*$TIMX::ptr()).psc.read().psc().bits()}; + let arr: u16 = unsafe{(*$TIMX::ptr()).psc.read().psc().bits()}; + + // Length in ms of an internal clock pulse + (clk.0 / u32(psc * arr)).hz() + } + + fn set_period(&mut self, period: T) where + T: Into { + let clk = self.clk; + + let ticks = clk.0 / period.into().0; + let psc = u16(ticks / (1 << 16)).unwrap(); + let arr = u16(ticks / u32(psc + 1)).unwrap(); + unsafe { + (*$TIMX::ptr()).psc.write(|w| w.psc().bits(psc)); + (*$TIMX::ptr()).arr.write(|w| w.arr().bits(arr)); + } + } } - impl hal::PwmPin for Pwm<$TIMX, C1> { + impl hal::PwmPin for PwmChannel<$TIMX, C1> { type Duty = u16; fn disable(&mut self) { @@ -267,7 +395,7 @@ macro_rules! hal { } } - impl hal::PwmPin for Pwm<$TIMX, C2> { + impl hal::PwmPin for PwmChannel<$TIMX, C2> { type Duty = u16; fn disable(&mut self) { @@ -291,7 +419,7 @@ macro_rules! hal { } } - impl hal::PwmPin for Pwm<$TIMX, C3> { + impl hal::PwmPin for PwmChannel<$TIMX, C3> { type Duty = u16; fn disable(&mut self) { @@ -315,7 +443,7 @@ macro_rules! hal { } } - impl hal::PwmPin for Pwm<$TIMX, C4> { + impl hal::PwmPin for PwmChannel<$TIMX, C4> { type Duty = u16; fn disable(&mut self) { diff --git a/src/time.rs b/src/time.rs index f8093cef..a1af4788 100644 --- a/src/time.rs +++ b/src/time.rs @@ -101,6 +101,9 @@ pub struct MegaHertz(pub u32); #[derive(PartialEq, PartialOrd, Clone, Copy)] pub struct MilliSeconds(pub u32); +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub struct MicroSeconds(pub u32); + /// Extension trait that adds convenience methods to the `u32` type pub trait U32Ext { /// Wrap in `Bps` @@ -117,6 +120,9 @@ pub trait U32Ext { /// Wrap in `MilliSeconds` fn ms(self) -> MilliSeconds; + + /// Wrap in `MicroSeconds` + fn us(self) -> MicroSeconds; } impl U32Ext for u32 { @@ -139,6 +145,10 @@ impl U32Ext for u32 { fn ms(self) -> MilliSeconds { MilliSeconds(self) } + + fn us(self) -> MicroSeconds { + MicroSeconds(self) + } } impl From for Hertz { @@ -159,6 +169,18 @@ impl From for KiloHertz { } } +impl Into for MilliSeconds { + fn into(self) -> Hertz { + Hertz(1_000 / self.0) + } +} + +impl Into for MicroSeconds { + fn into(self) -> Hertz { + Hertz(1_000_000 / self.0) + } +} + /// A monotonic non-decreasing timer #[derive(Clone, Copy)] pub struct MonoTimer {