Raspberry PIO stepper library (pio_stepper_lib) is a C++ library that runs stepper motors in PIO state machines. It's intended to be easy to integrate and use in Pico projects.
Check Raspberry PIO stepper library documentation - 1: intro, set up project and simple example to get a motor running. This exercise builds upon that example.
In the first exercise, you asked PIO to spin a motor. You then waited long enough to know it finished.
This exercise uses the library notification functionality to inform each time the PIO finishes a command.
Let's start with changing the class we use to control our motor, and add some housekeeping to the initialisation*
using motor_t = stepper::stepper_callback_controller; motor_t motor1(piostep, sm); const uint pio_irq = 0; void init_pio() { // program the pio used for the motors // do this only once per used pio motor_t::pio_program(piostep); // initialise and enable the motor state machine motor1.register_pio_interrupt(pio_irq, true); // ... rest of example 1 init code
The change looks small (from stepper_controller to stepper_callback_controller). But you get new functionality:
- the motor object can now tell you how many commands it has executed
- the object can notify your program each time a command has finished
* PIO to ARM interface has 2 interrupt channels, 0 and 1. We have to tell the library which one to use.
Simple scenario: loop until your commands are completed
The first option allows for simple programs, that validate that all commands are executed:
int main() { // ... motor.reset_commands(); // reset count of commands executed motor1.take_steps({400, false}); // 400 steps clockwise while (motor.commands() < 1) {} // wait until the motor object tells it's finished
It's a straightforward solution. It's better than the sleep() option we used in exercise 1, though.
If you would have sent 7 commands to the motor, you'd check for motor.commands() < 7.
So that's what you get by just changing the class that manages the motor.
Behind the scenes, an interrupt mechanism is set up, and the library manages the conversion from a low level PIO interrupt to the actual motor that caused it. There's a few clock ticks involved, and the class maintain a (single, really small) register of objects vs interrupt source. |
Advanced scenario: get a notification when commands are completed
The previous example hoggs the ARM core while waiting for the stepper to complete. This can be improved
The stepper_callback_controller supports callbacks. A function in your firmware that will be called each time the motor has completed a command.
After you tell the motor to do a set of commands, you can do other things in firmware. Once the motor has performed all the steps you requested, you can react.
A possible scenario: the motor controls an axis of the base plate of a CNC. Once it has done moving the base plate, you want to start drilling.
This is your callback. It could change the state of your firmware, to indicate it can start milling:
volatile state_t machine_state = machine_startup;
volatile int steps_countdown = 0U;
void on_complete(const motor_t &stepper) {
if (&motor1 == &stepper) {
steps_countdown = steps_countdown - 1;
printf("motor1 executed command %d\n", motor1.commands());
}
}
Your main code could be the typical state machine loop. It would have a guard condition to detect the changed state.
int main() {
// register the callback
motor1.on_complete_callback(on_complete);
// ...
while (true) { // state machinbe stuff
// ...
switch (machine_state) {
case machine_startup:
// maybe do health checks?
machine_state = move_baseplate;
break;
case move_baseplate:
steps_countdown = 1;
motor1.take_steps({400, false});
machine_state = moving_baseplate;
break;
case moving_baseplate:
if (steps_countdown == 0) {
machine_state = start_milling;
}
// do something in your firmware. ARM has free time
// then
break;
case start_milling:
// ...
break
}
// ...
}
classes used
understanding the callback
The callback function is called when a PIO state machine has finished generating all pulses of a command.
The signature of you callback is void function_name(stepper_callback_controller& stepper). This should get you all the info you need.
The parameter stepper is a reference to the motor object that just finished a command.
You can use that to check what motor notified you. And you could also ask it how many commands it executed. Or just ignore the parameter.
You can use one callback function for all motors, or define a specific callback for each motor. It depends on the design you want to make, and on your preferences.
The stepper object that's passed to the notification handler is immutable. If you want to take an action on the stepper when receiving the notification, then:
|
Keep the code in the notification handler short. While you are in the handler, the Pico core that services the notification* doesn't run your normal firmware flow. * the core that sets the PIO interrupt will be the one that services the request. |
PIO resource use: You can run 4 stepper motors per PIO (Pico 1 has 2 PIOs, Pico 2 has 3). Each motor uses 1 state machine The PIO program uses 11 instructions (out of 32). This is what's left for other activities:
The design requires the use of 1 (out of 2) PIO IRQ channel. It can be shared across the PIOs, but should not be shared with other designs. |
The pio_stepper_lib is hosted on GitHub. You don't need to clone or build it, to use the library in your project. CMake does that for you.
A working project, using the Texas Instruments DRV8711 as stepper driver, is also in GitHub.
next chapter: Raspberry PIO stepper library documentation - 3: control multiple motors with 1 or more PIOs