From 6026ea4ae37d8e1f5bad3c0361ada34beca7de58 Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Fri, 18 Mar 2022 10:55:31 +0100 Subject: [PATCH] Allow to start multiple state machines in sync (#301) * add example of synchronized PIOs * Synchronize state machines using WAIT IRQ instruction * Use "irq wait 0" instead of "wait 1 irq 0" This way, the initial value of the interrupt flag doesn't matter * Start state machines synchronized without IRQ WAIT instruction * Improve API Co-authored-by: Andrew Straw --- rp2040-hal/examples/pio_synchronized.rs | 96 +++++++ rp2040-hal/src/pio.rs | 346 ++++++++++++++++++++++++ 2 files changed, 442 insertions(+) create mode 100644 rp2040-hal/examples/pio_synchronized.rs diff --git a/rp2040-hal/examples/pio_synchronized.rs b/rp2040-hal/examples/pio_synchronized.rs new file mode 100644 index 0000000..39cf23d --- /dev/null +++ b/rp2040-hal/examples/pio_synchronized.rs @@ -0,0 +1,96 @@ +//! This example toggles the GPIO0 and GPIO1 pins, with each controlled from a +//! separate PIO state machine. +//! +//! Despite running in separate state machines, the clocks are sychronized at +//! the rise and fall times will be simultaneous. +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use hal::gpio::{FunctionPio0, Pin}; +use hal::pac; +use hal::pio::PIOExt; +use hal::Sio; +use panic_halt as _; +use rp2040_hal as hal; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure pins for Pio0. + let _: Pin<_, FunctionPio0> = pins.gpio0.into_mode(); + let _: Pin<_, FunctionPio0> = pins.gpio1.into_mode(); + + // PIN id for use inside of PIO + let pin0 = 0; + let pin1 = 1; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + " +.wrap_target + set pins, 1 [31] + set pins, 0 [31] +.wrap + " + ); + + // Initialize and start PIO + let (mut pio, sm0, sm1, _, _) = pac.PIO0.split(&mut pac.RESETS); + // I'm "measuring" the phase offset between the two pins by connecting + // then through a LED. If there is a clock offset, there will be a + // short time with a voltage between the pins, so the LED will flash up. + // With a slow clock this is not visible, so use a reasonably fast clock. + let div = 256f32; + + let installed = pio.install(&program.program).unwrap(); + let (mut sm0, _, _) = rp2040_hal::pio::PIOBuilder::from_program(installed) + .set_pins(pin0, 1) + .clock_divisor(div) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm0.set_pindirs([(pin0, hal::pio::PinDir::Output)]); + + // NOTE: with the current rp-hal, I need to call pio.install() twice. This + // should be investigated further as it seems wrong. + let installed = pio.install(&program.program).unwrap(); + let (mut sm1, _, _) = rp2040_hal::pio::PIOBuilder::from_program(installed) + .set_pins(pin1, 1) + .clock_divisor(div) + .build(sm1); + // The GPIO pin needs to be configured as an output. + sm1.set_pindirs([(pin1, hal::pio::PinDir::Output)]); + + // Start both SMs at the same time + let group = sm0.with(sm1).sync().start(); + cortex_m::asm::delay(10_000_000); + + // Stop both SMs at the same time + let group = group.stop(); + cortex_m::asm::delay(10_000_000); + + // Start them again and extract the individual state machines + let (sm1, sm2) = group.start().free(); + cortex_m::asm::delay(10_000_000); + + // Stop the two state machines separately + let _sm1 = sm1.stop(); + cortex_m::asm::delay(10_000_000); + let _sm2 = sm2.stop(); + + #[allow(clippy::empty_loop)] + loop {} +} diff --git a/rp2040-hal/src/pio.rs b/rp2040-hal/src/pio.rs index 9bb4a8e..d7a3d01 100644 --- a/rp2040-hal/src/pio.rs +++ b/rp2040-hal/src/pio.rs @@ -655,6 +655,352 @@ impl StateMachine<(P, SM), Stopped> { } } +impl StateMachine<(P, SM), State> { + /// Create a group of state machines, which can be started/stopped synchonously + pub fn with( + self, + other_sm: StateMachine<(P, SM2), State>, + ) -> StateMachineGroup2 { + StateMachineGroup2 { + sm1: self, + sm2: other_sm, + } + } +} + +/// Group of 2 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup2< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, +} + +/// Group of 3 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup3< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, +} + +/// Group of 4 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup4< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, + sm4: StateMachine<(P, SM4Idx), State>, +} + +impl + StateMachineGroup2 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + ) { + (self.sm1, self.sm2) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM3Idx), State>, + ) -> StateMachineGroup3 { + StateMachineGroup3 { + sm1: self.sm1, + sm2: self.sm2, + sm3: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, + > StateMachineGroup3 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM4Idx), State>, + ) -> StateMachineGroup4 { + StateMachineGroup4 { + sm1: self.sm1, + sm2: self.sm2, + sm3: self.sm3, + sm4: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, + > StateMachineGroup4 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + StateMachine<(P, SM4Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3, self.sm4) + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) | (1 << SM4Idx::id()) + } +} + +impl + StateMachineGroup2 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup2 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup3 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup4 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl + StateMachineGroup2 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup2 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup3 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup4 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + /// Type which, once destructed, restarts the clock dividers for all selected state machines, /// effectively synchronizing them. pub struct Synchronize<'sm, SM: ValidStateMachine> {