aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src/pio_programs
diff options
context:
space:
mode:
authorCaleb Jamison <[email protected]>2024-10-09 10:04:35 -0400
committerCaleb Jamison <[email protected]>2024-10-09 10:18:00 -0400
commit57c1fbf3089e2a2dc9fe5b7d1f1e094596566395 (patch)
tree833856b7da855b8de56dec1494c2da88ac29e415 /embassy-rp/src/pio_programs
parent456c226b29799f7db56ab60b0ae3d95cc7d6a32a (diff)
Move pio programs into embassy-rp
Diffstat (limited to 'embassy-rp/src/pio_programs')
-rw-r--r--embassy-rp/src/pio_programs/hd44780.rs203
-rw-r--r--embassy-rp/src/pio_programs/i2s.rs97
-rw-r--r--embassy-rp/src/pio_programs/mod.rs10
-rw-r--r--embassy-rp/src/pio_programs/onewire.rs109
-rw-r--r--embassy-rp/src/pio_programs/pwm.rs114
-rw-r--r--embassy-rp/src/pio_programs/rotary_encoder.rs72
-rw-r--r--embassy-rp/src/pio_programs/stepper.rs146
-rw-r--r--embassy-rp/src/pio_programs/uart.rs186
-rw-r--r--embassy-rp/src/pio_programs/ws2812.rs118
9 files changed, 1055 insertions, 0 deletions
diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs
new file mode 100644
index 000000000..9bbf44fc4
--- /dev/null
+++ b/embassy-rp/src/pio_programs/hd44780.rs
@@ -0,0 +1,203 @@
1//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
2
3use crate::dma::{AnyChannel, Channel};
4use crate::pio::{
5 Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection,
6 StateMachine,
7};
8use crate::{into_ref, Peripheral, PeripheralRef};
9
10/// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>)
11pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> {
12 prg: LoadedProgram<'a, PIO>,
13}
14
15impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> {
16 /// Load the program into the given pio
17 pub fn new(common: &mut Common<'a, PIO>) -> Self {
18 let prg = pio_proc::pio_asm!(
19 r#"
20 .side_set 1 opt
21 .origin 20
22
23 loop:
24 out x, 24
25 delay:
26 jmp x--, delay
27 out pins, 4 side 1
28 out null, 4 side 0
29 jmp !osre, loop
30 irq 0
31 "#,
32 );
33
34 let prg = common.load_program(&prg.program);
35
36 Self { prg }
37 }
38}
39
40/// This struct represents a HD44780 program that takes command sequences (<rs:1> <count:7>, data...)
41pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> {
42 prg: LoadedProgram<'a, PIO>,
43}
44
45impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> {
46 /// Load the program into the given pio
47 pub fn new(common: &mut Common<'a, PIO>) -> Self {
48 // many side sets are only there to free up a delay bit!
49 let prg = pio_proc::pio_asm!(
50 r#"
51 .origin 27
52 .side_set 1
53
54 .wrap_target
55 pull side 0
56 out x 1 side 0 ; !rs
57 out y 7 side 0 ; #data - 1
58
59 ; rs/rw to e: >= 60ns
60 ; e high time: >= 500ns
61 ; e low time: >= 500ns
62 ; read data valid after e falling: ~5ns
63 ; write data hold after e falling: ~10ns
64
65 loop:
66 pull side 0
67 jmp !x data side 0
68 command:
69 set pins 0b00 side 0
70 jmp shift side 0
71 data:
72 set pins 0b01 side 0
73 shift:
74 out pins 4 side 1 [9]
75 nop side 0 [9]
76 out pins 4 side 1 [9]
77 mov osr null side 0 [7]
78 out pindirs 4 side 0
79 set pins 0b10 side 0
80 busy:
81 nop side 1 [9]
82 jmp pin more side 0 [9]
83 mov osr ~osr side 1 [9]
84 nop side 0 [4]
85 out pindirs 4 side 0
86 jmp y-- loop side 0
87 .wrap
88 more:
89 nop side 1 [9]
90 jmp busy side 0 [9]
91 "#
92 );
93
94 let prg = common.load_program(&prg.program);
95
96 Self { prg }
97 }
98}
99
100/// Pio backed HD44780 driver
101pub struct PioHD44780<'l, P: Instance, const S: usize> {
102 dma: PeripheralRef<'l, AnyChannel>,
103 sm: StateMachine<'l, P, S>,
104
105 buf: [u8; 40],
106}
107
108impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
109 /// Configure the given state machine to first init, then write data to, a HD44780 display.
110 pub async fn new(
111 common: &mut Common<'l, P>,
112 mut sm: StateMachine<'l, P, S>,
113 mut irq: Irq<'l, P, S>,
114 dma: impl Peripheral<P = impl Channel> + 'l,
115 rs: impl PioPin,
116 rw: impl PioPin,
117 e: impl PioPin,
118 db4: impl PioPin,
119 db5: impl PioPin,
120 db6: impl PioPin,
121 db7: impl PioPin,
122 word_prg: &PioHD44780CommandWordProgram<'l, P>,
123 seq_prg: &PioHD44780CommandSequenceProgram<'l, P>,
124 ) -> PioHD44780<'l, P, S> {
125 into_ref!(dma);
126
127 let rs = common.make_pio_pin(rs);
128 let rw = common.make_pio_pin(rw);
129 let e = common.make_pio_pin(e);
130 let db4 = common.make_pio_pin(db4);
131 let db5 = common.make_pio_pin(db5);
132 let db6 = common.make_pio_pin(db6);
133 let db7 = common.make_pio_pin(db7);
134
135 sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]);
136
137 let mut cfg = Config::default();
138 cfg.use_program(&word_prg.prg, &[&e]);
139 cfg.clock_divider = 125u8.into();
140 cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
141 cfg.shift_out = ShiftConfig {
142 auto_fill: true,
143 direction: ShiftDirection::Left,
144 threshold: 32,
145 };
146 cfg.fifo_join = FifoJoin::TxOnly;
147 sm.set_config(&cfg);
148
149 sm.set_enable(true);
150 // init to 8 bit thrice
151 sm.tx().push((50000 << 8) | 0x30);
152 sm.tx().push((5000 << 8) | 0x30);
153 sm.tx().push((200 << 8) | 0x30);
154 // init 4 bit
155 sm.tx().push((200 << 8) | 0x20);
156 // set font and lines
157 sm.tx().push((50 << 8) | 0x20);
158 sm.tx().push(0b1100_0000);
159
160 irq.wait().await;
161 sm.set_enable(false);
162
163 let mut cfg = Config::default();
164 cfg.use_program(&seq_prg.prg, &[&e]);
165 cfg.clock_divider = 8u8.into(); // ~64ns/insn
166 cfg.set_jmp_pin(&db7);
167 cfg.set_set_pins(&[&rs, &rw]);
168 cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
169 cfg.shift_out.direction = ShiftDirection::Left;
170 cfg.fifo_join = FifoJoin::TxOnly;
171 sm.set_config(&cfg);
172
173 sm.set_enable(true);
174
175 // display on and cursor on and blinking, reset display
176 sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await;
177
178 Self {
179 dma: dma.map_into(),
180 sm,
181 buf: [0x20; 40],
182 }
183 }
184
185 /// Write a line to the display
186 pub async fn add_line(&mut self, s: &[u8]) {
187 // move cursor to 0:0, prepare 16 characters
188 self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]);
189 // move line 2 up
190 self.buf.copy_within(22..38, 3);
191 // move cursor to 1:0, prepare 16 characters
192 self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]);
193 // file line 2 with spaces
194 self.buf[22..38].fill(0x20);
195 // copy input line
196 let len = s.len().min(16);
197 self.buf[22..22 + len].copy_from_slice(&s[0..len]);
198 // set cursor to 1:15
199 self.buf[38..].copy_from_slice(&[0x80, 0xcf]);
200
201 self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await;
202 }
203}
diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs
new file mode 100644
index 000000000..3c8ef8bb6
--- /dev/null
+++ b/embassy-rp/src/pio_programs/i2s.rs
@@ -0,0 +1,97 @@
1//! Pio backed I2s output
2
3use crate::{
4 dma::{AnyChannel, Channel, Transfer},
5 into_ref,
6 pio::{
7 Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
8 },
9 Peripheral, PeripheralRef,
10};
11use fixed::traits::ToFixed;
12
13/// This struct represents an i2s output driver program
14pub struct PioI2sOutProgram<'a, PIO: Instance> {
15 prg: LoadedProgram<'a, PIO>,
16}
17
18impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> {
19 /// Load the program into the given pio
20 pub fn new(common: &mut Common<'a, PIO>) -> Self {
21 let prg = pio_proc::pio_asm!(
22 ".side_set 2",
23 " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
24 "left_data:",
25 " out pins, 1 side 0b00",
26 " jmp x-- left_data side 0b01",
27 " out pins 1 side 0b10",
28 " set x, 14 side 0b11",
29 "right_data:",
30 " out pins 1 side 0b10",
31 " jmp x-- right_data side 0b11",
32 " out pins 1 side 0b00",
33 );
34
35 let prg = common.load_program(&prg.program);
36
37 Self { prg }
38 }
39}
40
41/// Pio backed I2s output driver
42pub struct PioI2sOut<'a, P: Instance, const S: usize> {
43 dma: PeripheralRef<'a, AnyChannel>,
44 sm: StateMachine<'a, P, S>,
45}
46
47impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> {
48 /// Configure a state machine to output I2s
49 pub fn new(
50 common: &mut Common<'a, P>,
51 mut sm: StateMachine<'a, P, S>,
52 dma: impl Peripheral<P = impl Channel> + 'a,
53 data_pin: impl PioPin,
54 bit_clock_pin: impl PioPin,
55 lr_clock_pin: impl PioPin,
56 sample_rate: u32,
57 bit_depth: u32,
58 channels: u32,
59 program: &PioI2sOutProgram<'a, P>,
60 ) -> Self {
61 into_ref!(dma);
62
63 let data_pin = common.make_pio_pin(data_pin);
64 let bit_clock_pin = common.make_pio_pin(bit_clock_pin);
65 let left_right_clock_pin = common.make_pio_pin(lr_clock_pin);
66
67 let cfg = {
68 let mut cfg = Config::default();
69 cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
70 cfg.set_out_pins(&[&data_pin]);
71 let clock_frequency = sample_rate * bit_depth * channels;
72 cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed();
73 cfg.shift_out = ShiftConfig {
74 threshold: 32,
75 direction: ShiftDirection::Left,
76 auto_fill: true,
77 };
78 // join fifos to have twice the time to start the next dma transfer
79 cfg.fifo_join = FifoJoin::TxOnly;
80 cfg
81 };
82 sm.set_config(&cfg);
83 sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]);
84
85 sm.set_enable(true);
86
87 Self {
88 dma: dma.map_into(),
89 sm,
90 }
91 }
92
93 /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
94 pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> {
95 self.sm.tx().dma_push(self.dma.reborrow(), buff)
96 }
97}
diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs
new file mode 100644
index 000000000..74537825b
--- /dev/null
+++ b/embassy-rp/src/pio_programs/mod.rs
@@ -0,0 +1,10 @@
1//! Pre-built pio programs for common interfaces
2
3pub mod hd44780;
4pub mod i2s;
5pub mod onewire;
6pub mod pwm;
7pub mod rotary_encoder;
8pub mod stepper;
9pub mod uart;
10pub mod ws2812;
diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs
new file mode 100644
index 000000000..f3bc5fcd7
--- /dev/null
+++ b/embassy-rp/src/pio_programs/onewire.rs
@@ -0,0 +1,109 @@
1//! OneWire pio driver
2
3use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine};
4
5/// This struct represents an onewire driver program
6pub struct PioOneWireProgram<'a, PIO: Instance> {
7 prg: LoadedProgram<'a, PIO>,
8}
9
10impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
11 /// Load the program into the given pio
12 pub fn new(common: &mut Common<'a, PIO>) -> Self {
13 let prg = pio_proc::pio_asm!(
14 r#"
15 .wrap_target
16 again:
17 pull block
18 mov x, osr
19 jmp !x, read
20 write:
21 set pindirs, 1
22 set pins, 0
23 loop1:
24 jmp x--,loop1
25 set pindirs, 0 [31]
26 wait 1 pin 0 [31]
27 pull block
28 mov x, osr
29 bytes1:
30 pull block
31 set y, 7
32 set pindirs, 1
33 bit1:
34 set pins, 0 [1]
35 out pins,1 [31]
36 set pins, 1 [20]
37 jmp y--,bit1
38 jmp x--,bytes1
39 set pindirs, 0 [31]
40 jmp again
41 read:
42 pull block
43 mov x, osr
44 bytes2:
45 set y, 7
46 bit2:
47 set pindirs, 1
48 set pins, 0 [1]
49 set pindirs, 0 [5]
50 in pins,1 [10]
51 jmp y--,bit2
52 jmp x--,bytes2
53 .wrap
54 "#,
55 );
56 let prg = common.load_program(&prg.program);
57
58 Self { prg }
59 }
60}
61
62/// Pio backed OneWire driver
63pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> {
64 sm: StateMachine<'d, PIO, SM>,
65}
66
67impl<'d, PIO: pio::Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
68 /// Create a new instance the driver
69 pub fn new(
70 common: &mut Common<'d, PIO>,
71 mut sm: StateMachine<'d, PIO, SM>,
72 pin: impl PioPin,
73 program: &PioOneWireProgram<'d, PIO>,
74 ) -> Self {
75 let pin = common.make_pio_pin(pin);
76 let mut cfg = Config::default();
77 cfg.use_program(&program.prg, &[]);
78 cfg.set_out_pins(&[&pin]);
79 cfg.set_in_pins(&[&pin]);
80 cfg.set_set_pins(&[&pin]);
81 cfg.shift_in = ShiftConfig {
82 auto_fill: true,
83 direction: ShiftDirection::Right,
84 threshold: 8,
85 };
86 cfg.clock_divider = 255_u8.into();
87 sm.set_config(&cfg);
88 sm.set_enable(true);
89 Self { sm }
90 }
91
92 /// Write bytes over the wire
93 pub async fn write_bytes(&mut self, bytes: &[u8]) {
94 self.sm.tx().wait_push(250).await;
95 self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
96 for b in bytes {
97 self.sm.tx().wait_push(*b as u32).await;
98 }
99 }
100
101 /// Read bytes from the wire
102 pub async fn read_bytes(&mut self, bytes: &mut [u8]) {
103 self.sm.tx().wait_push(0).await;
104 self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
105 for b in bytes.iter_mut() {
106 *b = (self.sm.rx().wait_pull().await >> 24) as u8;
107 }
108 }
109}
diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs
new file mode 100644
index 000000000..5dfd70cc9
--- /dev/null
+++ b/embassy-rp/src/pio_programs/pwm.rs
@@ -0,0 +1,114 @@
1//! PIO backed PWM driver
2
3use core::time::Duration;
4
5use crate::{
6 clocks,
7 gpio::Level,
8 pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine},
9};
10use pio::InstructionOperands;
11
12fn to_pio_cycles(duration: Duration) -> u32 {
13 (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
14}
15
16/// This struct represents a PWM program loaded into pio instruction memory.
17pub struct PioPwmProgram<'a, PIO: Instance> {
18 prg: LoadedProgram<'a, PIO>,
19}
20
21impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
22 /// Load the program into the given pio
23 pub fn new(common: &mut Common<'a, PIO>) -> Self {
24 let prg = pio_proc::pio_asm!(
25 ".side_set 1 opt"
26 "pull noblock side 0"
27 "mov x, osr"
28 "mov y, isr"
29 "countloop:"
30 "jmp x!=y noset"
31 "jmp skip side 1"
32 "noset:"
33 "nop"
34 "skip:"
35 "jmp y-- countloop"
36 );
37
38 let prg = common.load_program(&prg.program);
39
40 Self { prg }
41 }
42}
43
44/// Pio backed PWM output
45pub struct PioPwm<'d, T: Instance, const SM: usize> {
46 sm: StateMachine<'d, T, SM>,
47}
48
49impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
50 /// Configure a state machine as a PWM output
51 pub fn new(
52 pio: &mut Common<'d, T>,
53 mut sm: StateMachine<'d, T, SM>,
54 pin: impl PioPin,
55 program: &PioPwmProgram<'d, T>,
56 ) -> Self {
57 let pin = pio.make_pio_pin(pin);
58 sm.set_pins(Level::High, &[&pin]);
59 sm.set_pin_dirs(Direction::Out, &[&pin]);
60
61 let mut cfg = Config::default();
62 cfg.use_program(&program.prg, &[&pin]);
63
64 sm.set_config(&cfg);
65
66 Self { sm }
67 }
68
69 /// Enable PWM output
70 pub fn start(&mut self) {
71 self.sm.set_enable(true);
72 }
73
74 /// Disable PWM output
75 pub fn stop(&mut self) {
76 self.sm.set_enable(false);
77 }
78
79 /// Set pwm period
80 pub fn set_period(&mut self, duration: Duration) {
81 let is_enabled = self.sm.is_enabled();
82 while !self.sm.tx().empty() {} // Make sure that the queue is empty
83 self.sm.set_enable(false);
84 self.sm.tx().push(to_pio_cycles(duration));
85 unsafe {
86 self.sm.exec_instr(
87 InstructionOperands::PULL {
88 if_empty: false,
89 block: false,
90 }
91 .encode(),
92 );
93 self.sm.exec_instr(
94 InstructionOperands::OUT {
95 destination: ::pio::OutDestination::ISR,
96 bit_count: 32,
97 }
98 .encode(),
99 );
100 };
101 if is_enabled {
102 self.sm.set_enable(true) // Enable if previously enabled
103 }
104 }
105
106 fn set_level(&mut self, level: u32) {
107 self.sm.tx().push(level);
108 }
109
110 /// Set the pulse width high time
111 pub fn write(&mut self, duration: Duration) {
112 self.set_level(to_pio_cycles(duration));
113 }
114}
diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs
new file mode 100644
index 000000000..323f839bc
--- /dev/null
+++ b/embassy-rp/src/pio_programs/rotary_encoder.rs
@@ -0,0 +1,72 @@
1//! PIO backed quadrature encoder
2
3use crate::gpio::Pull;
4use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine};
5use fixed::traits::ToFixed;
6
7/// This struct represents an Encoder program loaded into pio instruction memory.
8pub struct PioEncoderProgram<'a, PIO: Instance> {
9 prg: LoadedProgram<'a, PIO>,
10}
11
12impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> {
13 /// Load the program into the given pio
14 pub fn new(common: &mut Common<'a, PIO>) -> Self {
15 let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
16
17 let prg = common.load_program(&prg.program);
18
19 Self { prg }
20 }
21}
22
23/// Pio Backed quadrature encoder reader
24pub struct PioEncoder<'d, T: Instance, const SM: usize> {
25 sm: StateMachine<'d, T, SM>,
26}
27
28impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
29 /// Configure a state machine with the loaded [PioEncoderProgram]
30 pub fn new(
31 pio: &mut Common<'d, T>,
32 mut sm: StateMachine<'d, T, SM>,
33 pin_a: impl PioPin,
34 pin_b: impl PioPin,
35 program: &PioEncoderProgram<'d, T>,
36 ) -> Self {
37 let mut pin_a = pio.make_pio_pin(pin_a);
38 let mut pin_b = pio.make_pio_pin(pin_b);
39 pin_a.set_pull(Pull::Up);
40 pin_b.set_pull(Pull::Up);
41 sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]);
42
43 let mut cfg = Config::default();
44 cfg.set_in_pins(&[&pin_a, &pin_b]);
45 cfg.fifo_join = FifoJoin::RxOnly;
46 cfg.shift_in.direction = ShiftDirection::Left;
47 cfg.clock_divider = 10_000.to_fixed();
48 cfg.use_program(&program.prg, &[]);
49 sm.set_config(&cfg);
50 sm.set_enable(true);
51 Self { sm }
52 }
53
54 /// Read a single count from the encoder
55 pub async fn read(&mut self) -> Direction {
56 loop {
57 match self.sm.rx().wait_pull().await {
58 0 => return Direction::CounterClockwise,
59 1 => return Direction::Clockwise,
60 _ => {}
61 }
62 }
63 }
64}
65
66/// Encoder Count Direction
67pub enum Direction {
68 /// Encoder turned clockwise
69 Clockwise,
70 /// Encoder turned counter clockwise
71 CounterClockwise,
72}
diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs
new file mode 100644
index 000000000..0ecc4eff0
--- /dev/null
+++ b/embassy-rp/src/pio_programs/stepper.rs
@@ -0,0 +1,146 @@
1//! Pio Stepper Driver for 5-wire steppers
2
3use core::mem::{self, MaybeUninit};
4
5use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
6use fixed::traits::ToFixed;
7use fixed::types::extra::U8;
8use fixed::FixedU32;
9
10/// This struct represents a Stepper driver program loaded into pio instruction memory.
11pub struct PioStepperProgram<'a, PIO: Instance> {
12 prg: LoadedProgram<'a, PIO>,
13}
14
15impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> {
16 /// Load the program into the given pio
17 pub fn new(common: &mut Common<'a, PIO>) -> Self {
18 let prg = pio_proc::pio_asm!(
19 "pull block",
20 "mov x, osr",
21 "pull block",
22 "mov y, osr",
23 "jmp !x end",
24 "loop:",
25 "jmp !osre step",
26 "mov osr, y",
27 "step:",
28 "out pins, 4 [31]"
29 "jmp x-- loop",
30 "end:",
31 "irq 0 rel"
32 );
33
34 let prg = common.load_program(&prg.program);
35
36 Self { prg }
37 }
38}
39
40/// Pio backed Stepper driver
41pub struct PioStepper<'d, T: Instance, const SM: usize> {
42 irq: Irq<'d, T, SM>,
43 sm: StateMachine<'d, T, SM>,
44}
45
46impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
47 /// Configure a state machine to drive a stepper
48 pub fn new(
49 pio: &mut Common<'d, T>,
50 mut sm: StateMachine<'d, T, SM>,
51 irq: Irq<'d, T, SM>,
52 pin0: impl PioPin,
53 pin1: impl PioPin,
54 pin2: impl PioPin,
55 pin3: impl PioPin,
56 program: &PioStepperProgram<'d, T>,
57 ) -> Self {
58 let pin0 = pio.make_pio_pin(pin0);
59 let pin1 = pio.make_pio_pin(pin1);
60 let pin2 = pio.make_pio_pin(pin2);
61 let pin3 = pio.make_pio_pin(pin3);
62 sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
63 let mut cfg = Config::default();
64 cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
65 cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
66 cfg.use_program(&program.prg, &[]);
67 sm.set_config(&cfg);
68 sm.set_enable(true);
69 Self { irq, sm }
70 }
71
72 /// Set pulse frequency
73 pub fn set_frequency(&mut self, freq: u32) {
74 let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
75 assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
76 assert!(clock_divider >= 1, "clkdiv must be >= 1");
77 self.sm.set_clock_divider(clock_divider);
78 self.sm.clkdiv_restart();
79 }
80
81 /// Full step, one phase
82 pub async fn step(&mut self, steps: i32) {
83 if steps > 0 {
84 self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
85 } else {
86 self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
87 }
88 }
89
90 /// Full step, two phase
91 pub async fn step2(&mut self, steps: i32) {
92 if steps > 0 {
93 self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
94 } else {
95 self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
96 }
97 }
98
99 /// Half step
100 pub async fn step_half(&mut self, steps: i32) {
101 if steps > 0 {
102 self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
103 } else {
104 self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
105 }
106 }
107
108 async fn run(&mut self, steps: i32, pattern: u32) {
109 self.sm.tx().wait_push(steps as u32).await;
110 self.sm.tx().wait_push(pattern).await;
111 let drop = OnDrop::new(|| {
112 self.sm.clear_fifos();
113 unsafe {
114 self.sm.exec_instr(
115 pio::InstructionOperands::JMP {
116 address: 0,
117 condition: pio::JmpCondition::Always,
118 }
119 .encode(),
120 );
121 }
122 });
123 self.irq.wait().await;
124 drop.defuse();
125 }
126}
127
128struct OnDrop<F: FnOnce()> {
129 f: MaybeUninit<F>,
130}
131
132impl<F: FnOnce()> OnDrop<F> {
133 pub fn new(f: F) -> Self {
134 Self { f: MaybeUninit::new(f) }
135 }
136
137 pub fn defuse(self) {
138 mem::forget(self)
139 }
140}
141
142impl<F: FnOnce()> Drop for OnDrop<F> {
143 fn drop(&mut self) {
144 unsafe { self.f.as_ptr().read()() }
145 }
146}
diff --git a/embassy-rp/src/pio_programs/uart.rs b/embassy-rp/src/pio_programs/uart.rs
new file mode 100644
index 000000000..f4b3b204e
--- /dev/null
+++ b/embassy-rp/src/pio_programs/uart.rs
@@ -0,0 +1,186 @@
1//! Pio backed uart drivers
2
3use crate::{
4 clocks::clk_sys_freq,
5 gpio::Level,
6 pio::{
7 Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection,
8 StateMachine,
9 },
10};
11use core::convert::Infallible;
12use embedded_io_async::{ErrorType, Read, Write};
13use fixed::traits::ToFixed;
14
15/// This struct represents a uart tx program loaded into pio instruction memory.
16pub struct PioUartTxProgram<'a, PIO: Instance> {
17 prg: LoadedProgram<'a, PIO>,
18}
19
20impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> {
21 /// Load the uart tx program into the given pio
22 pub fn new(common: &mut Common<'a, PIO>) -> Self {
23 let prg = pio_proc::pio_asm!(
24 r#"
25 .side_set 1 opt
26
27 ; An 8n1 UART transmit program.
28 ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
29
30 pull side 1 [7] ; Assert stop bit, or stall with line in idle state
31 set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
32 bitloop: ; This loop will run 8 times (8n1 UART)
33 out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
34 jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
35 "#
36 );
37
38 let prg = common.load_program(&prg.program);
39
40 Self { prg }
41 }
42}
43
44/// PIO backed Uart transmitter
45pub struct PioUartTx<'a, PIO: Instance, const SM: usize> {
46 sm_tx: StateMachine<'a, PIO, SM>,
47}
48
49impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> {
50 /// Configure a pio state machine to use the loaded tx program.
51 pub fn new(
52 baud: u32,
53 common: &mut Common<'a, PIO>,
54 mut sm_tx: StateMachine<'a, PIO, SM>,
55 tx_pin: impl PioPin,
56 program: &PioUartTxProgram<'a, PIO>,
57 ) -> Self {
58 let tx_pin = common.make_pio_pin(tx_pin);
59 sm_tx.set_pins(Level::High, &[&tx_pin]);
60 sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]);
61
62 let mut cfg = Config::default();
63
64 cfg.set_out_pins(&[&tx_pin]);
65 cfg.use_program(&program.prg, &[&tx_pin]);
66 cfg.shift_out.auto_fill = false;
67 cfg.shift_out.direction = ShiftDirection::Right;
68 cfg.fifo_join = FifoJoin::TxOnly;
69 cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
70 sm_tx.set_config(&cfg);
71 sm_tx.set_enable(true);
72
73 Self { sm_tx }
74 }
75
76 /// Write a single u8
77 pub async fn write_u8(&mut self, data: u8) {
78 self.sm_tx.tx().wait_push(data as u32).await;
79 }
80}
81
82impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> {
83 type Error = Infallible;
84}
85
86impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> {
87 async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
88 for byte in buf {
89 self.write_u8(*byte).await;
90 }
91 Ok(buf.len())
92 }
93}
94
95/// This struct represents a Uart Rx program loaded into pio instruction memory.
96pub struct PioUartRxProgram<'a, PIO: Instance> {
97 prg: LoadedProgram<'a, PIO>,
98}
99
100impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> {
101 /// Load the uart rx program into the given pio
102 pub fn new(common: &mut Common<'a, PIO>) -> Self {
103 let prg = pio_proc::pio_asm!(
104 r#"
105 ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
106 ; break conditions more gracefully.
107 ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
108
109 start:
110 wait 0 pin 0 ; Stall until start bit is asserted
111 set x, 7 [10] ; Preload bit counter, then delay until halfway through
112 rx_bitloop: ; the first data bit (12 cycles incl wait, set).
113 in pins, 1 ; Shift data bit into ISR
114 jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
115 jmp pin good_rx_stop ; Check stop bit (should be high)
116
117 irq 4 rel ; Either a framing error or a break. Set a sticky flag,
118 wait 1 pin 0 ; and wait for line to return to idle state.
119 jmp start ; Don't push data if we didn't see good framing.
120
121 good_rx_stop: ; No delay before returning to start; a little slack is
122 in null 24
123 push ; important in case the TX clock is slightly too fast.
124 "#
125 );
126
127 let prg = common.load_program(&prg.program);
128
129 Self { prg }
130 }
131}
132
133/// PIO backed Uart reciever
134pub struct PioUartRx<'a, PIO: Instance, const SM: usize> {
135 sm_rx: StateMachine<'a, PIO, SM>,
136}
137
138impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> {
139 /// Configure a pio state machine to use the loaded rx program.
140 pub fn new(
141 baud: u32,
142 common: &mut Common<'a, PIO>,
143 mut sm_rx: StateMachine<'a, PIO, SM>,
144 rx_pin: impl PioPin,
145 program: &PioUartRxProgram<'a, PIO>,
146 ) -> Self {
147 let mut cfg = Config::default();
148 cfg.use_program(&program.prg, &[]);
149
150 let rx_pin = common.make_pio_pin(rx_pin);
151 sm_rx.set_pins(Level::High, &[&rx_pin]);
152 cfg.set_in_pins(&[&rx_pin]);
153 cfg.set_jmp_pin(&rx_pin);
154 sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]);
155
156 cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
157 cfg.shift_in.auto_fill = false;
158 cfg.shift_in.direction = ShiftDirection::Right;
159 cfg.shift_in.threshold = 32;
160 cfg.fifo_join = FifoJoin::RxOnly;
161 sm_rx.set_config(&cfg);
162 sm_rx.set_enable(true);
163
164 Self { sm_rx }
165 }
166
167 /// Wait for a single u8
168 pub async fn read_u8(&mut self) -> u8 {
169 self.sm_rx.rx().wait_pull().await as u8
170 }
171}
172
173impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> {
174 type Error = Infallible;
175}
176
177impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> {
178 async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
179 let mut i = 0;
180 while i < buf.len() {
181 buf[i] = self.read_u8().await;
182 i += 1;
183 }
184 Ok(i)
185 }
186}
diff --git a/embassy-rp/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs
new file mode 100644
index 000000000..3fc7e1017
--- /dev/null
+++ b/embassy-rp/src/pio_programs/ws2812.rs
@@ -0,0 +1,118 @@
1//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
2
3use crate::{
4 clocks::clk_sys_freq,
5 dma::{AnyChannel, Channel},
6 into_ref,
7 pio::{Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine},
8 Peripheral, PeripheralRef,
9};
10use embassy_time::Timer;
11use fixed::types::U24F8;
12use smart_leds::RGB8;
13
14const T1: u8 = 2; // start bit
15const T2: u8 = 5; // data bit
16const T3: u8 = 3; // stop bit
17const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
18
19/// This struct represents a ws2812 program loaded into pio instruction memory.
20pub struct PioWs2812Program<'a, PIO: Instance> {
21 prg: LoadedProgram<'a, PIO>,
22}
23
24impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> {
25 /// Load the ws2812 program into the given pio
26 pub fn new(common: &mut Common<'a, PIO>) -> Self {
27 let side_set = pio::SideSet::new(false, 1, false);
28 let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set);
29
30 let mut wrap_target = a.label();
31 let mut wrap_source = a.label();
32 let mut do_zero = a.label();
33 a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0);
34 a.bind(&mut wrap_target);
35 // Do stop bit
36 a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
37 // Do start bit
38 a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
39 // Do data bit = 1
40 a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
41 a.bind(&mut do_zero);
42 // Do data bit = 0
43 a.nop_with_delay_and_side_set(T2 - 1, 0);
44 a.bind(&mut wrap_source);
45
46 let prg = a.assemble_with_wrap(wrap_source, wrap_target);
47 let prg = common.load_program(&prg);
48
49 Self { prg }
50 }
51}
52
53/// Pio backed ws2812 driver
54/// Const N is the number of ws2812 leds attached to this pin
55pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> {
56 dma: PeripheralRef<'d, AnyChannel>,
57 sm: StateMachine<'d, P, S>,
58}
59
60impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> {
61 /// Configure a pio state machine to use the loaded ws2812 program.
62 pub fn new(
63 pio: &mut Common<'d, P>,
64 mut sm: StateMachine<'d, P, S>,
65 dma: impl Peripheral<P = impl Channel> + 'd,
66 pin: impl PioPin,
67 program: &PioWs2812Program<'d, P>,
68 ) -> Self {
69 into_ref!(dma);
70
71 // Setup sm0
72 let mut cfg = Config::default();
73
74 // Pin config
75 let out_pin = pio.make_pio_pin(pin);
76 cfg.set_out_pins(&[&out_pin]);
77 cfg.set_set_pins(&[&out_pin]);
78
79 cfg.use_program(&program.prg, &[&out_pin]);
80
81 // Clock config, measured in kHz to avoid overflows
82 let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
83 let ws2812_freq = U24F8::from_num(800);
84 let bit_freq = ws2812_freq * CYCLES_PER_BIT;
85 cfg.clock_divider = clock_freq / bit_freq;
86
87 // FIFO config
88 cfg.fifo_join = FifoJoin::TxOnly;
89 cfg.shift_out = ShiftConfig {
90 auto_fill: true,
91 threshold: 24,
92 direction: ShiftDirection::Left,
93 };
94
95 sm.set_config(&cfg);
96 sm.set_enable(true);
97
98 Self {
99 dma: dma.map_into(),
100 sm,
101 }
102 }
103
104 /// Write a buffer of [smart_leds::RGB8] to the ws2812 string
105 pub async fn write(&mut self, colors: &[RGB8; N]) {
106 // Precompute the word bytes from the colors
107 let mut words = [0u32; N];
108 for i in 0..N {
109 let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
110 words[i] = word;
111 }
112
113 // DMA transfer
114 self.sm.tx().dma_push(self.dma.reborrow(), &words).await;
115
116 Timer::after_micros(55).await;
117 }
118}