mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-30 22:01:07 +00:00
rp: add pio spi runtime reconfiguration
This commit is contained in:
parent
83b42e0db6
commit
62ff0194f4
@ -11,12 +11,13 @@ use fixed::types::extra::U8;
|
|||||||
use crate::clocks::clk_sys_freq;
|
use crate::clocks::clk_sys_freq;
|
||||||
use crate::dma::{AnyChannel, Channel};
|
use crate::dma::{AnyChannel, Channel};
|
||||||
use crate::gpio::Level;
|
use crate::gpio::Level;
|
||||||
use crate::pio::{Common, Direction, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine};
|
use crate::pio::{Common, Direction, Instance, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine};
|
||||||
use crate::spi::{Async, Blocking, Mode};
|
use crate::spi::{Async, Blocking, Config, Mode};
|
||||||
|
|
||||||
/// This struct represents a uart tx program loaded into pio instruction memory.
|
/// This struct represents an SPI program loaded into pio instruction memory.
|
||||||
pub struct PioSpiProgram<'d, PIO: Instance> {
|
struct PioSpiProgram<'d, PIO: Instance> {
|
||||||
prg: LoadedProgram<'d, PIO>,
|
prg: LoadedProgram<'d, PIO>,
|
||||||
|
phase: Phase,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
|
impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
|
||||||
@ -30,11 +31,11 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
|
|||||||
// - MOSI is OUT pin 0
|
// - MOSI is OUT pin 0
|
||||||
// - MISO is IN pin 0
|
// - MISO is IN pin 0
|
||||||
//
|
//
|
||||||
// Autopush and autopull must be enabled, and the serial frame size is set by
|
// Auto-push and auto-pull must be enabled, and the serial frame size is set by
|
||||||
// configuring the push/pull threshold. Shift left/right is fine, but you must
|
// configuring the push/pull threshold. Shift left/right is fine, but you must
|
||||||
// justify the data yourself. This is done most conveniently for frame sizes of
|
// justify the data yourself. This is done most conveniently for frame sizes of
|
||||||
// 8 or 16 bits by using the narrow store replication and narrow load byte
|
// 8 or 16 bits by using the narrow store replication and narrow load byte
|
||||||
// picking behaviour of RP2040's IO fabric.
|
// picking behavior of RP2040's IO fabric.
|
||||||
|
|
||||||
let prg = match phase {
|
let prg = match phase {
|
||||||
Phase::CaptureOnFirstTransition => {
|
Phase::CaptureOnFirstTransition => {
|
||||||
@ -60,9 +61,9 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
|
|||||||
; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
|
; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
|
||||||
; is captured on the trailing edge.
|
; is captured on the trailing edge.
|
||||||
|
|
||||||
out x, 1 side 0 ; Stall here on empty (keep SCK deasserted)
|
out x, 1 side 0 ; Stall here on empty (keep SCK de-asserted)
|
||||||
mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
|
mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
|
||||||
in pins, 1 side 0 ; Input data, deassert SCK
|
in pins, 1 side 0 ; Input data, de-assert SCK
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self { prg }
|
Self { prg, phase }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,35 +84,19 @@ pub enum Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// PIO based Spi driver.
|
/// PIO based Spi driver.
|
||||||
///
|
/// Unlike other PIO programs, the PIO SPI driver owns and holds a reference to
|
||||||
/// This driver is less flexible than the hardware backed one. Configuration can
|
/// the PIO memory it uses. This is so that it can be reconfigured at runtime if
|
||||||
/// not be changed at runtime.
|
/// desired.
|
||||||
pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> {
|
pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> {
|
||||||
sm: StateMachine<'d, PIO, SM>,
|
sm: StateMachine<'d, PIO, SM>,
|
||||||
|
cfg: crate::pio::Config<'d, PIO>,
|
||||||
|
program: Option<PioSpiProgram<'d, PIO>>,
|
||||||
|
clk_pin: Pin<'d, PIO>,
|
||||||
tx_dma: Option<Peri<'d, AnyChannel>>,
|
tx_dma: Option<Peri<'d, AnyChannel>>,
|
||||||
rx_dma: Option<Peri<'d, AnyChannel>>,
|
rx_dma: Option<Peri<'d, AnyChannel>>,
|
||||||
phantom: PhantomData<M>,
|
phantom: PhantomData<M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PIO SPI configuration.
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Config {
|
|
||||||
/// Frequency (Hz).
|
|
||||||
pub frequency: u32,
|
|
||||||
/// Polarity.
|
|
||||||
pub polarity: Polarity,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
frequency: 1_000_000,
|
|
||||||
polarity: Polarity::IdleLow,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
|
impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn new_inner(
|
fn new_inner(
|
||||||
@ -122,9 +107,10 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
|
|||||||
miso_pin: Peri<'d, impl PioPin>,
|
miso_pin: Peri<'d, impl PioPin>,
|
||||||
tx_dma: Option<Peri<'d, AnyChannel>>,
|
tx_dma: Option<Peri<'d, AnyChannel>>,
|
||||||
rx_dma: Option<Peri<'d, AnyChannel>>,
|
rx_dma: Option<Peri<'d, AnyChannel>>,
|
||||||
program: &PioSpiProgram<'d, PIO>,
|
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let program = PioSpiProgram::new(pio, config.phase);
|
||||||
|
|
||||||
let mut clk_pin = pio.make_pio_pin(clk_pin);
|
let mut clk_pin = pio.make_pio_pin(clk_pin);
|
||||||
let mosi_pin = pio.make_pio_pin(mosi_pin);
|
let mosi_pin = pio.make_pio_pin(mosi_pin);
|
||||||
let miso_pin = pio.make_pio_pin(miso_pin);
|
let miso_pin = pio.make_pio_pin(miso_pin);
|
||||||
@ -153,15 +139,16 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
|
|||||||
cfg.shift_out.direction = ShiftDirection::Left;
|
cfg.shift_out.direction = ShiftDirection::Left;
|
||||||
cfg.shift_out.threshold = 8;
|
cfg.shift_out.threshold = 8;
|
||||||
|
|
||||||
let sys_freq = clk_sys_freq().to_fixed::<fixed::FixedU64<U8>>();
|
cfg.clock_divider = calculate_clock_divider(config.frequency);
|
||||||
let target_freq = (config.frequency * 4).to_fixed::<fixed::FixedU64<U8>>();
|
|
||||||
cfg.clock_divider = (sys_freq / target_freq).to_fixed();
|
|
||||||
|
|
||||||
sm.set_config(&cfg);
|
sm.set_config(&cfg);
|
||||||
sm.set_enable(true);
|
sm.set_enable(true);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
sm,
|
sm,
|
||||||
|
program: Some(program),
|
||||||
|
cfg,
|
||||||
|
clk_pin,
|
||||||
tx_dma,
|
tx_dma,
|
||||||
rx_dma,
|
rx_dma,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
@ -242,6 +229,63 @@ impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set SPI frequency.
|
||||||
|
pub fn set_frequency(&mut self, freq: u32) {
|
||||||
|
self.sm.set_enable(false);
|
||||||
|
|
||||||
|
let divider = calculate_clock_divider(freq);
|
||||||
|
|
||||||
|
// save into the config for later but dont use sm.set_config() since
|
||||||
|
// that operation is relatively more expensive than just setting the
|
||||||
|
// clock divider
|
||||||
|
self.cfg.clock_divider = divider;
|
||||||
|
self.sm.set_clock_divider(divider);
|
||||||
|
|
||||||
|
self.sm.set_enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set SPI config.
|
||||||
|
///
|
||||||
|
/// This operation will panic if the PIO program needs to be reloaded and
|
||||||
|
/// there is insufficient room. This is unlikely since the programs for each
|
||||||
|
/// phase only differ in size by a single instruction.
|
||||||
|
pub fn set_config(&mut self, pio: &mut Common<'d, PIO>, config: &Config) {
|
||||||
|
self.sm.set_enable(false);
|
||||||
|
|
||||||
|
self.cfg.clock_divider = calculate_clock_divider(config.frequency);
|
||||||
|
|
||||||
|
if let Polarity::IdleHigh = config.polarity {
|
||||||
|
self.clk_pin.set_output_inversion(true);
|
||||||
|
} else {
|
||||||
|
self.clk_pin.set_output_inversion(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.program.as_ref().unwrap().phase != config.phase {
|
||||||
|
let old_program = self.program.take().unwrap();
|
||||||
|
|
||||||
|
// SAFETY: the state machine is disabled while this happens
|
||||||
|
unsafe { pio.free_instr(old_program.prg.used_memory) };
|
||||||
|
|
||||||
|
let new_program = PioSpiProgram::new(pio, config.phase);
|
||||||
|
|
||||||
|
self.cfg.use_program(&new_program.prg, &[&self.clk_pin]);
|
||||||
|
self.program = Some(new_program);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sm.set_config(&self.cfg);
|
||||||
|
self.sm.restart();
|
||||||
|
|
||||||
|
self.sm.set_enable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_clock_divider(frequency_hz: u32) -> fixed::FixedU32<U8> {
|
||||||
|
// we multiply by 4 since each clock period is equal to 4 instructions
|
||||||
|
|
||||||
|
let sys_freq = clk_sys_freq().to_fixed::<fixed::FixedU64<U8>>();
|
||||||
|
let target_freq = (frequency_hz * 4).to_fixed::<fixed::FixedU64<U8>>();
|
||||||
|
(sys_freq / target_freq).to_fixed()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> {
|
impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> {
|
||||||
@ -252,10 +296,9 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> {
|
|||||||
clk: Peri<'d, impl PioPin>,
|
clk: Peri<'d, impl PioPin>,
|
||||||
mosi: Peri<'d, impl PioPin>,
|
mosi: Peri<'d, impl PioPin>,
|
||||||
miso: Peri<'d, impl PioPin>,
|
miso: Peri<'d, impl PioPin>,
|
||||||
program: &PioSpiProgram<'d, PIO>,
|
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_inner(pio, sm, clk, mosi, miso, None, None, program, config)
|
Self::new_inner(pio, sm, clk, mosi, miso, None, None, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +313,6 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> {
|
|||||||
miso: Peri<'d, impl PioPin>,
|
miso: Peri<'d, impl PioPin>,
|
||||||
tx_dma: Peri<'d, impl Channel>,
|
tx_dma: Peri<'d, impl Channel>,
|
||||||
rx_dma: Peri<'d, impl Channel>,
|
rx_dma: Peri<'d, impl Channel>,
|
||||||
program: &PioSpiProgram<'d, PIO>,
|
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_inner(
|
Self::new_inner(
|
||||||
@ -281,7 +323,6 @@ impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> {
|
|||||||
miso,
|
miso,
|
||||||
Some(tx_dma.into()),
|
Some(tx_dma.into()),
|
||||||
Some(rx_dma.into()),
|
Some(rx_dma.into()),
|
||||||
program,
|
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio_programs::spi::{Config, PioSpiProgram, Spi};
|
use embassy_rp::pio_programs::spi::Spi;
|
||||||
use embassy_rp::spi::Phase;
|
use embassy_rp::spi::Config;
|
||||||
use embassy_rp::{bind_interrupts, pio};
|
use embassy_rp::{bind_interrupts, pio};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
@ -25,7 +25,7 @@ async fn main(_spawner: Spawner) {
|
|||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
// These pins are routed to differnet hardware SPI peripherals, but we can
|
// These pins are routed to different hardware SPI peripherals, but we can
|
||||||
// use them together regardless
|
// use them together regardless
|
||||||
let mosi = p.PIN_6; // SPI0 SCLK
|
let mosi = p.PIN_6; // SPI0 SCLK
|
||||||
let miso = p.PIN_7; // SPI0 MOSI
|
let miso = p.PIN_7; // SPI0 MOSI
|
||||||
@ -33,20 +33,8 @@ async fn main(_spawner: Spawner) {
|
|||||||
|
|
||||||
let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
|
let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
|
||||||
|
|
||||||
// The PIO program must be configured with the clock phase
|
|
||||||
let program = PioSpiProgram::new(&mut common, Phase::CaptureOnFirstTransition);
|
|
||||||
|
|
||||||
// Construct an SPI driver backed by a PIO state machine
|
// Construct an SPI driver backed by a PIO state machine
|
||||||
let mut spi = Spi::new_blocking(
|
let mut spi = Spi::new_blocking(&mut common, sm0, clk, mosi, miso, Config::default());
|
||||||
&mut common,
|
|
||||||
sm0,
|
|
||||||
clk,
|
|
||||||
mosi,
|
|
||||||
miso,
|
|
||||||
&program,
|
|
||||||
// Only the frequency and polarity are set here
|
|
||||||
Config::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let tx_buf = [1_u8, 2, 3, 4, 5, 6];
|
let tx_buf = [1_u8, 2, 3, 4, 5, 6];
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio_programs::spi::{Config, PioSpiProgram, Spi};
|
use embassy_rp::pio_programs::spi::Spi;
|
||||||
use embassy_rp::spi::Phase;
|
use embassy_rp::spi::Config;
|
||||||
use embassy_rp::{bind_interrupts, pio};
|
use embassy_rp::{bind_interrupts, pio};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
@ -25,7 +25,7 @@ async fn main(_spawner: Spawner) {
|
|||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
// These pins are routed to differnet hardware SPI peripherals, but we can
|
// These pins are routed to different hardware SPI peripherals, but we can
|
||||||
// use them together regardless
|
// use them together regardless
|
||||||
let mosi = p.PIN_6; // SPI0 SCLK
|
let mosi = p.PIN_6; // SPI0 SCLK
|
||||||
let miso = p.PIN_7; // SPI0 MOSI
|
let miso = p.PIN_7; // SPI0 MOSI
|
||||||
@ -33,9 +33,6 @@ async fn main(_spawner: Spawner) {
|
|||||||
|
|
||||||
let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
|
let pio::Pio { mut common, sm0, .. } = pio::Pio::new(p.PIO0, Irqs);
|
||||||
|
|
||||||
// The PIO program must be configured with the clock phase
|
|
||||||
let program = PioSpiProgram::new(&mut common, Phase::CaptureOnFirstTransition);
|
|
||||||
|
|
||||||
// Construct an SPI driver backed by a PIO state machine
|
// Construct an SPI driver backed by a PIO state machine
|
||||||
let mut spi = Spi::new(
|
let mut spi = Spi::new(
|
||||||
&mut common,
|
&mut common,
|
||||||
@ -46,7 +43,6 @@ async fn main(_spawner: Spawner) {
|
|||||||
p.DMA_CH0,
|
p.DMA_CH0,
|
||||||
p.DMA_CH1,
|
p.DMA_CH1,
|
||||||
&program,
|
&program,
|
||||||
// Only the frequency and polarity are set here
|
|
||||||
Config::default(),
|
Config::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user