Skip to content

RFC: docs and examples for custom selection of PWM channels #60

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

Merged
merged 4 commits into from
May 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Add methods `stop`, `release` and `clear_update_interrupt_flag` to `Timer` (`clear_update_interrupt_flag` does not apply to `Timer<SYST>`)
- Add timer interrupt example using RTFM
- Implement IndependentWatchdog for the IWDG peripheral
- Remove all PWM channel configurations except 'all the channels for default remapping' configuratons
- Update PWM documentation: clarify custom selection of channels
- Add PWM example for custom selection of channels

### Changed

Expand Down
2 changes: 1 addition & 1 deletion examples/pwm.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Testing PWM output
//! Testing PWM output for pre-defined pin combination: all pins for default mapping

#![deny(unsafe_code)]
#![no_main]
Expand Down
91 changes: 91 additions & 0 deletions examples/pwm_custom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! Testing PWM output for custom pin combinations

#![deny(unsafe_code)]
#![no_main]
#![no_std]

use panic_halt as _;

use cortex_m::asm;
use stm32f1xx_hal::{
gpio::gpiob::{PB4, PB5},
gpio::{Alternate, PushPull},
pac,
prelude::*,
pwm::{Pins, Pwm, C1, C2},
stm32::TIM3,
};

use cortex_m_rt::entry;

// Using PB5 channel for TIM3 PWM output
// struct MyChannels(PB5<Alternate<PushPull>>);
// impl Pins<TIM3> for MyChannels {
// const REMAP: u8 = 0b10;
// const C1: bool = false;
// const C2: bool = true;
// const C3: bool = false;
// const C4: bool = false;
// type Channels = Pwm<TIM3, C2>;
// }

// Using PB4 and PB5 channels for TIM3 PWM output
struct MyChannels(PB4<Alternate<PushPull>>, PB5<Alternate<PushPull>>);
impl Pins<TIM3> for MyChannels {
const REMAP: u8 = 0b10;
const C1: bool = true;
const C2: bool = true;
const C3: bool = false;
const C4: bool = false;
type Channels = (Pwm<TIM3, C1>, Pwm<TIM3, C2>);
}

#[entry]
fn main() -> ! {
let p = pac::Peripherals::take().unwrap();

let mut flash = p.FLASH.constrain();
let mut rcc = p.RCC.constrain();

let clocks = rcc.cfgr.freeze(&mut flash.acr);

let mut afio = p.AFIO.constrain(&mut rcc.apb2);

let mut gpiob = p.GPIOB.split(&mut rcc.apb2);

// TIM3
let p0 = gpiob.pb4.into_alternate_push_pull(&mut gpiob.crl);
let p1 = gpiob.pb5.into_alternate_push_pull(&mut gpiob.crl);

let mut pwm = p.TIM3.pwm(
MyChannels(p0, p1),
&mut afio.mapr,
1.khz(),
clocks,
&mut rcc.apb1,
);

let max = pwm.0.get_max_duty();

pwm.0.enable();
pwm.1.enable();

// full
pwm.0.set_duty(max);
pwm.1.set_duty(max);

asm::bkpt();

// dim
pwm.1.set_duty(max / 4);

asm::bkpt();

// zero
pwm.0.set_duty(0);
pwm.1.set_duty(0);

asm::bkpt();

loop {}
}
124 changes: 97 additions & 27 deletions src/pwm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
# Pulse width modulation

The general purpose timers (`TIM2`, `TIM3`, and `TIM4`) can be used to output
pulse width modulated signals on some pins. The timers support up to 4 simultaneous
pwm outputs in separate `Channels`
pulse width modulated signals on some pins. The timers support up to 4
simultaneous pwm outputs in separate `Channels`

## Usage
## Usage for pre-defined channel combinations

Start by setting all the pins for the timer you want to use to alternate
push pull pins. The `Pins` trait shows the mapping between timers, output pins
and channels.
This crate only defines basic channel combinations for default AFIO remappings,
where all the channels are enabled. Start by setting all the pins for the
timer you want to use to alternate push pull pins:

```rust
let gpioa = ..; // Set up and split GPIOA
Expand All @@ -21,7 +21,7 @@
);
```

Then call the `pwm` function on the corresponding timer
Then call the `pwm` function on the corresponding timer:

```
let device: pac::Peripherals = ..;
Expand All @@ -31,16 +31,104 @@
let (c0, c1, c2, c3) = device.TIM2.pwm(
pins,
&mut afio.mapr,
100.hz()
100.hz(),
clocks,
&mut rcc.apb
&mut rcc.apb1
);

// Set the duty cycle of channel 0 to 50%
c0.set_duty(c0.get_max_duty() / 2);
// PWM outputs are disabled by default
c0.enable()
```

## Usage for custom channel combinations

Note that crate itself defines only basic channel combinations for default AFIO remappings,
where all the channels are enabled. Meanwhile it is possible to configure PWM for any custom
selection of channels. The `Pins` trait shows the mapping between timers, output pins and
channels. So this trait needs to be implemented for the custom combination of channels and
AFIO remappings. However minor additional efforts are needed since it is not possible to
implement a foreign trait for a foreign type. The trick is to use the newtype pattern.

The first example selects PB5 channel for TIM3 PWM output:

```
struct MyChannels(PB5<Alternate<PushPull>>);

impl Pins<TIM3> for MyChannels {
const REMAP: u8 = 0b10; // use TIM3 AFIO remapping for PB4, PB5, PB0, PB1 pins
const C1: bool = false;
const C2: bool = true; // use channel C2
const C3: bool = false;
const C4: bool = false;
type Channels = Pwm<TIM3, C2>;
}
```

The second example selects PC8 and PC9 channels for TIM3 PWM output:

```
struct MyChannels(PB5<Alternate<PushPull>>);

impl Pins<TIM3> for MyChannels {
const REMAP: u8 = 0b11; // use TIM3 AFIO remapping for PC6, PC7, PC8, PC9 pins
const C1: bool = false;
const C2: bool = false;
const C3: bool = true; // use channel C3
const C4: bool = true; // use channel C4
type Channels = (Pwm<TIM3, C3>, Pwm<TIM3, C4>);
}
```

REMAP value and channel pins should be specified according to the stm32f1xx specification,
e.g. the section 9.3.7 "Timer alternate function remapping" in RM0008 Rev 20.

Finally, here is a complete example for two channels:

```
use stm32f1xx_hal::stm32::TIM3;
use stm32f1xx_hal::gpio::gpiob::{PB4, PB5};
use stm32f1xx_hal::gpio::{Alternate, PushPull};
use stm32f1xx_hal::pwm::{Pins, Pwm, C1, C2, C3, C4};

struct MyChannels(PB4<Alternate<PushPull>>, PB5<Alternate<PushPull>>);

impl Pins<TIM3> for MyChannels
{
const REMAP: u8 = 0b10;
const C1: bool = true;
const C2: bool = true;
const C3: bool = false;
const C4: bool = false;
type Channels = (Pwm<TIM3, C1>, Pwm<TIM3, C2>)
}

...

let gpiob = ..; // Set up and split GPIOB

let p1 = gpiob.pb4.into_alternate_push_pull(&mut gpiob.crl);
let p2 = gpiob.pb5.into_alternate_push_pull(&mut gpiob.crl);

...

let device: pac::Peripherals = ..;

let (c1, c2) = device.TIM3.pwm(
MyChannels(p1, p2),
&mut afio.mapr,
100.hz(),
clocks,
&mut rcc.apb1
);

// Set the duty cycle of channel 0 to 50%
c1.set_duty(c1.get_max_duty() / 2);
// PWM outputs are disabled by default
c1.enable()

```
*/

use core::marker::PhantomData;
Expand Down Expand Up @@ -84,15 +172,6 @@ impl Pins<TIM2>
type Channels = (Pwm<TIM2, C1>, Pwm<TIM2, C2>, Pwm<TIM2, C3>, Pwm<TIM2, C4>);
}

impl Pins<TIM2> for PA0<Alternate<PushPull>> {
const REMAP: u8 = 0b00;
const C1: bool = true;
const C2: bool = false;
const C3: bool = false;
const C4: bool = false;
type Channels = Pwm<TIM2, C1>;
}

impl Pins<TIM3>
for (
PA6<Alternate<PushPull>>,
Expand All @@ -109,15 +188,6 @@ impl Pins<TIM3>
type Channels = (Pwm<TIM3, C1>, Pwm<TIM3, C2>, Pwm<TIM3, C3>, Pwm<TIM3, C4>);
}

impl Pins<TIM3> for (PB0<Alternate<PushPull>>, PB1<Alternate<PushPull>>) {
const REMAP: u8 = 0b00;
const C1: bool = false;
const C2: bool = false;
const C3: bool = true;
const C4: bool = true;
type Channels = (Pwm<TIM3, C3>, Pwm<TIM3, C4>);
}

impl Pins<TIM4>
for (
PB6<Alternate<PushPull>>,
Expand Down