mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-27 12:20:37 +00:00
122 lines
3.6 KiB
Rust
122 lines
3.6 KiB
Rust
//! PIO backed PWM driver
|
|
|
|
use core::time::Duration;
|
|
|
|
use pio::InstructionOperands;
|
|
|
|
use crate::clocks;
|
|
use crate::gpio::Level;
|
|
use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine};
|
|
|
|
/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time
|
|
fn to_pio_cycles(duration: Duration) -> u32 {
|
|
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
|
|
}
|
|
|
|
/// This struct represents a PWM program loaded into pio instruction memory.
|
|
pub struct PioPwmProgram<'a, PIO: Instance> {
|
|
prg: LoadedProgram<'a, PIO>,
|
|
}
|
|
|
|
impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
|
|
/// Load the program into the given pio
|
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
|
let prg = pio_proc::pio_asm!(
|
|
".side_set 1 opt"
|
|
"pull noblock side 0"
|
|
"mov x, osr"
|
|
"mov y, isr"
|
|
"countloop:"
|
|
"jmp x!=y noset"
|
|
"jmp skip side 1"
|
|
"noset:"
|
|
"nop"
|
|
"skip:"
|
|
"jmp y-- countloop"
|
|
);
|
|
|
|
let prg = common.load_program(&prg.program);
|
|
|
|
Self { prg }
|
|
}
|
|
}
|
|
|
|
/// Pio backed PWM output
|
|
pub struct PioPwm<'d, T: Instance, const SM: usize> {
|
|
sm: StateMachine<'d, T, SM>,
|
|
pin: Pin<'d, T>,
|
|
}
|
|
|
|
impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
|
|
/// Configure a state machine as a PWM output
|
|
pub fn new(
|
|
pio: &mut Common<'d, T>,
|
|
mut sm: StateMachine<'d, T, SM>,
|
|
pin: impl PioPin,
|
|
program: &PioPwmProgram<'d, T>,
|
|
) -> Self {
|
|
let pin = pio.make_pio_pin(pin);
|
|
sm.set_pins(Level::High, &[&pin]);
|
|
sm.set_pin_dirs(Direction::Out, &[&pin]);
|
|
|
|
let mut cfg = Config::default();
|
|
cfg.use_program(&program.prg, &[&pin]);
|
|
|
|
sm.set_config(&cfg);
|
|
|
|
Self { sm, pin }
|
|
}
|
|
|
|
/// Enable's the PIO program, continuing the wave generation from the PIO program.
|
|
pub fn start(&mut self) {
|
|
self.sm.set_enable(true);
|
|
}
|
|
|
|
/// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO.
|
|
pub fn stop(&mut self) {
|
|
self.sm.set_enable(false);
|
|
}
|
|
|
|
/// Sets the pwm period, which is the length of time for each pio wave until reset.
|
|
pub fn set_period(&mut self, duration: Duration) {
|
|
let is_enabled = self.sm.is_enabled();
|
|
while !self.sm.tx().empty() {} // Make sure that the queue is empty
|
|
self.sm.set_enable(false);
|
|
self.sm.tx().push(to_pio_cycles(duration));
|
|
unsafe {
|
|
self.sm.exec_instr(
|
|
InstructionOperands::PULL {
|
|
if_empty: false,
|
|
block: false,
|
|
}
|
|
.encode(),
|
|
);
|
|
self.sm.exec_instr(
|
|
InstructionOperands::OUT {
|
|
destination: ::pio::OutDestination::ISR,
|
|
bit_count: 32,
|
|
}
|
|
.encode(),
|
|
);
|
|
};
|
|
if is_enabled {
|
|
self.sm.set_enable(true) // Enable if previously enabled
|
|
}
|
|
}
|
|
|
|
/// Set the number of pio cycles to set the wave on high to.
|
|
pub fn set_level(&mut self, level: u32) {
|
|
self.sm.tx().push(level);
|
|
}
|
|
|
|
/// Set the pulse width high time
|
|
pub fn write(&mut self, duration: Duration) {
|
|
self.set_level(to_pio_cycles(duration));
|
|
}
|
|
|
|
// Return the state machine and pin.
|
|
pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) {
|
|
(self.sm, self.pin)
|
|
}
|
|
}
|