element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Raspberry Pi
  • Products
  • More
Raspberry Pi
Blog Raspberry PIO stepper library documentation - 2: advanced example with notification
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Raspberry Pi requires membership for participation - click to join
Featured Articles
Announcing Pi
Technical Specifications
Raspberry Pi FAQs
Win a Pi
GPIO Pinout
Raspberry Pi Wishlist
Comparison Chart
Quiz
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 18 Apr 2025 5:29 PM Date Created
  • Views 237 views
  • Likes 4 likes
  • Comments 3 comments
Related
Recommended
  • raspberry
  • pico_pio_stepper_lib
  • pico
  • PIO
  • stepper-motor
  • c++

Raspberry PIO stepper library documentation - 2: advanced example with notification

Jan Cumps
Jan Cumps
18 Apr 2025

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. 


You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

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

image

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.
You can't change its state in the callback, E.g.: disable the motor. 
Note that by the time you receive the notification, the motor will already be doing a next instruction.
The PIO state machine doesn't wait for the handler to complete, before continuing. It's a notify-and-move-on service.

If you want to take an action on the stepper when receiving the notification, then:

  • pass one command to the motor, not a batch.
    This will guarantee that the stepper is doing nothing when you receive the notification.
    You then have ample time to react on the event.
  • and use the compare mechanism to find what motor has finished:
    if (&motor1 == &stepper) { ... }
  • or use a dedicated callback for the motor that needs specific handling upon notify: 
    void on_complete_motor1_specific(const motor_t &stepper) {
      // you know this is a notification for motor1, because this is
      // its dedicated callback handler
      ...
    }

    motor1.on_complete_motor1_specific(on_complete);

    // one command, so that the state machine is not
    // executing a next command when the notification arrives
    motor1.take_steps({400, false});
    // ...

Keep the code in the notification handler short.
It behaves the same as an interrupt handler (because it actually is one).
Changing a status, or set a flag is really all you should do here.

While you are in the handler, the Pico core that services the notification* doesn't run your normal firmware flow.
The PIO will happily continue driving the motors though.

* the core that sets the PIO interrupt will be the one that services the request.
In this library, it's the core that made the call to stepper_callback_controller::register_pio_interrupt).

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:

  • 21 instructions (65%) of instruction space in the PIO(s) that you are using.
  • all resources of the PIO(s) that you don't use

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.
(unless you use an interrupt number that's out of range for the stepper design, but that's a bit involved)

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 

view all posts

  • Sign in to reply
  • DAB
    DAB 29 days ago

    Great post Jan.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube