aboutsummaryrefslogtreecommitdiff
path: root/examples/rp
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-04-26 20:24:30 +0000
committerGitHub <[email protected]>2024-04-26 20:24:30 +0000
commit5b0735688df0da67b5fe2f1b96090f1eba83c884 (patch)
tree414a267a707866d623889d2e52a0668737a33e12 /examples/rp
parent49b143ec6d29aa9373049098a2875aec3f3721f6 (diff)
parentd2ba751c06725c62a576da9a27be301666118955 (diff)
Merge pull request #2846 from BjornTheProgrammer/main
Added PIO pwm examples for rp
Diffstat (limited to 'examples/rp')
-rw-r--r--examples/rp/src/bin/pio_pwm.rs118
-rw-r--r--examples/rp/src/bin/pio_servo.rs208
2 files changed, 326 insertions, 0 deletions
diff --git a/examples/rp/src/bin/pio_pwm.rs b/examples/rp/src/bin/pio_pwm.rs
new file mode 100644
index 000000000..23d63d435
--- /dev/null
+++ b/examples/rp/src/bin/pio_pwm.rs
@@ -0,0 +1,118 @@
1//! This example shows how to create a pwm using the PIO module in the RP2040 chip.
2
3#![no_std]
4#![no_main]
5use core::time::Duration;
6
7use embassy_executor::Spawner;
8use embassy_rp::gpio::Level;
9use embassy_rp::peripherals::PIO0;
10use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
11use embassy_rp::{bind_interrupts, clocks};
12use embassy_time::Timer;
13use pio::InstructionOperands;
14use {defmt_rtt as _, panic_probe as _};
15
16const REFRESH_INTERVAL: u64 = 20000;
17
18bind_interrupts!(struct Irqs {
19 PIO0_IRQ_0 => InterruptHandler<PIO0>;
20});
21
22pub fn to_pio_cycles(duration: Duration) -> u32 {
23 (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
24}
25
26pub struct PwmPio<'d, T: Instance, const SM: usize> {
27 sm: StateMachine<'d, T, SM>,
28}
29
30impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> {
31 pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self {
32 let prg = pio_proc::pio_asm!(
33 ".side_set 1 opt"
34 "pull noblock side 0"
35 "mov x, osr"
36 "mov y, isr"
37 "countloop:"
38 "jmp x!=y noset"
39 "jmp skip side 1"
40 "noset:"
41 "nop"
42 "skip:"
43 "jmp y-- countloop"
44 );
45
46 pio.load_program(&prg.program);
47 let pin = pio.make_pio_pin(pin);
48 sm.set_pins(Level::High, &[&pin]);
49 sm.set_pin_dirs(Direction::Out, &[&pin]);
50
51 let mut cfg = Config::default();
52 cfg.use_program(&pio.load_program(&prg.program), &[&pin]);
53
54 sm.set_config(&cfg);
55
56 Self { sm }
57 }
58
59 pub fn start(&mut self) {
60 self.sm.set_enable(true);
61 }
62
63 pub fn stop(&mut self) {
64 self.sm.set_enable(false);
65 }
66
67 pub fn set_period(&mut self, duration: Duration) {
68 let is_enabled = self.sm.is_enabled();
69 while !self.sm.tx().empty() {} // Make sure that the queue is empty
70 self.sm.set_enable(false);
71 self.sm.tx().push(to_pio_cycles(duration));
72 unsafe {
73 self.sm.exec_instr(
74 InstructionOperands::PULL {
75 if_empty: false,
76 block: false,
77 }
78 .encode(),
79 );
80 self.sm.exec_instr(
81 InstructionOperands::OUT {
82 destination: ::pio::OutDestination::ISR,
83 bit_count: 32,
84 }
85 .encode(),
86 );
87 };
88 if is_enabled {
89 self.sm.set_enable(true) // Enable if previously enabled
90 }
91 }
92
93 pub fn set_level(&mut self, level: u32) {
94 self.sm.tx().push(level);
95 }
96
97 pub fn write(&mut self, duration: Duration) {
98 self.set_level(to_pio_cycles(duration));
99 }
100}
101
102#[embassy_executor::main]
103async fn main(_spawner: Spawner) {
104 let p = embassy_rp::init(Default::default());
105 let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
106
107 // Note that PIN_25 is the led pin on the Pico
108 let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25);
109 pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL));
110 pwm_pio.start();
111
112 let mut duration = 0;
113 loop {
114 duration = (duration + 1) % 1000;
115 pwm_pio.write(Duration::from_micros(duration));
116 Timer::after_millis(1).await;
117 }
118}
diff --git a/examples/rp/src/bin/pio_servo.rs b/examples/rp/src/bin/pio_servo.rs
new file mode 100644
index 000000000..a79540479
--- /dev/null
+++ b/examples/rp/src/bin/pio_servo.rs
@@ -0,0 +1,208 @@
1//! This example shows how to create a pwm using the PIO module in the RP2040 chip.
2
3#![no_std]
4#![no_main]
5use core::time::Duration;
6
7use embassy_executor::Spawner;
8use embassy_rp::gpio::Level;
9use embassy_rp::peripherals::PIO0;
10use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
11use embassy_rp::{bind_interrupts, clocks};
12use embassy_time::Timer;
13use pio::InstructionOperands;
14use {defmt_rtt as _, panic_probe as _};
15
16const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo
17const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo
18const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical
19const REFRESH_INTERVAL: u64 = 20000; // The period of each cycle
20
21bind_interrupts!(struct Irqs {
22 PIO0_IRQ_0 => InterruptHandler<PIO0>;
23});
24
25pub fn to_pio_cycles(duration: Duration) -> u32 {
26 (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
27}
28
29pub struct PwmPio<'d, T: Instance, const SM: usize> {
30 sm: StateMachine<'d, T, SM>,
31}
32
33impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> {
34 pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self {
35 let prg = pio_proc::pio_asm!(
36 ".side_set 1 opt"
37 "pull noblock side 0"
38 "mov x, osr"
39 "mov y, isr"
40 "countloop:"
41 "jmp x!=y noset"
42 "jmp skip side 1"
43 "noset:"
44 "nop"
45 "skip:"
46 "jmp y-- countloop"
47 );
48
49 pio.load_program(&prg.program);
50 let pin = pio.make_pio_pin(pin);
51 sm.set_pins(Level::High, &[&pin]);
52 sm.set_pin_dirs(Direction::Out, &[&pin]);
53
54 let mut cfg = Config::default();
55 cfg.use_program(&pio.load_program(&prg.program), &[&pin]);
56
57 sm.set_config(&cfg);
58
59 Self { sm }
60 }
61
62 pub fn start(&mut self) {
63 self.sm.set_enable(true);
64 }
65
66 pub fn stop(&mut self) {
67 self.sm.set_enable(false);
68 }
69
70 pub fn set_period(&mut self, duration: Duration) {
71 let is_enabled = self.sm.is_enabled();
72 while !self.sm.tx().empty() {} // Make sure that the queue is empty
73 self.sm.set_enable(false);
74 self.sm.tx().push(to_pio_cycles(duration));
75 unsafe {
76 self.sm.exec_instr(
77 InstructionOperands::PULL {
78 if_empty: false,
79 block: false,
80 }
81 .encode(),
82 );
83 self.sm.exec_instr(
84 InstructionOperands::OUT {
85 destination: ::pio::OutDestination::ISR,
86 bit_count: 32,
87 }
88 .encode(),
89 );
90 };
91 if is_enabled {
92 self.sm.set_enable(true) // Enable if previously enabled
93 }
94 }
95
96 pub fn set_level(&mut self, level: u32) {
97 self.sm.tx().push(level);
98 }
99
100 pub fn write(&mut self, duration: Duration) {
101 self.set_level(to_pio_cycles(duration));
102 }
103}
104
105pub struct ServoBuilder<'d, T: Instance, const SM: usize> {
106 pwm: PwmPio<'d, T, SM>,
107 period: Duration,
108 min_pulse_width: Duration,
109 max_pulse_width: Duration,
110 max_degree_rotation: u64,
111}
112
113impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> {
114 pub fn new(pwm: PwmPio<'d, T, SM>) -> Self {
115 Self {
116 pwm,
117 period: Duration::from_micros(REFRESH_INTERVAL),
118 min_pulse_width: Duration::from_micros(DEFAULT_MIN_PULSE_WIDTH),
119 max_pulse_width: Duration::from_micros(DEFAULT_MAX_PULSE_WIDTH),
120 max_degree_rotation: DEFAULT_MAX_DEGREE_ROTATION,
121 }
122 }
123
124 pub fn set_period(mut self, duration: Duration) -> Self {
125 self.period = duration;
126 self
127 }
128
129 pub fn set_min_pulse_width(mut self, duration: Duration) -> Self {
130 self.min_pulse_width = duration;
131 self
132 }
133
134 pub fn set_max_pulse_width(mut self, duration: Duration) -> Self {
135 self.max_pulse_width = duration;
136 self
137 }
138
139 pub fn set_max_degree_rotation(mut self, degree: u64) -> Self {
140 self.max_degree_rotation = degree;
141 self
142 }
143
144 pub fn build(mut self) -> Servo<'d, T, SM> {
145 self.pwm.set_period(self.period);
146 Servo {
147 pwm: self.pwm,
148 min_pulse_width: self.min_pulse_width,
149 max_pulse_width: self.max_pulse_width,
150 max_degree_rotation: self.max_degree_rotation,
151 }
152 }
153}
154
155pub struct Servo<'d, T: Instance, const SM: usize> {
156 pwm: PwmPio<'d, T, SM>,
157 min_pulse_width: Duration,
158 max_pulse_width: Duration,
159 max_degree_rotation: u64,
160}
161
162impl<'d, T: Instance, const SM: usize> Servo<'d, T, SM> {
163 pub fn start(&mut self) {
164 self.pwm.start();
165 }
166
167 pub fn stop(&mut self) {
168 self.pwm.stop();
169 }
170
171 pub fn write_time(&mut self, duration: Duration) {
172 self.pwm.write(duration);
173 }
174
175 pub fn rotate(&mut self, degree: u64) {
176 let degree_per_nano_second = (self.max_pulse_width.as_nanos() as u64 - self.min_pulse_width.as_nanos() as u64)
177 / self.max_degree_rotation;
178 let mut duration =
179 Duration::from_nanos(degree * degree_per_nano_second + self.min_pulse_width.as_nanos() as u64);
180 if self.max_pulse_width < duration {
181 duration = self.max_pulse_width;
182 }
183
184 self.pwm.write(duration);
185 }
186}
187
188#[embassy_executor::main]
189async fn main(_spawner: Spawner) {
190 let p = embassy_rp::init(Default::default());
191 let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
192
193 let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1);
194 let mut servo = ServoBuilder::new(pwm_pio)
195 .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo
196 .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment.
197 .set_max_pulse_width(Duration::from_micros(2600)) // Along with this value.
198 .build();
199
200 servo.start();
201
202 let mut degree = 0;
203 loop {
204 degree = (degree + 1) % 120;
205 servo.rotate(degree);
206 Timer::after_millis(50).await;
207 }
208}