-
Notifications
You must be signed in to change notification settings - Fork 184
Implement full embedded_hal::Pwm #176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ed4f6f0
5050dda
f37b17c
8d0012c
5a2f9f6
bb0113d
4dfe267
a275dc1
69235c7
8d26086
4a0a15f
7175ebe
0a555c9
89ef755
a6bbac1
06f5a47
6a6b08a
0cbc67f
a77eea3
b22ff3b
a6f21b4
ca3b1a9
87179b1
cf5a152
62ec7bd
b2b3a07
aa3c331
4c4c2d1
1098ab2
a1c54ff
cedb98f
bca666d
16431d3
013478a
7904998
abb1a17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<REMAP, P> { | ||
|
@@ -76,6 +78,26 @@ pub trait Pins<REMAP, P> { | |
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still not a fan of this panic, and I'm not sure if check_used is even nessecary anymore There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @TheZoq2 It took me a bit to understand as this was a @burbull construction, but the function seems to make sense to me. Without the function there is no way for the per-channel functions to know if that channel is, in-fact, being used. As an example, lets say that I configure the Timer to use only Channel 3 passing a single pin. But then I call something like:
Without the This was the real reason behind me using a macro in my original incarnation of this functionality. The macro expanded to only include channels that were active and would default to doing nothing through a default match. In this incantation, this has been shifted to be run-time vice compile-time. Now, concerning the panic. Although I had originally default to no action, I actually see no issue with this panic as it will aggressively inform the user that he/she has something amis. I kind of feel that this is a better approach than doing nothing. In the do nothing variant, I could mis-type something and work for hours trying to figure out why my channel duty is not acting correctly; only to find out that I had put the wrong channel in there. If there was a panic, the compiler would yell at me (I think). If I am mis-understanding that it would occur at the compiler phase and it would be runtime, I would have a potential backtrace which would point to the exact place that needed to be checked. @burrbull I am cc'ing you here to see if you have any specific comments on this above what I have put here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed, panic is better than doing nothing
Panics happen at runtime, if possible I would like to move this to compile time but that is a difficult task.
Yes, but backtraces can be a bit of a pain to get a hold of in embedded contexts But again, we can probably take care of this in the future in another PR, so unless burbull has additional comments, I think this is good to go There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know how to do this check compile time because In fact we need possibility to write |
||
} | ||
} | ||
} | ||
|
||
#[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<REMAP> + gpio::Mode<Alternate<PushPull>>,)+ | ||
{ | ||
$(const $ENCHX: bool = true;)+ | ||
type Channels = ($(Pwm<TIM, $ENCHX>),+); | ||
type Channels = ($(PwmChannel<TIM, $ENCHX>),+); | ||
} | ||
)+ | ||
}; | ||
|
@@ -115,7 +137,7 @@ pins_impl!( | |
|
||
#[cfg(any(feature = "stm32f100", feature = "stm32f103", feature = "stm32f105",))] | ||
impl Timer<TIM1> { | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm<TIM1, REMAP, P, PINS> | ||
where | ||
REMAP: Remap<Periph = TIM1>, | ||
PINS: Pins<REMAP, P>, | ||
|
@@ -133,7 +155,7 @@ impl Timer<TIM1> { | |
} | ||
|
||
impl Timer<TIM2> { | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm<TIM2, REMAP, P, PINS> | ||
where | ||
REMAP: Remap<Periph = TIM2>, | ||
PINS: Pins<REMAP, P>, | ||
|
@@ -147,7 +169,7 @@ impl Timer<TIM2> { | |
} | ||
|
||
impl Timer<TIM3> { | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm<TIM3, REMAP, P, PINS> | ||
where | ||
REMAP: Remap<Periph = TIM3>, | ||
PINS: Pins<REMAP, P>, | ||
|
@@ -162,7 +184,7 @@ impl Timer<TIM3> { | |
|
||
#[cfg(feature = "medium")] | ||
impl Timer<TIM4> { | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> PINS::Channels | ||
pub fn pwm<REMAP, P, PINS, T>(self, _pins: PINS, mapr: &mut MAPR, freq: T) -> Pwm<TIM4, REMAP, P, PINS> | ||
where | ||
REMAP: Remap<Periph = TIM4>, | ||
PINS: Pins<REMAP, P>, | ||
|
@@ -175,7 +197,26 @@ impl Timer<TIM4> { | |
} | ||
} | ||
|
||
pub struct Pwm<TIM, CHANNEL> { | ||
pub struct Pwm<TIM, REMAP, P, PINS> | ||
where | ||
REMAP: Remap<Periph = TIM>, | ||
PINS: Pins<REMAP, P> | ||
{ | ||
clk: Hertz, | ||
_pins: PhantomData<(TIM, REMAP, P, PINS)>, | ||
} | ||
|
||
impl<TIM, REMAP, P, PINS> Pwm<TIM, REMAP, P, PINS> | ||
where | ||
REMAP: Remap<Periph = TIM>, | ||
PINS: Pins<REMAP, P> | ||
{ | ||
pub fn split(self) -> PINS::Channels { | ||
unsafe { mem::MaybeUninit::uninit().assume_init() } | ||
justacec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
pub struct PwmChannel<TIM, CHANNEL> { | ||
_channel: PhantomData<CHANNEL>, | ||
_tim: PhantomData<TIM>, | ||
} | ||
|
@@ -193,7 +234,7 @@ macro_rules! hal { | |
_pins: PINS, | ||
freq: Hertz, | ||
clk: Hertz, | ||
) -> PINS::Channels | ||
) -> Pwm<$TIMX, REMAP, P, PINS> | ||
where | ||
REMAP: Remap<Periph = $TIMX>, | ||
PINS: Pins<REMAP, P>, | ||
|
@@ -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<REMAP, P, PINS> hal::Pwm for Pwm<$TIMX, REMAP, P, PINS> where | ||
REMAP: Remap<Periph = $TIMX>, | ||
PINS: Pins<REMAP, P>, | ||
{ | ||
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<T>(&mut self, period: T) where | ||
T: Into<Self::Time> { | ||
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) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.