Merge pull request #1707 from pennae/rp-pio-load

rp: relocate programs implicitly during load
This commit is contained in:
Dario Nieuwenhuis 2023-07-28 17:47:34 +00:00 committed by GitHub
commit e3cc0d168c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 200 additions and 71 deletions

View File

@ -8,7 +8,6 @@ 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::relocate::RelocatedProgram;
use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef};
use fixed::FixedU32;
use pio_proc::pio_asm;
@ -88,8 +87,6 @@ where
".wrap"
);
let relocated = RelocatedProgram::new(&program.program);
let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio);
pin_io.set_pull(Pull::None);
pin_io.set_schmitt(true);
@ -102,7 +99,8 @@ where
pin_clk.set_slew_rate(SlewRate::Fast);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&relocated), &[&pin_clk]);
let loaded_program = common.load_program(&program.program);
cfg.use_program(&loaded_program, &[&pin_clk]);
cfg.set_out_pins(&[&pin_io]);
cfg.set_in_pins(&[&pin_io]);
cfg.set_set_pins(&[&pin_io]);
@ -142,7 +140,7 @@ where
sm,
irq,
dma: dma.into_ref(),
wrap_target: relocated.wrap().target,
wrap_target: loaded_program.wrap.target,
}
}

View File

@ -33,7 +33,7 @@ pub mod watchdog;
// TODO: move `pio_instr_util` and `relocate` to inside `pio`
pub mod pio;
pub mod pio_instr_util;
pub mod relocate;
pub(crate) mod relocate;
// Reexports
pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};

View File

@ -11,7 +11,7 @@ use fixed::types::extra::U8;
use fixed::FixedU32;
use pac::io::vals::Gpio0ctrlFuncsel;
use pac::pio::vals::SmExecctrlStatusSel;
use pio::{SideSet, Wrap};
use pio::{Program, SideSet, Wrap};
use crate::dma::{Channel, Transfer, Word};
use crate::gpio::sealed::Pin as SealedPin;
@ -734,23 +734,67 @@ pub struct InstanceMemory<'d, PIO: Instance> {
pub struct LoadedProgram<'d, PIO: Instance> {
pub used_memory: InstanceMemory<'d, PIO>,
origin: u8,
wrap: Wrap,
side_set: SideSet,
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> {
pub fn load_program<const SIZE: usize>(&mut self, prog: &RelocatedProgram<SIZE>) -> LoadedProgram<'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<const SIZE: usize>(&mut self, prog: &Program<SIZE>) -> LoadedProgram<'d, PIO> {
match self.try_load_program(prog) {
Ok(r) => r,
Err(at) => panic!("Trying to write already used PIO instruction memory at {}", at),
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<const SIZE: usize>(
&mut self,
prog: &RelocatedProgram<SIZE>,
prog: &Program<SIZE>,
) -> Result<LoadedProgram<'d, PIO>, 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<const SIZE: usize>(
&mut self,
prog: &Program<SIZE>,
origin: u8,
) -> Result<LoadedProgram<'d, PIO>, 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,
@ -760,7 +804,7 @@ impl<'d, PIO: Instance> Common<'d, PIO> {
})
}
pub fn try_write_instr<I>(&mut self, start: usize, instrs: I) -> Result<InstanceMemory<'d, PIO>, usize>
fn try_write_instr<I>(&mut self, start: usize, instrs: I) -> Result<InstanceMemory<'d, PIO>, usize>
where
I: Iterator<Item = u16>,
{

View File

@ -41,11 +41,6 @@ pub struct RelocatedProgram<'a, const PROGRAM_SIZE: usize> {
}
impl<'a, const PROGRAM_SIZE: usize> RelocatedProgram<'a, PROGRAM_SIZE> {
pub fn new(program: &Program<PROGRAM_SIZE>) -> RelocatedProgram<PROGRAM_SIZE> {
let origin = program.origin.unwrap_or(0);
RelocatedProgram { program, origin }
}
pub fn new_with_origin(program: &Program<PROGRAM_SIZE>, origin: u8) -> RelocatedProgram<PROGRAM_SIZE> {
RelocatedProgram { program, origin }
}

View File

@ -8,7 +8,6 @@ use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine};
use embassy_rp::relocate::RelocatedProgram;
use fixed::traits::ToFixed;
use fixed_macro::types::U56F8;
use {defmt_rtt as _, panic_probe as _};
@ -29,9 +28,8 @@ fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a,
".wrap",
);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&pio.load_program(&relocated), &[]);
cfg.use_program(&pio.load_program(&prg.program), &[]);
let out_pin = pio.make_pio_pin(pin);
cfg.set_out_pins(&[&out_pin]);
cfg.set_set_pins(&[&out_pin]);
@ -65,9 +63,8 @@ fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a,
".wrap",
);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&pio.load_program(&relocated), &[]);
cfg.use_program(&pio.load_program(&prg.program), &[]);
cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed();
cfg.shift_in.auto_fill = true;
cfg.shift_in.direction = ShiftDirection::Right;
@ -96,9 +93,8 @@ fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a,
"irq 3 [15]",
".wrap",
);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&pio.load_program(&relocated), &[]);
cfg.use_program(&pio.load_program(&prg.program), &[]);
cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed();
sm.set_config(&cfg);
}

View File

@ -8,7 +8,6 @@ use embassy_executor::Spawner;
use embassy_futures::join::join;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection};
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{bind_interrupts, Peripheral};
use fixed::traits::ToFixed;
use fixed_macro::types::U56F8;
@ -46,9 +45,8 @@ async fn main(_spawner: Spawner) {
".wrap",
);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&relocated), &[]);
cfg.use_program(&common.load_program(&prg.program), &[]);
cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(10_000)).to_fixed();
cfg.shift_in = ShiftConfig {
auto_fill: true,

View File

@ -14,7 +14,6 @@ use embassy_rp::pio::{
Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use embassy_rp::pwm::{self, Pwm};
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef};
use embassy_time::{Duration, Instant, Timer};
use {defmt_rtt as _, panic_probe as _};
@ -127,9 +126,8 @@ impl<'l> HD44780<'l> {
sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&relocated), &[&e]);
cfg.use_program(&common.load_program(&prg.program), &[&e]);
cfg.clock_divider = 125u8.into();
cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
cfg.shift_out = ShiftConfig {
@ -201,9 +199,8 @@ impl<'l> HD44780<'l> {
"#
);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&relocated), &[&e]);
cfg.use_program(&common.load_program(&prg.program), &[&e]);
cfg.clock_divider = 8u8.into(); // ~64ns/insn
cfg.set_jmp_pin(&db7);
cfg.set_set_pins(&[&rs, &rw]);

View File

@ -222,8 +222,8 @@ mod uart {
mut common, sm0, sm1, ..
} = Pio::new(pio, Irqs);
let (tx, origin) = PioUartTx::new(&mut common, sm0, tx_pin, baud, None);
let (rx, _) = PioUartRx::new(&mut common, sm1, rx_pin, baud, Some(origin));
let tx = PioUartTx::new(&mut common, sm0, tx_pin, baud);
let rx = PioUartRx::new(&mut common, sm1, rx_pin, baud);
PioUart { tx, rx }
}
@ -240,7 +240,6 @@ mod uart_tx {
use embassy_rp::gpio::Level;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine};
use embassy_rp::relocate::RelocatedProgram;
use embedded_io::asynch::Write;
use embedded_io::Io;
use fixed::traits::ToFixed;
@ -256,9 +255,8 @@ mod uart_tx {
mut sm_tx: StateMachine<'a, PIO0, 0>,
tx_pin: impl PioPin,
baud: u64,
origin: Option<u8>,
) -> (Self, u8) {
let mut prg = pio_proc::pio_asm!(
) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.side_set 1 opt
@ -272,17 +270,14 @@ mod uart_tx {
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
"#
);
prg.program.origin = origin;
let tx_pin = common.make_pio_pin(tx_pin);
sm_tx.set_pins(Level::High, &[&tx_pin]);
sm_tx.set_pin_dirs(Direction::Out, &[&tx_pin]);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.set_out_pins(&[&tx_pin]);
cfg.use_program(&common.load_program(&relocated), &[&tx_pin]);
cfg.use_program(&common.load_program(&prg.program), &[&tx_pin]);
cfg.shift_out.auto_fill = false;
cfg.shift_out.direction = ShiftDirection::Right;
cfg.fifo_join = FifoJoin::TxOnly;
@ -290,18 +285,7 @@ mod uart_tx {
sm_tx.set_config(&cfg);
sm_tx.set_enable(true);
// The 4 state machines of the PIO each have their own program counter that starts taking
// instructions at an offset (origin) of the 32 instruction "space" the PIO device has.
// It is up to the programmer to sort out where to place these instructions.
// From the pio_asm! macro you get a ProgramWithDefines which has a field .program.origin
// which takes an Option<u8>.
//
// When you load more than one RelocatedProgram into the PIO,
// you load your first program at origin = 0.
// The RelocatedProgram has .code().count() which returns a usize,
// for which you can then use as your next program's origin.
let offset = relocated.code().count() as u8 + origin.unwrap_or_default();
(Self { sm_tx }, offset)
Self { sm_tx }
}
pub async fn write_u8(&mut self, data: u8) {
@ -329,7 +313,6 @@ mod uart_rx {
use embassy_rp::gpio::Level;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine};
use embassy_rp::relocate::RelocatedProgram;
use embedded_io::asynch::Read;
use embedded_io::Io;
use fixed::traits::ToFixed;
@ -345,9 +328,8 @@ mod uart_rx {
mut sm_rx: StateMachine<'a, PIO0, 1>,
rx_pin: impl PioPin,
baud: u64,
origin: Option<u8>,
) -> (Self, u8) {
let mut prg = pio_proc::pio_asm!(
) -> Self {
let prg = pio_proc::pio_asm!(
r#"
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
; break conditions more gracefully.
@ -369,10 +351,8 @@ mod uart_rx {
push ; important in case the TX clock is slightly too fast.
"#
);
prg.program.origin = origin;
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&relocated), &[]);
cfg.use_program(&common.load_program(&prg.program), &[]);
let rx_pin = common.make_pio_pin(rx_pin);
sm_rx.set_pins(Level::High, &[&rx_pin]);
@ -387,8 +367,7 @@ mod uart_rx {
sm_rx.set_config(&cfg);
sm_rx.set_enable(true);
let offset = relocated.code().count() as u8 + origin.unwrap_or_default();
(Self { sm_rx }, offset)
Self { sm_rx }
}
pub async fn read_u8(&mut self) -> u8 {

View File

@ -12,7 +12,6 @@ use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{
Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef};
use embassy_time::{Duration, Timer};
use fixed::types::U24F8;
@ -73,8 +72,7 @@ impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> {
cfg.set_out_pins(&[&out_pin]);
cfg.set_set_pins(&[&out_pin]);
let relocated = RelocatedProgram::new(&prg);
cfg.use_program(&pio.load_program(&relocated), &[&out_pin]);
cfg.use_program(&pio.load_program(&prg), &[&out_pin]);
// Clock config, measured in kHz to avoid overflows
// TODO CLOCK_FREQ should come from embassy_rp

View File

@ -9,7 +9,6 @@ use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Config, InterruptHandler, Pio};
use embassy_rp::relocate::RelocatedProgram;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
@ -35,9 +34,8 @@ async fn main(_spawner: Spawner) {
"irq wait 1",
);
let relocated = RelocatedProgram::new(&prg.program);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&relocated), &[]);
cfg.use_program(&common.load_program(&prg.program), &[]);
sm.set_config(&cfg);
sm.set_enable(true);

View File

@ -0,0 +1,126 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#[path = "../common.rs"]
mod common;
use defmt::info;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Config, InterruptHandler, LoadError, Pio};
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let pio = p.PIO0;
let Pio {
mut common,
mut sm0,
mut sm1,
mut sm2,
irq_flags,
..
} = Pio::new(pio, Irqs);
// load with explicit origin works
let prg1 = pio_proc::pio_asm!(
".origin 4"
"nop",
"nop",
"nop",
"nop",
"nop",
"nop",
"nop",
"irq 0",
"nop",
"nop",
);
let loaded1 = common.load_program(&prg1.program);
assert_eq!(loaded1.origin, 4);
assert_eq!(loaded1.wrap.source, 13);
assert_eq!(loaded1.wrap.target, 4);
// load without origin chooses a free space
let prg2 = pio_proc::pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 1", "nop", "nop",);
let loaded2 = common.load_program(&prg2.program);
assert_eq!(loaded2.origin, 14);
assert_eq!(loaded2.wrap.source, 23);
assert_eq!(loaded2.wrap.target, 14);
// wrapping around the end of program space automatically works
let prg3 =
pio_proc::pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 2",);
let loaded3 = common.load_program(&prg3.program);
assert_eq!(loaded3.origin, 24);
assert_eq!(loaded3.wrap.source, 3);
assert_eq!(loaded3.wrap.target, 24);
// check that the programs actually work
{
let mut cfg = Config::default();
cfg.use_program(&loaded1, &[]);
sm0.set_config(&cfg);
sm0.set_enable(true);
while !irq_flags.check(0) {}
sm0.set_enable(false);
}
{
let mut cfg = Config::default();
cfg.use_program(&loaded2, &[]);
sm1.set_config(&cfg);
sm1.set_enable(true);
while !irq_flags.check(1) {}
sm1.set_enable(false);
}
{
let mut cfg = Config::default();
cfg.use_program(&loaded3, &[]);
sm2.set_config(&cfg);
sm2.set_enable(true);
while !irq_flags.check(2) {}
sm2.set_enable(false);
}
// instruction memory is full now. all loads should fail.
{
let prg = pio_proc::pio_asm!(".origin 0", "nop");
match common.try_load_program(&prg.program) {
Err(LoadError::AddressInUse(0)) => (),
_ => panic!("program loaded when it shouldn't"),
};
let prg = pio_proc::pio_asm!("nop");
match common.try_load_program(&prg.program) {
Err(LoadError::InsufficientSpace) => (),
_ => panic!("program loaded when it shouldn't"),
};
}
// freeing some memory should allow further loads though.
unsafe {
common.free_instr(loaded3.used_memory);
}
{
let prg = pio_proc::pio_asm!(".origin 0", "nop");
match common.try_load_program(&prg.program) {
Ok(_) => (),
_ => panic!("program didn't loaded when it shouldn"),
};
let prg = pio_proc::pio_asm!("nop");
match common.try_load_program(&prg.program) {
Ok(_) => (),
_ => panic!("program didn't loaded when it shouldn"),
};
}
info!("Test OK");
cortex_m::asm::bkpt();
}