Skip to content

Commit 63697e5

Browse files
Merge pull request #200 from kalkyl/pwm
Add PWM module
2 parents 5e98514 + 8341d92 commit 63697e5

File tree

6 files changed

+1323
-0
lines changed

6 files changed

+1323
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ members = [
1616
"examples/lpcomp-demo",
1717
"examples/qdec-demo",
1818
"examples/comp-demo",
19+
"examples/pwm-demo",
1920
"examples/twim-demo",
2021
"examples/twis-demo",
2122
]

examples/pwm-demo/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "pwm-demo"
3+
version = "0.1.0"
4+
authors = ["Henrik Alsér"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
cortex-m = "0.6.2"
11+
cortex-m-rtic = "0.5.3"
12+
rtt-target = {version = "0.2.0", features = ["cortex-m"] }
13+
nrf52840-hal = { features = ["rt"], path = "../../nrf52840-hal" }
14+
15+
[dependencies.embedded-hal]
16+
version = "0.2.3"
17+
features = ["unproven"]

examples/pwm-demo/Embed.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[default.probe]
2+
protocol = "Swd"
3+
4+
[default.flashing]
5+
enabled = true
6+
halt_afterwards = false
7+
restore_unwritten_bytes = false
8+
9+
[default.general]
10+
chip = "nRF52840"
11+
chip_descriptions = []
12+
log_level = "Warn"
13+
14+
[default.rtt]
15+
enabled = true
16+
channels = []
17+
timeout = 3000
18+
show_timestamps = true
19+
20+
[default.gdb]
21+
enabled = false

examples/pwm-demo/src/main.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use embedded_hal::digital::v2::InputPin;
5+
use {
6+
core::{
7+
panic::PanicInfo,
8+
sync::atomic::{compiler_fence, Ordering},
9+
},
10+
hal::{
11+
gpio::{p0::Parts, Input, Level, Pin, PullUp},
12+
gpiote::Gpiote,
13+
pac::PWM0,
14+
pwm::*,
15+
time::*,
16+
},
17+
nrf52840_hal as hal,
18+
rtic::cyccnt::U32Ext as _,
19+
rtt_target::{rprintln, rtt_init_print},
20+
};
21+
22+
#[derive(Debug, PartialEq)]
23+
pub enum AppStatus {
24+
Idle,
25+
Demo1A,
26+
Demo1B,
27+
Demo1C,
28+
Demo2A,
29+
Demo2B,
30+
Demo2C,
31+
Demo3,
32+
Demo4,
33+
}
34+
35+
#[rtic::app(device = crate::hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
36+
const APP: () = {
37+
struct Resources {
38+
gpiote: Gpiote,
39+
btn1: Pin<Input<PullUp>>,
40+
btn2: Pin<Input<PullUp>>,
41+
btn3: Pin<Input<PullUp>>,
42+
btn4: Pin<Input<PullUp>>,
43+
pwm: Pwm<PWM0>,
44+
#[init(AppStatus::Idle)]
45+
status: AppStatus,
46+
}
47+
48+
#[init]
49+
fn init(mut ctx: init::Context) -> init::LateResources {
50+
let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc();
51+
ctx.core.DCB.enable_trace();
52+
ctx.core.DWT.enable_cycle_counter();
53+
rtt_init_print!();
54+
55+
let p0 = Parts::new(ctx.device.P0);
56+
let btn1 = p0.p0_11.into_pullup_input().degrade();
57+
let btn2 = p0.p0_12.into_pullup_input().degrade();
58+
let btn3 = p0.p0_24.into_pullup_input().degrade();
59+
let btn4 = p0.p0_25.into_pullup_input().degrade();
60+
let led1 = p0.p0_13.into_push_pull_output(Level::High).degrade();
61+
let led2 = p0.p0_14.into_push_pull_output(Level::High).degrade();
62+
let led3 = p0.p0_15.into_push_pull_output(Level::High).degrade();
63+
let led4 = p0.p0_16.into_push_pull_output(Level::High).degrade();
64+
65+
let pwm = Pwm::new(ctx.device.PWM0);
66+
pwm.set_period(500u32.hz())
67+
.set_output_pin(Channel::C0, &led1)
68+
.set_output_pin(Channel::C1, &led2)
69+
.set_output_pin(Channel::C2, &led3)
70+
.set_output_pin(Channel::C3, &led4)
71+
.enable_interrupt(PwmEvent::Stopped)
72+
.enable();
73+
74+
let gpiote = Gpiote::new(ctx.device.GPIOTE);
75+
gpiote.port().input_pin(&btn1).low();
76+
gpiote.port().input_pin(&btn2).low();
77+
gpiote.port().input_pin(&btn3).low();
78+
gpiote.port().input_pin(&btn4).low();
79+
gpiote.port().enable_interrupt();
80+
81+
init::LateResources {
82+
gpiote,
83+
btn1,
84+
btn2,
85+
btn3,
86+
btn4,
87+
pwm,
88+
}
89+
}
90+
91+
#[idle]
92+
fn idle(_: idle::Context) -> ! {
93+
rprintln!("Press a button to start a demo");
94+
loop {
95+
cortex_m::asm::wfi();
96+
}
97+
}
98+
99+
#[task(binds = PWM0, resources = [pwm])]
100+
fn on_pwm(ctx: on_pwm::Context) {
101+
let pwm = ctx.resources.pwm;
102+
if pwm.is_event_triggered(PwmEvent::Stopped) {
103+
pwm.reset_event(PwmEvent::Stopped);
104+
rprintln!("PWM generation was stopped");
105+
}
106+
}
107+
108+
#[task(binds = GPIOTE, resources = [gpiote], schedule = [debounce])]
109+
fn on_gpiote(ctx: on_gpiote::Context) {
110+
ctx.resources.gpiote.reset_events();
111+
ctx.schedule.debounce(ctx.start + 3_000_000.cycles()).ok();
112+
}
113+
114+
#[task(resources = [btn1, btn2, btn3, btn4, pwm, status])]
115+
fn debounce(ctx: debounce::Context) {
116+
static mut BUF: [u16; 48] = [0u16; 48];
117+
let status = ctx.resources.status;
118+
119+
let pwm = ctx.resources.pwm;
120+
let max_duty = pwm.max_duty();
121+
let (ch0, ch1, ch2, ch3) = pwm.split_channels();
122+
let (grp0, grp1) = pwm.split_groups();
123+
124+
if ctx.resources.btn1.is_low().unwrap() {
125+
match status {
126+
AppStatus::Demo1B => {
127+
rprintln!("DEMO 1C: Individual channel duty cycle");
128+
*status = AppStatus::Demo1C;
129+
ch0.set_duty(max_duty / 10);
130+
ch1.set_duty(max_duty / 50);
131+
ch2.set_duty(max_duty / 100);
132+
ch3.set_duty(max_duty / 500);
133+
}
134+
AppStatus::Demo1A => {
135+
rprintln!("DEMO 1B: Group duty cycle");
136+
*status = AppStatus::Demo1B;
137+
grp0.set_duty(max_duty / 300);
138+
grp1.set_duty(max_duty / 10);
139+
}
140+
_ => {
141+
rprintln!("DEMO 1A: Common duty cycle for all channels");
142+
*status = AppStatus::Demo1A;
143+
pwm.set_duty_on_common(max_duty / 10);
144+
}
145+
}
146+
}
147+
if ctx.resources.btn2.is_low().unwrap() {
148+
match status {
149+
AppStatus::Demo2B => {
150+
rprintln!("DEMO 2C: Play grouped sequence 4 times");
151+
*status = AppStatus::Demo2C;
152+
let ampl = max_duty as i32 / 20;
153+
let len: usize = 12;
154+
// In `Grouped` mode, each step consists of two values [G0, G1]
155+
for x in 0..len {
156+
BUF[x * 2] = triangle_wave(x, len, ampl, 6, 0) as u16;
157+
BUF[x * 2 + 1] = triangle_wave(x, len, ampl, 0, 0) as u16;
158+
}
159+
pwm.set_load_mode(LoadMode::Grouped)
160+
.set_step_mode(StepMode::Auto)
161+
.set_seq_refresh(Seq::Seq0, 70) // Playback rate (periods per step)
162+
.set_seq_refresh(Seq::Seq1, 30)
163+
.repeat(4);
164+
pwm.load_seq(Seq::Seq0, &BUF[..len]).ok();
165+
pwm.load_seq(Seq::Seq1, &BUF[len..(2 * len)]).ok();
166+
pwm.start_seq(Seq::Seq0);
167+
}
168+
AppStatus::Demo2A => {
169+
rprintln!("DEMO 2B: Loop individual sequences");
170+
*status = AppStatus::Demo2B;
171+
let ampl = max_duty as i32 / 5;
172+
let offset = max_duty as i32 / 300;
173+
let len = 12;
174+
// In `Individual` mode, each step consists of four values [C0, C1, C2, C3]
175+
for x in 0..len {
176+
BUF[4 * x] = triangle_wave(x, len, ampl, 0, offset) as u16;
177+
BUF[4 * x + 1] = triangle_wave(x, len, ampl, 3, offset) as u16;
178+
BUF[4 * x + 2] = triangle_wave(x, len, ampl, 6, offset) as u16;
179+
BUF[4 * x + 3] = triangle_wave(x, len, ampl, 9, offset) as u16;
180+
}
181+
pwm.set_load_mode(LoadMode::Individual)
182+
.set_seq_refresh(Seq::Seq0, 30)
183+
.set_seq_refresh(Seq::Seq1, 30)
184+
.loop_inf();
185+
pwm.load_seq(Seq::Seq0, &BUF[..(4 * len)]).ok();
186+
pwm.load_seq(Seq::Seq1, &BUF[..(4 * len)]).ok();
187+
pwm.start_seq(Seq::Seq0);
188+
}
189+
_ => {
190+
rprintln!("DEMO 2A: Play common sequence once");
191+
*status = AppStatus::Demo2A;
192+
let len = 10;
193+
// In `Common` mode, each step consists of one value for all channels.
194+
for x in 0..len {
195+
BUF[x] = triangle_wave(x, len, 2000, 0, 100) as u16;
196+
}
197+
pwm.set_load_mode(LoadMode::Common)
198+
.set_step_mode(StepMode::Auto)
199+
.set_seq_refresh(Seq::Seq0, 50)
200+
.one_shot()
201+
.load_seq(Seq::Seq0, &BUF[..len])
202+
.ok();
203+
pwm.start_seq(Seq::Seq0);
204+
}
205+
}
206+
}
207+
if ctx.resources.btn3.is_low().unwrap() {
208+
match status {
209+
AppStatus::Demo3 => {
210+
rprintln!("DEMO 3: Next step");
211+
pwm.next_step();
212+
if pwm.is_event_triggered(PwmEvent::SeqEnd(Seq::Seq1)) {
213+
rprintln!("DEMO 3: End");
214+
pwm.reset_event(PwmEvent::SeqEnd(Seq::Seq1));
215+
pwm.stop();
216+
*status = AppStatus::Idle;
217+
}
218+
}
219+
_ => {
220+
rprintln!("DEMO 3: Manually step through sequence");
221+
*status = AppStatus::Demo3;
222+
let amplitude = max_duty as i32 / 20;
223+
let offset = max_duty as i32 / 300;
224+
let len = 6;
225+
for x in 0..len {
226+
BUF[x] = triangle_wave(x, len, amplitude, 0, offset) as u16;
227+
}
228+
pwm.set_load_mode(LoadMode::Common)
229+
.set_step_mode(StepMode::NextStep)
230+
.loop_inf();
231+
pwm.load_seq(Seq::Seq0, &BUF[..(len / 2)]).ok();
232+
pwm.load_seq(Seq::Seq1, &BUF[(len / 2)..len]).ok();
233+
pwm.start_seq(Seq::Seq0);
234+
}
235+
}
236+
}
237+
if ctx.resources.btn4.is_low().unwrap() {
238+
rprintln!("DEMO 4: Waveform mode");
239+
*status = AppStatus::Demo4;
240+
let len = 12;
241+
// In `Waveform` mode, each step consists of four values [C0, C1, C2, MAX_DUTY]
242+
// So the maximum duty cycle can be set on a per step basis, affecting the PWM frequency
243+
for x in 0..len {
244+
let current_max = x * 2_200 + 5_000;
245+
BUF[4 * x] = ((x % 3) * current_max / (5 * (x + 1))) as u16;
246+
BUF[4 * x + 1] = (((x + 1) % 3) * current_max / (5 * (x + 1))) as u16;
247+
BUF[4 * x + 2] = (((x + 2) % 3) * current_max / (5 * (x + 1))) as u16;
248+
BUF[4 * x + 3] = current_max as u16;
249+
}
250+
pwm.set_load_mode(LoadMode::Waveform)
251+
.set_step_mode(StepMode::Auto)
252+
.set_seq_refresh(Seq::Seq0, 150)
253+
.set_seq_refresh(Seq::Seq1, 150)
254+
.loop_inf();
255+
pwm.load_seq(Seq::Seq0, &BUF[..(4 * len)]).ok();
256+
pwm.load_seq(Seq::Seq1, &BUF[..(4 * len)]).ok();
257+
pwm.start_seq(Seq::Seq0);
258+
}
259+
}
260+
261+
extern "C" {
262+
fn SWI0_EGU0();
263+
}
264+
};
265+
266+
fn triangle_wave(x: usize, length: usize, ampl: i32, phase: i32, y_offset: i32) -> i32 {
267+
let x = x as i32;
268+
let length = length as i32;
269+
ampl - ((2 * (x + phase) * ampl / length) % (2 * ampl) - ampl).abs() + y_offset
270+
}
271+
272+
#[inline(never)]
273+
#[panic_handler]
274+
fn panic(info: &PanicInfo) -> ! {
275+
cortex_m::interrupt::disable();
276+
rprintln!("{}", info);
277+
loop {
278+
compiler_fence(Ordering::SeqCst);
279+
}
280+
}

nrf-hal-common/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ pub mod gpiote;
4141
pub mod lpcomp;
4242
#[cfg(not(feature = "9160"))]
4343
pub mod ppi;
44+
#[cfg(any(feature = "52833", feature = "52840"))]
45+
pub mod pwm;
4446
#[cfg(not(any(feature = "51", feature = "9160")))]
4547
pub mod qdec;
4648
#[cfg(not(feature = "9160"))]

0 commit comments

Comments
 (0)