mirror of
https://github.com/italicsjenga/rp-hal-boards.git
synced 2025-01-23 01:36:35 +11:00
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.
This commit is contained in:
parent
4d97d9fe75
commit
515eac5553
1 changed files with 148 additions and 91 deletions
|
@ -15,32 +15,32 @@ pub trait PIOExt:
|
|||
resets: &mut pac::RESETS,
|
||||
) -> (
|
||||
PIO<Self>,
|
||||
StateMachine<Self>,
|
||||
StateMachine<Self>,
|
||||
StateMachine<Self>,
|
||||
StateMachine<Self>,
|
||||
UninitStateMachine<Self>,
|
||||
UninitStateMachine<Self>,
|
||||
UninitStateMachine<Self>,
|
||||
UninitStateMachine<Self>,
|
||||
) {
|
||||
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<P: PIOExt> core::fmt::Debug for PIO<P> {
|
|||
unsafe impl<P: PIOExt + Send> Send for PIO<P> {}
|
||||
|
||||
impl<P: PIOExt> PIO<P> {
|
||||
/*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<P>,
|
||||
_sm1: StateMachine<P>,
|
||||
_sm2: StateMachine<P>,
|
||||
_sm3: StateMachine<P>,
|
||||
_sm0: UninitStateMachine<P>,
|
||||
_sm1: UninitStateMachine<P>,
|
||||
_sm2: UninitStateMachine<P>,
|
||||
_sm3: UninitStateMachine<P>,
|
||||
) -> P {
|
||||
// TODO: Disable the PIO block.
|
||||
self.pio
|
||||
|
@ -205,7 +162,7 @@ impl<P: PIOExt> PIO<P> {
|
|||
pub fn install(
|
||||
&mut self,
|
||||
p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>,
|
||||
) -> Result<InstalledProgram, InstallError> {
|
||||
) -> Result<InstalledProgram<P>, 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<P: PIOExt> PIO<P> {
|
|||
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<P: PIOExt> PIO<P> {
|
|||
}
|
||||
|
||||
/// 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<P>) {
|
||||
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<P, Stopped>]) {
|
||||
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<P: PIOExt> PIO<P> {
|
|||
///
|
||||
/// TODO: Write an example?
|
||||
#[derive(Debug)]
|
||||
pub struct InstalledProgram {
|
||||
pub struct InstalledProgram<P> {
|
||||
offset: u8,
|
||||
length: u8,
|
||||
side_set: SideSet,
|
||||
wrap: Wrap,
|
||||
_phantom: core::marker::PhantomData<P>,
|
||||
}
|
||||
|
||||
impl InstalledProgram {
|
||||
impl<P: PIOExt> InstalledProgram<P> {
|
||||
/// 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<P> {
|
||||
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<P: PIOExt> {
|
||||
pub struct UninitStateMachine<P: PIOExt> {
|
||||
id: u8,
|
||||
block: *const rp2040_pac::pio0::RegisterBlock,
|
||||
sm: *const rp2040_pac::pio0::SM,
|
||||
_phantom: core::marker::PhantomData<P>,
|
||||
}
|
||||
|
||||
// `StateMachine` doesn't implement `Send` because it sometimes accesses shared registers, e.g. `sm_enable`.
|
||||
// unsafe impl<P: Instance + Send> Send for StateMachine<P> {}
|
||||
// Safety: `UninitStateMachine` only uses atomic accesses to shared registers.
|
||||
unsafe impl<P: PIOExt + Send> Send for UninitStateMachine<P> {}
|
||||
|
||||
impl<P: PIOExt> StateMachine<P> {
|
||||
impl<P: PIOExt> UninitStateMachine<P> {
|
||||
/// 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<P: PIOExt> StateMachine<P> {
|
|||
});
|
||||
}
|
||||
|
||||
/// 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<P: PIOExt, State> {
|
||||
sm: UninitStateMachine<P>,
|
||||
program: InstalledProgram<P>,
|
||||
_phantom: core::marker::PhantomData<State>,
|
||||
}
|
||||
|
||||
/// Marker for an initialized, but stopped state machine.
|
||||
pub struct Stopped;
|
||||
/// Marker for an initialized and running state machine.
|
||||
pub struct Running;
|
||||
|
||||
impl<P: PIOExt, State> StateMachine<P, State> {
|
||||
pub fn uninit(mut self) -> (UninitStateMachine<P>, InstalledProgram<P>) {
|
||||
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<u32> {
|
||||
// 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<P: PIOExt> StateMachine<P> {
|
|||
/// 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<P: PIOExt> StateMachine<P, Stopped> {
|
||||
/// Starts execution of the selected program.
|
||||
pub fn start(mut self) -> StateMachine<P, Running> {
|
||||
// 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,
|
||||
// <direction>".
|
||||
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<P: PIOExt> StateMachine<P> {
|
|||
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<P: PIOExt> StateMachine<P, Running> {
|
||||
/// Stops execution of the selected program.
|
||||
pub fn stop(mut self) -> StateMachine<P, Stopped> {
|
||||
// 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<P> {
|
||||
/// Clock divisor.
|
||||
clock_divisor: f32,
|
||||
|
||||
/// Program location and configuration.
|
||||
program: InstalledProgram,
|
||||
program: InstalledProgram<P>,
|
||||
/// GPIO pin used by `jmp pin` instruction.
|
||||
jmp_pin: u8,
|
||||
|
||||
|
@ -772,10 +826,10 @@ pub enum InstallError {
|
|||
NoSpace,
|
||||
}
|
||||
|
||||
impl PIOBuilder {
|
||||
impl<P: PIOExt> PIOBuilder<P> {
|
||||
/// 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<P>) -> 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<P: PIOExt>(self, sm: &mut StateMachine<P>) {
|
||||
pub fn build(self, mut sm: UninitStateMachine<P>) -> StateMachine<P, Stopped> {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue