Skip to content

Commit

Permalink
Implement full embedded_hal::Pwm (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
justacec authored Mar 31, 2020
1 parent 01f1390 commit afcb5bf
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 42 additions & 9 deletions examples/pwm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use stm32f1xx_hal::{
prelude::*,
pac,
timer::{Tim2NoRemap, Timer},
time::U32Ext,
pwm::Channel
};
use cortex_m_rt::entry;

Expand Down Expand Up @@ -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::<Tim2NoRemap, _, _, _>(pins, &mut afio.mapr, 1.khz())
.2;
.pwm::<Tim2NoRemap, _, _, _>(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();

Expand Down
21 changes: 12 additions & 9 deletions examples/pwm_custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
152 changes: 140 additions & 12 deletions src/pwm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
*/

use core::marker::PhantomData;
use core::marker::{Copy};
use core::mem;

use crate::hal;
Expand All @@ -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> {
Expand All @@ -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")
}
}
}

#[derive(Clone, Copy, PartialEq)]
pub enum Channel {
C1,
C2,
C3,
C4,
}

use crate::timer::sealed::{Ch1, Ch2, Ch3, Ch4, Remap};
Expand All @@ -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>),+);
}
)+
};
Expand All @@ -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>,
Expand All @@ -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>,
Expand All @@ -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>,
Expand All @@ -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>,
Expand All @@ -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() }
}
}

pub struct PwmChannel<TIM, CHANNEL> {
_channel: PhantomData<CHANNEL>,
_tim: PhantomData<TIM>,
}
Expand All @@ -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>,
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit afcb5bf

Please sign in to comment.