From e45e3e76b564b0589a24c1ca56599640238fd672 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 19 Dec 2023 10:11:19 +0100 Subject: docs: embassy-rp rustdoc and refactoring --- cyw43-pio/src/lib.rs | 20 +- embassy-nrf/README.md | 2 + embassy-rp/README.md | 23 + embassy-rp/src/clocks.rs | 26 + embassy-rp/src/lib.rs | 12 +- embassy-rp/src/pio.rs | 1121 ---------------------------------- embassy-rp/src/pio/instr.rs | 101 ++++ embassy-rp/src/pio/mod.rs | 1244 ++++++++++++++++++++++++++++++++++++++ embassy-rp/src/pio_instr_util.rs | 90 --- embassy-rp/src/uart/buffered.rs | 20 + embassy-rp/src/uart/mod.rs | 4 + embassy-rp/src/usb.rs | 10 + 12 files changed, 1450 insertions(+), 1223 deletions(-) create mode 100644 embassy-rp/README.md delete mode 100644 embassy-rp/src/pio.rs create mode 100644 embassy-rp/src/pio/instr.rs create mode 100644 embassy-rp/src/pio/mod.rs delete mode 100644 embassy-rp/src/pio_instr_util.rs diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 41b670324..8304740b3 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -6,8 +6,8 @@ use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::dma::Channel; use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate}; -use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; -use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef}; +use embassy_rp::pio::{instr, Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::{Peripheral, PeripheralRef}; use fixed::FixedU32; use pio_proc::pio_asm; @@ -152,10 +152,10 @@ where defmt::trace!("write={} read={}", write_bits, read_bits); unsafe { - pio_instr_util::set_x(&mut self.sm, write_bits as u32); - pio_instr_util::set_y(&mut self.sm, read_bits as u32); - pio_instr_util::set_pindir(&mut self.sm, 0b1); - pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + instr::set_x(&mut self.sm, write_bits as u32); + instr::set_y(&mut self.sm, read_bits as u32); + instr::set_pindir(&mut self.sm, 0b1); + instr::exec_jmp(&mut self.sm, self.wrap_target); } self.sm.set_enable(true); @@ -179,10 +179,10 @@ where defmt::trace!("write={} read={}", write_bits, read_bits); unsafe { - pio_instr_util::set_y(&mut self.sm, read_bits as u32); - pio_instr_util::set_x(&mut self.sm, write_bits as u32); - pio_instr_util::set_pindir(&mut self.sm, 0b1); - pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + instr::set_y(&mut self.sm, read_bits as u32); + instr::set_x(&mut self.sm, write_bits as u32); + instr::set_pindir(&mut self.sm, 0b1); + instr::exec_jmp(&mut self.sm, self.wrap_target); } // self.cs.set_low(); diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md index 129ec0c01..39de3854b 100644 --- a/embassy-nrf/README.md +++ b/embassy-nrf/README.md @@ -6,6 +6,8 @@ The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. +NOTE: The Embassy HALs can be used both for non-async and async operations. For async, you can choose which runtime you want to use. + ## EasyDMA considerations On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting diff --git a/embassy-rp/README.md b/embassy-rp/README.md new file mode 100644 index 000000000..cd79fe501 --- /dev/null +++ b/embassy-rp/README.md @@ -0,0 +1,23 @@ +# Embassy RP HAL + +HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. + +The Embassy RP HAL targets the Raspberry Pi 2040 family of hardware. The HAL implements both blocking and async APIs +for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to +complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. + +NOTE: The Embassy HALs can be used both for non-async and async operations. For async, you can choose which runtime you want to use. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 220665462..2f0645615 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -1,3 +1,4 @@ +//! Clock configuration for the RP2040 use core::arch::asm; use core::marker::PhantomData; use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; @@ -44,6 +45,7 @@ static CLOCKS: Clocks = Clocks { rtc: AtomicU16::new(0), }; +/// Enumeration of supported clock sources. #[repr(u8)] #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -57,15 +59,24 @@ pub enum PeriClkSrc { // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , } +/// CLock configuration. #[non_exhaustive] pub struct ClockConfig { + /// Ring oscillator configuration. pub rosc: Option, + /// External oscillator configuration. pub xosc: Option, + /// Reference clock configuration. pub ref_clk: RefClkConfig, + /// System clock configuration. pub sys_clk: SysClkConfig, + /// Peripheral clock source configuration. pub peri_clk_src: Option, + /// USB clock configuration. pub usb_clk: Option, + /// ADC clock configuration. pub adc_clk: Option, + /// RTC clock configuration. pub rtc_clk: Option, // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, @@ -189,31 +200,46 @@ pub enum RoscRange { TooHigh = pac::rosc::vals::FreqRange::TOOHIGH.0, } +/// On-chip ring oscillator configuration. pub struct RoscConfig { /// Final frequency of the oscillator, after the divider has been applied. /// The oscillator has a nominal frequency of 6.5MHz at medium range with /// divider 16 and all drive strengths set to 0, other values should be /// measured in situ. pub hz: u32, + /// Oscillator range. pub range: RoscRange, + /// Drive strength for oscillator. pub drive_strength: [u8; 8], + /// Output divider. pub div: u16, } +/// Crystal oscillator configuration. pub struct XoscConfig { + /// Final frequency of the oscillator. pub hz: u32, + /// Configuring PLL for the system clock. pub sys_pll: Option, + /// Configuring PLL for the USB clock. pub usb_pll: Option, + /// Multiplier for the startup delay. pub delay_multiplier: u32, } +/// PLL configuration. pub struct PllConfig { + /// Reference divisor. pub refdiv: u8, + /// Feedback divisor. pub fbdiv: u16, + /// Output divisor 1. pub post_div1: u8, + /// Output divisor 2. pub post_div2: u8, } +/// Reference pub struct RefClkConfig { pub src: RefClkSrc, pub div: u8, diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 5151323a9..2c49787df 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; @@ -31,9 +32,7 @@ pub mod usb; pub mod watchdog; // PIO -// TODO: move `pio_instr_util` and `relocate` to inside `pio` pub mod pio; -pub mod pio_instr_util; pub(crate) mod relocate; // Reexports @@ -302,11 +301,14 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { Ok(()) } +/// HAL configuration for RP. pub mod config { use crate::clocks::ClockConfig; + /// HAL configuration passed when initializing. #[non_exhaustive] pub struct Config { + /// Clock configuration. pub clocks: ClockConfig, } @@ -319,12 +321,18 @@ pub mod config { } impl Config { + /// Create a new configuration with the provided clock config. pub fn new(clocks: ClockConfig) -> Self { Self { clocks } } } } +/// Initialize the `embassy-rp` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. pub fn init(config: config::Config) -> Peripherals { // Do this first, so that it panics if user is calling `init` a second time // before doing anything important. diff --git a/embassy-rp/src/pio.rs b/embassy-rp/src/pio.rs deleted file mode 100644 index 97dfce2e6..000000000 --- a/embassy-rp/src/pio.rs +++ /dev/null @@ -1,1121 +0,0 @@ -use core::future::Future; -use core::marker::PhantomData; -use core::pin::Pin as FuturePin; -use core::sync::atomic::{compiler_fence, Ordering}; -use core::task::{Context, Poll}; - -use atomic_polyfill::{AtomicU32, AtomicU8}; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; -use embassy_sync::waitqueue::AtomicWaker; -use fixed::types::extra::U8; -use fixed::FixedU32; -use pac::io::vals::Gpio0ctrlFuncsel; -use pac::pio::vals::SmExecctrlStatusSel; -use pio::{Program, SideSet, Wrap}; - -use crate::dma::{Channel, Transfer, Word}; -use crate::gpio::sealed::Pin as SealedPin; -use crate::gpio::{self, AnyPin, Drive, Level, Pull, SlewRate}; -use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; -use crate::pac::dma::vals::TreqSel; -use crate::relocate::RelocatedProgram; -use crate::{pac, peripherals, pio_instr_util, RegExt}; - -pub struct Wakers([AtomicWaker; 12]); - -impl Wakers { - #[inline(always)] - fn fifo_in(&self) -> &[AtomicWaker] { - &self.0[0..4] - } - #[inline(always)] - fn fifo_out(&self) -> &[AtomicWaker] { - &self.0[4..8] - } - #[inline(always)] - fn irq(&self) -> &[AtomicWaker] { - &self.0[8..12] - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum FifoJoin { - /// Both TX and RX fifo is enabled - #[default] - Duplex, - /// Rx fifo twice as deep. TX fifo disabled - RxOnly, - /// Tx fifo twice as deep. RX fifo disabled - TxOnly, -} - -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum ShiftDirection { - #[default] - Right = 1, - Left = 0, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum Direction { - In = 0, - Out = 1, -} - -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum StatusSource { - #[default] - TxFifoLevel = 0, - RxFifoLevel = 1, -} - -const RXNEMPTY_MASK: u32 = 1 << 0; -const TXNFULL_MASK: u32 = 1 << 4; -const SMIRQ_MASK: u32 = 1 << 8; - -pub struct InterruptHandler { - _pio: PhantomData, -} - -impl Handler for InterruptHandler { - unsafe fn on_interrupt() { - let ints = PIO::PIO.irqs(0).ints().read().0; - for bit in 0..12 { - if ints & (1 << bit) != 0 { - PIO::wakers().0[bit].wake(); - } - } - PIO::PIO.irqs(0).inte().write_clear(|m| m.0 = ints); - } -} - -/// Future that waits for TX-FIFO to become writable -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct FifoOutFuture<'a, 'd, PIO: Instance, const SM: usize> { - sm_tx: &'a mut StateMachineTx<'d, PIO, SM>, - value: u32, -} - -impl<'a, 'd, PIO: Instance, const SM: usize> FifoOutFuture<'a, 'd, PIO, SM> { - pub fn new(sm: &'a mut StateMachineTx<'d, PIO, SM>, value: u32) -> Self { - FifoOutFuture { sm_tx: sm, value } - } -} - -impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoOutFuture<'a, 'd, PIO, SM> { - type Output = (); - fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { - //debug!("Poll {},{}", PIO::PIO_NO, SM); - let value = self.value; - if self.get_mut().sm_tx.try_push(value) { - Poll::Ready(()) - } else { - PIO::wakers().fifo_out()[SM].register(cx.waker()); - PIO::PIO.irqs(0).inte().write_set(|m| { - m.0 = TXNFULL_MASK << SM; - }); - // debug!("Pending"); - Poll::Pending - } - } -} - -impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoOutFuture<'a, 'd, PIO, SM> { - fn drop(&mut self) { - PIO::PIO.irqs(0).inte().write_clear(|m| { - m.0 = TXNFULL_MASK << SM; - }); - } -} - -/// Future that waits for RX-FIFO to become readable -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct FifoInFuture<'a, 'd, PIO: Instance, const SM: usize> { - sm_rx: &'a mut StateMachineRx<'d, PIO, SM>, -} - -impl<'a, 'd, PIO: Instance, const SM: usize> FifoInFuture<'a, 'd, PIO, SM> { - pub fn new(sm: &'a mut StateMachineRx<'d, PIO, SM>) -> Self { - FifoInFuture { sm_rx: sm } - } -} - -impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoInFuture<'a, 'd, PIO, SM> { - type Output = u32; - fn poll(mut self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { - //debug!("Poll {},{}", PIO::PIO_NO, SM); - if let Some(v) = self.sm_rx.try_pull() { - Poll::Ready(v) - } else { - PIO::wakers().fifo_in()[SM].register(cx.waker()); - PIO::PIO.irqs(0).inte().write_set(|m| { - m.0 = RXNEMPTY_MASK << SM; - }); - //debug!("Pending"); - Poll::Pending - } - } -} - -impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoInFuture<'a, 'd, PIO, SM> { - fn drop(&mut self) { - PIO::PIO.irqs(0).inte().write_clear(|m| { - m.0 = RXNEMPTY_MASK << SM; - }); - } -} - -/// Future that waits for IRQ -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct IrqFuture<'a, 'd, PIO: Instance> { - pio: PhantomData<&'a mut Irq<'d, PIO, 0>>, - irq_no: u8, -} - -impl<'a, 'd, PIO: Instance> Future for IrqFuture<'a, 'd, PIO> { - type Output = (); - fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { - //debug!("Poll {},{}", PIO::PIO_NO, SM); - - // Check if IRQ flag is already set - if PIO::PIO.irq().read().0 & (1 << self.irq_no) != 0 { - PIO::PIO.irq().write(|m| m.0 = 1 << self.irq_no); - return Poll::Ready(()); - } - - PIO::wakers().irq()[self.irq_no as usize].register(cx.waker()); - PIO::PIO.irqs(0).inte().write_set(|m| { - m.0 = SMIRQ_MASK << self.irq_no; - }); - Poll::Pending - } -} - -impl<'a, 'd, PIO: Instance> Drop for IrqFuture<'a, 'd, PIO> { - fn drop(&mut self) { - PIO::PIO.irqs(0).inte().write_clear(|m| { - m.0 = SMIRQ_MASK << self.irq_no; - }); - } -} - -pub struct Pin<'l, PIO: Instance> { - pin: PeripheralRef<'l, AnyPin>, - pio: PhantomData, -} - -impl<'l, PIO: Instance> Pin<'l, PIO> { - /// Set the pin's drive strength. - #[inline] - pub fn set_drive_strength(&mut self, strength: Drive) { - self.pin.pad_ctrl().modify(|w| { - w.set_drive(match strength { - Drive::_2mA => pac::pads::vals::Drive::_2MA, - Drive::_4mA => pac::pads::vals::Drive::_4MA, - Drive::_8mA => pac::pads::vals::Drive::_8MA, - Drive::_12mA => pac::pads::vals::Drive::_12MA, - }); - }); - } - - // Set the pin's slew rate. - #[inline] - pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { - self.pin.pad_ctrl().modify(|w| { - w.set_slewfast(slew_rate == SlewRate::Fast); - }); - } - - /// Set the pin's pull. - #[inline] - pub fn set_pull(&mut self, pull: Pull) { - self.pin.pad_ctrl().modify(|w| { - w.set_pue(pull == Pull::Up); - w.set_pde(pull == Pull::Down); - }); - } - - /// Set the pin's schmitt trigger. - #[inline] - pub fn set_schmitt(&mut self, enable: bool) { - self.pin.pad_ctrl().modify(|w| { - w.set_schmitt(enable); - }); - } - - pub fn set_input_sync_bypass<'a>(&mut self, bypass: bool) { - let mask = 1 << self.pin(); - if bypass { - PIO::PIO.input_sync_bypass().write_set(|w| *w = mask); - } else { - PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask); - } - } - - pub fn pin(&self) -> u8 { - self.pin._pin() - } -} - -pub struct StateMachineRx<'d, PIO: Instance, const SM: usize> { - pio: PhantomData<&'d mut PIO>, -} - -impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { - pub fn empty(&self) -> bool { - PIO::PIO.fstat().read().rxempty() & (1u8 << SM) != 0 - } - - pub fn full(&self) -> bool { - PIO::PIO.fstat().read().rxfull() & (1u8 << SM) != 0 - } - - pub fn level(&self) -> u8 { - (PIO::PIO.flevel().read().0 >> (SM * 8 + 4)) as u8 & 0x0f - } - - pub fn stalled(&self) -> bool { - let fdebug = PIO::PIO.fdebug(); - let ret = fdebug.read().rxstall() & (1 << SM) != 0; - if ret { - fdebug.write(|w| w.set_rxstall(1 << SM)); - } - ret - } - - pub fn underflowed(&self) -> bool { - let fdebug = PIO::PIO.fdebug(); - let ret = fdebug.read().rxunder() & (1 << SM) != 0; - if ret { - fdebug.write(|w| w.set_rxunder(1 << SM)); - } - ret - } - - pub fn pull(&mut self) -> u32 { - PIO::PIO.rxf(SM).read() - } - - pub fn try_pull(&mut self) -> Option { - if self.empty() { - return None; - } - Some(self.pull()) - } - - pub fn wait_pull<'a>(&'a mut self) -> FifoInFuture<'a, 'd, PIO, SM> { - FifoInFuture::new(self) - } - - pub fn dma_pull<'a, C: Channel, W: Word>( - &'a mut self, - ch: PeripheralRef<'a, C>, - data: &'a mut [W], - ) -> Transfer<'a, C> { - let pio_no = PIO::PIO_NO; - let p = ch.regs(); - p.write_addr().write_value(data.as_ptr() as u32); - p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); - p.trans_count().write_value(data.len() as u32); - compiler_fence(Ordering::SeqCst); - p.ctrl_trig().write(|w| { - // Set RX DREQ for this statemachine - w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8 + 4)); - w.set_data_size(W::size()); - w.set_chain_to(ch.number()); - w.set_incr_read(false); - w.set_incr_write(true); - w.set_en(true); - }); - compiler_fence(Ordering::SeqCst); - Transfer::new(ch) - } -} - -pub struct StateMachineTx<'d, PIO: Instance, const SM: usize> { - pio: PhantomData<&'d mut PIO>, -} - -impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { - pub fn empty(&self) -> bool { - PIO::PIO.fstat().read().txempty() & (1u8 << SM) != 0 - } - pub fn full(&self) -> bool { - PIO::PIO.fstat().read().txfull() & (1u8 << SM) != 0 - } - - pub fn level(&self) -> u8 { - (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f - } - - pub fn stalled(&self) -> bool { - let fdebug = PIO::PIO.fdebug(); - let ret = fdebug.read().txstall() & (1 << SM) != 0; - if ret { - fdebug.write(|w| w.set_txstall(1 << SM)); - } - ret - } - - pub fn overflowed(&self) -> bool { - let fdebug = PIO::PIO.fdebug(); - let ret = fdebug.read().txover() & (1 << SM) != 0; - if ret { - fdebug.write(|w| w.set_txover(1 << SM)); - } - ret - } - - pub fn push(&mut self, v: u32) { - PIO::PIO.txf(SM).write_value(v); - } - - pub fn try_push(&mut self, v: u32) -> bool { - if self.full() { - return false; - } - self.push(v); - true - } - - pub fn wait_push<'a>(&'a mut self, value: u32) -> FifoOutFuture<'a, 'd, PIO, SM> { - FifoOutFuture::new(self, value) - } - - pub fn dma_push<'a, C: Channel, W: Word>(&'a mut self, ch: PeripheralRef<'a, C>, data: &'a [W]) -> Transfer<'a, C> { - let pio_no = PIO::PIO_NO; - let p = ch.regs(); - p.read_addr().write_value(data.as_ptr() as u32); - p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32); - p.trans_count().write_value(data.len() as u32); - compiler_fence(Ordering::SeqCst); - p.ctrl_trig().write(|w| { - // Set TX DREQ for this statemachine - w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8)); - w.set_data_size(W::size()); - w.set_chain_to(ch.number()); - w.set_incr_read(true); - w.set_incr_write(false); - w.set_en(true); - }); - compiler_fence(Ordering::SeqCst); - Transfer::new(ch) - } -} - -pub struct StateMachine<'d, PIO: Instance, const SM: usize> { - rx: StateMachineRx<'d, PIO, SM>, - tx: StateMachineTx<'d, PIO, SM>, -} - -impl<'d, PIO: Instance, const SM: usize> Drop for StateMachine<'d, PIO, SM> { - fn drop(&mut self) { - PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(1 << SM)); - on_pio_drop::(); - } -} - -fn assert_consecutive<'d, PIO: Instance>(pins: &[&Pin<'d, PIO>]) { - for (p1, p2) in pins.iter().zip(pins.iter().skip(1)) { - // purposely does not allow wrap-around because we can't claim pins 30 and 31. - assert!(p1.pin() + 1 == p2.pin(), "pins must be consecutive"); - } -} - -#[derive(Clone, Copy, Default, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[non_exhaustive] -pub struct ExecConfig { - pub side_en: bool, - pub side_pindir: bool, - pub jmp_pin: u8, - pub wrap_top: u8, - pub wrap_bottom: u8, -} - -#[derive(Clone, Copy, Default, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ShiftConfig { - pub threshold: u8, - pub direction: ShiftDirection, - pub auto_fill: bool, -} - -#[derive(Clone, Copy, Default, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PinConfig { - pub sideset_count: u8, - pub set_count: u8, - pub out_count: u8, - pub in_base: u8, - pub sideset_base: u8, - pub set_base: u8, - pub out_base: u8, -} - -#[derive(Clone, Copy, Debug)] -pub struct Config<'d, PIO: Instance> { - // CLKDIV - pub clock_divider: FixedU32, - // EXECCTRL - pub out_en_sel: u8, - pub inline_out_en: bool, - pub out_sticky: bool, - pub status_sel: StatusSource, - pub status_n: u8, - exec: ExecConfig, - origin: Option, - // SHIFTCTRL - pub fifo_join: FifoJoin, - pub shift_in: ShiftConfig, - pub shift_out: ShiftConfig, - // PINCTRL - pins: PinConfig, - in_count: u8, - _pio: PhantomData<&'d mut PIO>, -} - -impl<'d, PIO: Instance> Default for Config<'d, PIO> { - fn default() -> Self { - Self { - clock_divider: 1u8.into(), - out_en_sel: Default::default(), - inline_out_en: Default::default(), - out_sticky: Default::default(), - status_sel: Default::default(), - status_n: Default::default(), - exec: Default::default(), - origin: Default::default(), - fifo_join: Default::default(), - shift_in: Default::default(), - shift_out: Default::default(), - pins: Default::default(), - in_count: Default::default(), - _pio: Default::default(), - } - } -} - -impl<'d, PIO: Instance> Config<'d, PIO> { - pub fn get_exec(&self) -> ExecConfig { - self.exec - } - pub unsafe fn set_exec(&mut self, e: ExecConfig) { - self.exec = e; - } - - pub fn get_pins(&self) -> PinConfig { - self.pins - } - pub unsafe fn set_pins(&mut self, p: PinConfig) { - self.pins = p; - } - - /// Configures this state machine to use the given program, including jumping to the origin - /// of the program. The state machine is not started. - /// - /// `side_set` sets the range of pins affected by side-sets. The range must be consecutive. - /// Side-set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be - /// effective. - pub fn use_program(&mut self, prog: &LoadedProgram<'d, PIO>, side_set: &[&Pin<'d, PIO>]) { - assert!((prog.side_set.bits() - prog.side_set.optional() as u8) as usize == side_set.len()); - assert_consecutive(side_set); - self.exec.side_en = prog.side_set.optional(); - self.exec.side_pindir = prog.side_set.pindirs(); - self.exec.wrap_bottom = prog.wrap.target; - self.exec.wrap_top = prog.wrap.source; - self.pins.sideset_count = prog.side_set.bits(); - self.pins.sideset_base = side_set.first().map_or(0, |p| p.pin()); - self.origin = Some(prog.origin); - } - - pub fn set_jmp_pin(&mut self, pin: &Pin<'d, PIO>) { - self.exec.jmp_pin = pin.pin(); - } - - /// Sets the range of pins affected by SET instructions. The range must be consecutive. - /// Set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be - /// effective. - pub fn set_set_pins(&mut self, pins: &[&Pin<'d, PIO>]) { - assert!(pins.len() <= 5); - assert_consecutive(pins); - self.pins.set_base = pins.first().map_or(0, |p| p.pin()); - self.pins.set_count = pins.len() as u8; - } - - /// Sets the range of pins affected by OUT instructions. The range must be consecutive. - /// Out pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be - /// effective. - pub fn set_out_pins(&mut self, pins: &[&Pin<'d, PIO>]) { - assert_consecutive(pins); - self.pins.out_base = pins.first().map_or(0, |p| p.pin()); - self.pins.out_count = pins.len() as u8; - } - - /// Sets the range of pins used by IN instructions. The range must be consecutive. - /// In pins must configured as inputs using [`StateMachine::set_pin_dirs`] to be - /// effective. - pub fn set_in_pins(&mut self, pins: &[&Pin<'d, PIO>]) { - assert_consecutive(pins); - self.pins.in_base = pins.first().map_or(0, |p| p.pin()); - self.in_count = pins.len() as u8; - } -} - -impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { - pub fn set_config(&mut self, config: &Config<'d, PIO>) { - // sm expects 0 for 65536, truncation makes that happen - assert!(config.clock_divider <= 65536, "clkdiv must be <= 65536"); - assert!(config.clock_divider >= 1, "clkdiv must be >= 1"); - assert!(config.out_en_sel < 32, "out_en_sel must be < 32"); - assert!(config.status_n < 32, "status_n must be < 32"); - // sm expects 0 for 32, truncation makes that happen - assert!(config.shift_in.threshold <= 32, "shift_in.threshold must be <= 32"); - assert!(config.shift_out.threshold <= 32, "shift_out.threshold must be <= 32"); - let sm = Self::this_sm(); - sm.clkdiv().write(|w| w.0 = config.clock_divider.to_bits() << 8); - sm.execctrl().write(|w| { - w.set_side_en(config.exec.side_en); - w.set_side_pindir(config.exec.side_pindir); - w.set_jmp_pin(config.exec.jmp_pin); - w.set_out_en_sel(config.out_en_sel); - w.set_inline_out_en(config.inline_out_en); - w.set_out_sticky(config.out_sticky); - w.set_wrap_top(config.exec.wrap_top); - w.set_wrap_bottom(config.exec.wrap_bottom); - w.set_status_sel(match config.status_sel { - StatusSource::TxFifoLevel => SmExecctrlStatusSel::TXLEVEL, - StatusSource::RxFifoLevel => SmExecctrlStatusSel::RXLEVEL, - }); - w.set_status_n(config.status_n); - }); - sm.shiftctrl().write(|w| { - w.set_fjoin_rx(config.fifo_join == FifoJoin::RxOnly); - w.set_fjoin_tx(config.fifo_join == FifoJoin::TxOnly); - w.set_pull_thresh(config.shift_out.threshold); - w.set_push_thresh(config.shift_in.threshold); - w.set_out_shiftdir(config.shift_out.direction == ShiftDirection::Right); - w.set_in_shiftdir(config.shift_in.direction == ShiftDirection::Right); - w.set_autopull(config.shift_out.auto_fill); - w.set_autopush(config.shift_in.auto_fill); - }); - sm.pinctrl().write(|w| { - w.set_sideset_count(config.pins.sideset_count); - w.set_set_count(config.pins.set_count); - w.set_out_count(config.pins.out_count); - w.set_in_base(config.pins.in_base); - w.set_sideset_base(config.pins.sideset_base); - w.set_set_base(config.pins.set_base); - w.set_out_base(config.pins.out_base); - }); - if let Some(origin) = config.origin { - unsafe { pio_instr_util::exec_jmp(self, origin) } - } - } - - #[inline(always)] - fn this_sm() -> crate::pac::pio::StateMachine { - PIO::PIO.sm(SM) - } - - pub fn restart(&mut self) { - let mask = 1u8 << SM; - PIO::PIO.ctrl().write_set(|w| w.set_sm_restart(mask)); - } - pub fn set_enable(&mut self, enable: bool) { - let mask = 1u8 << SM; - if enable { - PIO::PIO.ctrl().write_set(|w| w.set_sm_enable(mask)); - } else { - PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(mask)); - } - } - - pub fn is_enabled(&self) -> bool { - PIO::PIO.ctrl().read().sm_enable() & (1u8 << SM) != 0 - } - - pub fn clkdiv_restart(&mut self) { - let mask = 1u8 << SM; - PIO::PIO.ctrl().write_set(|w| w.set_clkdiv_restart(mask)); - } - - fn with_paused(&mut self, f: impl FnOnce(&mut Self)) { - let enabled = self.is_enabled(); - self.set_enable(false); - let pincfg = Self::this_sm().pinctrl().read(); - let execcfg = Self::this_sm().execctrl().read(); - Self::this_sm().execctrl().write_clear(|w| w.set_out_sticky(true)); - f(self); - Self::this_sm().pinctrl().write_value(pincfg); - Self::this_sm().execctrl().write_value(execcfg); - self.set_enable(enabled); - } - - /// Sets pin directions. This pauses the current state machine to run `SET` commands - /// and temporarily unsets the `OUT_STICKY` bit. - pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) { - self.with_paused(|sm| { - for pin in pins { - Self::this_sm().pinctrl().write(|w| { - w.set_set_base(pin.pin()); - w.set_set_count(1); - }); - // SET PINDIRS, (dir) - unsafe { sm.exec_instr(0b111_00000_100_00000 | dir as u16) }; - } - }); - } - - /// Sets pin output values. This pauses the current state machine to run - /// `SET` commands and temporarily unsets the `OUT_STICKY` bit. - pub fn set_pins(&mut self, level: Level, pins: &[&Pin<'d, PIO>]) { - self.with_paused(|sm| { - for pin in pins { - Self::this_sm().pinctrl().write(|w| { - w.set_set_base(pin.pin()); - w.set_set_count(1); - }); - // SET PINS, (dir) - unsafe { sm.exec_instr(0b111_00000_000_00000 | level as u16) }; - } - }); - } - - pub fn clear_fifos(&mut self) { - // Toggle FJOIN_RX to flush FIFOs - let shiftctrl = Self::this_sm().shiftctrl(); - shiftctrl.modify(|w| { - w.set_fjoin_rx(!w.fjoin_rx()); - }); - shiftctrl.modify(|w| { - w.set_fjoin_rx(!w.fjoin_rx()); - }); - } - - pub unsafe fn exec_instr(&mut self, instr: u16) { - Self::this_sm().instr().write(|w| w.set_instr(instr)); - } - - pub fn rx(&mut self) -> &mut StateMachineRx<'d, PIO, SM> { - &mut self.rx - } - pub fn tx(&mut self) -> &mut StateMachineTx<'d, PIO, SM> { - &mut self.tx - } - pub fn rx_tx(&mut self) -> (&mut StateMachineRx<'d, PIO, SM>, &mut StateMachineTx<'d, PIO, SM>) { - (&mut self.rx, &mut self.tx) - } -} - -pub struct Common<'d, PIO: Instance> { - instructions_used: u32, - pio: PhantomData<&'d mut PIO>, -} - -impl<'d, PIO: Instance> Drop for Common<'d, PIO> { - fn drop(&mut self) { - on_pio_drop::(); - } -} - -pub struct InstanceMemory<'d, PIO: Instance> { - used_mask: u32, - pio: PhantomData<&'d mut PIO>, -} - -pub struct LoadedProgram<'d, PIO: Instance> { - pub used_memory: InstanceMemory<'d, PIO>, - pub origin: u8, - pub wrap: Wrap, - pub side_set: SideSet, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum LoadError { - /// Insufficient consecutive free instruction space to load program. - InsufficientSpace, - /// Loading the program would overwrite an instruction address already - /// used by another program. - AddressInUse(usize), -} - -impl<'d, PIO: Instance> Common<'d, PIO> { - /// Load a PIO program. This will automatically relocate the program to - /// an available chunk of free instruction memory if the program origin - /// was not explicitly specified, otherwise it will attempt to load the - /// program only at its origin. - pub fn load_program(&mut self, prog: &Program) -> LoadedProgram<'d, PIO> { - match self.try_load_program(prog) { - Ok(r) => r, - Err(e) => panic!("Failed to load PIO program: {:?}", e), - } - } - - /// Load a PIO program. This will automatically relocate the program to - /// an available chunk of free instruction memory if the program origin - /// was not explicitly specified, otherwise it will attempt to load the - /// program only at its origin. - pub fn try_load_program( - &mut self, - prog: &Program, - ) -> Result, LoadError> { - match prog.origin { - Some(origin) => self - .try_load_program_at(prog, origin) - .map_err(|a| LoadError::AddressInUse(a)), - None => { - // naively search for free space, allowing wraparound since - // PIO does support that. with only 32 instruction slots it - // doesn't make much sense to do anything more fancy. - let mut origin = 0; - while origin < 32 { - match self.try_load_program_at(prog, origin as _) { - Ok(r) => return Ok(r), - Err(a) => origin = a + 1, - } - } - Err(LoadError::InsufficientSpace) - } - } - } - - fn try_load_program_at( - &mut self, - prog: &Program, - origin: u8, - ) -> Result, usize> { - let prog = RelocatedProgram::new_with_origin(prog, origin); - let used_memory = self.try_write_instr(prog.origin() as _, prog.code())?; - Ok(LoadedProgram { - used_memory, - origin: prog.origin(), - wrap: prog.wrap(), - side_set: prog.side_set(), - }) - } - - fn try_write_instr(&mut self, start: usize, instrs: I) -> Result, usize> - where - I: Iterator, - { - let mut used_mask = 0; - for (i, instr) in instrs.enumerate() { - // wrapping around the end of program memory is valid, let's make use of that. - let addr = (i + start) % 32; - let mask = 1 << addr; - if (self.instructions_used | used_mask) & mask != 0 { - return Err(addr); - } - PIO::PIO.instr_mem(addr).write(|w| { - w.set_instr_mem(instr); - }); - used_mask |= mask; - } - self.instructions_used |= used_mask; - Ok(InstanceMemory { - used_mask, - pio: PhantomData, - }) - } - - /// Free instruction memory. This is always possible but unsafe if any - /// state machine is still using this bit of memory. - pub unsafe fn free_instr(&mut self, instrs: InstanceMemory) { - self.instructions_used &= !instrs.used_mask; - } - - pub fn set_input_sync_bypass<'a>(&'a mut self, bypass: u32, mask: u32) { - // this can interfere with per-pin bypass functions. splitting the - // modification is going to be fine since nothing that relies on - // it can reasonably run before we finish. - PIO::PIO.input_sync_bypass().write_set(|w| *w = mask & bypass); - PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask & !bypass); - } - - pub fn get_input_sync_bypass(&self) -> u32 { - PIO::PIO.input_sync_bypass().read() - } - - /// Register a pin for PIO usage. Pins will be released from the PIO block - /// (i.e., have their `FUNCSEL` reset to `NULL`) when the [`Common`] *and* - /// all [`StateMachine`]s for this block have been dropped. **Other members - /// of [`Pio`] do not keep pin registrations alive.** - pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { - into_ref!(pin); - pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); - // we can be relaxed about this because we're &mut here and nothing is cached - PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); - Pin { - pin: pin.into_ref().map_into(), - pio: PhantomData::default(), - } - } - - pub fn apply_sm_batch(&mut self, f: impl FnOnce(&mut PioBatch<'d, PIO>)) { - let mut batch = PioBatch { - clkdiv_restart: 0, - sm_restart: 0, - sm_enable_mask: 0, - sm_enable: 0, - _pio: PhantomData, - }; - f(&mut batch); - PIO::PIO.ctrl().modify(|w| { - w.set_clkdiv_restart(batch.clkdiv_restart); - w.set_sm_restart(batch.sm_restart); - w.set_sm_enable((w.sm_enable() & !batch.sm_enable_mask) | batch.sm_enable); - }); - } -} - -pub struct PioBatch<'a, PIO: Instance> { - clkdiv_restart: u8, - sm_restart: u8, - sm_enable_mask: u8, - sm_enable: u8, - _pio: PhantomData<&'a PIO>, -} - -impl<'a, PIO: Instance> PioBatch<'a, PIO> { - pub fn restart_clockdiv(&mut self, _sm: &mut StateMachine<'a, PIO, SM>) { - self.clkdiv_restart |= 1 << SM; - } - - pub fn restart(&mut self, _sm: &mut StateMachine<'a, PIO, SM>) { - self.clkdiv_restart |= 1 << SM; - } - - pub fn set_enable(&mut self, _sm: &mut StateMachine<'a, PIO, SM>, enable: bool) { - self.sm_enable_mask |= 1 << SM; - self.sm_enable |= (enable as u8) << SM; - } -} - -pub struct Irq<'d, PIO: Instance, const N: usize> { - pio: PhantomData<&'d mut PIO>, -} - -impl<'d, PIO: Instance, const N: usize> Irq<'d, PIO, N> { - pub fn wait<'a>(&'a mut self) -> IrqFuture<'a, 'd, PIO> { - IrqFuture { - pio: PhantomData, - irq_no: N as u8, - } - } -} - -#[derive(Clone)] -pub struct IrqFlags<'d, PIO: Instance> { - pio: PhantomData<&'d mut PIO>, -} - -impl<'d, PIO: Instance> IrqFlags<'d, PIO> { - pub fn check(&self, irq_no: u8) -> bool { - assert!(irq_no < 8); - self.check_any(1 << irq_no) - } - - pub fn check_any(&self, irqs: u8) -> bool { - PIO::PIO.irq().read().irq() & irqs != 0 - } - - pub fn check_all(&self, irqs: u8) -> bool { - PIO::PIO.irq().read().irq() & irqs == irqs - } - - pub fn clear(&self, irq_no: usize) { - assert!(irq_no < 8); - self.clear_all(1 << irq_no); - } - - pub fn clear_all(&self, irqs: u8) { - PIO::PIO.irq().write(|w| w.set_irq(irqs)) - } - - pub fn set(&self, irq_no: usize) { - assert!(irq_no < 8); - self.set_all(1 << irq_no); - } - - pub fn set_all(&self, irqs: u8) { - PIO::PIO.irq_force().write(|w| w.set_irq_force(irqs)) - } -} - -pub struct Pio<'d, PIO: Instance> { - pub common: Common<'d, PIO>, - pub irq_flags: IrqFlags<'d, PIO>, - pub irq0: Irq<'d, PIO, 0>, - pub irq1: Irq<'d, PIO, 1>, - pub irq2: Irq<'d, PIO, 2>, - pub irq3: Irq<'d, PIO, 3>, - pub sm0: StateMachine<'d, PIO, 0>, - pub sm1: StateMachine<'d, PIO, 1>, - pub sm2: StateMachine<'d, PIO, 2>, - pub sm3: StateMachine<'d, PIO, 3>, - _pio: PhantomData<&'d mut PIO>, -} - -impl<'d, PIO: Instance> Pio<'d, PIO> { - pub fn new(_pio: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { - PIO::state().users.store(5, Ordering::Release); - PIO::state().used_pins.store(0, Ordering::Release); - PIO::Interrupt::unpend(); - unsafe { PIO::Interrupt::enable() }; - Self { - common: Common { - instructions_used: 0, - pio: PhantomData, - }, - irq_flags: IrqFlags { pio: PhantomData }, - irq0: Irq { pio: PhantomData }, - irq1: Irq { pio: PhantomData }, - irq2: Irq { pio: PhantomData }, - irq3: Irq { pio: PhantomData }, - sm0: StateMachine { - rx: StateMachineRx { pio: PhantomData }, - tx: StateMachineTx { pio: PhantomData }, - }, - sm1: StateMachine { - rx: StateMachineRx { pio: PhantomData }, - tx: StateMachineTx { pio: PhantomData }, - }, - sm2: StateMachine { - rx: StateMachineRx { pio: PhantomData }, - tx: StateMachineTx { pio: PhantomData }, - }, - sm3: StateMachine { - rx: StateMachineRx { pio: PhantomData }, - tx: StateMachineTx { pio: PhantomData }, - }, - _pio: PhantomData, - } - } -} - -// we need to keep a record of which pins are assigned to each PIO. make_pio_pin -// notionally takes ownership of the pin it is given, but the wrapped pin cannot -// be treated as an owned resource since dropping it would have to deconfigure -// the pin, breaking running state machines in the process. pins are also shared -// between all state machines, which makes ownership even messier to track any -// other way. -pub struct State { - users: AtomicU8, - used_pins: AtomicU32, -} - -fn on_pio_drop() { - let state = PIO::state(); - if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { - let used_pins = state.used_pins.load(Ordering::Relaxed); - let null = Gpio0ctrlFuncsel::NULL as _; - // we only have 30 pins. don't test the other two since gpio() asserts. - for i in 0..30 { - if used_pins & (1 << i) != 0 { - pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); - } - } - } -} - -mod sealed { - use super::*; - - pub trait PioPin {} - - pub trait Instance { - const PIO_NO: u8; - const PIO: &'static crate::pac::pio::Pio; - const FUNCSEL: crate::pac::io::vals::Gpio0ctrlFuncsel; - type Interrupt: crate::interrupt::typelevel::Interrupt; - - #[inline] - fn wakers() -> &'static Wakers { - const NEW_AW: AtomicWaker = AtomicWaker::new(); - static WAKERS: Wakers = Wakers([NEW_AW; 12]); - - &WAKERS - } - - #[inline] - fn state() -> &'static State { - static STATE: State = State { - users: AtomicU8::new(0), - used_pins: AtomicU32::new(0), - }; - - &STATE - } - } -} - -pub trait Instance: sealed::Instance + Sized + Unpin {} - -macro_rules! impl_pio { - ($name:ident, $pio:expr, $pac:ident, $funcsel:ident, $irq:ident) => { - impl sealed::Instance for peripherals::$name { - const PIO_NO: u8 = $pio; - const PIO: &'static pac::pio::Pio = &pac::$pac; - const FUNCSEL: pac::io::vals::Gpio0ctrlFuncsel = pac::io::vals::Gpio0ctrlFuncsel::$funcsel; - type Interrupt = crate::interrupt::typelevel::$irq; - } - impl Instance for peripherals::$name {} - }; -} - -impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0); -impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0); - -pub trait PioPin: sealed::PioPin + gpio::Pin {} - -macro_rules! impl_pio_pin { - ($( $pin:ident, )*) => { - $( - impl sealed::PioPin for peripherals::$pin {} - impl PioPin for peripherals::$pin {} - )* - }; -} - -impl_pio_pin! { - PIN_0, - PIN_1, - PIN_2, - PIN_3, - PIN_4, - PIN_5, - PIN_6, - PIN_7, - PIN_8, - PIN_9, - PIN_10, - PIN_11, - PIN_12, - PIN_13, - PIN_14, - PIN_15, - PIN_16, - PIN_17, - PIN_18, - PIN_19, - PIN_20, - PIN_21, - PIN_22, - PIN_23, - PIN_24, - PIN_25, - PIN_26, - PIN_27, - PIN_28, - PIN_29, -} diff --git a/embassy-rp/src/pio/instr.rs b/embassy-rp/src/pio/instr.rs new file mode 100644 index 000000000..9a44088c6 --- /dev/null +++ b/embassy-rp/src/pio/instr.rs @@ -0,0 +1,101 @@ +//! Instructions controlling the PIO. +use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestination}; + +use crate::pio::{Instance, StateMachine}; + +/// Set value of scratch register X. +pub unsafe fn set_x(sm: &mut StateMachine, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::X, + bit_count: 32, + } + .encode(); + sm.tx().push(value); + sm.exec_instr(OUT); +} + +/// Get value of scratch register X. +pub unsafe fn get_x(sm: &mut StateMachine) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::X, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + sm.rx().pull() +} + +/// Set value of scratch register Y. +pub unsafe fn set_y(sm: &mut StateMachine, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::Y, + bit_count: 32, + } + .encode(); + sm.tx().push(value); + sm.exec_instr(OUT); +} + +/// Get value of scratch register Y. +pub unsafe fn get_y(sm: &mut StateMachine) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::Y, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + + sm.rx().pull() +} + +/// Set instruction for pindir destination. +pub unsafe fn set_pindir(sm: &mut StateMachine, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINDIRS, + data, + } + .encode(); + sm.exec_instr(set); +} + +/// Set instruction for pin destination. +pub unsafe fn set_pin(sm: &mut StateMachine, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINS, + data, + } + .encode(); + sm.exec_instr(set); +} + +/// Out instruction for pin destination. +pub unsafe fn set_out_pin(sm: &mut StateMachine, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINS, + bit_count: 32, + } + .encode(); + sm.tx().push(data); + sm.exec_instr(OUT); +} + +/// Out instruction for pindir destination. +pub unsafe fn set_out_pindir(sm: &mut StateMachine, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINDIRS, + bit_count: 32, + } + .encode(); + sm.tx().push(data); + sm.exec_instr(OUT); +} + +/// Jump instruction to address. +pub unsafe fn exec_jmp(sm: &mut StateMachine, to_addr: u8) { + let jmp: u16 = InstructionOperands::JMP { + address: to_addr, + condition: JmpCondition::Always, + } + .encode(); + sm.exec_instr(jmp); +} diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs new file mode 100644 index 000000000..ae91d1e83 --- /dev/null +++ b/embassy-rp/src/pio/mod.rs @@ -0,0 +1,1244 @@ +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin as FuturePin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use atomic_polyfill::{AtomicU32, AtomicU8}; +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use fixed::types::extra::U8; +use fixed::FixedU32; +use pac::io::vals::Gpio0ctrlFuncsel; +use pac::pio::vals::SmExecctrlStatusSel; +use pio::{Program, SideSet, Wrap}; + +use crate::dma::{Channel, Transfer, Word}; +use crate::gpio::sealed::Pin as SealedPin; +use crate::gpio::{self, AnyPin, Drive, Level, Pull, SlewRate}; +use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; +use crate::pac::dma::vals::TreqSel; +use crate::relocate::RelocatedProgram; +use crate::{pac, peripherals, RegExt}; + +pub mod instr; + +/// Wakers for interrupts and FIFOs. +pub struct Wakers([AtomicWaker; 12]); + +impl Wakers { + #[inline(always)] + fn fifo_in(&self) -> &[AtomicWaker] { + &self.0[0..4] + } + #[inline(always)] + fn fifo_out(&self) -> &[AtomicWaker] { + &self.0[4..8] + } + #[inline(always)] + fn irq(&self) -> &[AtomicWaker] { + &self.0[8..12] + } +} + +/// FIFO config. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum FifoJoin { + /// Both TX and RX fifo is enabled + #[default] + Duplex, + /// Rx fifo twice as deep. TX fifo disabled + RxOnly, + /// Tx fifo twice as deep. RX fifo disabled + TxOnly, +} + +/// Shift direction. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum ShiftDirection { + #[default] + Right = 1, + Left = 0, +} + +/// Pin direction. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Direction { + In = 0, + Out = 1, +} + +/// Which fifo level to use in status check. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum StatusSource { + #[default] + /// All-ones if TX FIFO level < N, otherwise all-zeroes. + TxFifoLevel = 0, + /// All-ones if RX FIFO level < N, otherwise all-zeroes. + RxFifoLevel = 1, +} + +const RXNEMPTY_MASK: u32 = 1 << 0; +const TXNFULL_MASK: u32 = 1 << 4; +const SMIRQ_MASK: u32 = 1 << 8; + +/// Interrupt handler for PIO. +pub struct InterruptHandler { + _pio: PhantomData, +} + +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + let ints = PIO::PIO.irqs(0).ints().read().0; + for bit in 0..12 { + if ints & (1 << bit) != 0 { + PIO::wakers().0[bit].wake(); + } + } + PIO::PIO.irqs(0).inte().write_clear(|m| m.0 = ints); + } +} + +/// Future that waits for TX-FIFO to become writable +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoOutFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_tx: &'a mut StateMachineTx<'d, PIO, SM>, + value: u32, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoOutFuture<'a, 'd, PIO, SM> { + /// Create a new future waiting for TX-FIFO to become writable. + pub fn new(sm: &'a mut StateMachineTx<'d, PIO, SM>, value: u32) -> Self { + FifoOutFuture { sm_tx: sm, value } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoOutFuture<'a, 'd, PIO, SM> { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + let value = self.value; + if self.get_mut().sm_tx.try_push(value) { + Poll::Ready(()) + } else { + PIO::wakers().fifo_out()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = TXNFULL_MASK << SM; + }); + // debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoOutFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = TXNFULL_MASK << SM; + }); + } +} + +/// Future that waits for RX-FIFO to become readable. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoInFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_rx: &'a mut StateMachineRx<'d, PIO, SM>, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoInFuture<'a, 'd, PIO, SM> { + /// Create future that waits for RX-FIFO to become readable. + pub fn new(sm: &'a mut StateMachineRx<'d, PIO, SM>) -> Self { + FifoInFuture { sm_rx: sm } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoInFuture<'a, 'd, PIO, SM> { + type Output = u32; + fn poll(mut self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + if let Some(v) = self.sm_rx.try_pull() { + Poll::Ready(v) + } else { + PIO::wakers().fifo_in()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + //debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoInFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + } +} + +/// Future that waits for IRQ +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct IrqFuture<'a, 'd, PIO: Instance> { + pio: PhantomData<&'a mut Irq<'d, PIO, 0>>, + irq_no: u8, +} + +impl<'a, 'd, PIO: Instance> Future for IrqFuture<'a, 'd, PIO> { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + + // Check if IRQ flag is already set + if PIO::PIO.irq().read().0 & (1 << self.irq_no) != 0 { + PIO::PIO.irq().write(|m| m.0 = 1 << self.irq_no); + return Poll::Ready(()); + } + + PIO::wakers().irq()[self.irq_no as usize].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + Poll::Pending + } +} + +impl<'a, 'd, PIO: Instance> Drop for IrqFuture<'a, 'd, PIO> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + } +} + +/// Type representing a PIO pin. +pub struct Pin<'l, PIO: Instance> { + pin: PeripheralRef<'l, AnyPin>, + pio: PhantomData, +} + +impl<'l, PIO: Instance> Pin<'l, PIO> { + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2MA, + Drive::_4mA => pac::pads::vals::Drive::_4MA, + Drive::_8mA => pac::pads::vals::Drive::_8MA, + Drive::_12mA => pac::pads::vals::Drive::_12MA, + }); + }); + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); + }); + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + self.pin.pad_ctrl().modify(|w| { + w.set_pue(pull == Pull::Up); + w.set_pde(pull == Pull::Down); + }); + } + + /// Set the pin's schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_schmitt(enable); + }); + } + + /// Set the pin's input sync bypass. + pub fn set_input_sync_bypass<'a>(&mut self, bypass: bool) { + let mask = 1 << self.pin(); + if bypass { + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask); + } else { + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask); + } + } + + /// Get the underlying pin number. + pub fn pin(&self) -> u8 { + self.pin._pin() + } +} + +/// Type representing a state machine RX FIFO. +pub struct StateMachineRx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { + /// Check if RX FIFO is empty. + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().rxempty() & (1u8 << SM) != 0 + } + + /// Check if RX FIFO is full. + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().rxfull() & (1u8 << SM) != 0 + } + + /// Check RX FIFO level. + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8 + 4)) as u8 & 0x0f + } + + /// Check if state machine has stalled on full RX FIFO. + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxstall(1 << SM)); + } + ret + } + + /// Check if RX FIFO underflow (i.e. read-on-empty by the system) has occurred. + pub fn underflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxunder() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxunder(1 << SM)); + } + ret + } + + /// Pull data from RX FIFO. + pub fn pull(&mut self) -> u32 { + PIO::PIO.rxf(SM).read() + } + + /// Attempt pulling data from RX FIFO. + pub fn try_pull(&mut self) -> Option { + if self.empty() { + return None; + } + Some(self.pull()) + } + + /// Wait for RX FIFO readable. + pub fn wait_pull<'a>(&'a mut self) -> FifoInFuture<'a, 'd, PIO, SM> { + FifoInFuture::new(self) + } + + /// Prepare DMA transfer from RX FIFO. + pub fn dma_pull<'a, C: Channel, W: Word>( + &'a mut self, + ch: PeripheralRef<'a, C>, + data: &'a mut [W], + ) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + let p = ch.regs(); + p.write_addr().write_value(data.as_ptr() as u32); + p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); + p.trans_count().write_value(data.len() as u32); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + // Set RX DREQ for this statemachine + w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8 + 4)); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(false); + w.set_incr_write(true); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } +} + +/// Type representing a state machine TX FIFO. +pub struct StateMachineTx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { + /// Check if TX FIFO is empty. + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().txempty() & (1u8 << SM) != 0 + } + + /// Check if TX FIFO is full. + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().txfull() & (1u8 << SM) != 0 + } + + /// Check TX FIFO level. + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f + } + + /// Check state machine has stalled on empty TX FIFO. + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txstall(1 << SM)); + } + ret + } + + /// Check if FIFO overflowed. + pub fn overflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txover() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txover(1 << SM)); + } + ret + } + + /// Force push data to TX FIFO. + pub fn push(&mut self, v: u32) { + PIO::PIO.txf(SM).write_value(v); + } + + /// Attempt to push data to TX FIFO. + pub fn try_push(&mut self, v: u32) -> bool { + if self.full() { + return false; + } + self.push(v); + true + } + + /// Wait until FIFO is ready for writing. + pub fn wait_push<'a>(&'a mut self, value: u32) -> FifoOutFuture<'a, 'd, PIO, SM> { + FifoOutFuture::new(self, value) + } + + /// Prepare a DMA transfer to TX FIFO. + pub fn dma_push<'a, C: Channel, W: Word>(&'a mut self, ch: PeripheralRef<'a, C>, data: &'a [W]) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + let p = ch.regs(); + p.read_addr().write_value(data.as_ptr() as u32); + p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32); + p.trans_count().write_value(data.len() as u32); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + // Set TX DREQ for this statemachine + w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8)); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(true); + w.set_incr_write(false); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } +} + +/// A type representing a single PIO state machine. +pub struct StateMachine<'d, PIO: Instance, const SM: usize> { + rx: StateMachineRx<'d, PIO, SM>, + tx: StateMachineTx<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> Drop for StateMachine<'d, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(1 << SM)); + on_pio_drop::(); + } +} + +fn assert_consecutive<'d, PIO: Instance>(pins: &[&Pin<'d, PIO>]) { + for (p1, p2) in pins.iter().zip(pins.iter().skip(1)) { + // purposely does not allow wrap-around because we can't claim pins 30 and 31. + assert!(p1.pin() + 1 == p2.pin(), "pins must be consecutive"); + } +} + +/// PIO Execution config. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ExecConfig { + /// If true, the MSB of the Delay/Side-set instruction field is used as side-set enable, rather than a side-set data bit. + pub side_en: bool, + /// If true, side-set data is asserted to pin directions, instead of pin values. + pub side_pindir: bool, + /// Pin to trigger jump. + pub jmp_pin: u8, + /// After reaching this address, execution is wrapped to wrap_bottom. + pub wrap_top: u8, + /// After reaching wrap_top, execution is wrapped to this address. + pub wrap_bottom: u8, +} + +/// PIO shift register config for input or output. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ShiftConfig { + /// Number of bits shifted out of OSR before autopull. + pub threshold: u8, + /// Shift direction. + pub direction: ShiftDirection, + /// For output: Pull automatically output shift register is emptied. + /// For input: Push automatically when the input shift register is filled. + pub auto_fill: bool, +} + +/// PIO pin config. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PinConfig { + /// The number of MSBs of the Delay/Side-set instruction field which are used for side-set. + pub sideset_count: u8, + /// The number of pins asserted by a SET. In the range 0 to 5 inclusive. + pub set_count: u8, + /// The number of pins asserted by an OUT PINS, OUT PINDIRS or MOV PINS instruction. In the range 0 to 32 inclusive. + pub out_count: u8, + /// The pin which is mapped to the least-significant bit of a state machine's IN data bus. + pub in_base: u8, + /// The lowest-numbered pin that will be affected by a side-set operation. + pub sideset_base: u8, + /// The lowest-numbered pin that will be affected by a SET PINS or SET PINDIRS instruction. + pub set_base: u8, + /// The lowest-numbered pin that will be affected by an OUT PINS, OUT PINDIRS or MOV PINS instruction. + pub out_base: u8, +} + +/// PIO config. +#[derive(Clone, Copy, Debug)] +pub struct Config<'d, PIO: Instance> { + /// Clock divisor register for state machines. + pub clock_divider: FixedU32, + /// Which data bit to use for inline OUT enable. + pub out_en_sel: u8, + /// Use a bit of OUT data as an auxiliary write enable When used in conjunction with OUT_STICKY. + pub inline_out_en: bool, + /// Continuously assert the most recent OUT/SET to the pins. + pub out_sticky: bool, + /// Which source to use for checking status. + pub status_sel: StatusSource, + /// Status comparison level. + pub status_n: u8, + exec: ExecConfig, + origin: Option, + /// Configure FIFO allocation. + pub fifo_join: FifoJoin, + /// Input shifting config. + pub shift_in: ShiftConfig, + /// Output shifting config. + pub shift_out: ShiftConfig, + // PINCTRL + pins: PinConfig, + in_count: u8, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Default for Config<'d, PIO> { + fn default() -> Self { + Self { + clock_divider: 1u8.into(), + out_en_sel: Default::default(), + inline_out_en: Default::default(), + out_sticky: Default::default(), + status_sel: Default::default(), + status_n: Default::default(), + exec: Default::default(), + origin: Default::default(), + fifo_join: Default::default(), + shift_in: Default::default(), + shift_out: Default::default(), + pins: Default::default(), + in_count: Default::default(), + _pio: Default::default(), + } + } +} + +impl<'d, PIO: Instance> Config<'d, PIO> { + /// Get execution configuration. + pub fn get_exec(&self) -> ExecConfig { + self.exec + } + + /// Update execution configuration. + pub unsafe fn set_exec(&mut self, e: ExecConfig) { + self.exec = e; + } + + /// Get pin configuration. + pub fn get_pins(&self) -> PinConfig { + self.pins + } + + /// Update pin configuration. + pub unsafe fn set_pins(&mut self, p: PinConfig) { + self.pins = p; + } + + /// Configures this state machine to use the given program, including jumping to the origin + /// of the program. The state machine is not started. + /// + /// `side_set` sets the range of pins affected by side-sets. The range must be consecutive. + /// Side-set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn use_program(&mut self, prog: &LoadedProgram<'d, PIO>, side_set: &[&Pin<'d, PIO>]) { + assert!((prog.side_set.bits() - prog.side_set.optional() as u8) as usize == side_set.len()); + assert_consecutive(side_set); + self.exec.side_en = prog.side_set.optional(); + self.exec.side_pindir = prog.side_set.pindirs(); + self.exec.wrap_bottom = prog.wrap.target; + self.exec.wrap_top = prog.wrap.source; + self.pins.sideset_count = prog.side_set.bits(); + self.pins.sideset_base = side_set.first().map_or(0, |p| p.pin()); + self.origin = Some(prog.origin); + } + + /// Set pin used to signal jump. + pub fn set_jmp_pin(&mut self, pin: &Pin<'d, PIO>) { + self.exec.jmp_pin = pin.pin(); + } + + /// Sets the range of pins affected by SET instructions. The range must be consecutive. + /// Set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_set_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert!(pins.len() <= 5); + assert_consecutive(pins); + self.pins.set_base = pins.first().map_or(0, |p| p.pin()); + self.pins.set_count = pins.len() as u8; + } + + /// Sets the range of pins affected by OUT instructions. The range must be consecutive. + /// Out pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_out_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.out_base = pins.first().map_or(0, |p| p.pin()); + self.pins.out_count = pins.len() as u8; + } + + /// Sets the range of pins used by IN instructions. The range must be consecutive. + /// In pins must configured as inputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_in_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.in_base = pins.first().map_or(0, |p| p.pin()); + self.in_count = pins.len() as u8; + } +} + +impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { + /// Set the config for a given PIO state machine. + pub fn set_config(&mut self, config: &Config<'d, PIO>) { + // sm expects 0 for 65536, truncation makes that happen + assert!(config.clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(config.clock_divider >= 1, "clkdiv must be >= 1"); + assert!(config.out_en_sel < 32, "out_en_sel must be < 32"); + assert!(config.status_n < 32, "status_n must be < 32"); + // sm expects 0 for 32, truncation makes that happen + assert!(config.shift_in.threshold <= 32, "shift_in.threshold must be <= 32"); + assert!(config.shift_out.threshold <= 32, "shift_out.threshold must be <= 32"); + let sm = Self::this_sm(); + sm.clkdiv().write(|w| w.0 = config.clock_divider.to_bits() << 8); + sm.execctrl().write(|w| { + w.set_side_en(config.exec.side_en); + w.set_side_pindir(config.exec.side_pindir); + w.set_jmp_pin(config.exec.jmp_pin); + w.set_out_en_sel(config.out_en_sel); + w.set_inline_out_en(config.inline_out_en); + w.set_out_sticky(config.out_sticky); + w.set_wrap_top(config.exec.wrap_top); + w.set_wrap_bottom(config.exec.wrap_bottom); + w.set_status_sel(match config.status_sel { + StatusSource::TxFifoLevel => SmExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => SmExecctrlStatusSel::RXLEVEL, + }); + w.set_status_n(config.status_n); + }); + sm.shiftctrl().write(|w| { + w.set_fjoin_rx(config.fifo_join == FifoJoin::RxOnly); + w.set_fjoin_tx(config.fifo_join == FifoJoin::TxOnly); + w.set_pull_thresh(config.shift_out.threshold); + w.set_push_thresh(config.shift_in.threshold); + w.set_out_shiftdir(config.shift_out.direction == ShiftDirection::Right); + w.set_in_shiftdir(config.shift_in.direction == ShiftDirection::Right); + w.set_autopull(config.shift_out.auto_fill); + w.set_autopush(config.shift_in.auto_fill); + }); + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base); + w.set_sideset_base(config.pins.sideset_base); + w.set_set_base(config.pins.set_base); + w.set_out_base(config.pins.out_base); + }); + if let Some(origin) = config.origin { + unsafe { instr::exec_jmp(self, origin) } + } + } + + #[inline(always)] + fn this_sm() -> crate::pac::pio::StateMachine { + PIO::PIO.sm(SM) + } + + /// Restart this state machine. + pub fn restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_sm_restart(mask)); + } + + /// Enable state machine. + pub fn set_enable(&mut self, enable: bool) { + let mask = 1u8 << SM; + if enable { + PIO::PIO.ctrl().write_set(|w| w.set_sm_enable(mask)); + } else { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(mask)); + } + } + + /// Check if state machine is enabled. + pub fn is_enabled(&self) -> bool { + PIO::PIO.ctrl().read().sm_enable() & (1u8 << SM) != 0 + } + + /// Restart a state machine's clock divider from an initial phase of 0. + pub fn clkdiv_restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_clkdiv_restart(mask)); + } + + fn with_paused(&mut self, f: impl FnOnce(&mut Self)) { + let enabled = self.is_enabled(); + self.set_enable(false); + let pincfg = Self::this_sm().pinctrl().read(); + let execcfg = Self::this_sm().execctrl().read(); + Self::this_sm().execctrl().write_clear(|w| w.set_out_sticky(true)); + f(self); + Self::this_sm().pinctrl().write_value(pincfg); + Self::this_sm().execctrl().write_value(execcfg); + self.set_enable(enabled); + } + + /// Sets pin directions. This pauses the current state machine to run `SET` commands + /// and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin()); + w.set_set_count(1); + }); + // SET PINDIRS, (dir) + unsafe { sm.exec_instr(0b111_00000_100_00000 | dir as u16) }; + } + }); + } + + /// Sets pin output values. This pauses the current state machine to run + /// `SET` commands and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pins(&mut self, level: Level, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin()); + w.set_set_count(1); + }); + // SET PINS, (dir) + unsafe { sm.exec_instr(0b111_00000_000_00000 | level as u16) }; + } + }); + } + + /// Flush FIFOs for state machine. + pub fn clear_fifos(&mut self) { + // Toggle FJOIN_RX to flush FIFOs + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + } + + /// Instruct state machine to execute a given instructions + /// + /// SAFETY: The state machine must be in a state where executing + /// an arbitrary instruction does not crash it. + pub unsafe fn exec_instr(&mut self, instr: u16) { + Self::this_sm().instr().write(|w| w.set_instr(instr)); + } + + /// Return a read handle for reading state machine outputs. + pub fn rx(&mut self) -> &mut StateMachineRx<'d, PIO, SM> { + &mut self.rx + } + + /// Return a handle for writing to inputs. + pub fn tx(&mut self) -> &mut StateMachineTx<'d, PIO, SM> { + &mut self.tx + } + + /// Return both read and write handles for the state machine. + pub fn rx_tx(&mut self) -> (&mut StateMachineRx<'d, PIO, SM>, &mut StateMachineTx<'d, PIO, SM>) { + (&mut self.rx, &mut self.tx) + } +} + +/// PIO handle. +pub struct Common<'d, PIO: Instance> { + instructions_used: u32, + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Drop for Common<'d, PIO> { + fn drop(&mut self) { + on_pio_drop::(); + } +} + +/// Memory of PIO instance. +pub struct InstanceMemory<'d, PIO: Instance> { + used_mask: u32, + pio: PhantomData<&'d mut PIO>, +} + +/// A loaded PIO program. +pub struct LoadedProgram<'d, PIO: Instance> { + /// Memory used by program. + pub used_memory: InstanceMemory<'d, PIO>, + /// Program origin for loading. + pub origin: u8, + /// Wrap controls what to do once program is done executing. + pub wrap: Wrap, + /// Data for 'side' set instruction parameters. + pub side_set: SideSet, +} + +/// Errors loading a PIO program. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LoadError { + /// Insufficient consecutive free instruction space to load program. + InsufficientSpace, + /// Loading the program would overwrite an instruction address already + /// used by another program. + AddressInUse(usize), +} + +impl<'d, PIO: Instance> Common<'d, PIO> { + /// Load a PIO program. This will automatically relocate the program to + /// an available chunk of free instruction memory if the program origin + /// was not explicitly specified, otherwise it will attempt to load the + /// program only at its origin. + pub fn load_program(&mut self, prog: &Program) -> LoadedProgram<'d, PIO> { + match self.try_load_program(prog) { + Ok(r) => r, + Err(e) => panic!("Failed to load PIO program: {:?}", e), + } + } + + /// Load a PIO program. This will automatically relocate the program to + /// an available chunk of free instruction memory if the program origin + /// was not explicitly specified, otherwise it will attempt to load the + /// program only at its origin. + pub fn try_load_program( + &mut self, + prog: &Program, + ) -> Result, LoadError> { + match prog.origin { + Some(origin) => self + .try_load_program_at(prog, origin) + .map_err(|a| LoadError::AddressInUse(a)), + None => { + // naively search for free space, allowing wraparound since + // PIO does support that. with only 32 instruction slots it + // doesn't make much sense to do anything more fancy. + let mut origin = 0; + while origin < 32 { + match self.try_load_program_at(prog, origin as _) { + Ok(r) => return Ok(r), + Err(a) => origin = a + 1, + } + } + Err(LoadError::InsufficientSpace) + } + } + } + + fn try_load_program_at( + &mut self, + prog: &Program, + origin: u8, + ) -> Result, usize> { + let prog = RelocatedProgram::new_with_origin(prog, origin); + let used_memory = self.try_write_instr(prog.origin() as _, prog.code())?; + Ok(LoadedProgram { + used_memory, + origin: prog.origin(), + wrap: prog.wrap(), + side_set: prog.side_set(), + }) + } + + fn try_write_instr(&mut self, start: usize, instrs: I) -> Result, usize> + where + I: Iterator, + { + let mut used_mask = 0; + for (i, instr) in instrs.enumerate() { + // wrapping around the end of program memory is valid, let's make use of that. + let addr = (i + start) % 32; + let mask = 1 << addr; + if (self.instructions_used | used_mask) & mask != 0 { + return Err(addr); + } + PIO::PIO.instr_mem(addr).write(|w| { + w.set_instr_mem(instr); + }); + used_mask |= mask; + } + self.instructions_used |= used_mask; + Ok(InstanceMemory { + used_mask, + pio: PhantomData, + }) + } + + /// Free instruction memory. This is always possible but unsafe if any + /// state machine is still using this bit of memory. + pub unsafe fn free_instr(&mut self, instrs: InstanceMemory) { + self.instructions_used &= !instrs.used_mask; + } + + /// Bypass flipflop synchronizer on GPIO inputs. + pub fn set_input_sync_bypass<'a>(&'a mut self, bypass: u32, mask: u32) { + // this can interfere with per-pin bypass functions. splitting the + // modification is going to be fine since nothing that relies on + // it can reasonably run before we finish. + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask & bypass); + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask & !bypass); + } + + /// Get bypass configuration. + pub fn get_input_sync_bypass(&self) -> u32 { + PIO::PIO.input_sync_bypass().read() + } + + /// Register a pin for PIO usage. Pins will be released from the PIO block + /// (i.e., have their `FUNCSEL` reset to `NULL`) when the [`Common`] *and* + /// all [`StateMachine`]s for this block have been dropped. **Other members + /// of [`Pio`] do not keep pin registrations alive.** + pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { + into_ref!(pin); + pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + // we can be relaxed about this because we're &mut here and nothing is cached + PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); + Pin { + pin: pin.into_ref().map_into(), + pio: PhantomData::default(), + } + } + + /// Apply changes to all state machines in a batch. + pub fn apply_sm_batch(&mut self, f: impl FnOnce(&mut PioBatch<'d, PIO>)) { + let mut batch = PioBatch { + clkdiv_restart: 0, + sm_restart: 0, + sm_enable_mask: 0, + sm_enable: 0, + _pio: PhantomData, + }; + f(&mut batch); + PIO::PIO.ctrl().modify(|w| { + w.set_clkdiv_restart(batch.clkdiv_restart); + w.set_sm_restart(batch.sm_restart); + w.set_sm_enable((w.sm_enable() & !batch.sm_enable_mask) | batch.sm_enable); + }); + } +} + +/// Represents multiple state machines in a single type. +pub struct PioBatch<'a, PIO: Instance> { + clkdiv_restart: u8, + sm_restart: u8, + sm_enable_mask: u8, + sm_enable: u8, + _pio: PhantomData<&'a PIO>, +} + +impl<'a, PIO: Instance> PioBatch<'a, PIO> { + /// Restart a state machine's clock divider from an initial phase of 0. + pub fn restart(&mut self, _sm: &mut StateMachine<'a, PIO, SM>) { + self.clkdiv_restart |= 1 << SM; + } + + /// Enable a specific state machine. + pub fn set_enable(&mut self, _sm: &mut StateMachine<'a, PIO, SM>, enable: bool) { + self.sm_enable_mask |= 1 << SM; + self.sm_enable |= (enable as u8) << SM; + } +} + +/// Type representing a PIO interrupt. +pub struct Irq<'d, PIO: Instance, const N: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const N: usize> Irq<'d, PIO, N> { + /// Wait for an IRQ to fire. + pub fn wait<'a>(&'a mut self) -> IrqFuture<'a, 'd, PIO> { + IrqFuture { + pio: PhantomData, + irq_no: N as u8, + } + } +} + +/// Interrupt flags for a PIO instance. +#[derive(Clone)] +pub struct IrqFlags<'d, PIO: Instance> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> IrqFlags<'d, PIO> { + /// Check if interrupt fired. + pub fn check(&self, irq_no: u8) -> bool { + assert!(irq_no < 8); + self.check_any(1 << irq_no) + } + + /// Check if any of the interrupts in the bitmap fired. + pub fn check_any(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs != 0 + } + + /// Check if all interrupts have fired. + pub fn check_all(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs == irqs + } + + /// Clear interrupt for interrupt number. + pub fn clear(&self, irq_no: usize) { + assert!(irq_no < 8); + self.clear_all(1 << irq_no); + } + + /// Clear all interrupts set in the bitmap. + pub fn clear_all(&self, irqs: u8) { + PIO::PIO.irq().write(|w| w.set_irq(irqs)) + } + + /// Fire a given interrupt. + pub fn set(&self, irq_no: usize) { + assert!(irq_no < 8); + self.set_all(1 << irq_no); + } + + /// Fire all interrupts. + pub fn set_all(&self, irqs: u8) { + PIO::PIO.irq_force().write(|w| w.set_irq_force(irqs)) + } +} + +/// An instance of the PIO driver. +pub struct Pio<'d, PIO: Instance> { + /// PIO handle. + pub common: Common<'d, PIO>, + /// PIO IRQ flags. + pub irq_flags: IrqFlags<'d, PIO>, + /// IRQ0 configuration. + pub irq0: Irq<'d, PIO, 0>, + /// IRQ1 configuration. + pub irq1: Irq<'d, PIO, 1>, + /// IRQ2 configuration. + pub irq2: Irq<'d, PIO, 2>, + /// IRQ3 configuration. + pub irq3: Irq<'d, PIO, 3>, + /// State machine 0 handle. + pub sm0: StateMachine<'d, PIO, 0>, + /// State machine 1 handle. + pub sm1: StateMachine<'d, PIO, 1>, + /// State machine 2 handle. + pub sm2: StateMachine<'d, PIO, 2>, + /// State machine 3 handle. + pub sm3: StateMachine<'d, PIO, 3>, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Pio<'d, PIO> { + /// Create a new instance of a PIO peripheral. + pub fn new(_pio: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { + PIO::state().users.store(5, Ordering::Release); + PIO::state().used_pins.store(0, Ordering::Release); + PIO::Interrupt::unpend(); + unsafe { PIO::Interrupt::enable() }; + Self { + common: Common { + instructions_used: 0, + pio: PhantomData, + }, + irq_flags: IrqFlags { pio: PhantomData }, + irq0: Irq { pio: PhantomData }, + irq1: Irq { pio: PhantomData }, + irq2: Irq { pio: PhantomData }, + irq3: Irq { pio: PhantomData }, + sm0: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm1: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm2: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm3: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + _pio: PhantomData, + } + } +} + +/// Representation of the PIO state keeping a record of which pins are assigned to +/// each PIO. +// make_pio_pin notionally takes ownership of the pin it is given, but the wrapped pin +// cannot be treated as an owned resource since dropping it would have to deconfigure +// the pin, breaking running state machines in the process. pins are also shared +// between all state machines, which makes ownership even messier to track any +// other way. +pub struct State { + users: AtomicU8, + used_pins: AtomicU32, +} + +fn on_pio_drop() { + let state = PIO::state(); + if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { + let used_pins = state.used_pins.load(Ordering::Relaxed); + let null = Gpio0ctrlFuncsel::NULL as _; + // we only have 30 pins. don't test the other two since gpio() asserts. + for i in 0..30 { + if used_pins & (1 << i) != 0 { + pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); + } + } + } +} + +mod sealed { + use super::*; + + pub trait PioPin {} + + pub trait Instance { + const PIO_NO: u8; + const PIO: &'static crate::pac::pio::Pio; + const FUNCSEL: crate::pac::io::vals::Gpio0ctrlFuncsel; + type Interrupt: crate::interrupt::typelevel::Interrupt; + + #[inline] + fn wakers() -> &'static Wakers { + const NEW_AW: AtomicWaker = AtomicWaker::new(); + static WAKERS: Wakers = Wakers([NEW_AW; 12]); + + &WAKERS + } + + #[inline] + fn state() -> &'static State { + static STATE: State = State { + users: AtomicU8::new(0), + used_pins: AtomicU32::new(0), + }; + + &STATE + } + } +} + +/// PIO instance. +pub trait Instance: sealed::Instance + Sized + Unpin {} + +macro_rules! impl_pio { + ($name:ident, $pio:expr, $pac:ident, $funcsel:ident, $irq:ident) => { + impl sealed::Instance for peripherals::$name { + const PIO_NO: u8 = $pio; + const PIO: &'static pac::pio::Pio = &pac::$pac; + const FUNCSEL: pac::io::vals::Gpio0ctrlFuncsel = pac::io::vals::Gpio0ctrlFuncsel::$funcsel; + type Interrupt = crate::interrupt::typelevel::$irq; + } + impl Instance for peripherals::$name {} + }; +} + +impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0); +impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0); + +/// PIO pin. +pub trait PioPin: sealed::PioPin + gpio::Pin {} + +macro_rules! impl_pio_pin { + ($( $pin:ident, )*) => { + $( + impl sealed::PioPin for peripherals::$pin {} + impl PioPin for peripherals::$pin {} + )* + }; +} + +impl_pio_pin! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, +} diff --git a/embassy-rp/src/pio_instr_util.rs b/embassy-rp/src/pio_instr_util.rs deleted file mode 100644 index 25393b476..000000000 --- a/embassy-rp/src/pio_instr_util.rs +++ /dev/null @@ -1,90 +0,0 @@ -use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestination}; - -use crate::pio::{Instance, StateMachine}; - -pub unsafe fn set_x(sm: &mut StateMachine, value: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::X, - bit_count: 32, - } - .encode(); - sm.tx().push(value); - sm.exec_instr(OUT); -} - -pub unsafe fn get_x(sm: &mut StateMachine) -> u32 { - const IN: u16 = InstructionOperands::IN { - source: InSource::X, - bit_count: 32, - } - .encode(); - sm.exec_instr(IN); - sm.rx().pull() -} - -pub unsafe fn set_y(sm: &mut StateMachine, value: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::Y, - bit_count: 32, - } - .encode(); - sm.tx().push(value); - sm.exec_instr(OUT); -} - -pub unsafe fn get_y(sm: &mut StateMachine) -> u32 { - const IN: u16 = InstructionOperands::IN { - source: InSource::Y, - bit_count: 32, - } - .encode(); - sm.exec_instr(IN); - - sm.rx().pull() -} - -pub unsafe fn set_pindir(sm: &mut StateMachine, data: u8) { - let set: u16 = InstructionOperands::SET { - destination: SetDestination::PINDIRS, - data, - } - .encode(); - sm.exec_instr(set); -} - -pub unsafe fn set_pin(sm: &mut StateMachine, data: u8) { - let set: u16 = InstructionOperands::SET { - destination: SetDestination::PINS, - data, - } - .encode(); - sm.exec_instr(set); -} - -pub unsafe fn set_out_pin(sm: &mut StateMachine, data: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::PINS, - bit_count: 32, - } - .encode(); - sm.tx().push(data); - sm.exec_instr(OUT); -} -pub unsafe fn set_out_pindir(sm: &mut StateMachine, data: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::PINDIRS, - bit_count: 32, - } - .encode(); - sm.tx().push(data); - sm.exec_instr(OUT); -} - -pub unsafe fn exec_jmp(sm: &mut StateMachine, to_addr: u8) { - let jmp: u16 = InstructionOperands::JMP { - address: to_addr, - condition: JmpCondition::Always, - } - .encode(); - sm.exec_instr(jmp); -} diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index ca030f560..f14e08525 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -38,15 +38,18 @@ impl State { } } +/// Buffered UART driver. pub struct BufferedUart<'d, T: Instance> { pub(crate) rx: BufferedUartRx<'d, T>, pub(crate) tx: BufferedUartTx<'d, T>, } +/// Buffered UART RX handle. pub struct BufferedUartRx<'d, T: Instance> { pub(crate) phantom: PhantomData<&'d mut T>, } +/// Buffered UART TX handle. pub struct BufferedUartTx<'d, T: Instance> { pub(crate) phantom: PhantomData<&'d mut T>, } @@ -84,6 +87,7 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>( } impl<'d, T: Instance> BufferedUart<'d, T> { + /// Create a buffered UART instance. pub fn new( _uart: impl Peripheral

+ 'd, irq: impl Binding>, @@ -104,6 +108,7 @@ impl<'d, T: Instance> BufferedUart<'d, T> { } } + /// Create a buffered UART instance with flow control. pub fn new_with_rtscts( _uart: impl Peripheral

+ 'd, irq: impl Binding>, @@ -132,32 +137,39 @@ impl<'d, T: Instance> BufferedUart<'d, T> { } } + /// Write to UART TX buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result { self.tx.blocking_write(buffer) } + /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { self.tx.blocking_flush() } + /// Read from UART RX buffer blocking execution until done. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { self.rx.blocking_read(buffer) } + /// Check if UART is busy transmitting. pub fn busy(&self) -> bool { self.tx.busy() } + /// Wait until TX is empty and send break condition. pub async fn send_break(&mut self, bits: u32) { self.tx.send_break(bits).await } + /// Split into separate RX and TX handles. pub fn split(self) -> (BufferedUartRx<'d, T>, BufferedUartTx<'d, T>) { (self.rx, self.tx) } } impl<'d, T: Instance> BufferedUartRx<'d, T> { + /// Create a new buffered UART RX. pub fn new( _uart: impl Peripheral

+ 'd, irq: impl Binding>, @@ -173,6 +185,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { Self { phantom: PhantomData } } + /// Create a new buffered UART RX with flow control. pub fn new_with_rts( _uart: impl Peripheral

+ 'd, irq: impl Binding>, @@ -253,6 +266,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { Poll::Ready(result) } + /// Read from UART RX buffer blocking execution until done. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result { loop { match Self::try_read(buf) { @@ -303,6 +317,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { } impl<'d, T: Instance> BufferedUartTx<'d, T> { + /// Create a new buffered UART TX. pub fn new( _uart: impl Peripheral

+ 'd, irq: impl Binding>, @@ -318,6 +333,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { Self { phantom: PhantomData } } + /// Create a new buffered UART TX with flow control. pub fn new_with_cts( _uart: impl Peripheral

+ 'd, irq: impl Binding>, @@ -373,6 +389,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { }) } + /// Write to UART TX buffer blocking execution until done. pub fn blocking_write(&mut self, buf: &[u8]) -> Result { if buf.is_empty() { return Ok(0); @@ -398,6 +415,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } } + /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { loop { let state = T::buffered_state(); @@ -407,6 +425,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } } + /// Check if UART is busy. pub fn busy(&self) -> bool { T::regs().uartfr().read().busy() } @@ -466,6 +485,7 @@ impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { } } +/// Interrupt handler. pub struct BufferedInterruptHandler { _uart: PhantomData, } diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index f82b9036b..26f21193a 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -938,9 +938,13 @@ macro_rules! impl_instance { impl_instance!(UART0, UART0_IRQ, 20, 21); impl_instance!(UART1, UART1_IRQ, 22, 23); +/// Trait for TX pins. pub trait TxPin: sealed::TxPin + crate::gpio::Pin {} +/// Trait for RX pins. pub trait RxPin: sealed::RxPin + crate::gpio::Pin {} +/// Trait for Clear To Send (CTS) pins. pub trait CtsPin: sealed::CtsPin + crate::gpio::Pin {} +/// Trait for Request To Send (RTS) pins. pub trait RtsPin: sealed::RtsPin + crate::gpio::Pin {} macro_rules! impl_pin { diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 4a74ee6f7..bcd848222 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -20,7 +20,9 @@ pub(crate) mod sealed { } } +/// USB peripheral instance. pub trait Instance: sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -96,6 +98,7 @@ impl EndpointData { } } +/// RP2040 USB driver handle. pub struct Driver<'d, T: Instance> { phantom: PhantomData<&'d mut T>, ep_in: [EndpointData; EP_COUNT], @@ -104,6 +107,7 @@ pub struct Driver<'d, T: Instance> { } impl<'d, T: Instance> Driver<'d, T> { + /// Create a new USB driver. pub fn new(_usb: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; @@ -240,6 +244,7 @@ impl<'d, T: Instance> Driver<'d, T> { } } +/// USB interrupt handler. pub struct InterruptHandler { _uart: PhantomData, } @@ -342,6 +347,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { } } +/// Type representing the RP USB bus. pub struct Bus<'d, T: Instance> { phantom: PhantomData<&'d mut T>, ep_out: [EndpointData; EP_COUNT], @@ -461,6 +467,7 @@ trait Dir { fn waker(i: usize) -> &'static AtomicWaker; } +/// Type for In direction. pub enum In {} impl Dir for In { fn dir() -> Direction { @@ -473,6 +480,7 @@ impl Dir for In { } } +/// Type for Out direction. pub enum Out {} impl Dir for Out { fn dir() -> Direction { @@ -485,6 +493,7 @@ impl Dir for Out { } } +/// Endpoint for RP USB driver. pub struct Endpoint<'d, T: Instance, D> { _phantom: PhantomData<(&'d mut T, D)>, info: EndpointInfo, @@ -616,6 +625,7 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { } } +/// Control pipe for RP USB driver. pub struct ControlPipe<'d, T: Instance> { _phantom: PhantomData<&'d mut T>, max_packet_size: u16, -- cgit From 486b67e89522d7e36f6b1078ff8018d64447b39e Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 19 Dec 2023 11:26:08 +0100 Subject: docs: document spi, rtc and rest of uart for embassy-rp --- embassy-rp/src/pwm.rs | 5 +++++ embassy-rp/src/rtc/mod.rs | 1 + embassy-rp/src/spi.rs | 31 ++++++++++++++++++++++++++++++ embassy-rp/src/uart/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 516b8254b..5b96557a3 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -61,9 +61,13 @@ impl Default for Config { } } +/// PWM input mode. pub enum InputMode { + /// Level mode. Level, + /// Rising edge mode. RisingEdge, + /// Falling edge mode. FallingEdge, } @@ -77,6 +81,7 @@ impl From for Divmode { } } +/// PWM driver. pub struct Pwm<'d, T: Channel> { inner: PeripheralRef<'d, T>, pin_a: Option>, diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs index 60ca8627b..c3df3ee57 100644 --- a/embassy-rp/src/rtc/mod.rs +++ b/embassy-rp/src/rtc/mod.rs @@ -194,6 +194,7 @@ mod sealed { } } +/// RTC peripheral instance. pub trait Instance: sealed::Instance {} impl sealed::Instance for crate::peripherals::RTC { diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index 6ba985a65..a2a22ffe5 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -11,6 +11,7 @@ use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::{pac, peripherals, Peripheral}; +/// SPI errors. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -18,11 +19,15 @@ pub enum Error { // No errors for now } +/// SPI configuration. #[non_exhaustive] #[derive(Clone)] pub struct Config { + /// Frequency. pub frequency: u32, + /// Phase. pub phase: Phase, + /// Polarity. pub polarity: Polarity, } @@ -36,6 +41,7 @@ impl Default for Config { } } +/// SPI driver. pub struct Spi<'d, T: Instance, M: Mode> { inner: PeripheralRef<'d, T>, tx_dma: Option>, @@ -119,6 +125,7 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { } } + /// Write data to SPI blocking execution until done. pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { let p = self.inner.regs(); for &b in data { @@ -131,6 +138,7 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { Ok(()) } + /// Transfer data in place to SPI blocking execution until done. pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { let p = self.inner.regs(); for b in data { @@ -143,6 +151,7 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { Ok(()) } + /// Read data from SPI blocking execution until done. pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { let p = self.inner.regs(); for b in data { @@ -155,6 +164,7 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { Ok(()) } + /// Transfer data to SPI blocking execution until done. pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { let p = self.inner.regs(); let len = read.len().max(write.len()); @@ -172,12 +182,14 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { Ok(()) } + /// Block execution until SPI is done. pub fn flush(&mut self) -> Result<(), Error> { let p = self.inner.regs(); while p.sr().read().bsy() {} Ok(()) } + /// Set SPI frequency. pub fn set_frequency(&mut self, freq: u32) { let (presc, postdiv) = calc_prescs(freq); let p = self.inner.regs(); @@ -196,6 +208,7 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { } impl<'d, T: Instance> Spi<'d, T, Blocking> { + /// Create an SPI driver in blocking mode. pub fn new_blocking( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, @@ -216,6 +229,7 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { ) } + /// Create an SPI driver in blocking mode supporting writes only. pub fn new_blocking_txonly( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, @@ -235,6 +249,7 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { ) } + /// Create an SPI driver in blocking mode supporting reads only. pub fn new_blocking_rxonly( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, @@ -256,6 +271,7 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { } impl<'d, T: Instance> Spi<'d, T, Async> { + /// Create an SPI driver in async mode supporting DMA operations. pub fn new( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, @@ -278,6 +294,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> { ) } + /// Create an SPI driver in async mode supporting DMA write operations only. pub fn new_txonly( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, @@ -298,6 +315,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> { ) } + /// Create an SPI driver in async mode supporting DMA read operations only. pub fn new_rxonly( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, @@ -318,6 +336,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> { ) } + /// Write data to SPI using DMA. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { let tx_ch = self.tx_dma.as_mut().unwrap(); let tx_transfer = unsafe { @@ -340,6 +359,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> { Ok(()) } + /// Read data from SPI using DMA. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { // Start RX first. Transfer starts when TX starts, if RX // is not started yet we might lose bytes. @@ -365,10 +385,12 @@ impl<'d, T: Instance> Spi<'d, T, Async> { Ok(()) } + /// Transfer data to SPI using DMA. pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> { self.transfer_inner(rx_buffer, tx_buffer).await } + /// Transfer data in place to SPI using DMA. pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { self.transfer_inner(words, words).await } @@ -434,7 +456,10 @@ mod sealed { } } +/// Mode. pub trait Mode: sealed::Mode {} + +/// SPI instance trait. pub trait Instance: sealed::Instance {} macro_rules! impl_instance { @@ -454,9 +479,13 @@ macro_rules! impl_instance { impl_instance!(SPI0, Spi0, 16, 17); impl_instance!(SPI1, Spi1, 18, 19); +/// CLK pin. pub trait ClkPin: GpioPin {} +/// CS pin. pub trait CsPin: GpioPin {} +/// MOSI pin. pub trait MosiPin: GpioPin {} +/// MISO pin. pub trait MisoPin: GpioPin {} macro_rules! impl_pin { @@ -503,7 +532,9 @@ macro_rules! impl_mode { }; } +/// Blocking mode. pub struct Blocking; +/// Async mode. pub struct Async; impl_mode!(Blocking); diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 26f21193a..32be7661d 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -20,11 +20,16 @@ use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; mod buffered; pub use buffered::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, BufferedUartTx}; +/// Word length. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum DataBits { + /// 5 bits. DataBits5, + /// 6 bits. DataBits6, + /// 7 bits. DataBits7, + /// 8 bits. DataBits8, } @@ -39,13 +44,18 @@ impl DataBits { } } +/// Parity bit. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Parity { + /// No parity. ParityNone, + /// Even parity. ParityEven, + /// Odd parity. ParityOdd, } +/// Stop bits. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum StopBits { #[doc = "1 stop bit"] @@ -54,20 +64,25 @@ pub enum StopBits { STOP2, } +/// UART config. #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Config { + /// Baud rate. pub baudrate: u32, + /// Word length. pub data_bits: DataBits, + /// Stop bits. pub stop_bits: StopBits, + /// Parity bit. pub parity: Parity, /// Invert the tx pin output pub invert_tx: bool, /// Invert the rx pin input pub invert_rx: bool, - // Invert the rts pin + /// Invert the rts pin pub invert_rts: bool, - // Invert the cts pin + /// Invert the cts pin pub invert_cts: bool, } @@ -102,21 +117,25 @@ pub enum Error { Framing, } +/// Internal DMA state of UART RX. pub struct DmaState { rx_err_waker: AtomicWaker, rx_errs: AtomicU16, } +/// UART driver. pub struct Uart<'d, T: Instance, M: Mode> { tx: UartTx<'d, T, M>, rx: UartRx<'d, T, M>, } +/// UART TX driver. pub struct UartTx<'d, T: Instance, M: Mode> { tx_dma: Option>, phantom: PhantomData<(&'d mut T, M)>, } +/// UART RX driver. pub struct UartRx<'d, T: Instance, M: Mode> { rx_dma: Option>, phantom: PhantomData<(&'d mut T, M)>, @@ -142,6 +161,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { } } + /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { let r = T::regs(); for &b in buffer { @@ -151,12 +171,14 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { Ok(()) } + /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { let r = T::regs(); while !r.uartfr().read().txfe() {} Ok(()) } + /// Check if UART is busy transmitting. pub fn busy(&self) -> bool { T::regs().uartfr().read().busy() } @@ -191,6 +213,8 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { } impl<'d, T: Instance> UartTx<'d, T, Blocking> { + /// Convert this uart TX instance into a buffered uart using the provided + /// irq and transmit buffer. pub fn into_buffered( self, irq: impl Binding>, @@ -203,6 +227,7 @@ impl<'d, T: Instance> UartTx<'d, T, Blocking> { } impl<'d, T: Instance> UartTx<'d, T, Async> { + /// Write to UART TX from the provided buffer using DMA. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { let ch = self.tx_dma.as_mut().unwrap(); let transfer = unsafe { @@ -246,6 +271,7 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { } } + /// Read from UART RX blocking execution until done. pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> { while buffer.len() > 0 { let received = self.drain_fifo(buffer)?; @@ -294,6 +320,7 @@ impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { } impl<'d, T: Instance> UartRx<'d, T, Blocking> { + /// Create a new UART RX instance for blocking mode operations. pub fn new_blocking( _uart: impl Peripheral

+ 'd, rx: impl Peripheral

> + 'd, @@ -304,6 +331,8 @@ impl<'d, T: Instance> UartRx<'d, T, Blocking> { Self::new_inner(false, None) } + /// Convert this uart RX instance into a buffered uart using the provided + /// irq and receive buffer. pub fn into_buffered( self, irq: impl Binding>, @@ -315,6 +344,7 @@ impl<'d, T: Instance> UartRx<'d, T, Blocking> { } } +/// Interrupt handler. pub struct InterruptHandler { _uart: PhantomData, } @@ -338,6 +368,7 @@ impl interrupt::typelevel::Handler for InterruptHandl } impl<'d, T: Instance> UartRx<'d, T, Async> { + /// Read from UART RX into the provided buffer. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { // clear error flags before we drain the fifo. errors that have accumulated // in the flags will also be present in the fifo. @@ -458,6 +489,8 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { ) } + /// Convert this uart instance into a buffered uart using the provided + /// irq, transmit and receive buffers. pub fn into_buffered( self, irq: impl Binding>, @@ -667,22 +700,27 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { } impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { + /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buffer) } + /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { self.tx.blocking_flush() } + /// Read from UART RX blocking execution until done. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.blocking_read(buffer) } + /// Check if UART is busy transmitting. pub fn busy(&self) -> bool { self.tx.busy() } + /// Wait until TX is empty and send break condition. pub async fn send_break(&mut self, bits: u32) { self.tx.send_break(bits).await } @@ -695,10 +733,12 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { } impl<'d, T: Instance> Uart<'d, T, Async> { + /// Write to UART TX from the provided buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.write(buffer).await } + /// Read from UART RX into the provided buffer. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await } @@ -889,6 +929,7 @@ mod sealed { pub trait RtsPin {} } +/// UART mode. pub trait Mode: sealed::Mode {} macro_rules! impl_mode { @@ -898,12 +939,15 @@ macro_rules! impl_mode { }; } +/// Blocking mode. pub struct Blocking; +/// Async mode. pub struct Async; impl_mode!(Blocking); impl_mode!(Async); +/// UART instance trait. pub trait Instance: sealed::Instance {} macro_rules! impl_instance { -- cgit