-
Notifications
You must be signed in to change notification settings - Fork 1.4k
RP: add pio program to drive bipolar steppers through driver board #5808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
1-rafael-1
wants to merge
4
commits into
embassy-rs:main
Choose a base branch
from
1-rafael-1:bipolar-4-wire-stepper
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+517
−0
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
42d4871
add a new pio submodule for pulse and direction control that can be used
1-rafael-1 c90945e
add examples in rp and rp23
1-rafael-1 a95ea66
refactor after testing: no misleading continuous mode, some
1-rafael-1 10dfdfb
correct outdated comments, add drop
1-rafael-1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,310 @@ | ||
| //! PIO-backed Step/Dir pulse generator for stepper motor drivers. | ||
| //! | ||
| //! This module targets drivers that expect a STEP pulse input and a DIR level input, | ||
| //! such as TMC2209 and similar. | ||
|
|
||
| use core::mem::{self, MaybeUninit}; | ||
|
|
||
| use crate::gpio::{Level, Output, Pin as GpioPin}; | ||
| use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, Pin as PioPinHandle, PioPin, StateMachine}; | ||
| use crate::pio_programs::clock_divider::calculate_pio_clock_divider; | ||
| use crate::{Peri, clocks}; | ||
|
|
||
| /// Default pulse frequency used during initialization. | ||
| const DEFAULT_FREQUENCY_HZ: u32 = 100; | ||
|
|
||
| /// Configure the STEP pulse timing (cycles per pulse). | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| pub enum StepPulseTiming { | ||
| /// 34 cycles high + 34 cycles low + 2 jump cycles = 68 total. | ||
| Cycles68, | ||
| /// 66 cycles high + 66 cycles low + 2 jump cycles = 132 total. | ||
| Cycles132, | ||
| /// 130 cycles high + 130 cycles low + 2 jump cycles = 260 total. | ||
| Cycles260, | ||
| } | ||
|
|
||
| impl Default for StepPulseTiming { | ||
| fn default() -> Self { | ||
| StepPulseTiming::Cycles68 | ||
| } | ||
| } | ||
|
|
||
| impl StepPulseTiming { | ||
| fn cycles_per_pulse(self) -> u32 { | ||
| match self { | ||
| StepPulseTiming::Cycles68 => 68, | ||
| StepPulseTiming::Cycles132 => 132, | ||
| StepPulseTiming::Cycles260 => 260, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Program loaded into PIO instruction memory for STEP pulse generation. | ||
| pub struct PioStepDirProgram<'a, PIO: Instance> { | ||
| prg: LoadedProgram<'a, PIO>, | ||
| cycles_per_pulse: u32, | ||
| } | ||
|
|
||
| impl<'a, PIO: Instance> PioStepDirProgram<'a, PIO> { | ||
| /// Load the STEP pulse program into the given PIO using the default timing. | ||
| pub fn new(common: &mut Common<'a, PIO>) -> Self { | ||
| Self::new_with_timing(common, StepPulseTiming::default()) | ||
| } | ||
|
|
||
| /// Load the STEP pulse program into the given PIO using the selected timing. | ||
| pub fn new_with_timing(common: &mut Common<'a, PIO>, timing: StepPulseTiming) -> Self { | ||
| let (prg, cycles_per_pulse) = match timing { | ||
| StepPulseTiming::Cycles68 => { | ||
| let prg = pio::pio_asm!( | ||
| "pull block ; wait for step count word", | ||
| "mov x, osr ; X = steps", | ||
| "loop:", | ||
| "jmp !x end ; if 0 steps, finish", | ||
| "set pins, 1 [31] ; STEP high, hold for 32 cycles", | ||
| "nop [1] ; extend high by 2 cycles", | ||
| "set pins, 0 [31] ; STEP low, hold for 32 cycles", | ||
| "jmp x-- loop ; decrement steps and loop", | ||
| "end:", | ||
| "irq 0 rel ; signal completion" | ||
| ); | ||
| (common.load_program(&prg.program), timing.cycles_per_pulse()) | ||
| } | ||
| StepPulseTiming::Cycles132 => { | ||
| let prg = pio::pio_asm!( | ||
| "pull block ; wait for step count word", | ||
| "mov x, osr ; X = steps", | ||
| "loop:", | ||
| "jmp !x end ; if 0 steps, finish", | ||
| "set pins, 1 [31] ; STEP high, hold for 32 cycles", | ||
| "nop [31] ; extend high by 32 cycles", | ||
| "nop [1] ; extend high by 2 cycles", | ||
| "set pins, 0 [31] ; STEP low, hold for 32 cycles", | ||
| "nop [31] ; extend low by 32 cycles", | ||
| "jmp x-- loop ; decrement steps and loop", | ||
| "end:", | ||
| "irq 0 rel ; signal completion" | ||
| ); | ||
| (common.load_program(&prg.program), timing.cycles_per_pulse()) | ||
| } | ||
| StepPulseTiming::Cycles260 => { | ||
| let prg = pio::pio_asm!( | ||
| "pull block ; wait for step count word", | ||
| "mov x, osr ; X = steps", | ||
| "loop:", | ||
| "jmp !x end ; if 0 steps, finish", | ||
| "set pins, 1 [31] ; STEP high, hold for 32 cycles", | ||
| "nop [31] ; extend high by 32 cycles", | ||
| "nop [31] ; extend high by 32 cycles", | ||
| "nop [31] ; extend high by 32 cycles", | ||
| "nop [1] ; extend high by 2 cycles", | ||
| "set pins, 0 [31] ; STEP low, hold for 32 cycles", | ||
| "nop [31] ; extend low by 32 cycles", | ||
| "nop [31] ; extend low by 32 cycles", | ||
| "nop [31] ; extend low by 32 cycles", | ||
| "jmp x-- loop ; decrement steps and loop", | ||
| "end:", | ||
| "irq 0 rel ; signal completion" | ||
| ); | ||
| (common.load_program(&prg.program), timing.cycles_per_pulse()) | ||
| } | ||
| }; | ||
|
|
||
| Self { prg, cycles_per_pulse } | ||
| } | ||
|
|
||
| /// Return the cycles per pulse for this program. | ||
| pub fn cycles_per_pulse(&self) -> u32 { | ||
| self.cycles_per_pulse | ||
| } | ||
| } | ||
|
|
||
| /// Direction for motion. | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| pub enum StepDir { | ||
| /// Forward direction. | ||
| Forward, | ||
| /// Reverse direction. | ||
| Reverse, | ||
| } | ||
|
|
||
| /// Errors returned when setting the STEP pulse frequency. | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| pub enum StepDirFrequencyError { | ||
| /// Frequency was zero. | ||
| Zero, | ||
| /// Frequency is below the minimum supported. | ||
| TooLow { | ||
| /// The minimum supported frequency in Hz. | ||
| min_hz: u32, | ||
| }, | ||
| /// Frequency exceeds the maximum supported. | ||
| TooHigh { | ||
| /// The maximum supported frequency in Hz. | ||
| max_hz: u32, | ||
| }, | ||
| } | ||
|
|
||
| /// PIO-backed Step/Dir stepper pulse generator. | ||
| /// | ||
| /// One pin is driven by PIO for STEP pulses, and one GPIO pin is controlled by software for DIR. | ||
| pub struct PioStepDir<'d, T: Instance, const SM: usize> { | ||
| irq: Irq<'d, T, SM>, | ||
| sm: StateMachine<'d, T, SM>, | ||
| step: PioPinHandle<'d, T>, | ||
| dir: Output<'d>, | ||
| frequency_hz: u32, | ||
| cycles_per_pulse: u32, | ||
| } | ||
|
|
||
| impl<'d, T: Instance, const SM: usize> PioStepDir<'d, T, SM> { | ||
| /// Configure a state machine to generate STEP pulses and bind a DIR pin. | ||
| pub fn new( | ||
| pio: &mut Common<'d, T>, | ||
| mut sm: StateMachine<'d, T, SM>, | ||
| irq: Irq<'d, T, SM>, | ||
| step_pin: Peri<'d, impl PioPin>, | ||
| dir_pin: Peri<'d, impl GpioPin>, | ||
| program: &PioStepDirProgram<'d, T>, | ||
| ) -> Self { | ||
| let step = pio.make_pio_pin(step_pin); | ||
| sm.set_pins(Level::Low, &[&step]); | ||
| sm.set_pin_dirs(Direction::Out, &[&step]); | ||
|
|
||
| let mut cfg = Config::default(); | ||
| cfg.set_set_pins(&[&step]); | ||
| cfg.clock_divider = calculate_pio_clock_divider(DEFAULT_FREQUENCY_HZ * program.cycles_per_pulse()); | ||
| cfg.use_program(&program.prg, &[]); | ||
| sm.set_config(&cfg); | ||
| sm.set_enable(true); | ||
|
|
||
| let dir = Output::new(dir_pin, Level::Low); | ||
|
|
||
| Self { | ||
| irq, | ||
| sm, | ||
| step, | ||
| dir, | ||
| frequency_hz: DEFAULT_FREQUENCY_HZ, | ||
| cycles_per_pulse: program.cycles_per_pulse(), | ||
| } | ||
| } | ||
|
|
||
| /// Return the minimum supported pulse frequency in Hz for this program. | ||
| pub fn min_frequency(&self) -> u32 { | ||
| Self::calc_min_frequency(self.cycles_per_pulse) | ||
| } | ||
|
|
||
| fn calc_min_frequency(cycles_per_pulse: u32) -> u32 { | ||
| let max_clkdiv_int: u32 = 0xffff; | ||
| let min_pio_hz = (clocks::clk_sys_freq() + max_clkdiv_int - 1) / max_clkdiv_int; | ||
| (min_pio_hz + cycles_per_pulse - 1) / cycles_per_pulse | ||
| } | ||
|
|
||
| /// Set output pulse frequency in Hz. | ||
| pub fn set_frequency(&mut self, freq_hz: u32) -> Result<(), StepDirFrequencyError> { | ||
| if freq_hz == 0 { | ||
| return Err(StepDirFrequencyError::Zero); | ||
| } | ||
| let min_freq = Self::calc_min_frequency(self.cycles_per_pulse); | ||
| if freq_hz < min_freq { | ||
| return Err(StepDirFrequencyError::TooLow { min_hz: min_freq }); | ||
| } | ||
| let max_freq = (clocks::clk_sys_freq() as u64 / self.cycles_per_pulse as u64) as u32; | ||
| if freq_hz > max_freq { | ||
| return Err(StepDirFrequencyError::TooHigh { max_hz: max_freq }); | ||
| } | ||
|
|
||
| self.frequency_hz = freq_hz; | ||
| let clock_divider = calculate_pio_clock_divider(freq_hz * self.cycles_per_pulse); | ||
| self.sm.set_clock_divider(clock_divider); | ||
| self.sm.clkdiv_restart(); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Get the currently configured pulse frequency in Hz. | ||
| pub fn frequency(&self) -> u32 { | ||
| self.frequency_hz | ||
| } | ||
|
|
||
| /// Set motion direction. | ||
| pub fn set_direction(&mut self, direction: StepDir) { | ||
| match direction { | ||
| StepDir::Forward => self.dir.set_low(), | ||
| StepDir::Reverse => self.dir.set_high(), | ||
| } | ||
| } | ||
|
|
||
| /// Generate an exact number of STEP pulses in the given direction and wait until completion. | ||
| pub async fn move_with_dir(&mut self, steps: u32, direction: StepDir) { | ||
| self.set_direction(direction); | ||
| self.move_steps(steps).await; | ||
| } | ||
|
|
||
| /// Generate an exact number of STEP pulses and wait until completion. | ||
| /// | ||
| /// If this future is dropped before completion (e.g. cancelled via `select!`), | ||
| /// the PIO state machine is automatically reset and the STEP pin is driven low. | ||
| pub async fn move_steps(&mut self, steps: u32) { | ||
| if steps == 0 { | ||
| return; | ||
| } | ||
|
|
||
| self.sm.tx().wait_push(steps).await; | ||
| let drop = OnDrop::new(|| { | ||
| self.sm.clear_fifos(); | ||
| unsafe { | ||
| self.sm.exec_instr( | ||
| pio::InstructionOperands::JMP { | ||
| address: 0, | ||
| condition: pio::JmpCondition::Always, | ||
| } | ||
| .encode(), | ||
| ); | ||
| } | ||
| self.sm.set_pins(Level::Low, &[&self.step]); | ||
| }); | ||
| self.irq.wait().await; | ||
| drop.defuse(); | ||
| } | ||
|
|
||
| /// Stop pulse generation immediately and clear the state machine FIFOs. | ||
| pub fn stop(&mut self) { | ||
| self.sm.clear_fifos(); | ||
| unsafe { | ||
| self.sm.exec_instr( | ||
| pio::InstructionOperands::JMP { | ||
| address: 0, | ||
| condition: pio::JmpCondition::Always, | ||
| } | ||
| .encode(), | ||
| ); | ||
| } | ||
| self.sm.set_pins(Level::Low, &[&self.step]); | ||
| } | ||
|
1-rafael-1 marked this conversation as resolved.
|
||
|
|
||
| /// Release owned resources. | ||
| pub fn release(self) -> (Irq<'d, T, SM>, StateMachine<'d, T, SM>, PioPinHandle<'d, T>, Output<'d>) { | ||
| (self.irq, self.sm, self.step, self.dir) | ||
| } | ||
| } | ||
|
|
||
| struct OnDrop<F: FnOnce()> { | ||
| f: MaybeUninit<F>, | ||
| } | ||
|
|
||
| impl<F: FnOnce()> OnDrop<F> { | ||
| pub fn new(f: F) -> Self { | ||
| Self { f: MaybeUninit::new(f) } | ||
| } | ||
|
|
||
| pub fn defuse(self) { | ||
| mem::forget(self) | ||
| } | ||
| } | ||
|
|
||
| impl<F: FnOnce()> Drop for OnDrop<F> { | ||
| fn drop(&mut self) { | ||
| unsafe { self.f.as_ptr().read()() } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.