From 515eac5553e8a06908a73a37e24ec576b26969f5 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Tue, 28 Sep 2021 21:15:28 +0200 Subject: [PATCH] pio: Differentiate between uninitialized/stopped/running state machines. Some operations must only be performed in a specific state. For example, pin directions must not be changed while the state machine is running, as the operation modifies PINCTRL. The new API makes wrong usage a lot harder. Also, the code now supports uninitializing state machines to free instruction space or to select a different function. --- rp2040-hal/src/pio.rs | 239 ++++++++++++++++++++++++++---------------- 1 file changed, 148 insertions(+), 91 deletions(-) diff --git a/rp2040-hal/src/pio.rs b/rp2040-hal/src/pio.rs index d9d1d70..1abdbb6 100644 --- a/rp2040-hal/src/pio.rs +++ b/rp2040-hal/src/pio.rs @@ -15,32 +15,32 @@ pub trait PIOExt: resets: &mut pac::RESETS, ) -> ( PIO, - StateMachine, - StateMachine, - StateMachine, - StateMachine, + UninitStateMachine, + UninitStateMachine, + UninitStateMachine, + UninitStateMachine, ) { self.reset_bring_up(resets); - let sm0 = StateMachine { + let sm0 = UninitStateMachine { id: 0, block: self.deref(), sm: &self.deref().sm[0], _phantom: core::marker::PhantomData, }; - let sm1 = StateMachine { + let sm1 = UninitStateMachine { id: 0, block: self.deref(), sm: &self.deref().sm[0], _phantom: core::marker::PhantomData, }; - let sm2 = StateMachine { + let sm2 = UninitStateMachine { id: 0, block: self.deref(), sm: &self.deref().sm[0], _phantom: core::marker::PhantomData, }; - let sm3 = StateMachine { + let sm3 = UninitStateMachine { id: 0, block: self.deref(), sm: &self.deref().sm[0], @@ -95,56 +95,13 @@ impl core::fmt::Debug for PIO

{ unsafe impl Send for PIO

{} impl PIO

{ - /*pub fn new(pio: P, resets: &mut pac::RESETS) -> Self { - pio.reset_bring_up(resets); - - PIO { - used_instruction_space: core::cell::Cell::new(0), - state_machines: [ - StateMachine { - id: 0, - block: pio.deref(), - _phantom: core::marker::PhantomData, - }, - StateMachine { - id: 1, - block: pio.deref(), - _phantom: core::marker::PhantomData, - }, - StateMachine { - id: 2, - block: pio.deref(), - _phantom: core::marker::PhantomData, - }, - StateMachine { - id: 3, - block: pio.deref(), - _phantom: core::marker::PhantomData, - }, - ], - interrupts: [ - Interrupt { - id: 0, - block: pio.deref(), - _phantom: core::marker::PhantomData, - }, - Interrupt { - id: 1, - block: pio.deref(), - _phantom: core::marker::PhantomData, - }, - ], - pio, - } - }*/ - /// Free this instance. pub fn free( self, - _sm0: StateMachine

, - _sm1: StateMachine

, - _sm2: StateMachine

, - _sm3: StateMachine

, + _sm0: UninitStateMachine

, + _sm1: UninitStateMachine

, + _sm2: UninitStateMachine

, + _sm3: UninitStateMachine

, ) -> P { // TODO: Disable the PIO block. self.pio @@ -205,7 +162,7 @@ impl PIO

{ pub fn install( &mut self, p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>, - ) -> Result { + ) -> Result, InstallError> { if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) { for (i, instr) in p .code @@ -239,6 +196,7 @@ impl PIO

{ length: p.code.len() as u8, side_set: p.side_set, wrap: p.wrap, + _phantom: core::marker::PhantomData, }) } else { Err(InstallError::NoSpace) @@ -246,10 +204,33 @@ impl PIO

{ } /// Removes the specified program from instruction memory, freeing the allocated space. - pub fn uninstall(&mut self, p: InstalledProgram) { + pub fn uninstall(&mut self, p: InstalledProgram

) { let instr_mask = (1 << p.length as u32) << p.offset as u32; self.used_instruction_space = self.used_instruction_space & !instr_mask; } + + /// Restarts the clock dividers for the specified state machines. + /// + /// As a result, the clock will be synchronous for the state machines, which is a precondition + /// for synchronous operation. + pub fn synchronize(sm_set: &[&mut StateMachine]) { + if sm_set.len() == 0 { + return; + } + let mut sm_mask = 0; + for sm in sm_set { + // Bits 11:8 of CTRL contain CLKDIV_RESTART. + sm_mask |= ((1 << sm.sm.id) as u32) << 8; + } + const ATOMIC_SET_OFFSET: usize = 0x2000; + // Safety: We only use the atomic alias of the register. + unsafe { + *(*sm_set[0].sm.block) + .ctrl + .as_ptr() + .add(ATOMIC_SET_OFFSET / 4) = sm_mask; + } + } } /// Handle to a program that was placed in the PIO's instruction memory. @@ -272,14 +253,15 @@ impl PIO

{ /// /// TODO: Write an example? #[derive(Debug)] -pub struct InstalledProgram { +pub struct InstalledProgram

{ offset: u8, length: u8, side_set: SideSet, wrap: Wrap, + _phantom: core::marker::PhantomData

, } -impl InstalledProgram { +impl InstalledProgram

{ /// Clones this program handle so that it can be executed by two state machines at the same /// time. /// @@ -290,31 +272,32 @@ impl InstalledProgram { /// /// The user has to make sure to call `PIO::uninstall()` only once and only after all state /// machines using the program have been uninitialized. - pub unsafe fn share(&self) -> InstalledProgram { + pub unsafe fn share(&self) -> InstalledProgram

{ InstalledProgram { offset: self.offset, length: self.length, side_set: self.side_set, wrap: self.wrap, + _phantom: core::marker::PhantomData, } } } -/// PIO State Machine. +/// PIO State Machine (uninitialized, without a program). #[derive(Debug)] -pub struct StateMachine { +pub struct UninitStateMachine { id: u8, block: *const rp2040_pac::pio0::RegisterBlock, sm: *const rp2040_pac::pio0::SM, _phantom: core::marker::PhantomData

, } -// `StateMachine` doesn't implement `Send` because it sometimes accesses shared registers, e.g. `sm_enable`. -// unsafe impl Send for StateMachine

{} +// Safety: `UninitStateMachine` only uses atomic accesses to shared registers. +unsafe impl Send for UninitStateMachine

{} -impl StateMachine

{ +impl UninitStateMachine

{ /// Start and stop the state machine. - pub fn set_enabled(&mut self, enabled: bool) { + fn set_enabled(&mut self, enabled: bool) { // Bits 3:0 are SM_ENABLE. let mask = 1 << self.id; if enabled { @@ -365,40 +348,69 @@ impl StateMachine

{ }); } - /// The address of the instruction currently being executed. - pub fn instruction_address(&self) -> u32 { - self.sm().sm_addr.read().bits() - } - /// Set the current instruction. - pub fn set_instruction(&mut self, instruction: u16) { + fn set_instruction(&mut self, instruction: u16) { + // TODO: Check if this function is safe to call while the state machine is running. self.sm() .sm_instr .write(|w| unsafe { w.sm0_instr().bits(instruction) }) } - /// Check if the current instruction is stalled. - pub fn stalled(&self) -> bool { - self.sm().sm_execctrl.read().exec_stalled().bits() - } - fn sm(&self) -> &rp2040_pac::pio0::SM { unsafe { &*self.sm } } +} + +pub struct StateMachine { + sm: UninitStateMachine

, + program: InstalledProgram

, + _phantom: core::marker::PhantomData, +} + +/// Marker for an initialized, but stopped state machine. +pub struct Stopped; +/// Marker for an initialized and running state machine. +pub struct Running; + +impl StateMachine { + pub fn uninit(mut self) -> (UninitStateMachine

, InstalledProgram

) { + self.sm.set_enabled(false); + (self.sm, self.program) + } + + /// The address of the instruction currently being executed. + pub fn instruction_address(&self) -> u32 { + self.sm.sm().sm_addr.read().bits() + } + + /// Set the current instruction. + pub fn set_instruction(&mut self, instruction: u16) { + self.sm.set_instruction(instruction); + } + + /// Check if the current instruction is stalled. + pub fn stalled(&self) -> bool { + self.sm.sm().sm_execctrl.read().exec_stalled().bits() + } /// Get the next element from RX FIFO. /// /// Returns `None` if the FIFO is empty. pub fn read_rx(&mut self) -> Option { // Safety: The register is never written by software. - let is_empty = unsafe { &*self.block }.fstat.read().rxempty().bits() & (1 << self.id) != 0; + let is_empty = + unsafe { &*self.sm.block }.fstat.read().rxempty().bits() & (1 << self.sm.id) != 0; if is_empty { return None; } // Safety: The register is unique to this state machine. - Some(unsafe { &*self.block }.rxf[self.id as usize].read().bits()) + Some( + unsafe { &*self.sm.block }.rxf[self.sm.id as usize] + .read() + .bits(), + ) } /// Write an element to TX FIFO. @@ -406,32 +418,52 @@ impl StateMachine

{ /// Returns `true` if the value was written to FIFO, `false` otherwise. pub fn write_tx(&mut self, value: u32) -> bool { // Safety: The register is never written by software. - let is_full = unsafe { &*self.block }.fstat.read().txfull().bits() & (1 << self.id) != 0; + let is_full = + unsafe { &*self.sm.block }.fstat.read().txfull().bits() & (1 << self.sm.id) != 0; if is_full { return false; } // Safety: The register is unique to this state machine. - unsafe { &*self.block }.txf[self.id as usize].write(|w| unsafe { w.bits(value) }); + unsafe { &*self.sm.block }.txf[self.sm.id as usize].write(|w| unsafe { w.bits(value) }); true } +} + +impl StateMachine { + /// Starts execution of the selected program. + pub fn start(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(true); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } + } pub fn set_pindirs_with_mask(&mut self, mut pins: u32, pindir: u32) { let mut pin = 0; + let prev_pinctrl = self.sm.sm().sm_pinctrl.read().bits(); + // For each pin in the mask, we select the pin as a SET pin and then execute "set PINDIRS, + // ". while pins != 0 { if (pins & 1) != 0 { - self.sm().sm_pinctrl.write(|w| { + self.sm.sm().sm_pinctrl.write(|w| { unsafe { w.set_count().bits(1); w.set_base().bits(pin as u8); } w }); - self.sm().sm_instr.write(|w| { + self.sm.sm().sm_instr.write(|w| { unsafe { - w.sm0_instr().bits(0xe080 | ((pindir >> pin) & 0x1) as u16); + const SET_PINDIRS: u16 = 0xe080; + w.sm0_instr() + .bits(SET_PINDIRS | ((pindir >> pin) & 0x1) as u16); } w }); @@ -439,6 +471,28 @@ impl StateMachine

{ pin += 1; pins = pins >> 1; } + // We modified PINCTRL, yet the program assumes a certain configuration, so restore the + // previous value. + self.sm.sm().sm_pinctrl.write(|w| { + unsafe { + w.bits(prev_pinctrl); + } + w + }); + } +} + +impl StateMachine { + /// Stops execution of the selected program. + pub fn stop(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(false); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } } } @@ -706,12 +760,12 @@ impl ShiftDirection { /// Builder to deploy a fully configured PIO program on one of the state /// machines. #[derive(Debug)] -pub struct PIOBuilder { +pub struct PIOBuilder

{ /// Clock divisor. clock_divisor: f32, /// Program location and configuration. - program: InstalledProgram, + program: InstalledProgram

, /// GPIO pin used by `jmp pin` instruction. jmp_pin: u8, @@ -772,10 +826,10 @@ pub enum InstallError { NoSpace, } -impl PIOBuilder { +impl PIOBuilder

{ /// Set config settings based on information from the given [`pio::Program`]. /// Additional configuration may be needed in addition to this. - pub fn from_program(p: InstalledProgram) -> Self { + pub fn from_program(p: InstalledProgram

) -> Self { PIOBuilder { clock_divisor: 1.0, program: p, @@ -929,7 +983,7 @@ impl PIOBuilder { } /// Build the config and deploy it to a StateMachine. - pub fn build(self, sm: &mut StateMachine

) { + pub fn build(self, mut sm: UninitStateMachine

) -> StateMachine { // TODO: Currently, the program is just lost and can never be uninstalled again. let offset = self.program.offset; @@ -1034,7 +1088,10 @@ impl PIOBuilder { .encode(), ); - // Enable SM - sm.set_enabled(true); + StateMachine { + sm: sm, + program: self.program, + _phantom: core::marker::PhantomData, + } } }