diff options
| author | Caleb Jamison <[email protected]> | 2024-10-09 10:04:35 -0400 |
|---|---|---|
| committer | Caleb Jamison <[email protected]> | 2024-10-09 10:18:00 -0400 |
| commit | 57c1fbf3089e2a2dc9fe5b7d1f1e094596566395 (patch) | |
| tree | 833856b7da855b8de56dec1494c2da88ac29e415 /embassy-rp/src/pio_programs | |
| parent | 456c226b29799f7db56ab60b0ae3d95cc7d6a32a (diff) | |
Move pio programs into embassy-rp
Diffstat (limited to 'embassy-rp/src/pio_programs')
| -rw-r--r-- | embassy-rp/src/pio_programs/hd44780.rs | 203 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/i2s.rs | 97 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/mod.rs | 10 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/onewire.rs | 109 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/pwm.rs | 114 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/rotary_encoder.rs | 72 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/stepper.rs | 146 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/uart.rs | 186 | ||||
| -rw-r--r-- | embassy-rp/src/pio_programs/ws2812.rs | 118 |
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 | |||
| 3 | use crate::dma::{AnyChannel, Channel}; | ||
| 4 | use crate::pio::{ | ||
| 5 | Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, | ||
| 6 | StateMachine, | ||
| 7 | }; | ||
| 8 | use crate::{into_ref, Peripheral, PeripheralRef}; | ||
| 9 | |||
| 10 | /// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>) | ||
| 11 | pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> { | ||
| 12 | prg: LoadedProgram<'a, PIO>, | ||
| 13 | } | ||
| 14 | |||
| 15 | impl<'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...) | ||
| 41 | pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> { | ||
| 42 | prg: LoadedProgram<'a, PIO>, | ||
| 43 | } | ||
| 44 | |||
| 45 | impl<'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 | ||
| 101 | pub 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 | |||
| 108 | impl<'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 | |||
| 3 | use 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 | }; | ||
| 11 | use fixed::traits::ToFixed; | ||
| 12 | |||
| 13 | /// This struct represents an i2s output driver program | ||
| 14 | pub struct PioI2sOutProgram<'a, PIO: Instance> { | ||
| 15 | prg: LoadedProgram<'a, PIO>, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl<'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 | ||
| 42 | pub struct PioI2sOut<'a, P: Instance, const S: usize> { | ||
| 43 | dma: PeripheralRef<'a, AnyChannel>, | ||
| 44 | sm: StateMachine<'a, P, S>, | ||
| 45 | } | ||
| 46 | |||
| 47 | impl<'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 | |||
| 3 | pub mod hd44780; | ||
| 4 | pub mod i2s; | ||
| 5 | pub mod onewire; | ||
| 6 | pub mod pwm; | ||
| 7 | pub mod rotary_encoder; | ||
| 8 | pub mod stepper; | ||
| 9 | pub mod uart; | ||
| 10 | pub 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 | |||
| 3 | use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}; | ||
| 4 | |||
| 5 | /// This struct represents an onewire driver program | ||
| 6 | pub struct PioOneWireProgram<'a, PIO: Instance> { | ||
| 7 | prg: LoadedProgram<'a, PIO>, | ||
| 8 | } | ||
| 9 | |||
| 10 | impl<'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 | ||
| 63 | pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> { | ||
| 64 | sm: StateMachine<'d, PIO, SM>, | ||
| 65 | } | ||
| 66 | |||
| 67 | impl<'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 | |||
| 3 | use core::time::Duration; | ||
| 4 | |||
| 5 | use crate::{ | ||
| 6 | clocks, | ||
| 7 | gpio::Level, | ||
| 8 | pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine}, | ||
| 9 | }; | ||
| 10 | use pio::InstructionOperands; | ||
| 11 | |||
| 12 | fn 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. | ||
| 17 | pub struct PioPwmProgram<'a, PIO: Instance> { | ||
| 18 | prg: LoadedProgram<'a, PIO>, | ||
| 19 | } | ||
| 20 | |||
| 21 | impl<'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 | ||
| 45 | pub struct PioPwm<'d, T: Instance, const SM: usize> { | ||
| 46 | sm: StateMachine<'d, T, SM>, | ||
| 47 | } | ||
| 48 | |||
| 49 | impl<'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 | |||
| 3 | use crate::gpio::Pull; | ||
| 4 | use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}; | ||
| 5 | use fixed::traits::ToFixed; | ||
| 6 | |||
| 7 | /// This struct represents an Encoder program loaded into pio instruction memory. | ||
| 8 | pub struct PioEncoderProgram<'a, PIO: Instance> { | ||
| 9 | prg: LoadedProgram<'a, PIO>, | ||
| 10 | } | ||
| 11 | |||
| 12 | impl<'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 | ||
| 24 | pub struct PioEncoder<'d, T: Instance, const SM: usize> { | ||
| 25 | sm: StateMachine<'d, T, SM>, | ||
| 26 | } | ||
| 27 | |||
| 28 | impl<'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 | ||
| 67 | pub 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 | |||
| 3 | use core::mem::{self, MaybeUninit}; | ||
| 4 | |||
| 5 | use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; | ||
| 6 | use fixed::traits::ToFixed; | ||
| 7 | use fixed::types::extra::U8; | ||
| 8 | use fixed::FixedU32; | ||
| 9 | |||
| 10 | /// This struct represents a Stepper driver program loaded into pio instruction memory. | ||
| 11 | pub struct PioStepperProgram<'a, PIO: Instance> { | ||
| 12 | prg: LoadedProgram<'a, PIO>, | ||
| 13 | } | ||
| 14 | |||
| 15 | impl<'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 | ||
| 41 | pub struct PioStepper<'d, T: Instance, const SM: usize> { | ||
| 42 | irq: Irq<'d, T, SM>, | ||
| 43 | sm: StateMachine<'d, T, SM>, | ||
| 44 | } | ||
| 45 | |||
| 46 | impl<'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 | |||
| 128 | struct OnDrop<F: FnOnce()> { | ||
| 129 | f: MaybeUninit<F>, | ||
| 130 | } | ||
| 131 | |||
| 132 | impl<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 | |||
| 142 | impl<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 | |||
| 3 | use 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 | }; | ||
| 11 | use core::convert::Infallible; | ||
| 12 | use embedded_io_async::{ErrorType, Read, Write}; | ||
| 13 | use fixed::traits::ToFixed; | ||
| 14 | |||
| 15 | /// This struct represents a uart tx program loaded into pio instruction memory. | ||
| 16 | pub struct PioUartTxProgram<'a, PIO: Instance> { | ||
| 17 | prg: LoadedProgram<'a, PIO>, | ||
| 18 | } | ||
| 19 | |||
| 20 | impl<'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 | ||
| 45 | pub struct PioUartTx<'a, PIO: Instance, const SM: usize> { | ||
| 46 | sm_tx: StateMachine<'a, PIO, SM>, | ||
| 47 | } | ||
| 48 | |||
| 49 | impl<'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 | |||
| 82 | impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> { | ||
| 83 | type Error = Infallible; | ||
| 84 | } | ||
| 85 | |||
| 86 | impl<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. | ||
| 96 | pub struct PioUartRxProgram<'a, PIO: Instance> { | ||
| 97 | prg: LoadedProgram<'a, PIO>, | ||
| 98 | } | ||
| 99 | |||
| 100 | impl<'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 | ||
| 134 | pub struct PioUartRx<'a, PIO: Instance, const SM: usize> { | ||
| 135 | sm_rx: StateMachine<'a, PIO, SM>, | ||
| 136 | } | ||
| 137 | |||
| 138 | impl<'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 | |||
| 173 | impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> { | ||
| 174 | type Error = Infallible; | ||
| 175 | } | ||
| 176 | |||
| 177 | impl<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 | |||
| 3 | use 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 | }; | ||
| 10 | use embassy_time::Timer; | ||
| 11 | use fixed::types::U24F8; | ||
| 12 | use smart_leds::RGB8; | ||
| 13 | |||
| 14 | const T1: u8 = 2; // start bit | ||
| 15 | const T2: u8 = 5; // data bit | ||
| 16 | const T3: u8 = 3; // stop bit | ||
| 17 | const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; | ||
| 18 | |||
| 19 | /// This struct represents a ws2812 program loaded into pio instruction memory. | ||
| 20 | pub struct PioWs2812Program<'a, PIO: Instance> { | ||
| 21 | prg: LoadedProgram<'a, PIO>, | ||
| 22 | } | ||
| 23 | |||
| 24 | impl<'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 | ||
| 55 | pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { | ||
| 56 | dma: PeripheralRef<'d, AnyChannel>, | ||
| 57 | sm: StateMachine<'d, P, S>, | ||
| 58 | } | ||
| 59 | |||
| 60 | impl<'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 | } | ||
