The Pico has a set of PIO co-processors. They are real-time controllers that can execute logic with deterministic timing. Ideal to run strict-timed sequences and state machines. And to implement extra peripherals. |
Follow up of Stepper Motor Control with Raspberry Pico PIO and DRV8711 driver- Part 5: a more autonomous PIO .
In the previous post, I wrote my own PIO program that can independently do up to 2147483647 steps. And the PIO handles the DIR pin.
In this post I add speed control.
PIO code
.program stepper .side_set 1 .define public DELAY 0 .wrap_target start: pull block side 0 ; Pull from FIFO to OSR out pins, 1 side 0 ; shift direction out of OSR and output to dir mov x, osr side 0 ; Copy rest of OSR to scratch X jmp !x start side 0 jmp x-- tocks_h side 0 tocks_h: mov y, isr side 1 ; ISR contains number of tocks to hold level count_high: jmp y-- count_high side 1 [DELAY] ; keep high for tocks nop side 1 ; same size as low tocks_l: mov y, isr side 0 ; ISR contains number of tocks to hold level count_low: jmp y-- count_low side 0 [DELAY] ; keep high for tocks jmp x-- tocks_h side 0 ; not done, keep loopins .wrap
The code is somewhat extended since the version of the previous blog. I added an additional counter y, that holds the amount of ticks to stretch each half of the pulses.
It receives the value from the input shift register at the start of each half of the pulse, then keeps the step pin looping in that state until the y is 0.
Once that happens, it moves to the other half of the period, and does the same. Repeat until the pulse train is completed.
Here is how the pulse stretching works. The top trace is with no delay. Each half cycle takes 3 clock ticks. 6 clock ticks per full pulse period.
In the second trace, the delay was set to 1. The two half pulses now take an extra clock tick: 4 each, 8 ticks per full period. This effectively lowers the pulse train frequency, and thus slows the stepper motor down.
This is the tail of a command. The PIO has generated the amount of steps that were requested. X register has counted down to 0, and the flow stops, until a new command is sent by the ARM controller:
If you want to learn how these graphs were generated: emulate Raspberry PICO PIO programs .
The main program
Here's the function that sets the speed:
void pio_pwm_set_delay(PIO pio, uint sm, uint32_t delay) { pio_sm_set_enabled(pio, sm, false); pio_sm_put_blocking(pio, sm, delay); pio_sm_exec(pio, sm, pio_encode_pull(false, false)); pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32)); pio_sm_set_enabled(pio, sm, true); }
It stops the PIO, so don't use it (note to self) while the PIO is handling commands.
in main(), I just added a loop to slow down from requested speed in 4 steps
std::array<command, 6> cmd{{ {200 * microstep_multiplier, true}, {200 * microstep_multiplier, false}, {200 * microstep_multiplier, false}, {400 * microstep_multiplier, false}, {250 * microstep_multiplier, true}, {350 * microstep_multiplier, true}} }; uint32_t sleep_time = 0U; { wakeup_drv8711 w; // wake up the stepper driver sleep_ms(1); // see datasheet for (auto i = 0; i < 5; i++) { pio_pwm_set_delay(piostep, sm, i); for(auto c : cmd) { pio_stepper_set_steps(piostep, sm, c.steps, c.reverse); sleep_time += step_time(c.steps, i); } sleep_ms(sleep_time + 500); // give enough time to complete the action } }
The granularity is still rough. With these slow signals (850 Hz as fastest that my motor can handle in full-step mode), each delay adds 2 extra ticks. One tick is added to each half of the pulse. It's a lot. Making it more granular is something for a next exercise ...
Here is a full sequence, where I set the direction to 1, 2 steps, and a delay of 1. First 5 instructions are fetching the command, dir and delay. Then it's a loop, executed twice.
The last instruction wraps up after we're done, and lets the state machine fetch the next command.
The code is on GitHub.
Thank you for reading. I'll attach the project to the end of this post ...