Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
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