Merge pull request #143 from mgottschlag/pio-rework

Rework the PIO API
This commit is contained in:
9names 2021-10-02 16:41:38 +10:00 committed by GitHub
commit ede25a4f15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 645 additions and 216 deletions

View file

@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- implement `rng_core::RngCore` for `RingOscillator`
### Changed
- Modified PIO API for better ergonomics
## [0.3.0] - 2021-09-20
### Added

View file

@ -1,4 +1,5 @@
[workspace]
resolver = "2"
members = [
"rp2040-hal",
"boards/feather_rp2040",

View file

@ -19,6 +19,7 @@ nb = "1.0"
rp2040-pac = "0.1.5"
paste = "1.0"
pio = { git = "https://github.com/rp-rs/pio-rs.git", branch = "main" }
pio-proc = { git = "https://github.com/rp-rs/pio-rs.git", branch = "main" }
usb-device = "0.2.8"
vcell = "0.1"
void = { version = "1.0.2", default-features = false }

View file

@ -7,6 +7,7 @@
use cortex_m_rt::entry;
use hal::gpio::{FunctionPio0, Pin};
use hal::pac;
use hal::pio::PIOExt;
use hal::sio::Sio;
use panic_halt as _;
use rp2040_hal as hal;
@ -53,15 +54,14 @@ fn main() -> ! {
let program = a.assemble_with_wrap(wrap_source, wrap_target);
// Initialize and start PIO
let pio = rp2040_hal::pio::PIO::new(pac.PIO0, &mut pac.RESETS);
let sm = &pio.state_machines()[0];
let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
let installed = pio.install(&program).unwrap();
let div = 0f32; // as slow as possible (0 is interpreted as 65536)
rp2040_hal::pio::PIOBuilder::default()
.with_program(&program)
let (sm, _, _) = rp2040_hal::pio::PIOBuilder::from_program(installed)
.set_pins(led_pin_id, 1)
.clock_divisor(div)
.build(&pio, sm)
.unwrap();
.build(sm0);
sm.start();
// PIO runs in background, independently from CPU
#[allow(clippy::empty_loop)]

View file

@ -0,0 +1,62 @@
//! This example toggles the GPIO25 pin, using a PIO program compiled via pio_proc::pio!().
//!
//! If a LED is connected to that pin, like on a Pico board, the LED should blink.
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use hal::gpio::{FunctionPio0, Pin};
use hal::pac;
use hal::pio::PIOExt;
use hal::sio::Sio;
use panic_halt as _;
use rp2040_hal as hal;
#[link_section = ".boot2"]
#[used]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER;
#[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 LED pin for Pio0.
let _led: Pin<_, FunctionPio0> = pins.gpio25.into_mode();
// PIN id for use inside of PIO
let led_pin_id = 25;
// Define some simple PIO program.
let program = pio_proc::pio!(
32,
"
.wrap_target
set pins, 1 [31]
set pins, 0 [31]
.wrap
"
);
// Initialize and start PIO
let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
let installed = pio.install(&program.program).unwrap();
let div = 0f32; // as slow as possible (0 is interpreted as 65536)
let (mut sm, _, _) = rp2040_hal::pio::PIOBuilder::from_program(installed)
.set_pins(led_pin_id, 1)
.clock_divisor(div)
.build(sm0);
// The GPIO pin needs to be configured as an output.
sm.set_pindirs_with_mask(1 << led_pin_id, 1 << led_pin_id);
sm.start();
// PIO runs in background, independently from CPU
#[allow(clippy::empty_loop)]
loop {}
}

View file

@ -2,27 +2,84 @@
/// See [Chapter 3](https://rptl.io/pico-datasheet) for more details.
use crate::resets::SubsystemReset;
use pio::{Program, SideSet, Wrap};
use rp2040_pac::{PIO0, PIO1};
const PIO_INSTRUCTION_COUNT: usize = 32;
/// PIO Instance
pub trait Instance:
core::ops::Deref<Target = rp2040_pac::pio0::RegisterBlock> + SubsystemReset
pub trait PIOExt:
core::ops::Deref<Target = rp2040_pac::pio0::RegisterBlock> + SubsystemReset + Sized
{
/// Create a new PIO wrapper and split the state machines into individual objects.
#[allow(clippy::type_complexity)] // Required for symmetry with PIO::free().
fn split(
self,
resets: &mut pac::RESETS,
) -> (
PIO<Self>,
UninitStateMachine<(Self, SM0)>,
UninitStateMachine<(Self, SM1)>,
UninitStateMachine<(Self, SM2)>,
UninitStateMachine<(Self, SM3)>,
) {
self.reset_bring_up(resets);
let sm0 = UninitStateMachine {
block: self.deref(),
sm: &self.deref().sm[0],
_phantom: core::marker::PhantomData,
};
let sm1 = UninitStateMachine {
block: self.deref(),
sm: &self.deref().sm[1],
_phantom: core::marker::PhantomData,
};
let sm2 = UninitStateMachine {
block: self.deref(),
sm: &self.deref().sm[2],
_phantom: core::marker::PhantomData,
};
let sm3 = UninitStateMachine {
block: self.deref(),
sm: &self.deref().sm[3],
_phantom: core::marker::PhantomData,
};
(
PIO {
used_instruction_space: 0,
interrupts: [
Interrupt {
id: 0,
block: self.deref(),
_phantom: core::marker::PhantomData,
},
Interrupt {
id: 1,
block: self.deref(),
_phantom: core::marker::PhantomData,
},
],
pio: self,
},
sm0,
sm1,
sm2,
sm3,
)
}
}
impl Instance for rp2040_pac::PIO0 {}
impl Instance for rp2040_pac::PIO1 {}
impl PIOExt for PIO0 {}
impl PIOExt for PIO1 {}
/// Programmable IO Block
pub struct PIO<P: Instance> {
used_instruction_space: core::cell::Cell<u32>, // bit for each PIO_INSTRUCTION_COUNT
pub struct PIO<P: PIOExt> {
used_instruction_space: u32, // bit for each PIO_INSTRUCTION_COUNT
pio: P,
state_machines: [StateMachine<P>; 4],
interrupts: [Interrupt<P>; 2],
}
impl<P: Instance> core::fmt::Debug for PIO<P> {
impl<P: PIOExt> core::fmt::Debug for PIO<P> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("PIO")
.field("used_instruction_space", &self.used_instruction_space)
@ -31,64 +88,25 @@ impl<P: Instance> core::fmt::Debug for PIO<P> {
}
}
// Safety: `PIO` provides exclusive access to PIO registers.
unsafe impl<P: Instance + Send> Send for PIO<P> {}
impl<P: Instance> PIO<P> {
/// Create a new PIO wrapper.
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,
}
}
// Safety: `PIO` only provides access to those registers which are not directly used by
// `StateMachine`.
unsafe impl<P: PIOExt + Send> Send for PIO<P> {}
impl<P: PIOExt> PIO<P> {
/// Free this instance.
pub fn free(self) -> P {
///
/// All output pins are left in their current state.
pub fn free(
self,
_sm0: UninitStateMachine<(P, SM0)>,
_sm1: UninitStateMachine<(P, SM1)>,
_sm2: UninitStateMachine<(P, SM2)>,
_sm3: UninitStateMachine<(P, SM3)>,
) -> P {
// All state machines have already been stopped.
self.pio
}
/// This PIO's state machines.
pub fn state_machines(&self) -> &[StateMachine<P>; 4] {
&self.state_machines
}
/// This PIO's interrupts.
pub fn interrupts(&self) -> &[Interrupt<P>; 2] {
&self.interrupts
@ -99,10 +117,7 @@ impl<P: Instance> PIO<P> {
/// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of
/// the IRQ flags.
pub fn clear_irq(&self, flags: u8) {
self.pio
.deref()
.irq
.write(|w| unsafe { w.irq().bits(flags) });
self.pio.irq.write(|w| unsafe { w.irq().bits(flags) });
}
/// Force PIO's IRQ flags indicated by the bits.
@ -111,7 +126,6 @@ impl<P: Instance> PIO<P> {
/// the IRQ flags.
pub fn force_irq(&self, flags: u8) {
self.pio
.deref()
.irq_force
.write(|w| unsafe { w.irq_force().bits(flags) });
}
@ -123,7 +137,7 @@ impl<P: Instance> PIO<P> {
let mask = (1 << i.len()) - 1;
if let Some(origin) = origin {
if origin as usize > PIO_INSTRUCTION_COUNT - i.len()
|| self.used_instruction_space.get() & (mask << origin) != 0
|| self.used_instruction_space & (mask << origin) != 0
{
None
} else {
@ -131,7 +145,7 @@ impl<P: Instance> PIO<P> {
}
} else {
for i in (0..=32 - i.len()).rev() {
if self.used_instruction_space.get() & (mask << i) == 0 {
if self.used_instruction_space & (mask << i) == 0 {
return Some(i);
}
}
@ -140,17 +154,21 @@ impl<P: Instance> PIO<P> {
}
}
fn add_program(
&self,
instructions: &[u16],
origin: Option<u8>,
side_set: pio::SideSet,
) -> Option<usize> {
if let Some(offset) = self.find_offset_for_instructions(instructions, origin) {
for (i, instr) in instructions
/// Allocates space in instruction memory and installs the program.
///
/// The function returns a handle to the installed program that can be used to configure a
/// `StateMachine` via `PIOBuilder`. The program can be uninstalled to free instruction memory
/// via `uninstall()` once the state machine using the program has been uninitialized.
pub fn install(
&mut self,
p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>,
) -> Result<InstalledProgram<P>, InstallError> {
if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) {
for (i, instr) in p
.code
.iter()
.map(|instr| {
let mut instr = pio::Instruction::decode(*instr, side_set).unwrap();
let mut instr = pio::Instruction::decode(*instr, p.side_set).unwrap();
instr.operands = match instr.operands {
pio::InstructionOperands::JMP { condition, address } => {
@ -165,57 +183,238 @@ impl<P: Instance> PIO<P> {
_ => instr.operands,
};
instr.encode(side_set)
instr.encode(p.side_set)
})
.enumerate()
{
self.pio.instr_mem[i + offset].write(|w| unsafe { w.bits(instr as u32) })
}
self.used_instruction_space
.set(self.used_instruction_space.get() | ((1 << instructions.len()) - 1));
Some(offset)
self.used_instruction_space |= ((1 << p.code.len()) - 1) << offset;
Ok(InstalledProgram {
offset: offset as u8,
length: p.code.len() as u8,
side_set: p.side_set,
wrap: p.wrap,
_phantom: core::marker::PhantomData,
})
} else {
None
Err(InstallError::NoSpace)
}
}
/// Removes the specified program from instruction memory, freeing the allocated space.
pub fn uninstall(&mut self, p: InstalledProgram<P>) {
let instr_mask = ((1 << p.length as u32) - 1) << p.offset as u32;
self.used_instruction_space &= !instr_mask;
}
}
/// PIO State Machine.
/// Handle to a program that was placed in the PIO's instruction memory.
///
/// Objects of this type can be reused for multiple state machines of the same PIO block to save
/// memory if multiple state machines are supposed to perform the same function (for example, if
/// one PIO block is used to implement multiple I2C busses).
///
/// `PIO::uninstall(program)` can be used to free the space occupied by the program once it is no
/// longer used.
///
/// # Examples
///
/// ```no_run
/// use rp2040_hal::{pac, pio::PIOBuilder, pio::PIOExt};
/// let mut peripherals = pac::Peripherals::take().unwrap();
/// let (mut pio, sm0, _, _, _) = peripherals.PIO0.split(&mut peripherals.RESETS);
/// // Install a program in instruction memory.
/// let program = pio_proc::pio!(
/// 32,
/// ".wrap_target
/// set pins, 1 [31]
/// set pins, 0 [31]
/// .wrap
/// "
/// ).program;
/// let installed = pio.install(&program).unwrap();
/// // Configure a state machine to use the program.
/// let (sm, rx, tx) = PIOBuilder::from_program(installed).build(sm0);
/// // Uninitialize the state machine again, freeing the program.
/// let (sm, installed) = sm.uninit(rx, tx);
/// // Uninstall the program to free instruction memory.
/// pio.uninstall(installed);
/// ```
///
/// # Safety
///
/// Objects of this type can outlive their `PIO` object. If the PIO block is reinitialized, the API
/// does not prevent the user from calling `uninstall()` when the PIO block does not actually hold
/// the program anymore. The user must therefore make sure that `uninstall()` is only called on the
/// PIO object which was used to install the program.
///
/// ```ignore
/// let (mut pio, sm0, sm1, sm2, sm3) = pac.PIO0.split(&mut pac.RESETS);
/// // Install a program in instruction memory.
/// let installed = pio.install(&program).unwrap();
/// // Reinitialize PIO.
/// let pio0 = pio.free(sm0, sm1, sm2, sm3);
/// let (mut pio, _, _, _, _) = pio0.split(&mut pac.RESETS);
/// // Do not do the following, the program is not in instruction memory anymore!
/// pio.uninstall(installed);
/// ```
#[derive(Debug)]
pub struct StateMachine<P: Instance> {
id: u8,
block: *const rp2040_pac::pio0::RegisterBlock,
pub struct InstalledProgram<P> {
offset: u8,
length: u8,
side_set: SideSet,
wrap: Wrap,
_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> {}
impl<P: PIOExt> InstalledProgram<P> {
/// Clones this program handle so that it can be executed by two state machines at the same
/// time.
///
/// # Safety
///
/// This function is marked as unsafe because, once this function has been called, the
/// resulting handle can be used to call `PIO::uninstall()` while the program is still running.
///
/// 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<P> {
InstalledProgram {
offset: self.offset,
length: self.length,
side_set: self.side_set,
wrap: self.wrap,
_phantom: core::marker::PhantomData,
}
}
}
impl<P: Instance> StateMachine<P> {
/// State machine identifier (without a specified PIO block).
pub trait StateMachineIndex {
/// Numerical index of the state machine (0 to 3).
fn id() -> usize;
}
/// First state machine.
pub struct SM0;
/// Second state machine.
pub struct SM1;
/// Third state machine.
pub struct SM2;
/// Fourth state machine.
pub struct SM3;
impl StateMachineIndex for SM0 {
fn id() -> usize {
0
}
}
impl StateMachineIndex for SM1 {
fn id() -> usize {
1
}
}
impl StateMachineIndex for SM2 {
fn id() -> usize {
2
}
}
impl StateMachineIndex for SM3 {
fn id() -> usize {
3
}
}
/// Trait to identify a single state machine, as a generic type parameter to `UninitStateMachine`,
/// `InitStateMachine`, etc.
pub trait ValidStateMachine {
/// The PIO block to which this state machine belongs.
type PIO: PIOExt;
/// The index of this state machine (between 0 and 3).
fn id() -> usize;
}
/// First state machine of the first PIO block.
pub type PIO0SM0 = (PIO0, SM0);
/// Second state machine of the first PIO block.
pub type PIO0SM1 = (PIO0, SM1);
/// Third state machine of the first PIO block.
pub type PIO0SM2 = (PIO0, SM2);
/// Fourth state machine of the first PIO block.
pub type PIO0SM3 = (PIO0, SM3);
/// First state machine of the second PIO block.
pub type PIO1SM0 = (PIO1, SM0);
/// Second state machine of the second PIO block.
pub type PIO1SM1 = (PIO1, SM1);
/// Third state machine of the second PIO block.
pub type PIO1SM2 = (PIO1, SM2);
/// Fourth state machine of the second PIO block.
pub type PIO1SM3 = (PIO1, SM3);
impl<P: PIOExt, SM: StateMachineIndex> ValidStateMachine for (P, SM) {
type PIO = P;
fn id() -> usize {
SM::id()
}
}
/// PIO State Machine (uninitialized, without a program).
#[derive(Debug)]
pub struct UninitStateMachine<SM: ValidStateMachine> {
block: *const rp2040_pac::pio0::RegisterBlock,
sm: *const rp2040_pac::pio0::SM,
_phantom: core::marker::PhantomData<SM>,
}
// Safety: `UninitStateMachine` only uses atomic accesses to shared registers.
unsafe impl<SM: ValidStateMachine + Send> Send for UninitStateMachine<SM> {}
impl<SM: ValidStateMachine> UninitStateMachine<SM> {
/// Start and stop the state machine.
pub fn set_enabled(&self, enabled: bool) {
let mask = 1 << self.id;
fn set_enabled(&mut self, enabled: bool) {
// Bits 3:0 are SM_ENABLE.
let mask = 1 << SM::id();
if enabled {
self.block()
.ctrl
.modify(|r, w| unsafe { w.sm_enable().bits(r.sm_enable().bits() | mask) })
self.set_ctrl_bits(mask);
} else {
self.block()
.ctrl
.modify(|r, w| unsafe { w.sm_enable().bits(r.sm_enable().bits() & !mask) })
self.clear_ctrl_bits(mask);
}
}
fn restart(&self) {
self.block()
.ctrl
.write(|w| unsafe { w.sm_restart().bits(1 << self.id) });
fn restart(&mut self) {
// Bits 7:4 are SM_RESTART.
self.set_ctrl_bits(1 << (SM::id() + 4));
}
fn reset_clock(&self) {
self.block()
.ctrl
.write(|w| unsafe { w.clkdiv_restart().bits(1 << self.id) });
fn reset_clock(&mut self) {
// Bits 11:8 are CLKDIV_RESTART.
self.set_ctrl_bits(1 << (SM::id() + 8));
}
fn set_ctrl_bits(&mut self, bits: u32) {
const ATOMIC_SET_OFFSET: usize = 0x2000;
// Safety: We only use the atomic alias of the register.
unsafe {
(*self.block)
.ctrl
.as_ptr()
.add(ATOMIC_SET_OFFSET / 4)
.write_volatile(bits);
}
}
fn clear_ctrl_bits(&mut self, bits: u32) {
const ATOMIC_CLEAR_OFFSET: usize = 0x3000;
// Safety: We only use the atomic alias of the register.
unsafe {
(*self.block)
.ctrl
.as_ptr()
.add(ATOMIC_CLEAR_OFFSET / 4)
.write_volatile(bits);
}
}
fn set_clock_divisor(&self, divisor: f32) {
@ -233,55 +432,243 @@ impl<P: Instance> 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(&self, instruction: u16) {
fn set_instruction(&mut self, instruction: u16) {
self.sm()
.sm_instr
.write(|w| unsafe { w.sm0_instr().bits(instruction) })
}
fn sm(&self) -> &rp2040_pac::pio0::SM {
unsafe { &*self.sm }
}
}
/// PIO State Machine with an associated program.
pub struct StateMachine<SM: ValidStateMachine, State> {
sm: UninitStateMachine<SM>,
program: InstalledProgram<SM::PIO>,
_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<SM: ValidStateMachine, State> StateMachine<SM, State> {
/// Stops the state machine if it is still running and returns its program.
///
/// The program can be uninstalled to free space once it is no longer used by any state
/// machine.
pub fn uninit(
mut self,
_rx: Rx<SM>,
_tx: Tx<SM>,
) -> (UninitStateMachine<SM>, InstalledProgram<SM::PIO>) {
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) {
// TODO: Check if this function is safe to call while the state machine is running.
self.sm.set_instruction(instruction);
}
/// Check if the current instruction is stalled.
pub fn stalled(&self) -> bool {
self.sm().sm_execctrl.read().exec_stalled().bits()
self.sm.sm().sm_execctrl.read().exec_stalled().bits()
}
}
impl<SM: ValidStateMachine> StateMachine<SM, Stopped> {
/// Starts execution of the selected program.
pub fn start(mut self) -> StateMachine<SM, Running> {
// Enable SM
self.sm.set_enabled(true);
StateMachine {
sm: self.sm,
program: self.program,
_phantom: core::marker::PhantomData,
}
}
fn block(&self) -> &rp2040_pac::pio0::RegisterBlock {
unsafe { &*self.block }
/// Sets the pin directions for the specified pins.
///
/// The `pins` parameter specifies a set of pins as a mask, and `pindir` contains the
/// directions that are configured for these pins. The bits in both masks correspond to the pin
/// number. The user has to make sure that they do not select any pins that are in use by any
/// other state machines of the same PIO block.
///
/// This function needs to be called for sideset pins if they are supposed to be used as
/// output pins.
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().sm_pinctrl.write(|w| {
unsafe {
w.set_count().bits(1);
w.set_base().bits(pin as u8);
}
w
});
let set_pindirs = pio::Instruction {
operands: pio::InstructionOperands::SET {
destination: pio::SetDestination::PINDIRS,
data: ((pindir >> pin) & 0x1) as u8,
},
delay: 0,
side_set: None,
}
.encode(SideSet::new(false, 0, false));
self.sm.sm().sm_instr.write(|w| {
unsafe {
w.sm0_instr().bits(set_pindirs);
}
w
});
}
pin += 1;
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
});
}
}
fn sm(&self) -> &rp2040_pac::pio0::SM {
&self.block().sm[self.id as usize]
impl<P: PIOExt, SM: StateMachineIndex> StateMachine<(P, SM), Stopped> {
/// 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.
///
/// The function returns an object that, once destructed, restarts the clock dividers. This
/// object allows further state machines to be added if more than two shall be synchronized.
///
/// # Example
///
/// ```ignore
/// sm0.synchronize_with(sm1).and_with(sm2);
/// ```
pub fn synchronize_with<'sm, SM2: StateMachineIndex>(
&'sm mut self,
_other_sm: &'sm mut StateMachine<(P, SM2), Stopped>,
) -> Synchronize<'sm, (P, SM)> {
let sm_mask = (1 << SM::id()) | (1 << SM2::id());
Synchronize { sm: self, sm_mask }
}
}
/// Type which, once destructed, restarts the clock dividers for all selected state machines,
/// effectively synchronizing them.
pub struct Synchronize<'sm, SM: ValidStateMachine> {
sm: &'sm mut StateMachine<SM, Stopped>,
sm_mask: u32,
}
impl<'sm, P: PIOExt, SM: StateMachineIndex> Synchronize<'sm, (P, SM)> {
/// Adds another state machine to be synchronized.
pub fn and_with<SM2: StateMachineIndex>(
mut self,
_other_sm: &'sm mut StateMachine<(P, SM2), Stopped>,
) -> Self {
// Add another state machine index to the mask.
self.sm_mask |= 1 << SM2::id();
self
}
}
impl<'sm, SM: ValidStateMachine> Drop for Synchronize<'sm, SM> {
fn drop(&mut self) {
// Restart the clocks of all state machines specified by the mask.
// Bits 11:8 of CTRL contain CLKDIV_RESTART.
let sm_mask = self.sm_mask << 8;
const ATOMIC_SET_OFFSET: usize = 0x2000;
// Safety: We only use the atomic alias of the register.
unsafe {
(*self.sm.sm.block)
.ctrl
.as_ptr()
.add(ATOMIC_SET_OFFSET / 4)
.write_volatile(sm_mask as u32);
}
}
}
impl<SM: ValidStateMachine> StateMachine<SM, Running> {
/// Stops execution of the selected program.
pub fn stop(mut self) -> StateMachine<SM, Stopped> {
// Enable SM
self.sm.set_enabled(false);
StateMachine {
sm: self.sm,
program: self.program,
_phantom: core::marker::PhantomData,
}
}
}
/// PIO RX FIFO handle.
pub struct Rx<SM: ValidStateMachine> {
block: *const rp2040_pac::pio0::RegisterBlock,
_phantom: core::marker::PhantomData<SM>,
}
impl<SM: ValidStateMachine> Rx<SM> {
/// Get the next element from RX FIFO.
///
/// Returns `None` if the FIFO is empty.
pub fn read_rx(&self) -> Option<u32> {
let is_empty = self.block().fstat.read().rxempty().bits() & (1 << self.id) != 0;
pub fn read(&mut self) -> Option<u32> {
// Safety: The register is never written by software.
let is_empty = unsafe { &*self.block }.fstat.read().rxempty().bits() & (1 << SM::id()) != 0;
if is_empty {
return None;
}
Some(self.block().rxf[self.id as usize].read().bits())
// Safety: The register is unique to this Rx instance.
Some(unsafe { &*self.block }.rxf[SM::id() as usize].read().bits())
}
}
/// PIO TX FIFO handle.
pub struct Tx<SM: ValidStateMachine> {
block: *const rp2040_pac::pio0::RegisterBlock,
_phantom: core::marker::PhantomData<SM>,
}
impl<SM: ValidStateMachine> Tx<SM> {
/// Write an element to TX FIFO.
///
/// Returns `true` if the value was written to FIFO, `false` otherwise.
pub fn write_tx(&self, value: u32) -> bool {
let is_full = self.block().fstat.read().txfull().bits() & (1 << self.id) != 0;
pub fn write(&mut self, value: u32) -> bool {
// Safety: The register is never written by software.
let is_full = unsafe { &*self.block }.fstat.read().txfull().bits() & (1 << SM::id()) != 0;
if is_full {
return false;
}
self.block().txf[self.id as usize].write(|w| unsafe { w.bits(value) });
// Safety: The register is unique to this Tx instance.
unsafe { &*self.block }.txf[SM::id()].write(|w| unsafe { w.bits(value) });
true
}
@ -289,16 +676,16 @@ impl<P: Instance> StateMachine<P> {
/// PIO Interrupt controller.
#[derive(Debug)]
pub struct Interrupt<P: Instance> {
pub struct Interrupt<P: PIOExt> {
id: u8,
block: *const rp2040_pac::pio0::RegisterBlock,
_phantom: core::marker::PhantomData<P>,
}
// Safety: `Interrupt` provides exclusive access to interrupt registers.
unsafe impl<P: Instance + Send> Send for Interrupt<P> {}
unsafe impl<P: PIOExt + Send> Send for Interrupt<P> {}
impl<P: Instance> Interrupt<P> {
impl<P: PIOExt> Interrupt<P> {
/// Enable interrupts raised by state machines.
///
/// The PIO peripheral has 4 outside visible interrupts that can be raised by the state machines. Note that this
@ -551,18 +938,12 @@ impl ShiftDirection {
/// Builder to deploy a fully configured PIO program on one of the state
/// machines.
#[derive(Debug)]
pub struct PIOBuilder<'a> {
pub struct PIOBuilder<P> {
/// Clock divisor.
clock_divisor: f32,
/// Instructions of the program.
instructions: &'a [u16],
/// Origin where this program should be loaded.
instruction_origin: Option<u8>,
/// Wrapping behavior.
wrap: Wrap,
/// Side-set behavior.
side_set: SideSet,
/// Program location and configuration.
program: InstalledProgram<P>,
/// GPIO pin used by `jmp pin` instruction.
jmp_pin: u8,
@ -582,13 +963,13 @@ pub struct PIOBuilder<'a> {
pull_threshold: u8,
/// Number of bits shifted into `ISR` before autopush or conditional push will take place.
push_threshold: u8,
// Shift direction for `OUT` instruction.
/// Shift direction for `OUT` instruction.
out_shiftdir: ShiftDirection,
// Shift direction for `IN` instruction.
/// Shift direction for `IN` instruction.
in_shiftdir: ShiftDirection,
// Enable autopull.
/// Enable autopull.
autopull: bool,
// Enable autopush.
/// Enable autopush.
autopush: bool,
/// Number of pins asserted by a `SET`.
@ -605,17 +986,31 @@ pub struct PIOBuilder<'a> {
out_base: u8,
}
impl<'a> Default for PIOBuilder<'a> {
fn default() -> Self {
/// Buffer sharing configuration.
#[derive(Debug, Clone, Copy)]
pub enum Buffers {
/// No sharing.
RxTx,
/// The memory of the RX FIFO is given to the TX FIFO to double its depth.
OnlyTx,
/// The memory of the TX FIFO is given to the RX FIFO to double its depth.
OnlyRx,
}
/// Errors that occurred during `PIO::install`.
#[derive(Debug)]
pub enum InstallError {
/// There was not enough space for the instructions on the selected PIO.
NoSpace,
}
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<P>) -> Self {
PIOBuilder {
clock_divisor: 1.0,
instructions: &[],
instruction_origin: None,
wrap: Wrap {
source: 31,
target: 0,
},
side_set: SideSet::default(),
program: p,
jmp_pin: 0,
out_sticky: false,
inline_out: None,
@ -635,47 +1030,6 @@ impl<'a> Default for PIOBuilder<'a> {
out_base: 0,
}
}
}
/// Buffer sharing configuration.
#[derive(Debug, Clone, Copy)]
pub enum Buffers {
/// No sharing.
RxTx,
/// The memory of the RX FIFO is given to the TX FIFO to double its depth.
OnlyTx,
/// The memory of the TX FIFO is given to the RX FIFO to double its depth.
OnlyRx,
}
/// Errors that occurred during `PIOBuilder::build`.
#[derive(Debug)]
pub enum BuildError {
/// There was not enough space for the instructions on the selected PIO.
NoSpace,
}
impl<'a> PIOBuilder<'a> {
/// Set config settings based on information from the given [`pio::Program`].
/// Additional configuration may be needed in addition to this.
pub fn with_program(mut self, p: &'a Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>) -> Self {
self.instruction_origin = p.origin;
self.instructions(&p.code).wrap(p.wrap).side_set(p.side_set)
}
/// Set the instructions of the program.
pub fn instructions(mut self, instructions: &'a [u16]) -> Self {
self.instructions = instructions;
self
}
/// Set the wrap source and target.
///
/// The program will automatically jump from the wrap bottom to the wrap top in 0 cycles.
pub fn wrap(mut self, wrap: Wrap) -> Self {
self.wrap = wrap;
self
}
/// Set the pins asserted by `SET` instruction.
///
@ -725,12 +1079,6 @@ impl<'a> PIOBuilder<'a> {
self
}
/// Set the side-set status.
pub fn side_set(mut self, side_set: SideSet) -> Self {
self.side_set = side_set;
self
}
/// Set buffer sharing.
///
/// See [`Buffers`] for more information.
@ -813,23 +1161,22 @@ impl<'a> PIOBuilder<'a> {
}
/// Build the config and deploy it to a StateMachine.
pub fn build<P: Instance>(self, pio: &PIO<P>, sm: &StateMachine<P>) -> Result<(), BuildError> {
let offset =
match pio.add_program(self.instructions, self.instruction_origin, self.side_set) {
Some(o) => o,
None => return Err(BuildError::NoSpace),
};
#[allow(clippy::type_complexity)] // The return type cannot really be simplified.
pub fn build<SM: StateMachineIndex>(
self,
mut sm: UninitStateMachine<(P, SM)>,
) -> (StateMachine<(P, SM), Stopped>, Rx<(P, SM)>, Tx<(P, SM)>) {
let offset = self.program.offset;
// Stop the SM
// TODO: This should probably do before we write the program source code
sm.set_enabled(false);
// Write all configuration bits
sm.set_clock_divisor(self.clock_divisor);
sm.sm().sm_execctrl.write(|w| {
w.side_en().bit(self.side_set.optional());
w.side_pindir().bit(self.side_set.pindirs());
w.side_en().bit(self.program.side_set.optional());
w.side_pindir().bit(self.program.side_set.pindirs());
unsafe {
w.jmp_pin().bits(self.jmp_pin);
@ -847,8 +1194,9 @@ impl<'a> PIOBuilder<'a> {
w.out_sticky().bit(self.out_sticky);
unsafe {
w.wrap_top().bits(offset as u8 + self.wrap.source);
w.wrap_bottom().bits(offset as u8 + self.wrap.target);
w.wrap_top().bits(offset as u8 + self.program.wrap.source);
w.wrap_bottom()
.bits(offset as u8 + self.program.wrap.target);
}
let n = match self.mov_status {
@ -894,7 +1242,7 @@ impl<'a> PIOBuilder<'a> {
sm.sm().sm_pinctrl.write(|w| {
unsafe {
w.sideset_count().bits(self.side_set.bits());
w.sideset_count().bits(self.program.side_set.bits());
w.set_count().bits(self.set_count);
w.out_count().bits(self.out_count);
@ -921,9 +1269,22 @@ impl<'a> PIOBuilder<'a> {
.encode(),
);
// Enable SM
sm.set_enabled(true);
Ok(())
let rx = Rx {
block: sm.block,
_phantom: core::marker::PhantomData,
};
let tx = Tx {
block: sm.block,
_phantom: core::marker::PhantomData,
};
(
StateMachine {
sm,
program: self.program,
_phantom: core::marker::PhantomData,
},
rx,
tx,
)
}
}

View file

@ -1,2 +1,3 @@
//! Prelude
pub use crate::clocks::Clock as _rphal_clocks_Clock;
pub use crate::pio::PIOExt as _rphal_pio_PIOExt;