Skip to content
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

Add PWM module #200

Merged
merged 14 commits into from
Aug 26, 2020
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ members = [
"examples/wdt-demo",
"examples/qdec-demo",
"examples/comp-demo",
"examples/pwm-demo",
]

[profile.dev]
Expand Down
17 changes: 17 additions & 0 deletions examples/pwm-demo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "pwm-demo"
version = "0.1.0"
authors = ["Henrik Alsér"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
cortex-m = "0.6.2"
cortex-m-rtic = "0.5.3"
rtt-target = {version = "0.2.0", features = ["cortex-m"] }
nrf52840-hal = { features = ["rt"], path = "../../nrf52840-hal" }

[dependencies.embedded-hal]
version = "0.2.3"
features = ["unproven"]
21 changes: 21 additions & 0 deletions examples/pwm-demo/Embed.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[default.probe]
protocol = "Swd"

[default.flashing]
enabled = true
halt_afterwards = false
restore_unwritten_bytes = false

[default.general]
chip = "nRF52840"
chip_descriptions = []
log_level = "Warn"

[default.rtt]
enabled = true
channels = []
timeout = 3000
show_timestamps = true

[default.gdb]
enabled = false
280 changes: 280 additions & 0 deletions examples/pwm-demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
#![no_std]
#![no_main]

use embedded_hal::digital::v2::InputPin;
use {
core::{
panic::PanicInfo,
sync::atomic::{compiler_fence, Ordering},
},
hal::{
gpio::{p0::Parts, Input, Level, Pin, PullUp},
gpiote::Gpiote,
pac::PWM0,
pwm::*,
time::*,
},
nrf52840_hal as hal,
rtic::cyccnt::U32Ext as _,
rtt_target::{rprintln, rtt_init_print},
};

#[derive(Debug, PartialEq)]
pub enum AppStatus {
Idle,
Demo1A,
Demo1B,
Demo1C,
Demo2A,
Demo2B,
Demo2C,
Demo3,
Demo4,
}

#[rtic::app(device = crate::hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
const APP: () = {
struct Resources {
gpiote: Gpiote,
btn1: Pin<Input<PullUp>>,
btn2: Pin<Input<PullUp>>,
btn3: Pin<Input<PullUp>>,
btn4: Pin<Input<PullUp>>,
pwm: Pwm<PWM0>,
#[init(AppStatus::Idle)]
status: AppStatus,
}

#[init]
fn init(mut ctx: init::Context) -> init::LateResources {
let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc();
ctx.core.DCB.enable_trace();
ctx.core.DWT.enable_cycle_counter();
rtt_init_print!();

let p0 = Parts::new(ctx.device.P0);
let btn1 = p0.p0_11.into_pullup_input().degrade();
let btn2 = p0.p0_12.into_pullup_input().degrade();
let btn3 = p0.p0_24.into_pullup_input().degrade();
let btn4 = p0.p0_25.into_pullup_input().degrade();
let led1 = p0.p0_13.into_push_pull_output(Level::High).degrade();
let led2 = p0.p0_14.into_push_pull_output(Level::High).degrade();
let led3 = p0.p0_15.into_push_pull_output(Level::High).degrade();
let led4 = p0.p0_16.into_push_pull_output(Level::High).degrade();

let pwm = Pwm::new(ctx.device.PWM0);
pwm.set_period(500u32.hz())
.set_output_pin(Channel::C0, &led1)
.set_output_pin(Channel::C1, &led2)
.set_output_pin(Channel::C2, &led3)
.set_output_pin(Channel::C3, &led4)
.enable_interrupt(PwmEvent::Stopped)
.enable();

let gpiote = Gpiote::new(ctx.device.GPIOTE);
gpiote.port().input_pin(&btn1).low();
gpiote.port().input_pin(&btn2).low();
gpiote.port().input_pin(&btn3).low();
gpiote.port().input_pin(&btn4).low();
gpiote.port().enable_interrupt();

init::LateResources {
gpiote,
btn1,
btn2,
btn3,
btn4,
pwm,
}
}

#[idle]
fn idle(_: idle::Context) -> ! {
rprintln!("Press a button to start a demo");
loop {
cortex_m::asm::wfi();
}
}

#[task(binds = PWM0, resources = [pwm])]
fn on_pwm(ctx: on_pwm::Context) {
let pwm = ctx.resources.pwm;
if pwm.is_event_triggered(PwmEvent::Stopped) {
pwm.reset_event(PwmEvent::Stopped);
rprintln!("PWM generation was stopped");
}
}

#[task(binds = GPIOTE, resources = [gpiote], schedule = [debounce])]
fn on_gpiote(ctx: on_gpiote::Context) {
ctx.resources.gpiote.reset_events();
ctx.schedule.debounce(ctx.start + 3_000_000.cycles()).ok();
}

#[task(resources = [btn1, btn2, btn3, btn4, pwm, status])]
fn debounce(ctx: debounce::Context) {
static mut BUF: [u16; 48] = [0u16; 48];
let status = ctx.resources.status;

let pwm = ctx.resources.pwm;
let max_duty = pwm.get_max_duty();
let (ch0, ch1, ch2, ch3) = pwm.split_channels();
let (grp0, grp1) = pwm.split_groups();

if ctx.resources.btn1.is_low().unwrap() {
match status {
AppStatus::Demo1B => {
rprintln!("DEMO 1C: Individual channel duty cycle");
*status = AppStatus::Demo1C;
ch0.set_duty(max_duty / 10);
ch1.set_duty(max_duty / 50);
ch2.set_duty(max_duty / 100);
ch3.set_duty(max_duty / 500);
}
AppStatus::Demo1A => {
rprintln!("DEMO 1B: Group duty cycle");
*status = AppStatus::Demo1B;
grp0.set_duty(max_duty / 300);
grp1.set_duty(max_duty / 10);
}
_ => {
rprintln!("DEMO 1A: Common duty cycle for all channels");
*status = AppStatus::Demo1A;
pwm.set_duty_on_common(max_duty / 10);
}
}
}
if ctx.resources.btn2.is_low().unwrap() {
match status {
AppStatus::Demo2B => {
rprintln!("DEMO 2C: Play grouped sequence 4 times");
*status = AppStatus::Demo2C;
let ampl = max_duty as i32 / 20;
let len: usize = 12;
// In `Grouped` mode, each step consists of two values [G0, G1]
for x in 0..len {
BUF[x * 2] = triangle_wave(x, len, ampl, 6, 0) as u16;
BUF[x * 2 + 1] = triangle_wave(x, len, ampl, 0, 0) as u16;
}
pwm.set_load_mode(LoadMode::Grouped)
.set_step_mode(StepMode::Auto)
.set_seq_refresh(Seq::Seq0, 70) // Playback rate (periods per step)
.set_seq_refresh(Seq::Seq1, 30)
.repeat(4);
pwm.load_seq(Seq::Seq0, &BUF[..len]).ok();
pwm.load_seq(Seq::Seq1, &BUF[len..(2 * len)]).ok();
pwm.start_seq(Seq::Seq0);
}
AppStatus::Demo2A => {
rprintln!("DEMO 2B: Loop individual sequences");
*status = AppStatus::Demo2B;
let ampl = max_duty as i32 / 5;
let offset = max_duty as i32 / 300;
let len = 12;
// In `Individual` mode, each step consists of four values [C0, C1, C2, C3]
for x in 0..len {
BUF[4 * x] = triangle_wave(x, len, ampl, 0, offset) as u16;
BUF[4 * x + 1] = triangle_wave(x, len, ampl, 3, offset) as u16;
BUF[4 * x + 2] = triangle_wave(x, len, ampl, 6, offset) as u16;
BUF[4 * x + 3] = triangle_wave(x, len, ampl, 9, offset) as u16;
}
pwm.set_load_mode(LoadMode::Individual)
.set_seq_refresh(Seq::Seq0, 30)
.set_seq_refresh(Seq::Seq1, 30)
.loop_inf();
pwm.load_seq(Seq::Seq0, &BUF[..(4 * len)]).ok();
pwm.load_seq(Seq::Seq1, &BUF[..(4 * len)]).ok();
pwm.start_seq(Seq::Seq0);
}
_ => {
rprintln!("DEMO 2A: Play common sequence once");
*status = AppStatus::Demo2A;
let len = 10;
// In `Common` mode, each step consists of one value for all channels.
for x in 0..len {
BUF[x] = triangle_wave(x, len, 2000, 0, 100) as u16;
}
pwm.set_load_mode(LoadMode::Common)
.set_step_mode(StepMode::Auto)
.set_seq_refresh(Seq::Seq0, 50)
.one_shot()
.load_seq(Seq::Seq0, &BUF[..len])
.ok();
pwm.start_seq(Seq::Seq0);
}
}
}
if ctx.resources.btn3.is_low().unwrap() {
match status {
AppStatus::Demo3 => {
rprintln!("DEMO 3: Next step");
pwm.next_step();
if pwm.is_event_triggered(PwmEvent::SeqEnd(Seq::Seq1)) {
rprintln!("DEMO 3: End");
pwm.reset_event(PwmEvent::SeqEnd(Seq::Seq1));
pwm.stop();
*status = AppStatus::Idle;
}
}
_ => {
rprintln!("DEMO 3: Manually step through sequence");
*status = AppStatus::Demo3;
let amplitude = max_duty as i32 / 20;
let offset = max_duty as i32 / 300;
let len = 6;
for x in 0..len {
BUF[x] = triangle_wave(x, len, amplitude, 0, offset) as u16;
}
pwm.set_load_mode(LoadMode::Common)
.set_step_mode(StepMode::NextStep)
.loop_inf();
pwm.load_seq(Seq::Seq0, &BUF[..(len / 2)]).ok();
pwm.load_seq(Seq::Seq1, &BUF[(len / 2)..len]).ok();
pwm.start_seq(Seq::Seq0);
}
}
}
if ctx.resources.btn4.is_low().unwrap() {
rprintln!("DEMO 4: Waveform mode");
*status = AppStatus::Demo4;
let len = 12;
// In `Waveform` mode, each step consists of four values [C0, C1, C2, MAX_DUTY]
// So the maximum duty cycle can be set on a per step basis, affecting the PWM frequency
for x in 0..len {
let current_max = x * 2_200 + 5_000;
BUF[4 * x] = ((x % 3) * current_max / (5 * (x + 1))) as u16;
BUF[4 * x + 1] = (((x + 1) % 3) * current_max / (5 * (x + 1))) as u16;
BUF[4 * x + 2] = (((x + 2) % 3) * current_max / (5 * (x + 1))) as u16;
BUF[4 * x + 3] = current_max as u16;
}
pwm.set_load_mode(LoadMode::Waveform)
.set_step_mode(StepMode::Auto)
.set_seq_refresh(Seq::Seq0, 150)
.set_seq_refresh(Seq::Seq1, 150)
.loop_inf();
pwm.load_seq(Seq::Seq0, &BUF[..(4 * len)]).ok();
pwm.load_seq(Seq::Seq1, &BUF[..(4 * len)]).ok();
pwm.start_seq(Seq::Seq0);
}
}

extern "C" {
fn SWI0_EGU0();
}
};

fn triangle_wave(x: usize, length: usize, ampl: i32, phase: i32, y_offset: i32) -> i32 {
let x = x as i32;
let length = length as i32;
ampl - ((2 * (x + phase) * ampl / length) % (2 * ampl) - ampl).abs() + y_offset
}

#[inline(never)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
cortex_m::interrupt::disable();
rprintln!("{}", info);
loop {
compiler_fence(Ordering::SeqCst);
}
}
2 changes: 2 additions & 0 deletions nrf-hal-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub mod gpio;
pub mod gpiote;
#[cfg(not(feature = "9160"))]
pub mod ppi;
#[cfg(any(feature = "52833", feature = "52840"))]
pub mod pwm;
#[cfg(not(any(feature = "51", feature = "9160")))]
pub mod qdec;
#[cfg(not(feature = "9160"))]
Expand Down
Loading