Move pio programs into embassy-rp
This commit is contained in:
		
							parent
							
								
									456c226b29
								
							
						
					
					
						commit
						57c1fbf308
					
				| @ -144,6 +144,7 @@ rp2040-boot2 = "0.3" | ||||
| document-features = "0.2.7" | ||||
| sha2-const-stable = "0.1" | ||||
| rp-binary-info = { version = "0.1.0", optional = true } | ||||
| smart-leds = "0.4.0" | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } | ||||
|  | ||||
| @ -34,6 +34,7 @@ pub mod i2c_slave; | ||||
| pub mod multicore; | ||||
| #[cfg(feature = "_rp235x")] | ||||
| pub mod otp; | ||||
| pub mod pio_programs; | ||||
| pub mod pwm; | ||||
| mod reset; | ||||
| pub mod rom_data; | ||||
|  | ||||
							
								
								
									
										203
									
								
								embassy-rp/src/pio_programs/hd44780.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								embassy-rp/src/pio_programs/hd44780.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,203 @@ | ||||
| //! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
 | ||||
| 
 | ||||
| use crate::dma::{AnyChannel, Channel}; | ||||
| use crate::pio::{ | ||||
|     Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, | ||||
|     StateMachine, | ||||
| }; | ||||
| use crate::{into_ref, Peripheral, PeripheralRef}; | ||||
| 
 | ||||
| /// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>)
 | ||||
| pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> { | ||||
|     /// Load the program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .side_set 1 opt | ||||
|                 .origin 20 | ||||
| 
 | ||||
|                 loop: | ||||
|                     out x,     24 | ||||
|                 delay: | ||||
|                     jmp x--,   delay | ||||
|                     out pins,  4     side 1 | ||||
|                     out null,  4     side 0 | ||||
|                     jmp !osre, loop | ||||
|                 irq 0 | ||||
|             "#,
 | ||||
|         ); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// This struct represents a HD44780 program that takes command sequences (<rs:1> <count:7>, data...)
 | ||||
| pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> { | ||||
|     /// Load the program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         // many side sets are only there to free up a delay bit!
 | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .origin 27 | ||||
|                 .side_set 1 | ||||
| 
 | ||||
|                 .wrap_target | ||||
|                 pull     side 0 | ||||
|                 out  x 1 side 0 ; !rs | ||||
|                 out  y 7 side 0 ; #data - 1 | ||||
| 
 | ||||
|                 ; rs/rw to e: >= 60ns | ||||
|                 ; e high time: >= 500ns | ||||
|                 ; e low time: >= 500ns | ||||
|                 ; read data valid after e falling: ~5ns | ||||
|                 ; write data hold after e falling: ~10ns | ||||
| 
 | ||||
|                 loop: | ||||
|                     pull                 side 0 | ||||
|                     jmp  !x       data   side 0 | ||||
|                 command: | ||||
|                     set  pins     0b00   side 0 | ||||
|                     jmp  shift           side 0 | ||||
|                 data: | ||||
|                     set  pins     0b01   side 0 | ||||
|                 shift: | ||||
|                     out  pins     4      side 1 [9] | ||||
|                     nop                  side 0 [9] | ||||
|                     out  pins     4      side 1 [9] | ||||
|                     mov  osr      null   side 0 [7] | ||||
|                     out  pindirs  4      side 0 | ||||
|                     set  pins     0b10   side 0 | ||||
|                 busy: | ||||
|                     nop                  side 1 [9] | ||||
|                     jmp  pin      more   side 0 [9] | ||||
|                     mov  osr      ~osr   side 1 [9] | ||||
|                     nop                  side 0 [4] | ||||
|                     out  pindirs  4      side 0 | ||||
|                     jmp  y--      loop   side 0 | ||||
|                 .wrap | ||||
|                 more: | ||||
|                     nop                  side 1 [9] | ||||
|                     jmp busy             side 0 [9] | ||||
|             "#
 | ||||
|         ); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pio backed HD44780 driver
 | ||||
| pub struct PioHD44780<'l, P: Instance, const S: usize> { | ||||
|     dma: PeripheralRef<'l, AnyChannel>, | ||||
|     sm: StateMachine<'l, P, S>, | ||||
| 
 | ||||
|     buf: [u8; 40], | ||||
| } | ||||
| 
 | ||||
| impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { | ||||
|     /// Configure the given state machine to first init, then write data to, a HD44780 display.
 | ||||
|     pub async fn new( | ||||
|         common: &mut Common<'l, P>, | ||||
|         mut sm: StateMachine<'l, P, S>, | ||||
|         mut irq: Irq<'l, P, S>, | ||||
|         dma: impl Peripheral<P = impl Channel> + 'l, | ||||
|         rs: impl PioPin, | ||||
|         rw: impl PioPin, | ||||
|         e: impl PioPin, | ||||
|         db4: impl PioPin, | ||||
|         db5: impl PioPin, | ||||
|         db6: impl PioPin, | ||||
|         db7: impl PioPin, | ||||
|         word_prg: &PioHD44780CommandWordProgram<'l, P>, | ||||
|         seq_prg: &PioHD44780CommandSequenceProgram<'l, P>, | ||||
|     ) -> PioHD44780<'l, P, S> { | ||||
|         into_ref!(dma); | ||||
| 
 | ||||
|         let rs = common.make_pio_pin(rs); | ||||
|         let rw = common.make_pio_pin(rw); | ||||
|         let e = common.make_pio_pin(e); | ||||
|         let db4 = common.make_pio_pin(db4); | ||||
|         let db5 = common.make_pio_pin(db5); | ||||
|         let db6 = common.make_pio_pin(db6); | ||||
|         let db7 = common.make_pio_pin(db7); | ||||
| 
 | ||||
|         sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&word_prg.prg, &[&e]); | ||||
|         cfg.clock_divider = 125u8.into(); | ||||
|         cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             direction: ShiftDirection::Left, | ||||
|             threshold: 32, | ||||
|         }; | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         sm.set_config(&cfg); | ||||
| 
 | ||||
|         sm.set_enable(true); | ||||
|         // init to 8 bit thrice
 | ||||
|         sm.tx().push((50000 << 8) | 0x30); | ||||
|         sm.tx().push((5000 << 8) | 0x30); | ||||
|         sm.tx().push((200 << 8) | 0x30); | ||||
|         // init 4 bit
 | ||||
|         sm.tx().push((200 << 8) | 0x20); | ||||
|         // set font and lines
 | ||||
|         sm.tx().push((50 << 8) | 0x20); | ||||
|         sm.tx().push(0b1100_0000); | ||||
| 
 | ||||
|         irq.wait().await; | ||||
|         sm.set_enable(false); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&seq_prg.prg, &[&e]); | ||||
|         cfg.clock_divider = 8u8.into(); // ~64ns/insn
 | ||||
|         cfg.set_jmp_pin(&db7); | ||||
|         cfg.set_set_pins(&[&rs, &rw]); | ||||
|         cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | ||||
|         cfg.shift_out.direction = ShiftDirection::Left; | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         sm.set_config(&cfg); | ||||
| 
 | ||||
|         sm.set_enable(true); | ||||
| 
 | ||||
|         // display on and cursor on and blinking, reset display
 | ||||
|         sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; | ||||
| 
 | ||||
|         Self { | ||||
|             dma: dma.map_into(), | ||||
|             sm, | ||||
|             buf: [0x20; 40], | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Write a line to the display
 | ||||
|     pub async fn add_line(&mut self, s: &[u8]) { | ||||
|         // move cursor to 0:0, prepare 16 characters
 | ||||
|         self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); | ||||
|         // move line 2 up
 | ||||
|         self.buf.copy_within(22..38, 3); | ||||
|         // move cursor to 1:0, prepare 16 characters
 | ||||
|         self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); | ||||
|         // file line 2 with spaces
 | ||||
|         self.buf[22..38].fill(0x20); | ||||
|         // copy input line
 | ||||
|         let len = s.len().min(16); | ||||
|         self.buf[22..22 + len].copy_from_slice(&s[0..len]); | ||||
|         // set cursor to 1:15
 | ||||
|         self.buf[38..].copy_from_slice(&[0x80, 0xcf]); | ||||
| 
 | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										97
									
								
								embassy-rp/src/pio_programs/i2s.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								embassy-rp/src/pio_programs/i2s.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| //! Pio backed I2s output
 | ||||
| 
 | ||||
| use crate::{ | ||||
|     dma::{AnyChannel, Channel, Transfer}, | ||||
|     into_ref, | ||||
|     pio::{ | ||||
|         Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, | ||||
|     }, | ||||
|     Peripheral, PeripheralRef, | ||||
| }; | ||||
| use fixed::traits::ToFixed; | ||||
| 
 | ||||
| /// This struct represents an i2s output driver program
 | ||||
| pub struct PioI2sOutProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> { | ||||
|     /// Load the program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             ".side_set 2", | ||||
|             "    set x, 14          side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
 | ||||
|             "left_data:", | ||||
|             "    out pins, 1        side 0b00", | ||||
|             "    jmp x-- left_data  side 0b01", | ||||
|             "    out pins 1         side 0b10", | ||||
|             "    set x, 14          side 0b11", | ||||
|             "right_data:", | ||||
|             "    out pins 1         side 0b10", | ||||
|             "    jmp x-- right_data side 0b11", | ||||
|             "    out pins 1         side 0b00", | ||||
|         ); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pio backed I2s output driver
 | ||||
| pub struct PioI2sOut<'a, P: Instance, const S: usize> { | ||||
|     dma: PeripheralRef<'a, AnyChannel>, | ||||
|     sm: StateMachine<'a, P, S>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> { | ||||
|     /// Configure a state machine to output I2s
 | ||||
|     pub fn new( | ||||
|         common: &mut Common<'a, P>, | ||||
|         mut sm: StateMachine<'a, P, S>, | ||||
|         dma: impl Peripheral<P = impl Channel> + 'a, | ||||
|         data_pin: impl PioPin, | ||||
|         bit_clock_pin: impl PioPin, | ||||
|         lr_clock_pin: impl PioPin, | ||||
|         sample_rate: u32, | ||||
|         bit_depth: u32, | ||||
|         channels: u32, | ||||
|         program: &PioI2sOutProgram<'a, P>, | ||||
|     ) -> Self { | ||||
|         into_ref!(dma); | ||||
| 
 | ||||
|         let data_pin = common.make_pio_pin(data_pin); | ||||
|         let bit_clock_pin = common.make_pio_pin(bit_clock_pin); | ||||
|         let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); | ||||
| 
 | ||||
|         let cfg = { | ||||
|             let mut cfg = Config::default(); | ||||
|             cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); | ||||
|             cfg.set_out_pins(&[&data_pin]); | ||||
|             let clock_frequency = sample_rate * bit_depth * channels; | ||||
|             cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); | ||||
|             cfg.shift_out = ShiftConfig { | ||||
|                 threshold: 32, | ||||
|                 direction: ShiftDirection::Left, | ||||
|                 auto_fill: true, | ||||
|             }; | ||||
|             // join fifos to have twice the time to start the next dma transfer
 | ||||
|             cfg.fifo_join = FifoJoin::TxOnly; | ||||
|             cfg | ||||
|         }; | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); | ||||
| 
 | ||||
|         sm.set_enable(true); | ||||
| 
 | ||||
|         Self { | ||||
|             dma: dma.map_into(), | ||||
|             sm, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
 | ||||
|     pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> { | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), buff) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								embassy-rp/src/pio_programs/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								embassy-rp/src/pio_programs/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| //! Pre-built pio programs for common interfaces
 | ||||
| 
 | ||||
| pub mod hd44780; | ||||
| pub mod i2s; | ||||
| pub mod onewire; | ||||
| pub mod pwm; | ||||
| pub mod rotary_encoder; | ||||
| pub mod stepper; | ||||
| pub mod uart; | ||||
| pub mod ws2812; | ||||
							
								
								
									
										109
									
								
								embassy-rp/src/pio_programs/onewire.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								embassy-rp/src/pio_programs/onewire.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| //! OneWire pio driver
 | ||||
| 
 | ||||
| use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}; | ||||
| 
 | ||||
| /// This struct represents an onewire driver program
 | ||||
| pub struct PioOneWireProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { | ||||
|     /// Load the program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .wrap_target | ||||
|                     again: | ||||
|                         pull block | ||||
|                         mov x, osr | ||||
|                         jmp !x, read | ||||
|                         write: | ||||
|                             set pindirs, 1 
 | ||||
|                             set pins, 0  
 | ||||
|                             loop1:  | ||||
|                                 jmp x--,loop1 | ||||
|                             set pindirs, 0 [31] | ||||
|                             wait 1 pin 0 [31] | ||||
|                             pull block | ||||
|                             mov x, osr | ||||
|                             bytes1: | ||||
|                                 pull block | ||||
|                                 set y, 7    
 | ||||
|                                 set pindirs, 1 
 | ||||
|                                 bit1: | ||||
|                                     set pins, 0 [1] | ||||
|                                     out pins,1 [31] | ||||
|                                     set pins, 1 [20] | ||||
|                                     jmp y--,bit1 | ||||
|                                 jmp x--,bytes1 | ||||
|                             set pindirs, 0 [31] | ||||
|                             jmp again | ||||
|                         read: | ||||
|                             pull block | ||||
|                             mov x, osr | ||||
|                             bytes2: | ||||
|                                 set y, 7 | ||||
|                                 bit2: | ||||
|                                     set pindirs, 1 
 | ||||
|                                     set pins, 0 [1]  
 | ||||
|                                     set pindirs, 0 [5] | ||||
|                                     in pins,1 [10]   
 | ||||
|                                     jmp y--,bit2 | ||||
|                             jmp x--,bytes2 | ||||
|                 .wrap | ||||
|             "#,
 | ||||
|         ); | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pio backed OneWire driver
 | ||||
| pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, PIO, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, PIO: pio::Instance, const SM: usize> PioOneWire<'d, PIO, SM> { | ||||
|     /// Create a new instance the driver
 | ||||
|     pub fn new( | ||||
|         common: &mut Common<'d, PIO>, | ||||
|         mut sm: StateMachine<'d, PIO, SM>, | ||||
|         pin: impl PioPin, | ||||
|         program: &PioOneWireProgram<'d, PIO>, | ||||
|     ) -> Self { | ||||
|         let pin = common.make_pio_pin(pin); | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&program.prg, &[]); | ||||
|         cfg.set_out_pins(&[&pin]); | ||||
|         cfg.set_in_pins(&[&pin]); | ||||
|         cfg.set_set_pins(&[&pin]); | ||||
|         cfg.shift_in = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             direction: ShiftDirection::Right, | ||||
|             threshold: 8, | ||||
|         }; | ||||
|         cfg.clock_divider = 255_u8.into(); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     /// Write bytes over the wire
 | ||||
|     pub async fn write_bytes(&mut self, bytes: &[u8]) { | ||||
|         self.sm.tx().wait_push(250).await; | ||||
|         self.sm.tx().wait_push(bytes.len() as u32 - 1).await; | ||||
|         for b in bytes { | ||||
|             self.sm.tx().wait_push(*b as u32).await; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Read bytes from the wire
 | ||||
|     pub async fn read_bytes(&mut self, bytes: &mut [u8]) { | ||||
|         self.sm.tx().wait_push(0).await; | ||||
|         self.sm.tx().wait_push(bytes.len() as u32 - 1).await; | ||||
|         for b in bytes.iter_mut() { | ||||
|             *b = (self.sm.rx().wait_pull().await >> 24) as u8; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										114
									
								
								embassy-rp/src/pio_programs/pwm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								embassy-rp/src/pio_programs/pwm.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| //! PIO backed PWM driver
 | ||||
| 
 | ||||
| use core::time::Duration; | ||||
| 
 | ||||
| use crate::{ | ||||
|     clocks, | ||||
|     gpio::Level, | ||||
|     pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine}, | ||||
| }; | ||||
| use pio::InstructionOperands; | ||||
| 
 | ||||
| fn to_pio_cycles(duration: Duration) -> u32 { | ||||
|     (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
 | ||||
| } | ||||
| 
 | ||||
| /// This struct represents a PWM program loaded into pio instruction memory.
 | ||||
| pub struct PioPwmProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> { | ||||
|     /// Load the program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             ".side_set 1 opt" | ||||
|                 "pull noblock    side 0" | ||||
|                 "mov x, osr" | ||||
|                 "mov y, isr" | ||||
|             "countloop:" | ||||
|                 "jmp x!=y noset" | ||||
|                 "jmp skip        side 1" | ||||
|             "noset:" | ||||
|                 "nop" | ||||
|             "skip:" | ||||
|                 "jmp y-- countloop" | ||||
|         ); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pio backed PWM output
 | ||||
| pub struct PioPwm<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { | ||||
|     /// Configure a state machine as a PWM output
 | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, T>, | ||||
|         mut sm: StateMachine<'d, T, SM>, | ||||
|         pin: impl PioPin, | ||||
|         program: &PioPwmProgram<'d, T>, | ||||
|     ) -> Self { | ||||
|         let pin = pio.make_pio_pin(pin); | ||||
|         sm.set_pins(Level::High, &[&pin]); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&program.prg, &[&pin]); | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
| 
 | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     /// Enable PWM output
 | ||||
|     pub fn start(&mut self) { | ||||
|         self.sm.set_enable(true); | ||||
|     } | ||||
| 
 | ||||
|     /// Disable PWM output
 | ||||
|     pub fn stop(&mut self) { | ||||
|         self.sm.set_enable(false); | ||||
|     } | ||||
| 
 | ||||
|     /// Set pwm period
 | ||||
|     pub fn set_period(&mut self, duration: Duration) { | ||||
|         let is_enabled = self.sm.is_enabled(); | ||||
|         while !self.sm.tx().empty() {} // Make sure that the queue is empty
 | ||||
|         self.sm.set_enable(false); | ||||
|         self.sm.tx().push(to_pio_cycles(duration)); | ||||
|         unsafe { | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::PULL { | ||||
|                     if_empty: false, | ||||
|                     block: false, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::OUT { | ||||
|                     destination: ::pio::OutDestination::ISR, | ||||
|                     bit_count: 32, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|         }; | ||||
|         if is_enabled { | ||||
|             self.sm.set_enable(true) // Enable if previously enabled
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn set_level(&mut self, level: u32) { | ||||
|         self.sm.tx().push(level); | ||||
|     } | ||||
| 
 | ||||
|     /// Set the pulse width high time
 | ||||
|     pub fn write(&mut self, duration: Duration) { | ||||
|         self.set_level(to_pio_cycles(duration)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										72
									
								
								embassy-rp/src/pio_programs/rotary_encoder.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								embassy-rp/src/pio_programs/rotary_encoder.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| //! PIO backed quadrature encoder
 | ||||
| 
 | ||||
| use crate::gpio::Pull; | ||||
| use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}; | ||||
| use fixed::traits::ToFixed; | ||||
| 
 | ||||
| /// This struct represents an Encoder program loaded into pio instruction memory.
 | ||||
| pub struct PioEncoderProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> { | ||||
|     /// Load the program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pio Backed quadrature encoder reader
 | ||||
| pub struct PioEncoder<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { | ||||
|     /// Configure a state machine with the loaded [PioEncoderProgram]
 | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, T>, | ||||
|         mut sm: StateMachine<'d, T, SM>, | ||||
|         pin_a: impl PioPin, | ||||
|         pin_b: impl PioPin, | ||||
|         program: &PioEncoderProgram<'d, T>, | ||||
|     ) -> Self { | ||||
|         let mut pin_a = pio.make_pio_pin(pin_a); | ||||
|         let mut pin_b = pio.make_pio_pin(pin_b); | ||||
|         pin_a.set_pull(Pull::Up); | ||||
|         pin_b.set_pull(Pull::Up); | ||||
|         sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.set_in_pins(&[&pin_a, &pin_b]); | ||||
|         cfg.fifo_join = FifoJoin::RxOnly; | ||||
|         cfg.shift_in.direction = ShiftDirection::Left; | ||||
|         cfg.clock_divider = 10_000.to_fixed(); | ||||
|         cfg.use_program(&program.prg, &[]); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     /// Read a single count from the encoder
 | ||||
|     pub async fn read(&mut self) -> Direction { | ||||
|         loop { | ||||
|             match self.sm.rx().wait_pull().await { | ||||
|                 0 => return Direction::CounterClockwise, | ||||
|                 1 => return Direction::Clockwise, | ||||
|                 _ => {} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Encoder Count Direction
 | ||||
| pub enum Direction { | ||||
|     /// Encoder turned clockwise
 | ||||
|     Clockwise, | ||||
|     /// Encoder turned counter clockwise
 | ||||
|     CounterClockwise, | ||||
| } | ||||
							
								
								
									
										146
									
								
								embassy-rp/src/pio_programs/stepper.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								embassy-rp/src/pio_programs/stepper.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,146 @@ | ||||
| //! Pio Stepper Driver for 5-wire steppers
 | ||||
| 
 | ||||
| use core::mem::{self, MaybeUninit}; | ||||
| 
 | ||||
| use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; | ||||
| use fixed::traits::ToFixed; | ||||
| use fixed::types::extra::U8; | ||||
| use fixed::FixedU32; | ||||
| 
 | ||||
| /// This struct represents a Stepper driver program loaded into pio instruction memory.
 | ||||
| pub struct PioStepperProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> { | ||||
|     /// Load the program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             "pull block", | ||||
|             "mov x, osr", | ||||
|             "pull block", | ||||
|             "mov y, osr", | ||||
|             "jmp !x end", | ||||
|             "loop:", | ||||
|             "jmp !osre step", | ||||
|             "mov osr, y", | ||||
|             "step:", | ||||
|             "out pins, 4 [31]" | ||||
|             "jmp x-- loop", | ||||
|             "end:", | ||||
|             "irq 0 rel" | ||||
|         ); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pio backed Stepper driver
 | ||||
| pub struct PioStepper<'d, T: Instance, const SM: usize> { | ||||
|     irq: Irq<'d, T, SM>, | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { | ||||
|     /// Configure a state machine to drive a stepper
 | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, T>, | ||||
|         mut sm: StateMachine<'d, T, SM>, | ||||
|         irq: Irq<'d, T, SM>, | ||||
|         pin0: impl PioPin, | ||||
|         pin1: impl PioPin, | ||||
|         pin2: impl PioPin, | ||||
|         pin3: impl PioPin, | ||||
|         program: &PioStepperProgram<'d, T>, | ||||
|     ) -> Self { | ||||
|         let pin0 = pio.make_pio_pin(pin0); | ||||
|         let pin1 = pio.make_pio_pin(pin1); | ||||
|         let pin2 = pio.make_pio_pin(pin2); | ||||
|         let pin3 = pio.make_pio_pin(pin3); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); | ||||
|         cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); | ||||
|         cfg.use_program(&program.prg, &[]); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { irq, sm } | ||||
|     } | ||||
| 
 | ||||
|     /// Set pulse frequency
 | ||||
|     pub fn set_frequency(&mut self, freq: u32) { | ||||
|         let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed(); | ||||
|         assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); | ||||
|         assert!(clock_divider >= 1, "clkdiv must be >= 1"); | ||||
|         self.sm.set_clock_divider(clock_divider); | ||||
|         self.sm.clkdiv_restart(); | ||||
|     } | ||||
| 
 | ||||
|     /// Full step, one phase
 | ||||
|     pub async fn step(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Full step, two phase
 | ||||
|     pub async fn step2(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Half step
 | ||||
|     pub async fn step_half(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn run(&mut self, steps: i32, pattern: u32) { | ||||
|         self.sm.tx().wait_push(steps as u32).await; | ||||
|         self.sm.tx().wait_push(pattern).await; | ||||
|         let drop = OnDrop::new(|| { | ||||
|             self.sm.clear_fifos(); | ||||
|             unsafe { | ||||
|                 self.sm.exec_instr( | ||||
|                     pio::InstructionOperands::JMP { | ||||
|                         address: 0, | ||||
|                         condition: pio::JmpCondition::Always, | ||||
|                     } | ||||
|                     .encode(), | ||||
|                 ); | ||||
|             } | ||||
|         }); | ||||
|         self.irq.wait().await; | ||||
|         drop.defuse(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct OnDrop<F: FnOnce()> { | ||||
|     f: MaybeUninit<F>, | ||||
| } | ||||
| 
 | ||||
| impl<F: FnOnce()> OnDrop<F> { | ||||
|     pub fn new(f: F) -> Self { | ||||
|         Self { f: MaybeUninit::new(f) } | ||||
|     } | ||||
| 
 | ||||
|     pub fn defuse(self) { | ||||
|         mem::forget(self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<F: FnOnce()> Drop for OnDrop<F> { | ||||
|     fn drop(&mut self) { | ||||
|         unsafe { self.f.as_ptr().read()() } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										186
									
								
								embassy-rp/src/pio_programs/uart.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								embassy-rp/src/pio_programs/uart.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,186 @@ | ||||
| //! Pio backed uart drivers
 | ||||
| 
 | ||||
| use crate::{ | ||||
|     clocks::clk_sys_freq, | ||||
|     gpio::Level, | ||||
|     pio::{ | ||||
|         Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, | ||||
|         StateMachine, | ||||
|     }, | ||||
| }; | ||||
| use core::convert::Infallible; | ||||
| use embedded_io_async::{ErrorType, Read, Write}; | ||||
| use fixed::traits::ToFixed; | ||||
| 
 | ||||
| /// This struct represents a uart tx program loaded into pio instruction memory.
 | ||||
| pub struct PioUartTxProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> { | ||||
|     /// Load the uart tx program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .side_set 1 opt | ||||
| 
 | ||||
|                 ; An 8n1 UART transmit program. | ||||
|                 ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. | ||||
| 
 | ||||
|                     pull       side 1 [7]  ; Assert stop bit, or stall with line in idle state | ||||
|                     set x, 7   side 0 [7]  ; Preload bit counter, assert start bit for 8 clocks | ||||
|                 bitloop:                   ; This loop will run 8 times (8n1 UART) | ||||
|                     out pins, 1            ; Shift 1 bit from OSR to the first OUT pin | ||||
|                     jmp x-- bitloop   [6]  ; Each loop iteration is 8 cycles. | ||||
|             "#
 | ||||
|         ); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// PIO backed Uart transmitter
 | ||||
| pub struct PioUartTx<'a, PIO: Instance, const SM: usize> { | ||||
|     sm_tx: StateMachine<'a, PIO, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> { | ||||
|     /// Configure a pio state machine to use the loaded tx program.
 | ||||
|     pub fn new( | ||||
|         baud: u32, | ||||
|         common: &mut Common<'a, PIO>, | ||||
|         mut sm_tx: StateMachine<'a, PIO, SM>, | ||||
|         tx_pin: impl PioPin, | ||||
|         program: &PioUartTxProgram<'a, PIO>, | ||||
|     ) -> Self { | ||||
|         let tx_pin = common.make_pio_pin(tx_pin); | ||||
|         sm_tx.set_pins(Level::High, &[&tx_pin]); | ||||
|         sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
| 
 | ||||
|         cfg.set_out_pins(&[&tx_pin]); | ||||
|         cfg.use_program(&program.prg, &[&tx_pin]); | ||||
|         cfg.shift_out.auto_fill = false; | ||||
|         cfg.shift_out.direction = ShiftDirection::Right; | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); | ||||
|         sm_tx.set_config(&cfg); | ||||
|         sm_tx.set_enable(true); | ||||
| 
 | ||||
|         Self { sm_tx } | ||||
|     } | ||||
| 
 | ||||
|     /// Write a single u8
 | ||||
|     pub async fn write_u8(&mut self, data: u8) { | ||||
|         self.sm_tx.tx().wait_push(data as u32).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> { | ||||
|     type Error = Infallible; | ||||
| } | ||||
| 
 | ||||
| impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> { | ||||
|     async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> { | ||||
|         for byte in buf { | ||||
|             self.write_u8(*byte).await; | ||||
|         } | ||||
|         Ok(buf.len()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// This struct represents a Uart Rx program loaded into pio instruction memory.
 | ||||
| pub struct PioUartRxProgram<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> { | ||||
|     /// Load the uart rx program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and | ||||
|                 ; break conditions more gracefully. | ||||
|                 ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. | ||||
| 
 | ||||
|                 start: | ||||
|                     wait 0 pin 0        ; Stall until start bit is asserted | ||||
|                     set x, 7    [10]    ; Preload bit counter, then delay until halfway through | ||||
|                 rx_bitloop:             ; the first data bit (12 cycles incl wait, set). | ||||
|                     in pins, 1          ; Shift data bit into ISR | ||||
|                     jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles | ||||
|                     jmp pin good_rx_stop   ; Check stop bit (should be high) | ||||
| 
 | ||||
|                     irq 4 rel           ; Either a framing error or a break. Set a sticky flag, | ||||
|                     wait 1 pin 0        ; and wait for line to return to idle state. | ||||
|                     jmp start           ; Don't push data if we didn't see good framing. | ||||
| 
 | ||||
|                 good_rx_stop:           ; No delay before returning to start; a little slack is | ||||
|                     in null 24 | ||||
|                     push                ; important in case the TX clock is slightly too fast. | ||||
|             "#
 | ||||
|         ); | ||||
| 
 | ||||
|         let prg = common.load_program(&prg.program); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// PIO backed Uart reciever
 | ||||
| pub struct PioUartRx<'a, PIO: Instance, const SM: usize> { | ||||
|     sm_rx: StateMachine<'a, PIO, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> { | ||||
|     /// Configure a pio state machine to use the loaded rx program.
 | ||||
|     pub fn new( | ||||
|         baud: u32, | ||||
|         common: &mut Common<'a, PIO>, | ||||
|         mut sm_rx: StateMachine<'a, PIO, SM>, | ||||
|         rx_pin: impl PioPin, | ||||
|         program: &PioUartRxProgram<'a, PIO>, | ||||
|     ) -> Self { | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&program.prg, &[]); | ||||
| 
 | ||||
|         let rx_pin = common.make_pio_pin(rx_pin); | ||||
|         sm_rx.set_pins(Level::High, &[&rx_pin]); | ||||
|         cfg.set_in_pins(&[&rx_pin]); | ||||
|         cfg.set_jmp_pin(&rx_pin); | ||||
|         sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]); | ||||
| 
 | ||||
|         cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); | ||||
|         cfg.shift_in.auto_fill = false; | ||||
|         cfg.shift_in.direction = ShiftDirection::Right; | ||||
|         cfg.shift_in.threshold = 32; | ||||
|         cfg.fifo_join = FifoJoin::RxOnly; | ||||
|         sm_rx.set_config(&cfg); | ||||
|         sm_rx.set_enable(true); | ||||
| 
 | ||||
|         Self { sm_rx } | ||||
|     } | ||||
| 
 | ||||
|     /// Wait for a single u8
 | ||||
|     pub async fn read_u8(&mut self) -> u8 { | ||||
|         self.sm_rx.rx().wait_pull().await as u8 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> { | ||||
|     type Error = Infallible; | ||||
| } | ||||
| 
 | ||||
| impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> { | ||||
|     async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> { | ||||
|         let mut i = 0; | ||||
|         while i < buf.len() { | ||||
|             buf[i] = self.read_u8().await; | ||||
|             i += 1; | ||||
|         } | ||||
|         Ok(i) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										118
									
								
								embassy-rp/src/pio_programs/ws2812.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								embassy-rp/src/pio_programs/ws2812.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| //! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
 | ||||
| 
 | ||||
| use crate::{ | ||||
|     clocks::clk_sys_freq, | ||||
|     dma::{AnyChannel, Channel}, | ||||
|     into_ref, | ||||
|     pio::{Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}, | ||||
|     Peripheral, PeripheralRef, | ||||
| }; | ||||
| use embassy_time::Timer; | ||||
| use fixed::types::U24F8; | ||||
| use smart_leds::RGB8; | ||||
| 
 | ||||
| const T1: u8 = 2; // start bit
 | ||||
| const T2: u8 = 5; // data bit
 | ||||
| const T3: u8 = 3; // stop bit
 | ||||
| const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; | ||||
| 
 | ||||
| /// This struct represents a ws2812 program loaded into pio instruction memory.
 | ||||
| pub struct PioWs2812Program<'a, PIO: Instance> { | ||||
|     prg: LoadedProgram<'a, PIO>, | ||||
| } | ||||
| 
 | ||||
| impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> { | ||||
|     /// Load the ws2812 program into the given pio
 | ||||
|     pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||||
|         let side_set = pio::SideSet::new(false, 1, false); | ||||
|         let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); | ||||
| 
 | ||||
|         let mut wrap_target = a.label(); | ||||
|         let mut wrap_source = a.label(); | ||||
|         let mut do_zero = a.label(); | ||||
|         a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); | ||||
|         a.bind(&mut wrap_target); | ||||
|         // Do stop bit
 | ||||
|         a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); | ||||
|         // Do start bit
 | ||||
|         a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); | ||||
|         // Do data bit = 1
 | ||||
|         a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); | ||||
|         a.bind(&mut do_zero); | ||||
|         // Do data bit = 0
 | ||||
|         a.nop_with_delay_and_side_set(T2 - 1, 0); | ||||
|         a.bind(&mut wrap_source); | ||||
| 
 | ||||
|         let prg = a.assemble_with_wrap(wrap_source, wrap_target); | ||||
|         let prg = common.load_program(&prg); | ||||
| 
 | ||||
|         Self { prg } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Pio backed ws2812 driver
 | ||||
| /// Const N is the number of ws2812 leds attached to this pin
 | ||||
| pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { | ||||
|     dma: PeripheralRef<'d, AnyChannel>, | ||||
|     sm: StateMachine<'d, P, S>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { | ||||
|     /// Configure a pio state machine to use the loaded ws2812 program.
 | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, P>, | ||||
|         mut sm: StateMachine<'d, P, S>, | ||||
|         dma: impl Peripheral<P = impl Channel> + 'd, | ||||
|         pin: impl PioPin, | ||||
|         program: &PioWs2812Program<'d, P>, | ||||
|     ) -> Self { | ||||
|         into_ref!(dma); | ||||
| 
 | ||||
|         // Setup sm0
 | ||||
|         let mut cfg = Config::default(); | ||||
| 
 | ||||
|         // Pin config
 | ||||
|         let out_pin = pio.make_pio_pin(pin); | ||||
|         cfg.set_out_pins(&[&out_pin]); | ||||
|         cfg.set_set_pins(&[&out_pin]); | ||||
| 
 | ||||
|         cfg.use_program(&program.prg, &[&out_pin]); | ||||
| 
 | ||||
|         // Clock config, measured in kHz to avoid overflows
 | ||||
|         let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); | ||||
|         let ws2812_freq = U24F8::from_num(800); | ||||
|         let bit_freq = ws2812_freq * CYCLES_PER_BIT; | ||||
|         cfg.clock_divider = clock_freq / bit_freq; | ||||
| 
 | ||||
|         // FIFO config
 | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             threshold: 24, | ||||
|             direction: ShiftDirection::Left, | ||||
|         }; | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
| 
 | ||||
|         Self { | ||||
|             dma: dma.map_into(), | ||||
|             sm, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Write a buffer of [smart_leds::RGB8] to the ws2812 string
 | ||||
|     pub async fn write(&mut self, colors: &[RGB8; N]) { | ||||
|         // Precompute the word bytes from the colors
 | ||||
|         let mut words = [0u32; N]; | ||||
|         for i in 0..N { | ||||
|             let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); | ||||
|             words[i] = word; | ||||
|         } | ||||
| 
 | ||||
|         // DMA transfer
 | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), &words).await; | ||||
| 
 | ||||
|         Timer::after_micros(55).await; | ||||
|     } | ||||
| } | ||||
| @ -42,7 +42,7 @@ embedded-graphics = "0.7.1" | ||||
| st7789 = "0.6.1" | ||||
| display-interface = "0.4.1" | ||||
| byte-slice-cast = { version = "1.2.0", default-features = false } | ||||
| smart-leds = "0.3.0" | ||||
| smart-leds = "0.4.0" | ||||
| heapless = "0.8" | ||||
| usbd-hid = "0.8.1" | ||||
| 
 | ||||
|  | ||||
| @ -7,13 +7,11 @@ | ||||
| use core::fmt::Write; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::dma::{AnyChannel, Channel}; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{ | ||||
|     Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, | ||||
| }; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; | ||||
| use embassy_rp::pwm::{self, Pwm}; | ||||
| use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; | ||||
| use embassy_time::{Instant, Timer}; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| @ -43,8 +41,27 @@ async fn main(_spawner: Spawner) { | ||||
|         c | ||||
|     }); | ||||
| 
 | ||||
|     let mut hd = HD44780::new( | ||||
|         p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, | ||||
|     let Pio { | ||||
|         mut common, sm0, irq0, .. | ||||
|     } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let word_prg = PioHD44780CommandWordProgram::new(&mut common); | ||||
|     let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); | ||||
| 
 | ||||
|     let mut hd = PioHD44780::new( | ||||
|         &mut common, | ||||
|         sm0, | ||||
|         irq0, | ||||
|         p.DMA_CH3, | ||||
|         p.PIN_0, | ||||
|         p.PIN_1, | ||||
|         p.PIN_2, | ||||
|         p.PIN_3, | ||||
|         p.PIN_4, | ||||
|         p.PIN_5, | ||||
|         p.PIN_6, | ||||
|         &word_prg, | ||||
|         &seq_prg, | ||||
|     ) | ||||
|     .await; | ||||
| 
 | ||||
| @ -68,173 +85,3 @@ async fn main(_spawner: Spawner) { | ||||
|         Timer::after_secs(1).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct HD44780<'l> { | ||||
|     dma: PeripheralRef<'l, AnyChannel>, | ||||
|     sm: StateMachine<'l, PIO0, 0>, | ||||
| 
 | ||||
|     buf: [u8; 40], | ||||
| } | ||||
| 
 | ||||
| impl<'l> HD44780<'l> { | ||||
|     pub async fn new( | ||||
|         pio: impl Peripheral<P = PIO0> + 'l, | ||||
|         irq: Irqs, | ||||
|         dma: impl Peripheral<P = impl Channel> + 'l, | ||||
|         rs: impl PioPin, | ||||
|         rw: impl PioPin, | ||||
|         e: impl PioPin, | ||||
|         db4: impl PioPin, | ||||
|         db5: impl PioPin, | ||||
|         db6: impl PioPin, | ||||
|         db7: impl PioPin, | ||||
|     ) -> HD44780<'l> { | ||||
|         into_ref!(dma); | ||||
| 
 | ||||
|         let Pio { | ||||
|             mut common, | ||||
|             mut irq0, | ||||
|             mut sm0, | ||||
|             .. | ||||
|         } = Pio::new(pio, irq); | ||||
| 
 | ||||
|         // takes command words (<wait:24> <command:4> <0:4>)
 | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .side_set 1 opt | ||||
|                 .origin 20 | ||||
| 
 | ||||
|                 loop: | ||||
|                     out x,     24 | ||||
|                 delay: | ||||
|                     jmp x--,   delay | ||||
|                     out pins,  4     side 1 | ||||
|                     out null,  4     side 0 | ||||
|                     jmp !osre, loop | ||||
|                 irq 0 | ||||
|             "#,
 | ||||
|         ); | ||||
| 
 | ||||
|         let rs = common.make_pio_pin(rs); | ||||
|         let rw = common.make_pio_pin(rw); | ||||
|         let e = common.make_pio_pin(e); | ||||
|         let db4 = common.make_pio_pin(db4); | ||||
|         let db5 = common.make_pio_pin(db5); | ||||
|         let db6 = common.make_pio_pin(db6); | ||||
|         let db7 = common.make_pio_pin(db7); | ||||
| 
 | ||||
|         sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&common.load_program(&prg.program), &[&e]); | ||||
|         cfg.clock_divider = 125u8.into(); | ||||
|         cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             direction: ShiftDirection::Left, | ||||
|             threshold: 32, | ||||
|         }; | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         sm0.set_config(&cfg); | ||||
| 
 | ||||
|         sm0.set_enable(true); | ||||
|         // init to 8 bit thrice
 | ||||
|         sm0.tx().push((50000 << 8) | 0x30); | ||||
|         sm0.tx().push((5000 << 8) | 0x30); | ||||
|         sm0.tx().push((200 << 8) | 0x30); | ||||
|         // init 4 bit
 | ||||
|         sm0.tx().push((200 << 8) | 0x20); | ||||
|         // set font and lines
 | ||||
|         sm0.tx().push((50 << 8) | 0x20); | ||||
|         sm0.tx().push(0b1100_0000); | ||||
| 
 | ||||
|         irq0.wait().await; | ||||
|         sm0.set_enable(false); | ||||
| 
 | ||||
|         // takes command sequences (<rs:1> <count:7>, data...)
 | ||||
|         // many side sets are only there to free up a delay bit!
 | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .origin 27 | ||||
|                 .side_set 1 | ||||
| 
 | ||||
|                 .wrap_target | ||||
|                 pull     side 0 | ||||
|                 out  x 1 side 0 ; !rs | ||||
|                 out  y 7 side 0 ; #data - 1 | ||||
| 
 | ||||
|                 ; rs/rw to e: >= 60ns | ||||
|                 ; e high time: >= 500ns | ||||
|                 ; e low time: >= 500ns | ||||
|                 ; read data valid after e falling: ~5ns | ||||
|                 ; write data hold after e falling: ~10ns | ||||
| 
 | ||||
|                 loop: | ||||
|                     pull                 side 0 | ||||
|                     jmp  !x       data   side 0 | ||||
|                 command: | ||||
|                     set  pins     0b00   side 0 | ||||
|                     jmp  shift           side 0 | ||||
|                 data: | ||||
|                     set  pins     0b01   side 0 | ||||
|                 shift: | ||||
|                     out  pins     4      side 1 [9] | ||||
|                     nop                  side 0 [9] | ||||
|                     out  pins     4      side 1 [9] | ||||
|                     mov  osr      null   side 0 [7] | ||||
|                     out  pindirs  4      side 0 | ||||
|                     set  pins     0b10   side 0 | ||||
|                 busy: | ||||
|                     nop                  side 1 [9] | ||||
|                     jmp  pin      more   side 0 [9] | ||||
|                     mov  osr      ~osr   side 1 [9] | ||||
|                     nop                  side 0 [4] | ||||
|                     out  pindirs  4      side 0 | ||||
|                     jmp  y--      loop   side 0 | ||||
|                 .wrap | ||||
|                 more: | ||||
|                     nop                  side 1 [9] | ||||
|                     jmp busy             side 0 [9] | ||||
|             "#
 | ||||
|         ); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&common.load_program(&prg.program), &[&e]); | ||||
|         cfg.clock_divider = 8u8.into(); // ~64ns/insn
 | ||||
|         cfg.set_jmp_pin(&db7); | ||||
|         cfg.set_set_pins(&[&rs, &rw]); | ||||
|         cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | ||||
|         cfg.shift_out.direction = ShiftDirection::Left; | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         sm0.set_config(&cfg); | ||||
| 
 | ||||
|         sm0.set_enable(true); | ||||
| 
 | ||||
|         // display on and cursor on and blinking, reset display
 | ||||
|         sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; | ||||
| 
 | ||||
|         Self { | ||||
|             dma: dma.map_into(), | ||||
|             sm: sm0, | ||||
|             buf: [0x20; 40], | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub async fn add_line(&mut self, s: &[u8]) { | ||||
|         // move cursor to 0:0, prepare 16 characters
 | ||||
|         self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); | ||||
|         // move line 2 up
 | ||||
|         self.buf.copy_within(22..38, 3); | ||||
|         // move cursor to 1:0, prepare 16 characters
 | ||||
|         self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); | ||||
|         // file line 2 with spaces
 | ||||
|         self.buf[22..38].fill(0x20); | ||||
|         // copy input line
 | ||||
|         let len = s.len().min(16); | ||||
|         self.buf[22..22 + len].copy_from_slice(&s[0..len]); | ||||
|         // set cursor to 1:15
 | ||||
|         self.buf[38..].copy_from_slice(&[0x80, 0xcf]); | ||||
| 
 | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,10 +13,10 @@ | ||||
| use core::mem; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; | ||||
| use embassy_rp::{bind_interrupts, Peripheral}; | ||||
| use fixed::traits::ToFixed; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; | ||||
| use static_cell::StaticCell; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| @ -25,61 +25,32 @@ bind_interrupts!(struct Irqs { | ||||
| }); | ||||
| 
 | ||||
| const SAMPLE_RATE: u32 = 48_000; | ||||
| const BIT_DEPTH: u32 = 16; | ||||
| const CHANNELS: u32 = 2; | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let mut p = embassy_rp::init(Default::default()); | ||||
| 
 | ||||
|     // Setup pio state machine for i2s output
 | ||||
|     let mut pio = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     #[rustfmt::skip] | ||||
|     let pio_program = pio_proc::pio_asm!( | ||||
|         ".side_set 2", | ||||
|         "    set x, 14          side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
 | ||||
|         "left_data:", | ||||
|         "    out pins, 1        side 0b00", | ||||
|         "    jmp x-- left_data  side 0b01", | ||||
|         "    out pins 1         side 0b10", | ||||
|         "    set x, 14          side 0b11", | ||||
|         "right_data:", | ||||
|         "    out pins 1         side 0b10", | ||||
|         "    jmp x-- right_data side 0b11", | ||||
|         "    out pins 1         side 0b00", | ||||
|     ); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let bit_clock_pin = p.PIN_18; | ||||
|     let left_right_clock_pin = p.PIN_19; | ||||
|     let data_pin = p.PIN_20; | ||||
| 
 | ||||
|     let data_pin = pio.common.make_pio_pin(data_pin); | ||||
|     let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); | ||||
|     let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); | ||||
| 
 | ||||
|     let cfg = { | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program( | ||||
|             &pio.common.load_program(&pio_program.program), | ||||
|             &[&bit_clock_pin, &left_right_clock_pin], | ||||
|         ); | ||||
|         cfg.set_out_pins(&[&data_pin]); | ||||
|         const BIT_DEPTH: u32 = 16; | ||||
|         const CHANNELS: u32 = 2; | ||||
|         let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; | ||||
|         cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             threshold: 32, | ||||
|             direction: ShiftDirection::Left, | ||||
|             auto_fill: true, | ||||
|         }; | ||||
|         // join fifos to have twice the time to start the next dma transfer
 | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         cfg | ||||
|     }; | ||||
|     pio.sm0.set_config(&cfg); | ||||
|     pio.sm0.set_pin_dirs( | ||||
|         embassy_rp::pio::Direction::Out, | ||||
|         &[&data_pin, &left_right_clock_pin, &bit_clock_pin], | ||||
|     let program = PioI2sOutProgram::new(&mut common); | ||||
|     let mut i2s = PioI2sOut::new( | ||||
|         &mut common, | ||||
|         sm0, | ||||
|         p.DMA_CH0, | ||||
|         data_pin, | ||||
|         bit_clock_pin, | ||||
|         left_right_clock_pin, | ||||
|         SAMPLE_RATE, | ||||
|         BIT_DEPTH, | ||||
|         CHANNELS, | ||||
|         &program, | ||||
|     ); | ||||
| 
 | ||||
|     // create two audio buffers (back and front) which will take turns being
 | ||||
| @ -90,17 +61,13 @@ async fn main(_spawner: Spawner) { | ||||
|     let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); | ||||
| 
 | ||||
|     // start pio state machine
 | ||||
|     pio.sm0.set_enable(true); | ||||
|     let tx = pio.sm0.tx(); | ||||
|     let mut dma_ref = p.DMA_CH0.into_ref(); | ||||
| 
 | ||||
|     let mut fade_value: i32 = 0; | ||||
|     let mut phase: i32 = 0; | ||||
| 
 | ||||
|     loop { | ||||
|         // trigger transfer of front buffer data to the pio fifo
 | ||||
|         // but don't await the returned future, yet
 | ||||
|         let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); | ||||
|         let dma_future = i2s.write(front_buffer); | ||||
| 
 | ||||
|         // fade in audio when bootsel is pressed
 | ||||
|         let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; | ||||
|  | ||||
| @ -6,7 +6,8 @@ use defmt::*; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; | ||||
| use embassy_rp::pio::{self, InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; | ||||
| use embassy_time::Timer; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| @ -18,7 +19,11 @@ bind_interrupts!(struct Irqs { | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let mut pio = Pio::new(p.PIO0, Irqs); | ||||
|     let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2); | ||||
| 
 | ||||
|     let prg = PioOneWireProgram::new(&mut pio.common); | ||||
|     let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); | ||||
| 
 | ||||
|     let mut sensor = Ds18b20::new(onewire); | ||||
| 
 | ||||
|     loop { | ||||
|         sensor.start().await; // Start a new measurement
 | ||||
| @ -33,89 +38,12 @@ async fn main(_spawner: Spawner) { | ||||
| 
 | ||||
| /// DS18B20 temperature sensor driver
 | ||||
| pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, PIO, SM>, | ||||
|     wire: PioOneWire<'d, PIO, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { | ||||
|     /// Create a new instance the driver
 | ||||
|     pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .wrap_target | ||||
|                     again: | ||||
|                         pull block | ||||
|                         mov x, osr | ||||
|                         jmp !x, read | ||||
|                         write: | ||||
|                             set pindirs, 1 
 | ||||
|                             set pins, 0  
 | ||||
|                             loop1:  | ||||
|                                 jmp x--,loop1 | ||||
|                             set pindirs, 0 [31] | ||||
|                             wait 1 pin 0 [31] | ||||
|                             pull block | ||||
|                             mov x, osr | ||||
|                             bytes1: | ||||
|                                 pull block | ||||
|                                 set y, 7    
 | ||||
|                                 set pindirs, 1 
 | ||||
|                                 bit1: | ||||
|                                     set pins, 0 [1] | ||||
|                                     out pins,1 [31] | ||||
|                                     set pins, 1 [20] | ||||
|                                     jmp y--,bit1 | ||||
|                                 jmp x--,bytes1 | ||||
|                             set pindirs, 0 [31] | ||||
|                             jmp again | ||||
|                         read: | ||||
|                             pull block | ||||
|                             mov x, osr | ||||
|                             bytes2: | ||||
|                                 set y, 7 | ||||
|                                 bit2: | ||||
|                                     set pindirs, 1 
 | ||||
|                                     set pins, 0 [1]  
 | ||||
|                                     set pindirs, 0 [5] | ||||
|                                     in pins,1 [10]   
 | ||||
|                                     jmp y--,bit2 | ||||
|                             jmp x--,bytes2 | ||||
|                 .wrap | ||||
|             "#,
 | ||||
|         ); | ||||
| 
 | ||||
|         let pin = common.make_pio_pin(pin); | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&common.load_program(&prg.program), &[]); | ||||
|         cfg.set_out_pins(&[&pin]); | ||||
|         cfg.set_in_pins(&[&pin]); | ||||
|         cfg.set_set_pins(&[&pin]); | ||||
|         cfg.shift_in = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             direction: ShiftDirection::Right, | ||||
|             threshold: 8, | ||||
|         }; | ||||
|         cfg.clock_divider = 255_u8.into(); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     /// Write bytes over the wire
 | ||||
|     async fn write_bytes(&mut self, bytes: &[u8]) { | ||||
|         self.sm.tx().wait_push(250).await; | ||||
|         self.sm.tx().wait_push(bytes.len() as u32 - 1).await; | ||||
|         for b in bytes { | ||||
|             self.sm.tx().wait_push(*b as u32).await; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Read bytes from the wire
 | ||||
|     async fn read_bytes(&mut self, bytes: &mut [u8]) { | ||||
|         self.sm.tx().wait_push(0).await; | ||||
|         self.sm.tx().wait_push(bytes.len() as u32 - 1).await; | ||||
|         for b in bytes.iter_mut() { | ||||
|             *b = (self.sm.rx().wait_pull().await >> 24) as u8; | ||||
|         } | ||||
|     pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { | ||||
|         Self { wire } | ||||
|     } | ||||
| 
 | ||||
|     /// Calculate CRC8 of the data
 | ||||
| @ -139,14 +67,14 @@ impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { | ||||
| 
 | ||||
|     /// Start a new measurement. Allow at least 1000ms before getting `temperature`.
 | ||||
|     pub async fn start(&mut self) { | ||||
|         self.write_bytes(&[0xCC, 0x44]).await; | ||||
|         self.wire.write_bytes(&[0xCC, 0x44]).await; | ||||
|     } | ||||
| 
 | ||||
|     /// Read the temperature. Ensure >1000ms has passed since `start` before calling this.
 | ||||
|     pub async fn temperature(&mut self) -> Result<f32, ()> { | ||||
|         self.write_bytes(&[0xCC, 0xBE]).await; | ||||
|         self.wire.write_bytes(&[0xCC, 0xBE]).await; | ||||
|         let mut data = [0; 9]; | ||||
|         self.read_bytes(&mut data).await; | ||||
|         self.wire.read_bytes(&mut data).await; | ||||
|         match Self::crc8(&data) == 0 { | ||||
|             true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), | ||||
|             false => Err(()), | ||||
|  | ||||
| @ -5,12 +5,11 @@ | ||||
| use core::time::Duration; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::gpio::Level; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; | ||||
| use embassy_rp::{bind_interrupts, clocks}; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; | ||||
| use embassy_time::Timer; | ||||
| use pio::InstructionOperands; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| const REFRESH_INTERVAL: u64 = 20000; | ||||
| @ -19,93 +18,14 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub fn to_pio_cycles(duration: Duration) -> u32 { | ||||
|     (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
 | ||||
| } | ||||
| 
 | ||||
| pub struct PwmPio<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { | ||||
|     pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             ".side_set 1 opt" | ||||
|                 "pull noblock    side 0" | ||||
|                 "mov x, osr" | ||||
|                 "mov y, isr" | ||||
|             "countloop:" | ||||
|                 "jmp x!=y noset" | ||||
|                 "jmp skip        side 1" | ||||
|             "noset:" | ||||
|                 "nop" | ||||
|             "skip:" | ||||
|                 "jmp y-- countloop" | ||||
|         ); | ||||
| 
 | ||||
|         pio.load_program(&prg.program); | ||||
|         let pin = pio.make_pio_pin(pin); | ||||
|         sm.set_pins(Level::High, &[&pin]); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[&pin]); | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
| 
 | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     pub fn start(&mut self) { | ||||
|         self.sm.set_enable(true); | ||||
|     } | ||||
| 
 | ||||
|     pub fn stop(&mut self) { | ||||
|         self.sm.set_enable(false); | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_period(&mut self, duration: Duration) { | ||||
|         let is_enabled = self.sm.is_enabled(); | ||||
|         while !self.sm.tx().empty() {} // Make sure that the queue is empty
 | ||||
|         self.sm.set_enable(false); | ||||
|         self.sm.tx().push(to_pio_cycles(duration)); | ||||
|         unsafe { | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::PULL { | ||||
|                     if_empty: false, | ||||
|                     block: false, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::OUT { | ||||
|                     destination: ::pio::OutDestination::ISR, | ||||
|                     bit_count: 32, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|         }; | ||||
|         if is_enabled { | ||||
|             self.sm.set_enable(true) // Enable if previously enabled
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_level(&mut self, level: u32) { | ||||
|         self.sm.tx().push(level); | ||||
|     } | ||||
| 
 | ||||
|     pub fn write(&mut self, duration: Duration) { | ||||
|         self.set_level(to_pio_cycles(duration)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     // Note that PIN_25 is the led pin on the Pico
 | ||||
|     let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); | ||||
|     let prg = PioPwmProgram::new(&mut common); | ||||
|     let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); | ||||
|     pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); | ||||
|     pwm_pio.start(); | ||||
| 
 | ||||
|  | ||||
| @ -5,70 +5,20 @@ | ||||
| 
 | ||||
| use defmt::info; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::gpio::Pull; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::{bind_interrupts, pio}; | ||||
| use fixed::traits::ToFixed; | ||||
| use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; | ||||
| use embassy_rp::{ | ||||
|     bind_interrupts, | ||||
|     pio::{InterruptHandler, Pio}, | ||||
|     pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}, | ||||
| }; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub struct PioEncoder<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, T>, | ||||
|         mut sm: StateMachine<'d, T, SM>, | ||||
|         pin_a: impl PioPin, | ||||
|         pin_b: impl PioPin, | ||||
|     ) -> Self { | ||||
|         let mut pin_a = pio.make_pio_pin(pin_a); | ||||
|         let mut pin_b = pio.make_pio_pin(pin_b); | ||||
|         pin_a.set_pull(Pull::Up); | ||||
|         pin_b.set_pull(Pull::Up); | ||||
|         sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); | ||||
| 
 | ||||
|         let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.set_in_pins(&[&pin_a, &pin_b]); | ||||
|         cfg.fifo_join = FifoJoin::RxOnly; | ||||
|         cfg.shift_in.direction = ShiftDirection::Left; | ||||
|         cfg.clock_divider = 10_000.to_fixed(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[]); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     pub async fn read(&mut self) -> Direction { | ||||
|         loop { | ||||
|             match self.sm.rx().wait_pull().await { | ||||
|                 0 => return Direction::CounterClockwise, | ||||
|                 1 => return Direction::Clockwise, | ||||
|                 _ => {} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub enum Direction { | ||||
|     Clockwise, | ||||
|     CounterClockwise, | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); | ||||
| 
 | ||||
| #[embassy_executor::task] | ||||
| async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { | ||||
|     let mut count = 0; | ||||
|     loop { | ||||
|         info!("Count: {}", count); | ||||
| @ -78,3 +28,30 @@ async fn main(_spawner: Spawner) { | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::task] | ||||
| async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { | ||||
|     let mut count = 0; | ||||
|     loop { | ||||
|         info!("Count: {}", count); | ||||
|         count += match encoder.read().await { | ||||
|             Direction::Clockwise => 1, | ||||
|             Direction::CounterClockwise => -1, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { | ||||
|         mut common, sm0, sm1, .. | ||||
|     } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let prg = PioEncoderProgram::new(&mut common); | ||||
|     let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); | ||||
|     let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); | ||||
| 
 | ||||
|     spawner.must_spawn(encoder_0(encoder0)); | ||||
|     spawner.must_spawn(encoder_1(encoder1)); | ||||
| } | ||||
|  | ||||
| @ -5,12 +5,11 @@ | ||||
| use core::time::Duration; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::gpio::Level; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; | ||||
| use embassy_rp::{bind_interrupts, clocks}; | ||||
| use embassy_rp::pio::{Instance, InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; | ||||
| use embassy_time::Timer; | ||||
| use pio::InstructionOperands; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo
 | ||||
| @ -22,88 +21,8 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub fn to_pio_cycles(duration: Duration) -> u32 { | ||||
|     (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
 | ||||
| } | ||||
| 
 | ||||
| pub struct PwmPio<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { | ||||
|     pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             ".side_set 1 opt" | ||||
|                 "pull noblock    side 0" | ||||
|                 "mov x, osr" | ||||
|                 "mov y, isr" | ||||
|             "countloop:" | ||||
|                 "jmp x!=y noset" | ||||
|                 "jmp skip        side 1" | ||||
|             "noset:" | ||||
|                 "nop" | ||||
|             "skip:" | ||||
|                 "jmp y-- countloop" | ||||
|         ); | ||||
| 
 | ||||
|         pio.load_program(&prg.program); | ||||
|         let pin = pio.make_pio_pin(pin); | ||||
|         sm.set_pins(Level::High, &[&pin]); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[&pin]); | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
| 
 | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     pub fn start(&mut self) { | ||||
|         self.sm.set_enable(true); | ||||
|     } | ||||
| 
 | ||||
|     pub fn stop(&mut self) { | ||||
|         self.sm.set_enable(false); | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_period(&mut self, duration: Duration) { | ||||
|         let is_enabled = self.sm.is_enabled(); | ||||
|         while !self.sm.tx().empty() {} // Make sure that the queue is empty
 | ||||
|         self.sm.set_enable(false); | ||||
|         self.sm.tx().push(to_pio_cycles(duration)); | ||||
|         unsafe { | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::PULL { | ||||
|                     if_empty: false, | ||||
|                     block: false, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::OUT { | ||||
|                     destination: ::pio::OutDestination::ISR, | ||||
|                     bit_count: 32, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|         }; | ||||
|         if is_enabled { | ||||
|             self.sm.set_enable(true) // Enable if previously enabled
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_level(&mut self, level: u32) { | ||||
|         self.sm.tx().push(level); | ||||
|     } | ||||
| 
 | ||||
|     pub fn write(&mut self, duration: Duration) { | ||||
|         self.set_level(to_pio_cycles(duration)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct ServoBuilder<'d, T: Instance, const SM: usize> { | ||||
|     pwm: PwmPio<'d, T, SM>, | ||||
|     pwm: PioPwm<'d, T, SM>, | ||||
|     period: Duration, | ||||
|     min_pulse_width: Duration, | ||||
|     max_pulse_width: Duration, | ||||
| @ -111,7 +30,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { | ||||
|     pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { | ||||
|     pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { | ||||
|         Self { | ||||
|             pwm, | ||||
|             period: Duration::from_micros(REFRESH_INTERVAL), | ||||
| @ -153,7 +72,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { | ||||
| } | ||||
| 
 | ||||
| pub struct Servo<'d, T: Instance, const SM: usize> { | ||||
|     pwm: PwmPio<'d, T, SM>, | ||||
|     pwm: PioPwm<'d, T, SM>, | ||||
|     min_pulse_width: Duration, | ||||
|     max_pulse_width: Duration, | ||||
|     max_degree_rotation: u64, | ||||
| @ -190,7 +109,8 @@ async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); | ||||
|     let prg = PioPwmProgram::new(&mut common); | ||||
|     let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); | ||||
|     let mut servo = ServoBuilder::new(pwm_pio) | ||||
|         .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo
 | ||||
|         .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment.
 | ||||
|  | ||||
| @ -3,143 +3,20 @@ | ||||
| 
 | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| use core::mem::{self, MaybeUninit}; | ||||
| 
 | ||||
| use defmt::info; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; | ||||
| use embassy_time::{with_timeout, Duration, Timer}; | ||||
| use fixed::traits::ToFixed; | ||||
| use fixed::types::extra::U8; | ||||
| use fixed::FixedU32; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub struct PioStepper<'d, T: Instance, const SM: usize> { | ||||
|     irq: Irq<'d, T, SM>, | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, T>, | ||||
|         mut sm: StateMachine<'d, T, SM>, | ||||
|         irq: Irq<'d, T, SM>, | ||||
|         pin0: impl PioPin, | ||||
|         pin1: impl PioPin, | ||||
|         pin2: impl PioPin, | ||||
|         pin3: impl PioPin, | ||||
|     ) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             "pull block", | ||||
|             "mov x, osr", | ||||
|             "pull block", | ||||
|             "mov y, osr", | ||||
|             "jmp !x end", | ||||
|             "loop:", | ||||
|             "jmp !osre step", | ||||
|             "mov osr, y", | ||||
|             "step:", | ||||
|             "out pins, 4 [31]" | ||||
|             "jmp x-- loop", | ||||
|             "end:", | ||||
|             "irq 0 rel" | ||||
|         ); | ||||
|         let pin0 = pio.make_pio_pin(pin0); | ||||
|         let pin1 = pio.make_pio_pin(pin1); | ||||
|         let pin2 = pio.make_pio_pin(pin2); | ||||
|         let pin3 = pio.make_pio_pin(pin3); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); | ||||
|         cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[]); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { irq, sm } | ||||
|     } | ||||
| 
 | ||||
|     // Set pulse frequency
 | ||||
|     pub fn set_frequency(&mut self, freq: u32) { | ||||
|         let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed(); | ||||
|         assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); | ||||
|         assert!(clock_divider >= 1, "clkdiv must be >= 1"); | ||||
|         self.sm.set_clock_divider(clock_divider); | ||||
|         self.sm.clkdiv_restart(); | ||||
|     } | ||||
| 
 | ||||
|     // Full step, one phase
 | ||||
|     pub async fn step(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Full step, two phase
 | ||||
|     pub async fn step2(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Half step
 | ||||
|     pub async fn step_half(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn run(&mut self, steps: i32, pattern: u32) { | ||||
|         self.sm.tx().wait_push(steps as u32).await; | ||||
|         self.sm.tx().wait_push(pattern).await; | ||||
|         let drop = OnDrop::new(|| { | ||||
|             self.sm.clear_fifos(); | ||||
|             unsafe { | ||||
|                 self.sm.exec_instr( | ||||
|                     pio::InstructionOperands::JMP { | ||||
|                         address: 0, | ||||
|                         condition: pio::JmpCondition::Always, | ||||
|                     } | ||||
|                     .encode(), | ||||
|                 ); | ||||
|             } | ||||
|         }); | ||||
|         self.irq.wait().await; | ||||
|         drop.defuse(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct OnDrop<F: FnOnce()> { | ||||
|     f: MaybeUninit<F>, | ||||
| } | ||||
| 
 | ||||
| impl<F: FnOnce()> OnDrop<F> { | ||||
|     pub fn new(f: F) -> Self { | ||||
|         Self { f: MaybeUninit::new(f) } | ||||
|     } | ||||
| 
 | ||||
|     pub fn defuse(self) { | ||||
|         mem::forget(self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<F: FnOnce()> Drop for OnDrop<F> { | ||||
|     fn drop(&mut self) { | ||||
|         unsafe { self.f.as_ptr().read()() } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
| @ -147,14 +24,18 @@ async fn main(_spawner: Spawner) { | ||||
|         mut common, irq0, sm0, .. | ||||
|     } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); | ||||
|     let prg = PioStepperProgram::new(&mut common); | ||||
|     let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); | ||||
|     stepper.set_frequency(120); | ||||
|     loop { | ||||
|         info!("CW full steps"); | ||||
|         stepper.step(1000).await; | ||||
| 
 | ||||
|         info!("CCW full steps, drop after 1 sec"); | ||||
|         if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)).await { | ||||
|         if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) | ||||
|             .await | ||||
|             .is_err() | ||||
|         { | ||||
|             info!("Time's up!"); | ||||
|             Timer::after(Duration::from_secs(1)).await; | ||||
|         } | ||||
|  | ||||
| @ -15,7 +15,8 @@ use embassy_executor::Spawner; | ||||
| use embassy_futures::join::{join, join3}; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::{PIO0, USB}; | ||||
| use embassy_rp::pio::InterruptHandler as PioInterruptHandler; | ||||
| use embassy_rp::pio; | ||||
| use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; | ||||
| use embassy_rp::usb::{Driver, Instance, InterruptHandler}; | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embassy_sync::pipe::Pipe; | ||||
| @ -25,13 +26,11 @@ use embassy_usb::{Builder, Config}; | ||||
| use embedded_io_async::{Read, Write}; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| use crate::uart::PioUart; | ||||
| use crate::uart_rx::PioUartRx; | ||||
| use crate::uart_tx::PioUartTx; | ||||
| //use crate::uart::PioUart;
 | ||||
| 
 | ||||
| bind_interrupts!(struct Irqs { | ||||
|     USBCTRL_IRQ => InterruptHandler<USB>; | ||||
|     PIO0_IRQ_0 => PioInterruptHandler<PIO0>; | ||||
|     PIO0_IRQ_0 => pio::InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| @ -85,8 +84,15 @@ async fn main(_spawner: Spawner) { | ||||
|     let usb_fut = usb.run(); | ||||
| 
 | ||||
|     // PIO UART setup
 | ||||
|     let uart = PioUart::new(9600, p.PIO0, p.PIN_4, p.PIN_5); | ||||
|     let (mut uart_tx, mut uart_rx) = uart.split(); | ||||
|     let pio::Pio { | ||||
|         mut common, sm0, sm1, .. | ||||
|     } = pio::Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let tx_program = PioUartTxProgram::new(&mut common); | ||||
|     let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); | ||||
| 
 | ||||
|     let rx_program = PioUartRxProgram::new(&mut common); | ||||
|     let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); | ||||
| 
 | ||||
|     // Pipe setup
 | ||||
|     let mut usb_pipe: Pipe<NoopRawMutex, 20> = Pipe::new(); | ||||
| @ -163,8 +169,8 @@ async fn usb_write<'d, T: Instance + 'd>( | ||||
| } | ||||
| 
 | ||||
| /// Read from the UART and write it to the USB TX pipe
 | ||||
| async fn uart_read( | ||||
|     uart_rx: &mut PioUartRx<'_>, | ||||
| async fn uart_read<PIO: pio::Instance, const SM: usize>( | ||||
|     uart_rx: &mut PioUartRx<'_, PIO, SM>, | ||||
|     usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, | ||||
| ) -> ! { | ||||
|     let mut buf = [0; 64]; | ||||
| @ -180,8 +186,8 @@ async fn uart_read( | ||||
| } | ||||
| 
 | ||||
| /// Read from the UART TX pipe and write it to the UART
 | ||||
| async fn uart_write( | ||||
|     uart_tx: &mut PioUartTx<'_>, | ||||
| async fn uart_write<PIO: pio::Instance, const SM: usize>( | ||||
|     uart_tx: &mut PioUartTx<'_, PIO, SM>, | ||||
|     uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, | ||||
| ) -> ! { | ||||
|     let mut buf = [0; 64]; | ||||
| @ -192,197 +198,3 @@ async fn uart_write( | ||||
|         let _ = uart_tx.write(&data).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| mod uart { | ||||
|     use embassy_rp::peripherals::PIO0; | ||||
|     use embassy_rp::pio::{Pio, PioPin}; | ||||
|     use embassy_rp::Peripheral; | ||||
| 
 | ||||
|     use crate::uart_rx::PioUartRx; | ||||
|     use crate::uart_tx::PioUartTx; | ||||
|     use crate::Irqs; | ||||
| 
 | ||||
|     pub struct PioUart<'a> { | ||||
|         tx: PioUartTx<'a>, | ||||
|         rx: PioUartRx<'a>, | ||||
|     } | ||||
| 
 | ||||
|     impl<'a> PioUart<'a> { | ||||
|         pub fn new( | ||||
|             baud: u64, | ||||
|             pio: impl Peripheral<P = PIO0> + 'a, | ||||
|             tx_pin: impl PioPin, | ||||
|             rx_pin: impl PioPin, | ||||
|         ) -> PioUart<'a> { | ||||
|             let Pio { | ||||
|                 mut common, sm0, sm1, .. | ||||
|             } = Pio::new(pio, Irqs); | ||||
| 
 | ||||
|             let tx = PioUartTx::new(&mut common, sm0, tx_pin, baud); | ||||
|             let rx = PioUartRx::new(&mut common, sm1, rx_pin, baud); | ||||
| 
 | ||||
|             PioUart { tx, rx } | ||||
|         } | ||||
| 
 | ||||
|         pub fn split(self) -> (PioUartTx<'a>, PioUartRx<'a>) { | ||||
|             (self.tx, self.rx) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| mod uart_tx { | ||||
|     use core::convert::Infallible; | ||||
| 
 | ||||
|     use embassy_rp::gpio::Level; | ||||
|     use embassy_rp::peripherals::PIO0; | ||||
|     use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; | ||||
|     use embedded_io_async::{ErrorType, Write}; | ||||
|     use fixed::traits::ToFixed; | ||||
|     use fixed_macro::types::U56F8; | ||||
| 
 | ||||
|     pub struct PioUartTx<'a> { | ||||
|         sm_tx: StateMachine<'a, PIO0, 0>, | ||||
|     } | ||||
| 
 | ||||
|     impl<'a> PioUartTx<'a> { | ||||
|         pub fn new( | ||||
|             common: &mut Common<'a, PIO0>, | ||||
|             mut sm_tx: StateMachine<'a, PIO0, 0>, | ||||
|             tx_pin: impl PioPin, | ||||
|             baud: u64, | ||||
|         ) -> Self { | ||||
|             let prg = pio_proc::pio_asm!( | ||||
|                 r#" | ||||
|                 .side_set 1 opt | ||||
| 
 | ||||
|                 ; An 8n1 UART transmit program. | ||||
|                 ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. | ||||
| 
 | ||||
|                     pull       side 1 [7]  ; Assert stop bit, or stall with line in idle state | ||||
|                     set x, 7   side 0 [7]  ; Preload bit counter, assert start bit for 8 clocks | ||||
|                 bitloop:                   ; This loop will run 8 times (8n1 UART) | ||||
|                     out pins, 1            ; Shift 1 bit from OSR to the first OUT pin | ||||
|                     jmp x-- bitloop   [6]  ; Each loop iteration is 8 cycles. | ||||
|             "#
 | ||||
|             ); | ||||
|             let tx_pin = common.make_pio_pin(tx_pin); | ||||
|             sm_tx.set_pins(Level::High, &[&tx_pin]); | ||||
|             sm_tx.set_pin_dirs(Direction::Out, &[&tx_pin]); | ||||
| 
 | ||||
|             let mut cfg = Config::default(); | ||||
| 
 | ||||
|             cfg.set_out_pins(&[&tx_pin]); | ||||
|             cfg.use_program(&common.load_program(&prg.program), &[&tx_pin]); | ||||
|             cfg.shift_out.auto_fill = false; | ||||
|             cfg.shift_out.direction = ShiftDirection::Right; | ||||
|             cfg.fifo_join = FifoJoin::TxOnly; | ||||
|             cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed(); | ||||
|             sm_tx.set_config(&cfg); | ||||
|             sm_tx.set_enable(true); | ||||
| 
 | ||||
|             Self { sm_tx } | ||||
|         } | ||||
| 
 | ||||
|         pub async fn write_u8(&mut self, data: u8) { | ||||
|             self.sm_tx.tx().wait_push(data as u32).await; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     impl ErrorType for PioUartTx<'_> { | ||||
|         type Error = Infallible; | ||||
|     } | ||||
| 
 | ||||
|     impl Write for PioUartTx<'_> { | ||||
|         async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> { | ||||
|             for byte in buf { | ||||
|                 self.write_u8(*byte).await; | ||||
|             } | ||||
|             Ok(buf.len()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| mod uart_rx { | ||||
|     use core::convert::Infallible; | ||||
| 
 | ||||
|     use embassy_rp::gpio::Level; | ||||
|     use embassy_rp::peripherals::PIO0; | ||||
|     use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; | ||||
|     use embedded_io_async::{ErrorType, Read}; | ||||
|     use fixed::traits::ToFixed; | ||||
|     use fixed_macro::types::U56F8; | ||||
| 
 | ||||
|     pub struct PioUartRx<'a> { | ||||
|         sm_rx: StateMachine<'a, PIO0, 1>, | ||||
|     } | ||||
| 
 | ||||
|     impl<'a> PioUartRx<'a> { | ||||
|         pub fn new( | ||||
|             common: &mut Common<'a, PIO0>, | ||||
|             mut sm_rx: StateMachine<'a, PIO0, 1>, | ||||
|             rx_pin: impl PioPin, | ||||
|             baud: u64, | ||||
|         ) -> Self { | ||||
|             let prg = pio_proc::pio_asm!( | ||||
|                 r#" | ||||
|                 ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and | ||||
|                 ; break conditions more gracefully. | ||||
|                 ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. | ||||
| 
 | ||||
|                 start: | ||||
|                     wait 0 pin 0        ; Stall until start bit is asserted | ||||
|                     set x, 7    [10]    ; Preload bit counter, then delay until halfway through | ||||
|                 rx_bitloop:             ; the first data bit (12 cycles incl wait, set). | ||||
|                     in pins, 1          ; Shift data bit into ISR | ||||
|                     jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles | ||||
|                     jmp pin good_rx_stop   ; Check stop bit (should be high) | ||||
| 
 | ||||
|                     irq 4 rel           ; Either a framing error or a break. Set a sticky flag, | ||||
|                     wait 1 pin 0        ; and wait for line to return to idle state. | ||||
|                     jmp start           ; Don't push data if we didn't see good framing. | ||||
| 
 | ||||
|                 good_rx_stop:           ; No delay before returning to start; a little slack is | ||||
|                     in null 24 | ||||
|                     push                ; important in case the TX clock is slightly too fast. | ||||
|             "#
 | ||||
|             ); | ||||
|             let mut cfg = Config::default(); | ||||
|             cfg.use_program(&common.load_program(&prg.program), &[]); | ||||
| 
 | ||||
|             let rx_pin = common.make_pio_pin(rx_pin); | ||||
|             sm_rx.set_pins(Level::High, &[&rx_pin]); | ||||
|             cfg.set_in_pins(&[&rx_pin]); | ||||
|             cfg.set_jmp_pin(&rx_pin); | ||||
|             sm_rx.set_pin_dirs(Direction::In, &[&rx_pin]); | ||||
| 
 | ||||
|             cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed(); | ||||
|             cfg.shift_in.auto_fill = false; | ||||
|             cfg.shift_in.direction = ShiftDirection::Right; | ||||
|             cfg.shift_in.threshold = 32; | ||||
|             cfg.fifo_join = FifoJoin::RxOnly; | ||||
|             sm_rx.set_config(&cfg); | ||||
|             sm_rx.set_enable(true); | ||||
| 
 | ||||
|             Self { sm_rx } | ||||
|         } | ||||
| 
 | ||||
|         pub async fn read_u8(&mut self) -> u8 { | ||||
|             self.sm_rx.rx().wait_pull().await as u8 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     impl ErrorType for PioUartRx<'_> { | ||||
|         type Error = Infallible; | ||||
|     } | ||||
| 
 | ||||
|     impl Read for PioUartRx<'_> { | ||||
|         async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> { | ||||
|             let mut i = 0; | ||||
|             while i < buf.len() { | ||||
|                 buf[i] = self.read_u8().await; | ||||
|                 i += 1; | ||||
|             } | ||||
|             Ok(i) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,15 +6,11 @@ | ||||
| 
 | ||||
| use defmt::*; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::dma::{AnyChannel, Channel}; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{ | ||||
|     Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, | ||||
| }; | ||||
| use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; | ||||
| use embassy_time::{Duration, Ticker, Timer}; | ||||
| use fixed::types::U24F8; | ||||
| use fixed_macro::fixed; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; | ||||
| use embassy_time::{Duration, Ticker}; | ||||
| use smart_leds::RGB8; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| @ -22,96 +18,6 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { | ||||
|     dma: PeripheralRef<'d, AnyChannel>, | ||||
|     sm: StateMachine<'d, P, S>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, P>, | ||||
|         mut sm: StateMachine<'d, P, S>, | ||||
|         dma: impl Peripheral<P = impl Channel> + 'd, | ||||
|         pin: impl PioPin, | ||||
|     ) -> Self { | ||||
|         into_ref!(dma); | ||||
| 
 | ||||
|         // Setup sm0
 | ||||
| 
 | ||||
|         // prepare the PIO program
 | ||||
|         let side_set = pio::SideSet::new(false, 1, false); | ||||
|         let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); | ||||
| 
 | ||||
|         const T1: u8 = 2; // start bit
 | ||||
|         const T2: u8 = 5; // data bit
 | ||||
|         const T3: u8 = 3; // stop bit
 | ||||
|         const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; | ||||
| 
 | ||||
|         let mut wrap_target = a.label(); | ||||
|         let mut wrap_source = a.label(); | ||||
|         let mut do_zero = a.label(); | ||||
|         a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); | ||||
|         a.bind(&mut wrap_target); | ||||
|         // Do stop bit
 | ||||
|         a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); | ||||
|         // Do start bit
 | ||||
|         a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); | ||||
|         // Do data bit = 1
 | ||||
|         a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); | ||||
|         a.bind(&mut do_zero); | ||||
|         // Do data bit = 0
 | ||||
|         a.nop_with_delay_and_side_set(T2 - 1, 0); | ||||
|         a.bind(&mut wrap_source); | ||||
| 
 | ||||
|         let prg = a.assemble_with_wrap(wrap_source, wrap_target); | ||||
|         let mut cfg = Config::default(); | ||||
| 
 | ||||
|         // Pin config
 | ||||
|         let out_pin = pio.make_pio_pin(pin); | ||||
|         cfg.set_out_pins(&[&out_pin]); | ||||
|         cfg.set_set_pins(&[&out_pin]); | ||||
| 
 | ||||
|         cfg.use_program(&pio.load_program(&prg), &[&out_pin]); | ||||
| 
 | ||||
|         // Clock config, measured in kHz to avoid overflows
 | ||||
|         // TODO CLOCK_FREQ should come from embassy_rp
 | ||||
|         let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); | ||||
|         let ws2812_freq = fixed!(800: U24F8); | ||||
|         let bit_freq = ws2812_freq * CYCLES_PER_BIT; | ||||
|         cfg.clock_divider = clock_freq / bit_freq; | ||||
| 
 | ||||
|         // FIFO config
 | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             threshold: 24, | ||||
|             direction: ShiftDirection::Left, | ||||
|         }; | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
| 
 | ||||
|         Self { | ||||
|             dma: dma.map_into(), | ||||
|             sm, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub async fn write(&mut self, colors: &[RGB8; N]) { | ||||
|         // Precompute the word bytes from the colors
 | ||||
|         let mut words = [0u32; N]; | ||||
|         for i in 0..N { | ||||
|             let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); | ||||
|             words[i] = word; | ||||
|         } | ||||
| 
 | ||||
|         // DMA transfer
 | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), &words).await; | ||||
| 
 | ||||
|         Timer::after_micros(55).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Input a value 0 to 255 to get a color value
 | ||||
| /// The colours are a transition r - g - b - back to r.
 | ||||
| fn wheel(mut wheel_pos: u8) -> RGB8 { | ||||
| @ -142,7 +48,8 @@ async fn main(_spawner: Spawner) { | ||||
|     // Common neopixel pins:
 | ||||
|     // Thing plus: 8
 | ||||
|     // Adafruit Feather: 16;  Adafruit Feather+RFM95: 4
 | ||||
|     let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); | ||||
|     let program = PioWs2812Program::new(&mut common); | ||||
|     let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); | ||||
| 
 | ||||
|     // Loop forever making RGB values and pushing them out to the WS2812.
 | ||||
|     let mut ticker = Ticker::every(Duration::from_millis(10)); | ||||
|  | ||||
| @ -7,14 +7,12 @@ | ||||
| use core::fmt::Write; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::dma::{AnyChannel, Channel}; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{ | ||||
|     Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, | ||||
| }; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; | ||||
| use embassy_rp::pwm::{self, Pwm}; | ||||
| use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; | ||||
| use embassy_time::{Instant, Timer}; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| @ -48,8 +46,27 @@ async fn main(_spawner: Spawner) { | ||||
|         c | ||||
|     }); | ||||
| 
 | ||||
|     let mut hd = HD44780::new( | ||||
|         p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, | ||||
|     let Pio { | ||||
|         mut common, sm0, irq0, .. | ||||
|     } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let word_prg = PioHD44780CommandWordProgram::new(&mut common); | ||||
|     let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); | ||||
| 
 | ||||
|     let mut hd = PioHD44780::new( | ||||
|         &mut common, | ||||
|         sm0, | ||||
|         irq0, | ||||
|         p.DMA_CH3, | ||||
|         p.PIN_0, | ||||
|         p.PIN_1, | ||||
|         p.PIN_2, | ||||
|         p.PIN_3, | ||||
|         p.PIN_4, | ||||
|         p.PIN_5, | ||||
|         p.PIN_6, | ||||
|         &word_prg, | ||||
|         &seq_prg, | ||||
|     ) | ||||
|     .await; | ||||
| 
 | ||||
| @ -73,173 +90,3 @@ async fn main(_spawner: Spawner) { | ||||
|         Timer::after_secs(1).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct HD44780<'l> { | ||||
|     dma: PeripheralRef<'l, AnyChannel>, | ||||
|     sm: StateMachine<'l, PIO0, 0>, | ||||
| 
 | ||||
|     buf: [u8; 40], | ||||
| } | ||||
| 
 | ||||
| impl<'l> HD44780<'l> { | ||||
|     pub async fn new( | ||||
|         pio: impl Peripheral<P = PIO0> + 'l, | ||||
|         irq: Irqs, | ||||
|         dma: impl Peripheral<P = impl Channel> + 'l, | ||||
|         rs: impl PioPin, | ||||
|         rw: impl PioPin, | ||||
|         e: impl PioPin, | ||||
|         db4: impl PioPin, | ||||
|         db5: impl PioPin, | ||||
|         db6: impl PioPin, | ||||
|         db7: impl PioPin, | ||||
|     ) -> HD44780<'l> { | ||||
|         into_ref!(dma); | ||||
| 
 | ||||
|         let Pio { | ||||
|             mut common, | ||||
|             mut irq0, | ||||
|             mut sm0, | ||||
|             .. | ||||
|         } = Pio::new(pio, irq); | ||||
| 
 | ||||
|         // takes command words (<wait:24> <command:4> <0:4>)
 | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .side_set 1 opt | ||||
|                 .origin 20 | ||||
| 
 | ||||
|                 loop: | ||||
|                     out x,     24 | ||||
|                 delay: | ||||
|                     jmp x--,   delay | ||||
|                     out pins,  4     side 1 | ||||
|                     out null,  4     side 0 | ||||
|                     jmp !osre, loop | ||||
|                 irq 0 | ||||
|             "#,
 | ||||
|         ); | ||||
| 
 | ||||
|         let rs = common.make_pio_pin(rs); | ||||
|         let rw = common.make_pio_pin(rw); | ||||
|         let e = common.make_pio_pin(e); | ||||
|         let db4 = common.make_pio_pin(db4); | ||||
|         let db5 = common.make_pio_pin(db5); | ||||
|         let db6 = common.make_pio_pin(db6); | ||||
|         let db7 = common.make_pio_pin(db7); | ||||
| 
 | ||||
|         sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&common.load_program(&prg.program), &[&e]); | ||||
|         cfg.clock_divider = 125u8.into(); | ||||
|         cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             direction: ShiftDirection::Left, | ||||
|             threshold: 32, | ||||
|         }; | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         sm0.set_config(&cfg); | ||||
| 
 | ||||
|         sm0.set_enable(true); | ||||
|         // init to 8 bit thrice
 | ||||
|         sm0.tx().push((50000 << 8) | 0x30); | ||||
|         sm0.tx().push((5000 << 8) | 0x30); | ||||
|         sm0.tx().push((200 << 8) | 0x30); | ||||
|         // init 4 bit
 | ||||
|         sm0.tx().push((200 << 8) | 0x20); | ||||
|         // set font and lines
 | ||||
|         sm0.tx().push((50 << 8) | 0x20); | ||||
|         sm0.tx().push(0b1100_0000); | ||||
| 
 | ||||
|         irq0.wait().await; | ||||
|         sm0.set_enable(false); | ||||
| 
 | ||||
|         // takes command sequences (<rs:1> <count:7>, data...)
 | ||||
|         // many side sets are only there to free up a delay bit!
 | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             r#" | ||||
|                 .origin 27 | ||||
|                 .side_set 1 | ||||
| 
 | ||||
|                 .wrap_target | ||||
|                 pull     side 0 | ||||
|                 out  x 1 side 0 ; !rs | ||||
|                 out  y 7 side 0 ; #data - 1 | ||||
| 
 | ||||
|                 ; rs/rw to e: >= 60ns | ||||
|                 ; e high time: >= 500ns | ||||
|                 ; e low time: >= 500ns | ||||
|                 ; read data valid after e falling: ~5ns | ||||
|                 ; write data hold after e falling: ~10ns | ||||
| 
 | ||||
|                 loop: | ||||
|                     pull                 side 0 | ||||
|                     jmp  !x       data   side 0 | ||||
|                 command: | ||||
|                     set  pins     0b00   side 0 | ||||
|                     jmp  shift           side 0 | ||||
|                 data: | ||||
|                     set  pins     0b01   side 0 | ||||
|                 shift: | ||||
|                     out  pins     4      side 1 [9] | ||||
|                     nop                  side 0 [9] | ||||
|                     out  pins     4      side 1 [9] | ||||
|                     mov  osr      null   side 0 [7] | ||||
|                     out  pindirs  4      side 0 | ||||
|                     set  pins     0b10   side 0 | ||||
|                 busy: | ||||
|                     nop                  side 1 [9] | ||||
|                     jmp  pin      more   side 0 [9] | ||||
|                     mov  osr      ~osr   side 1 [9] | ||||
|                     nop                  side 0 [4] | ||||
|                     out  pindirs  4      side 0 | ||||
|                     jmp  y--      loop   side 0 | ||||
|                 .wrap | ||||
|                 more: | ||||
|                     nop                  side 1 [9] | ||||
|                     jmp busy             side 0 [9] | ||||
|             "#
 | ||||
|         ); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&common.load_program(&prg.program), &[&e]); | ||||
|         cfg.clock_divider = 8u8.into(); // ~64ns/insn
 | ||||
|         cfg.set_jmp_pin(&db7); | ||||
|         cfg.set_set_pins(&[&rs, &rw]); | ||||
|         cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); | ||||
|         cfg.shift_out.direction = ShiftDirection::Left; | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         sm0.set_config(&cfg); | ||||
| 
 | ||||
|         sm0.set_enable(true); | ||||
| 
 | ||||
|         // display on and cursor on and blinking, reset display
 | ||||
|         sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; | ||||
| 
 | ||||
|         Self { | ||||
|             dma: dma.map_into(), | ||||
|             sm: sm0, | ||||
|             buf: [0x20; 40], | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub async fn add_line(&mut self, s: &[u8]) { | ||||
|         // move cursor to 0:0, prepare 16 characters
 | ||||
|         self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); | ||||
|         // move line 2 up
 | ||||
|         self.buf.copy_within(22..38, 3); | ||||
|         // move cursor to 1:0, prepare 16 characters
 | ||||
|         self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); | ||||
|         // file line 2 with spaces
 | ||||
|         self.buf[22..38].fill(0x20); | ||||
|         // copy input line
 | ||||
|         let len = s.len().min(16); | ||||
|         self.buf[22..22 + len].copy_from_slice(&s[0..len]); | ||||
|         // set cursor to 1:15
 | ||||
|         self.buf[38..].copy_from_slice(&[0x80, 0xcf]); | ||||
| 
 | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,11 +13,11 @@ | ||||
| use core::mem; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; | ||||
| use embassy_rp::{bind_interrupts, Peripheral}; | ||||
| use fixed::traits::ToFixed; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; | ||||
| use static_cell::StaticCell; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| @ -30,61 +30,32 @@ bind_interrupts!(struct Irqs { | ||||
| }); | ||||
| 
 | ||||
| const SAMPLE_RATE: u32 = 48_000; | ||||
| const BIT_DEPTH: u32 = 16; | ||||
| const CHANNELS: u32 = 2; | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let mut p = embassy_rp::init(Default::default()); | ||||
| 
 | ||||
|     // Setup pio state machine for i2s output
 | ||||
|     let mut pio = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     #[rustfmt::skip] | ||||
|     let pio_program = pio_proc::pio_asm!( | ||||
|         ".side_set 2", | ||||
|         "    set x, 14          side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
 | ||||
|         "left_data:", | ||||
|         "    out pins, 1        side 0b00", | ||||
|         "    jmp x-- left_data  side 0b01", | ||||
|         "    out pins 1         side 0b10", | ||||
|         "    set x, 14          side 0b11", | ||||
|         "right_data:", | ||||
|         "    out pins 1         side 0b10", | ||||
|         "    jmp x-- right_data side 0b11", | ||||
|         "    out pins 1         side 0b00", | ||||
|     ); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let bit_clock_pin = p.PIN_18; | ||||
|     let left_right_clock_pin = p.PIN_19; | ||||
|     let data_pin = p.PIN_20; | ||||
| 
 | ||||
|     let data_pin = pio.common.make_pio_pin(data_pin); | ||||
|     let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); | ||||
|     let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); | ||||
| 
 | ||||
|     let cfg = { | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program( | ||||
|             &pio.common.load_program(&pio_program.program), | ||||
|             &[&bit_clock_pin, &left_right_clock_pin], | ||||
|         ); | ||||
|         cfg.set_out_pins(&[&data_pin]); | ||||
|         const BIT_DEPTH: u32 = 16; | ||||
|         const CHANNELS: u32 = 2; | ||||
|         let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; | ||||
|         cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             threshold: 32, | ||||
|             direction: ShiftDirection::Left, | ||||
|             auto_fill: true, | ||||
|         }; | ||||
|         // join fifos to have twice the time to start the next dma transfer
 | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         cfg | ||||
|     }; | ||||
|     pio.sm0.set_config(&cfg); | ||||
|     pio.sm0.set_pin_dirs( | ||||
|         embassy_rp::pio::Direction::Out, | ||||
|         &[&data_pin, &left_right_clock_pin, &bit_clock_pin], | ||||
|     let program = PioI2sOutProgram::new(&mut common); | ||||
|     let mut i2s = PioI2sOut::new( | ||||
|         &mut common, | ||||
|         sm0, | ||||
|         p.DMA_CH0, | ||||
|         data_pin, | ||||
|         bit_clock_pin, | ||||
|         left_right_clock_pin, | ||||
|         SAMPLE_RATE, | ||||
|         BIT_DEPTH, | ||||
|         CHANNELS, | ||||
|         &program, | ||||
|     ); | ||||
| 
 | ||||
|     // create two audio buffers (back and front) which will take turns being
 | ||||
| @ -95,20 +66,16 @@ async fn main(_spawner: Spawner) { | ||||
|     let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); | ||||
| 
 | ||||
|     // start pio state machine
 | ||||
|     pio.sm0.set_enable(true); | ||||
|     let tx = pio.sm0.tx(); | ||||
|     let mut dma_ref = p.DMA_CH0.into_ref(); | ||||
| 
 | ||||
|     let mut fade_value: i32 = 0; | ||||
|     let mut phase: i32 = 0; | ||||
| 
 | ||||
|     loop { | ||||
|         // trigger transfer of front buffer data to the pio fifo
 | ||||
|         // but don't await the returned future, yet
 | ||||
|         let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); | ||||
|         let dma_future = i2s.write(front_buffer); | ||||
| 
 | ||||
|         // fade in audio
 | ||||
|         let fade_target = i32::MAX; | ||||
|         // fade in audio when bootsel is pressed
 | ||||
|         let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; | ||||
| 
 | ||||
|         // fill back buffer with fresh audio samples before awaiting the dma future
 | ||||
|         for s in back_buffer.iter_mut() { | ||||
|  | ||||
							
								
								
									
										88
									
								
								examples/rp23/src/bin/pio_onewire.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								examples/rp23/src/bin/pio_onewire.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | ||||
| //! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor.
 | ||||
| 
 | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| use defmt::*; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{self, InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; | ||||
| use embassy_time::Timer; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| #[link_section = ".start_block"] | ||||
| #[used] | ||||
| pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); | ||||
| 
 | ||||
| bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let mut pio = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let prg = PioOneWireProgram::new(&mut pio.common); | ||||
|     let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); | ||||
| 
 | ||||
|     let mut sensor = Ds18b20::new(onewire); | ||||
| 
 | ||||
|     loop { | ||||
|         sensor.start().await; // Start a new measurement
 | ||||
|         Timer::after_secs(1).await; // Allow 1s for the measurement to finish
 | ||||
|         match sensor.temperature().await { | ||||
|             Ok(temp) => info!("temp = {:?} deg C", temp), | ||||
|             _ => error!("sensor error"), | ||||
|         } | ||||
|         Timer::after_secs(1).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// DS18B20 temperature sensor driver
 | ||||
| pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { | ||||
|     wire: PioOneWire<'d, PIO, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { | ||||
|     pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { | ||||
|         Self { wire } | ||||
|     } | ||||
| 
 | ||||
|     /// Calculate CRC8 of the data
 | ||||
|     fn crc8(data: &[u8]) -> u8 { | ||||
|         let mut temp; | ||||
|         let mut data_byte; | ||||
|         let mut crc = 0; | ||||
|         for b in data { | ||||
|             data_byte = *b; | ||||
|             for _ in 0..8 { | ||||
|                 temp = (crc ^ data_byte) & 0x01; | ||||
|                 crc >>= 1; | ||||
|                 if temp != 0 { | ||||
|                     crc ^= 0x8C; | ||||
|                 } | ||||
|                 data_byte >>= 1; | ||||
|             } | ||||
|         } | ||||
|         crc | ||||
|     } | ||||
| 
 | ||||
|     /// Start a new measurement. Allow at least 1000ms before getting `temperature`.
 | ||||
|     pub async fn start(&mut self) { | ||||
|         self.wire.write_bytes(&[0xCC, 0x44]).await; | ||||
|     } | ||||
| 
 | ||||
|     /// Read the temperature. Ensure >1000ms has passed since `start` before calling this.
 | ||||
|     pub async fn temperature(&mut self) -> Result<f32, ()> { | ||||
|         self.wire.write_bytes(&[0xCC, 0xBE]).await; | ||||
|         let mut data = [0; 9]; | ||||
|         self.wire.read_bytes(&mut data).await; | ||||
|         match Self::crc8(&data) == 0 { | ||||
|             true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), | ||||
|             false => Err(()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -5,13 +5,12 @@ | ||||
| use core::time::Duration; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::gpio::Level; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; | ||||
| use embassy_rp::{bind_interrupts, clocks}; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; | ||||
| use embassy_time::Timer; | ||||
| use pio::InstructionOperands; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| #[link_section = ".start_block"] | ||||
| @ -24,93 +23,14 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub fn to_pio_cycles(duration: Duration) -> u32 { | ||||
|     (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
 | ||||
| } | ||||
| 
 | ||||
| pub struct PwmPio<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { | ||||
|     pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             ".side_set 1 opt" | ||||
|                 "pull noblock    side 0" | ||||
|                 "mov x, osr" | ||||
|                 "mov y, isr" | ||||
|             "countloop:" | ||||
|                 "jmp x!=y noset" | ||||
|                 "jmp skip        side 1" | ||||
|             "noset:" | ||||
|                 "nop" | ||||
|             "skip:" | ||||
|                 "jmp y-- countloop" | ||||
|         ); | ||||
| 
 | ||||
|         pio.load_program(&prg.program); | ||||
|         let pin = pio.make_pio_pin(pin); | ||||
|         sm.set_pins(Level::High, &[&pin]); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[&pin]); | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
| 
 | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     pub fn start(&mut self) { | ||||
|         self.sm.set_enable(true); | ||||
|     } | ||||
| 
 | ||||
|     pub fn stop(&mut self) { | ||||
|         self.sm.set_enable(false); | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_period(&mut self, duration: Duration) { | ||||
|         let is_enabled = self.sm.is_enabled(); | ||||
|         while !self.sm.tx().empty() {} // Make sure that the queue is empty
 | ||||
|         self.sm.set_enable(false); | ||||
|         self.sm.tx().push(to_pio_cycles(duration)); | ||||
|         unsafe { | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::PULL { | ||||
|                     if_empty: false, | ||||
|                     block: false, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::OUT { | ||||
|                     destination: ::pio::OutDestination::ISR, | ||||
|                     bit_count: 32, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|         }; | ||||
|         if is_enabled { | ||||
|             self.sm.set_enable(true) // Enable if previously enabled
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_level(&mut self, level: u32) { | ||||
|         self.sm.tx().push(level); | ||||
|     } | ||||
| 
 | ||||
|     pub fn write(&mut self, duration: Duration) { | ||||
|         self.set_level(to_pio_cycles(duration)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     // Note that PIN_25 is the led pin on the Pico
 | ||||
|     let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); | ||||
|     let prg = PioPwmProgram::new(&mut common); | ||||
|     let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); | ||||
|     pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); | ||||
|     pwm_pio.start(); | ||||
| 
 | ||||
|  | ||||
| @ -6,11 +6,12 @@ | ||||
| use defmt::info; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::gpio::Pull; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::{bind_interrupts, pio}; | ||||
| use fixed::traits::ToFixed; | ||||
| use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; | ||||
| use embassy_rp::{ | ||||
|     bind_interrupts, | ||||
|     pio::{InterruptHandler, Pio}, | ||||
|     pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}, | ||||
| }; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| #[link_section = ".start_block"] | ||||
| @ -21,59 +22,8 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub struct PioEncoder<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, T>, | ||||
|         mut sm: StateMachine<'d, T, SM>, | ||||
|         pin_a: impl PioPin, | ||||
|         pin_b: impl PioPin, | ||||
|     ) -> Self { | ||||
|         let mut pin_a = pio.make_pio_pin(pin_a); | ||||
|         let mut pin_b = pio.make_pio_pin(pin_b); | ||||
|         pin_a.set_pull(Pull::Up); | ||||
|         pin_b.set_pull(Pull::Up); | ||||
|         sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); | ||||
| 
 | ||||
|         let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.set_in_pins(&[&pin_a, &pin_b]); | ||||
|         cfg.fifo_join = FifoJoin::RxOnly; | ||||
|         cfg.shift_in.direction = ShiftDirection::Left; | ||||
|         cfg.clock_divider = 10_000.to_fixed(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[]); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     pub async fn read(&mut self) -> Direction { | ||||
|         loop { | ||||
|             match self.sm.rx().wait_pull().await { | ||||
|                 0 => return Direction::CounterClockwise, | ||||
|                 1 => return Direction::Clockwise, | ||||
|                 _ => {} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub enum Direction { | ||||
|     Clockwise, | ||||
|     CounterClockwise, | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); | ||||
| 
 | ||||
| #[embassy_executor::task] | ||||
| async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { | ||||
|     let mut count = 0; | ||||
|     loop { | ||||
|         info!("Count: {}", count); | ||||
| @ -83,3 +33,30 @@ async fn main(_spawner: Spawner) { | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::task] | ||||
| async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { | ||||
|     let mut count = 0; | ||||
|     loop { | ||||
|         info!("Count: {}", count); | ||||
|         count += match encoder.read().await { | ||||
|             Direction::Clockwise => 1, | ||||
|             Direction::CounterClockwise => -1, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { | ||||
|         mut common, sm0, sm1, .. | ||||
|     } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let prg = PioEncoderProgram::new(&mut common); | ||||
|     let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); | ||||
|     let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); | ||||
| 
 | ||||
|     spawner.must_spawn(encoder_0(encoder0)); | ||||
|     spawner.must_spawn(encoder_1(encoder1)); | ||||
| } | ||||
|  | ||||
| @ -5,13 +5,12 @@ | ||||
| use core::time::Duration; | ||||
| 
 | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::gpio::Level; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; | ||||
| use embassy_rp::{bind_interrupts, clocks}; | ||||
| use embassy_rp::pio::{Instance, InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; | ||||
| use embassy_time::Timer; | ||||
| use pio::InstructionOperands; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| #[link_section = ".start_block"] | ||||
| @ -27,88 +26,8 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub fn to_pio_cycles(duration: Duration) -> u32 { | ||||
|     (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
 | ||||
| } | ||||
| 
 | ||||
| pub struct PwmPio<'d, T: Instance, const SM: usize> { | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { | ||||
|     pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             ".side_set 1 opt" | ||||
|                 "pull noblock    side 0" | ||||
|                 "mov x, osr" | ||||
|                 "mov y, isr" | ||||
|             "countloop:" | ||||
|                 "jmp x!=y noset" | ||||
|                 "jmp skip        side 1" | ||||
|             "noset:" | ||||
|                 "nop" | ||||
|             "skip:" | ||||
|                 "jmp y-- countloop" | ||||
|         ); | ||||
| 
 | ||||
|         pio.load_program(&prg.program); | ||||
|         let pin = pio.make_pio_pin(pin); | ||||
|         sm.set_pins(Level::High, &[&pin]); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin]); | ||||
| 
 | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[&pin]); | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
| 
 | ||||
|         Self { sm } | ||||
|     } | ||||
| 
 | ||||
|     pub fn start(&mut self) { | ||||
|         self.sm.set_enable(true); | ||||
|     } | ||||
| 
 | ||||
|     pub fn stop(&mut self) { | ||||
|         self.sm.set_enable(false); | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_period(&mut self, duration: Duration) { | ||||
|         let is_enabled = self.sm.is_enabled(); | ||||
|         while !self.sm.tx().empty() {} // Make sure that the queue is empty
 | ||||
|         self.sm.set_enable(false); | ||||
|         self.sm.tx().push(to_pio_cycles(duration)); | ||||
|         unsafe { | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::PULL { | ||||
|                     if_empty: false, | ||||
|                     block: false, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|             self.sm.exec_instr( | ||||
|                 InstructionOperands::OUT { | ||||
|                     destination: ::pio::OutDestination::ISR, | ||||
|                     bit_count: 32, | ||||
|                 } | ||||
|                 .encode(), | ||||
|             ); | ||||
|         }; | ||||
|         if is_enabled { | ||||
|             self.sm.set_enable(true) // Enable if previously enabled
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_level(&mut self, level: u32) { | ||||
|         self.sm.tx().push(level); | ||||
|     } | ||||
| 
 | ||||
|     pub fn write(&mut self, duration: Duration) { | ||||
|         self.set_level(to_pio_cycles(duration)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct ServoBuilder<'d, T: Instance, const SM: usize> { | ||||
|     pwm: PwmPio<'d, T, SM>, | ||||
|     pwm: PioPwm<'d, T, SM>, | ||||
|     period: Duration, | ||||
|     min_pulse_width: Duration, | ||||
|     max_pulse_width: Duration, | ||||
| @ -116,7 +35,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { | ||||
|     pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { | ||||
|     pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { | ||||
|         Self { | ||||
|             pwm, | ||||
|             period: Duration::from_micros(REFRESH_INTERVAL), | ||||
| @ -158,7 +77,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { | ||||
| } | ||||
| 
 | ||||
| pub struct Servo<'d, T: Instance, const SM: usize> { | ||||
|     pwm: PwmPio<'d, T, SM>, | ||||
|     pwm: PioPwm<'d, T, SM>, | ||||
|     min_pulse_width: Duration, | ||||
|     max_pulse_width: Duration, | ||||
|     max_degree_rotation: u64, | ||||
| @ -195,7 +114,8 @@ async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
|     let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); | ||||
|     let prg = PioPwmProgram::new(&mut common); | ||||
|     let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); | ||||
|     let mut servo = ServoBuilder::new(pwm_pio) | ||||
|         .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo
 | ||||
|         .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment.
 | ||||
|  | ||||
| @ -3,18 +3,15 @@ | ||||
| 
 | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| use core::mem::{self, MaybeUninit}; | ||||
| 
 | ||||
| use defmt::info; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; | ||||
| use embassy_time::{with_timeout, Duration, Timer}; | ||||
| use fixed::traits::ToFixed; | ||||
| use fixed::types::extra::U8; | ||||
| use fixed::FixedU32; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| #[link_section = ".start_block"] | ||||
| @ -25,126 +22,6 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub struct PioStepper<'d, T: Instance, const SM: usize> { | ||||
|     irq: Irq<'d, T, SM>, | ||||
|     sm: StateMachine<'d, T, SM>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, T>, | ||||
|         mut sm: StateMachine<'d, T, SM>, | ||||
|         irq: Irq<'d, T, SM>, | ||||
|         pin0: impl PioPin, | ||||
|         pin1: impl PioPin, | ||||
|         pin2: impl PioPin, | ||||
|         pin3: impl PioPin, | ||||
|     ) -> Self { | ||||
|         let prg = pio_proc::pio_asm!( | ||||
|             "pull block", | ||||
|             "mov x, osr", | ||||
|             "pull block", | ||||
|             "mov y, osr", | ||||
|             "jmp !x end", | ||||
|             "loop:", | ||||
|             "jmp !osre step", | ||||
|             "mov osr, y", | ||||
|             "step:", | ||||
|             "out pins, 4 [31]" | ||||
|             "jmp x-- loop", | ||||
|             "end:", | ||||
|             "irq 0 rel" | ||||
|         ); | ||||
|         let pin0 = pio.make_pio_pin(pin0); | ||||
|         let pin1 = pio.make_pio_pin(pin1); | ||||
|         let pin2 = pio.make_pio_pin(pin2); | ||||
|         let pin3 = pio.make_pio_pin(pin3); | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); | ||||
|         let mut cfg = Config::default(); | ||||
|         cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); | ||||
|         cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); | ||||
|         cfg.use_program(&pio.load_program(&prg.program), &[]); | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
|         Self { irq, sm } | ||||
|     } | ||||
| 
 | ||||
|     // Set pulse frequency
 | ||||
|     pub fn set_frequency(&mut self, freq: u32) { | ||||
|         let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed(); | ||||
|         assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); | ||||
|         assert!(clock_divider >= 1, "clkdiv must be >= 1"); | ||||
|         self.sm.set_clock_divider(clock_divider); | ||||
|         self.sm.clkdiv_restart(); | ||||
|     } | ||||
| 
 | ||||
|     // Full step, one phase
 | ||||
|     pub async fn step(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Full step, two phase
 | ||||
|     pub async fn step2(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Half step
 | ||||
|     pub async fn step_half(&mut self, steps: i32) { | ||||
|         if steps > 0 { | ||||
|             self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await | ||||
|         } else { | ||||
|             self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn run(&mut self, steps: i32, pattern: u32) { | ||||
|         self.sm.tx().wait_push(steps as u32).await; | ||||
|         self.sm.tx().wait_push(pattern).await; | ||||
|         let drop = OnDrop::new(|| { | ||||
|             self.sm.clear_fifos(); | ||||
|             unsafe { | ||||
|                 self.sm.exec_instr( | ||||
|                     pio::InstructionOperands::JMP { | ||||
|                         address: 0, | ||||
|                         condition: pio::JmpCondition::Always, | ||||
|                     } | ||||
|                     .encode(), | ||||
|                 ); | ||||
|             } | ||||
|         }); | ||||
|         self.irq.wait().await; | ||||
|         drop.defuse(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct OnDrop<F: FnOnce()> { | ||||
|     f: MaybeUninit<F>, | ||||
| } | ||||
| 
 | ||||
| impl<F: FnOnce()> OnDrop<F> { | ||||
|     pub fn new(f: F) -> Self { | ||||
|         Self { f: MaybeUninit::new(f) } | ||||
|     } | ||||
| 
 | ||||
|     pub fn defuse(self) { | ||||
|         mem::forget(self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<F: FnOnce()> Drop for OnDrop<F> { | ||||
|     fn drop(&mut self) { | ||||
|         unsafe { self.f.as_ptr().read()() } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
| @ -152,14 +29,18 @@ async fn main(_spawner: Spawner) { | ||||
|         mut common, irq0, sm0, .. | ||||
|     } = Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); | ||||
|     let prg = PioStepperProgram::new(&mut common); | ||||
|     let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); | ||||
|     stepper.set_frequency(120); | ||||
|     loop { | ||||
|         info!("CW full steps"); | ||||
|         stepper.step(1000).await; | ||||
| 
 | ||||
|         info!("CCW full steps, drop after 1 sec"); | ||||
|         if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { | ||||
|         if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) | ||||
|             .await | ||||
|             .is_err() | ||||
|         { | ||||
|             info!("Time's up!"); | ||||
|             Timer::after(Duration::from_secs(1)).await; | ||||
|         } | ||||
|  | ||||
							
								
								
									
										203
									
								
								examples/rp23/src/bin/pio_uart.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								examples/rp23/src/bin/pio_uart.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,203 @@ | ||||
| //! This example shows how to use the PIO module in the RP2040 chip to implement a duplex UART.
 | ||||
| //! The PIO module is a very powerful peripheral that can be used to implement many different
 | ||||
| //! protocols. It is a very flexible state machine that can be programmed to do almost anything.
 | ||||
| //!
 | ||||
| //! This example opens up a USB device that implements a CDC ACM serial port. It then uses the
 | ||||
| //! PIO module to implement a UART that is connected to the USB serial port. This allows you to
 | ||||
| //! communicate with a device connected to the RP2040 over USB serial.
 | ||||
| 
 | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| #![allow(async_fn_in_trait)] | ||||
| 
 | ||||
| use defmt::{info, panic, trace}; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_futures::join::{join, join3}; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::peripherals::{PIO0, USB}; | ||||
| use embassy_rp::pio; | ||||
| use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; | ||||
| use embassy_rp::usb::{Driver, Instance, InterruptHandler}; | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embassy_sync::pipe::Pipe; | ||||
| use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; | ||||
| use embassy_usb::driver::EndpointError; | ||||
| use embassy_usb::{Builder, Config}; | ||||
| use embedded_io_async::{Read, Write}; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| #[link_section = ".start_block"] | ||||
| #[used] | ||||
| pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); | ||||
| 
 | ||||
| bind_interrupts!(struct Irqs { | ||||
|     USBCTRL_IRQ => InterruptHandler<USB>; | ||||
|     PIO0_IRQ_0 => pio::InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| #[embassy_executor::main] | ||||
| async fn main(_spawner: Spawner) { | ||||
|     info!("Hello there!"); | ||||
| 
 | ||||
|     let p = embassy_rp::init(Default::default()); | ||||
| 
 | ||||
|     // Create the driver, from the HAL.
 | ||||
|     let driver = Driver::new(p.USB, Irqs); | ||||
| 
 | ||||
|     // Create embassy-usb Config
 | ||||
|     let mut config = Config::new(0xc0de, 0xcafe); | ||||
|     config.manufacturer = Some("Embassy"); | ||||
|     config.product = Some("PIO UART example"); | ||||
|     config.serial_number = Some("12345678"); | ||||
|     config.max_power = 100; | ||||
|     config.max_packet_size_0 = 64; | ||||
| 
 | ||||
|     // Required for windows compatibility.
 | ||||
|     // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
 | ||||
|     config.device_class = 0xEF; | ||||
|     config.device_sub_class = 0x02; | ||||
|     config.device_protocol = 0x01; | ||||
|     config.composite_with_iads = true; | ||||
| 
 | ||||
|     // Create embassy-usb DeviceBuilder using the driver and config.
 | ||||
|     // It needs some buffers for building the descriptors.
 | ||||
|     let mut config_descriptor = [0; 256]; | ||||
|     let mut bos_descriptor = [0; 256]; | ||||
|     let mut control_buf = [0; 64]; | ||||
| 
 | ||||
|     let mut state = State::new(); | ||||
| 
 | ||||
|     let mut builder = Builder::new( | ||||
|         driver, | ||||
|         config, | ||||
|         &mut config_descriptor, | ||||
|         &mut bos_descriptor, | ||||
|         &mut [], // no msos descriptors
 | ||||
|         &mut control_buf, | ||||
|     ); | ||||
| 
 | ||||
|     // Create classes on the builder.
 | ||||
|     let class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||||
| 
 | ||||
|     // Build the builder.
 | ||||
|     let mut usb = builder.build(); | ||||
| 
 | ||||
|     // Run the USB device.
 | ||||
|     let usb_fut = usb.run(); | ||||
| 
 | ||||
|     // PIO UART setup
 | ||||
|     let pio::Pio { | ||||
|         mut common, sm0, sm1, .. | ||||
|     } = pio::Pio::new(p.PIO0, Irqs); | ||||
| 
 | ||||
|     let tx_program = PioUartTxProgram::new(&mut common); | ||||
|     let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); | ||||
| 
 | ||||
|     let rx_program = PioUartRxProgram::new(&mut common); | ||||
|     let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); | ||||
| 
 | ||||
|     // Pipe setup
 | ||||
|     let mut usb_pipe: Pipe<NoopRawMutex, 20> = Pipe::new(); | ||||
|     let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split(); | ||||
| 
 | ||||
|     let mut uart_pipe: Pipe<NoopRawMutex, 20> = Pipe::new(); | ||||
|     let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split(); | ||||
| 
 | ||||
|     let (mut usb_tx, mut usb_rx) = class.split(); | ||||
| 
 | ||||
|     // Read + write from USB
 | ||||
|     let usb_future = async { | ||||
|         loop { | ||||
|             info!("Wait for USB connection"); | ||||
|             usb_rx.wait_connection().await; | ||||
|             info!("Connected"); | ||||
|             let _ = join( | ||||
|                 usb_read(&mut usb_rx, &mut uart_pipe_writer), | ||||
|                 usb_write(&mut usb_tx, &mut usb_pipe_reader), | ||||
|             ) | ||||
|             .await; | ||||
|             info!("Disconnected"); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Read + write from UART
 | ||||
|     let uart_future = join( | ||||
|         uart_read(&mut uart_rx, &mut usb_pipe_writer), | ||||
|         uart_write(&mut uart_tx, &mut uart_pipe_reader), | ||||
|     ); | ||||
| 
 | ||||
|     // Run everything concurrently.
 | ||||
|     // If we had made everything `'static` above instead, we could do this using separate tasks instead.
 | ||||
|     join3(usb_fut, usb_future, uart_future).await; | ||||
| } | ||||
| 
 | ||||
| struct Disconnected {} | ||||
| 
 | ||||
| impl From<EndpointError> for Disconnected { | ||||
|     fn from(val: EndpointError) -> Self { | ||||
|         match val { | ||||
|             EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||||
|             EndpointError::Disabled => Disconnected {}, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Read from the USB and write it to the UART TX pipe
 | ||||
| async fn usb_read<'d, T: Instance + 'd>( | ||||
|     usb_rx: &mut Receiver<'d, Driver<'d, T>>, | ||||
|     uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, | ||||
| ) -> Result<(), Disconnected> { | ||||
|     let mut buf = [0; 64]; | ||||
|     loop { | ||||
|         let n = usb_rx.read_packet(&mut buf).await?; | ||||
|         let data = &buf[..n]; | ||||
|         trace!("USB IN: {:x}", data); | ||||
|         (*uart_pipe_writer).write(data).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Read from the USB TX pipe and write it to the USB
 | ||||
| async fn usb_write<'d, T: Instance + 'd>( | ||||
|     usb_tx: &mut Sender<'d, Driver<'d, T>>, | ||||
|     usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, | ||||
| ) -> Result<(), Disconnected> { | ||||
|     let mut buf = [0; 64]; | ||||
|     loop { | ||||
|         let n = (*usb_pipe_reader).read(&mut buf).await; | ||||
|         let data = &buf[..n]; | ||||
|         trace!("USB OUT: {:x}", data); | ||||
|         usb_tx.write_packet(&data).await?; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Read from the UART and write it to the USB TX pipe
 | ||||
| async fn uart_read<PIO: pio::Instance, const SM: usize>( | ||||
|     uart_rx: &mut PioUartRx<'_, PIO, SM>, | ||||
|     usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, | ||||
| ) -> ! { | ||||
|     let mut buf = [0; 64]; | ||||
|     loop { | ||||
|         let n = uart_rx.read(&mut buf).await.expect("UART read error"); | ||||
|         if n == 0 { | ||||
|             continue; | ||||
|         } | ||||
|         let data = &buf[..n]; | ||||
|         trace!("UART IN: {:x}", buf); | ||||
|         (*usb_pipe_writer).write(data).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Read from the UART TX pipe and write it to the UART
 | ||||
| async fn uart_write<PIO: pio::Instance, const SM: usize>( | ||||
|     uart_tx: &mut PioUartTx<'_, PIO, SM>, | ||||
|     uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, | ||||
| ) -> ! { | ||||
|     let mut buf = [0; 64]; | ||||
|     loop { | ||||
|         let n = (*uart_pipe_reader).read(&mut buf).await; | ||||
|         let data = &buf[..n]; | ||||
|         trace!("UART OUT: {:x}", data); | ||||
|         let _ = uart_tx.write(&data).await; | ||||
|     } | ||||
| } | ||||
| @ -6,16 +6,12 @@ | ||||
| 
 | ||||
| use defmt::*; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_rp::bind_interrupts; | ||||
| use embassy_rp::block::ImageDef; | ||||
| use embassy_rp::dma::{AnyChannel, Channel}; | ||||
| use embassy_rp::peripherals::PIO0; | ||||
| use embassy_rp::pio::{ | ||||
|     Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, | ||||
| }; | ||||
| use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; | ||||
| use embassy_time::{Duration, Ticker, Timer}; | ||||
| use fixed::types::U24F8; | ||||
| use fixed_macro::fixed; | ||||
| use embassy_rp::pio::{InterruptHandler, Pio}; | ||||
| use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; | ||||
| use embassy_time::{Duration, Ticker}; | ||||
| use smart_leds::RGB8; | ||||
| use {defmt_rtt as _, panic_probe as _}; | ||||
| 
 | ||||
| @ -27,96 +23,6 @@ bind_interrupts!(struct Irqs { | ||||
|     PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||||
| }); | ||||
| 
 | ||||
| pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { | ||||
|     dma: PeripheralRef<'d, AnyChannel>, | ||||
|     sm: StateMachine<'d, P, S>, | ||||
| } | ||||
| 
 | ||||
| impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { | ||||
|     pub fn new( | ||||
|         pio: &mut Common<'d, P>, | ||||
|         mut sm: StateMachine<'d, P, S>, | ||||
|         dma: impl Peripheral<P = impl Channel> + 'd, | ||||
|         pin: impl PioPin, | ||||
|     ) -> Self { | ||||
|         into_ref!(dma); | ||||
| 
 | ||||
|         // Setup sm0
 | ||||
| 
 | ||||
|         // prepare the PIO program
 | ||||
|         let side_set = pio::SideSet::new(false, 1, false); | ||||
|         let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); | ||||
| 
 | ||||
|         const T1: u8 = 2; // start bit
 | ||||
|         const T2: u8 = 5; // data bit
 | ||||
|         const T3: u8 = 3; // stop bit
 | ||||
|         const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; | ||||
| 
 | ||||
|         let mut wrap_target = a.label(); | ||||
|         let mut wrap_source = a.label(); | ||||
|         let mut do_zero = a.label(); | ||||
|         a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); | ||||
|         a.bind(&mut wrap_target); | ||||
|         // Do stop bit
 | ||||
|         a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); | ||||
|         // Do start bit
 | ||||
|         a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); | ||||
|         // Do data bit = 1
 | ||||
|         a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); | ||||
|         a.bind(&mut do_zero); | ||||
|         // Do data bit = 0
 | ||||
|         a.nop_with_delay_and_side_set(T2 - 1, 0); | ||||
|         a.bind(&mut wrap_source); | ||||
| 
 | ||||
|         let prg = a.assemble_with_wrap(wrap_source, wrap_target); | ||||
|         let mut cfg = Config::default(); | ||||
| 
 | ||||
|         // Pin config
 | ||||
|         let out_pin = pio.make_pio_pin(pin); | ||||
|         cfg.set_out_pins(&[&out_pin]); | ||||
|         cfg.set_set_pins(&[&out_pin]); | ||||
| 
 | ||||
|         cfg.use_program(&pio.load_program(&prg), &[&out_pin]); | ||||
| 
 | ||||
|         // Clock config, measured in kHz to avoid overflows
 | ||||
|         // TODO CLOCK_FREQ should come from embassy_rp
 | ||||
|         let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); | ||||
|         let ws2812_freq = fixed!(800: U24F8); | ||||
|         let bit_freq = ws2812_freq * CYCLES_PER_BIT; | ||||
|         cfg.clock_divider = clock_freq / bit_freq; | ||||
| 
 | ||||
|         // FIFO config
 | ||||
|         cfg.fifo_join = FifoJoin::TxOnly; | ||||
|         cfg.shift_out = ShiftConfig { | ||||
|             auto_fill: true, | ||||
|             threshold: 24, | ||||
|             direction: ShiftDirection::Left, | ||||
|         }; | ||||
| 
 | ||||
|         sm.set_config(&cfg); | ||||
|         sm.set_enable(true); | ||||
| 
 | ||||
|         Self { | ||||
|             dma: dma.map_into(), | ||||
|             sm, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub async fn write(&mut self, colors: &[RGB8; N]) { | ||||
|         // Precompute the word bytes from the colors
 | ||||
|         let mut words = [0u32; N]; | ||||
|         for i in 0..N { | ||||
|             let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); | ||||
|             words[i] = word; | ||||
|         } | ||||
| 
 | ||||
|         // DMA transfer
 | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), &words).await; | ||||
| 
 | ||||
|         Timer::after_micros(55).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Input a value 0 to 255 to get a color value
 | ||||
| /// The colours are a transition r - g - b - back to r.
 | ||||
| fn wheel(mut wheel_pos: u8) -> RGB8 { | ||||
| @ -147,7 +53,8 @@ async fn main(_spawner: Spawner) { | ||||
|     // Common neopixel pins:
 | ||||
|     // Thing plus: 8
 | ||||
|     // Adafruit Feather: 16;  Adafruit Feather+RFM95: 4
 | ||||
|     let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); | ||||
|     let program = PioWs2812Program::new(&mut common); | ||||
|     let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); | ||||
| 
 | ||||
|     // Loop forever making RGB values and pushing them out to the WS2812.
 | ||||
|     let mut ticker = Ticker::every(Duration::from_millis(10)); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user