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 & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • 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
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • 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 - 5: simple ramp up and down
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Raspberry Pi to participate - click to join for free!
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: 11 May 2025 10:39 AM Date Created
  • Views 2902 views
  • Likes 9 likes
  • Comments 20 comments
Related
Recommended
  • raspberry
  • pico_pio_stepper_lib
  • pico
  • PIO

Raspberry PIO stepper library documentation - 5: simple ramp up and down

Jan Cumps
Jan Cumps
11 May 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.

In this post: create ramps when motor starts, stops or changes direction.

The stepper motor is controlled by a PIO state machine. The Pico ARM (or RISC) cores are free at that time. While PIO is handling a command, we can use the cores to calculate if the next command is part of a direction change (or stop). And then slow down the motor for a smooth switch.

 image

The ramp logic is part of your firmware. It's not a stepper library function. It shows how you can use the library in complex scenarios, and use the cores to calculate the ideal path while PIO looks after the motor.

Ramp Logic

A stepper motor, when under load or at high speed, performs best if you ramp its stepping speed up and down. A motor will only be able to perform at its maximum, if you give it a little bump to reach. If you don't ramp, your motor may skip steps, overstep, or not step at all. Ramping up and down also makes movements less jerky.

When is a ramp advised?

  • motor start
  • motor changes direction
  • motor stop

Because the stepper library can handle a batch of commands, we can look forward in the command stack to see if we are dealing with a direction change. And because the controller is doing nothing while a motor is running, we can do this exercise for the next command, while we're running the current command. (even if the motor has to do a single step, there's ample time to do this)

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

video: a linear stepper motor executing the example code on repeat. It ramps up and down at start, end, and at direction switching

Border conditions:

We can see inside a stack of commands if we are involved in a direction change, but we don't know how a previous batch ended, or how a new batch will start. That's why the logic has two parameters for first and last commands. If you pass true, the motor will ramp up / down. If you pass false, it will start or end at its high speed.

Software

This is the stack of commands that the motor has to perform:

    std::array<stepper::command, 12> cmd{{
        {10 * microstep_x, false},
        {40 * microstep_x, false},
        {10 * microstep_x, false},
        {10 * microstep_x, true},
        {100 * microstep_x, true},
        {10 * microstep_x, true},
        {10 * microstep_x, false},
        {100 * microstep_x, false},
        {10 * microstep_x, false},
        {10 * microstep_x, true},
        {40 * microstep_x, true},
        {10 * microstep_x, true}}};

I've prepared them for this exercise. The firmware should run every first and last command in a particular direction, at the low (ramp) speed. Commands that are in between other commands in the same direction, should run at high speed. I wrote down the direction of each command, and tried to find a pattern that's easily translated into software.

image

  • x: involved in direction change (previous, next, or both different direction
  • blank: same direction as previous and next
  • !: start or stop: we do what the programmer asks us

I chose this algorithm: If the flags of the before, current and after direction are added up for the next command, and the sum is 0 or 3, there is no direction change. Else there is.

Translated into code:

void run_with_ramp(const commands_t & cmd, uint32_t slow_delay, uint32_t fast_delay, bool start_slow, bool end_slow) {
    bool ramp = start_slow;
    bool next_ramp_found = false;
    unsigned int bits = 0U;

    for (size_t i = 0U; i < cmd.size(); i++) {
        motor1.set_delay(ramp ? slow_delay : fast_delay);
        motor1.take_steps(cmd[i]);
        while (motor1.commands() < 1) {  // motor has to complete before we can send another delay command
            // use motor run time to calculate next speed
            if (! next_ramp_found) {
                ramp = end_slow; // by default, think it's the last command
                if (i < cmd.size() - 2) { // if not, find if dir is changed or will change
                    // check if next command has different direction
                    bits = (cmd[i] & 1) + (cmd[i + 1] & 1) + (cmd[i + 2] & 1); // compare dir bits
                    if (! (bits % 3)) { // in a dir change cycle, unless prev, cur and next command identical
                        ramp = false; // go fast
                    }
                }
                next_ramp_found = true;
            }
        }
        next_ramp_found = false;
        motor1.reset_commands();
    }
}

When you look at the code, take in mind that it always tries to figure out if the next command needs to be ramped.
In that context, cmd[i] (the one that's currently being executed by PIO) is the previous step, cmd[i + 1] is the one we are investigating, and cmd[i + 2] is going to be the step after that.

To run the batch of commands:

run_with_ramp(cmd, 7000, 4300, true, true);

When you add this line in the loop, when the evaluation is done:

printf("next step: %d, checksum: %d, ramp: %s\n", i + 1, bits, bits %3 ? "yes" : "no");

You get this output (step 0 and 11 are determined by the start_slow and end_slow parameters. They aren't shown below):

delays: slow = 7000, fast = 4300
next step: 1, checksum: 3, ramp: no
next step: 2, checksum: 2, ramp: yes
next step: 3, checksum: 1, ramp: yes
next step: 4, checksum: 0, ramp: no
next step: 5, checksum: 1, ramp: yes
next step: 6, checksum: 2, ramp: yes
next step: 7, checksum: 3, ramp: no
next step: 8, checksum: 2, ramp: yes
next step: 9, checksum: 1, ramp: yes
next step: 10, checksum: 0, ramp: no

The oscilloscope capture of DIR (yellow) and STEP (blue) shows the behaviour at a direction change:

image
the black line before the last quarter of the image above is a 1ms pause I intentionally put there in my test code, to get a hint when a batch starts

image
zoom in to a direction change with slow-down before and after. The firmware defines how many steps both part of the ramp last.

This example uses a rough ramp-up: slow speed and high speed. You can define how many slow steps it takes. In many cases that's enough to get the motor going. But there's enough core time available to construct more advanced up- and down-ramps. You could even write an algorithm that pre-analyses a set of commands, and compiles them into a new set with ideal (for your scenario) ramp patterns.

CMake project, with precompiled .uf2 file: pio_a4988_stepper_ramp_20250511.zip

view all posts

  • Sign in to reply

Top Comments

  • Jan Cumps
    Jan Cumps over 1 year ago +1
    First attempt for a more trapezoidal(ish) ramp: https://youtu.be/AyNQrvEplDw This (very early) attempt takes core (non-PIO) processor time. How do you ramp, as a library user? {{400 * microstep_x…
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to shabaz

    I'm going to claim that the lib supports all of that in its current state - with carefully constructing the commands, and maintaining the command FIFO as needed.

    1000.5 turns (could be fractional),

    Usually, a motor has 200 steps per turn. You'd take take 200 * 1000.5 steps. This turns out to be an integer. But if you need to have fractional, you could use a stepper driver like Texas Instruments DRV8711, that supports 128 micro-steps. You 'd end up with an integer at the end.
    There are 400-steps-per-rotation motors that are affordable.
    More micro-steps? I believe there's no stepper motor in our payrate that can do that. But a gearbox would help in that case.

    The ramping could be based on my approach here. I just used one in-between from stop to middle to full throttle. But the algorithm can be fine-tuned to more intermediate speeds (at the cost of a little more core cpu time) - Even at a per-step time, because the cpu can burn a lot of (currently underused) calculations while the PIO does just one single step.

    next answer in next comment

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz over 1 year ago

    Impressive work!

    I don't know enough about this subject, but hopefully others do.

    As a use-case, if I wanted to control the number of rotations precisely, say, 1000.5 turns (could be fractional), it would be interesting to see how that sort of thing could theoretically be implemented to work with the library with ramping up, maintaining speed and then ramping down (I suspect that would require to be mostly outside PIO for computational flexibility, e.g. a computed list as Jan shows, especially if it needed to be modified in future, and it's also very likely it would be too large for PIO anyway). 

    Another theoretical requirement could well be to do an ad-hoc 'abort' and ramp down part-way through a list, if a user hits a button part-way. I can see all of this could get very complicated very quickly though.

    And I've never understood, how does a n-axis machine work, where you may want several motors to be at particular turn counts at the same time, e.g. motor 1 to be at 10.5 turns, and motor 2 to be at 4.6 turns, and then later on, at two different turns values. Would ramping be used, or would one just stick to a slow speed with no ramping? It sounds massively complicated, I guess in practise a huge list would be built for the motors to execute. I'm just wondering out aloud, more rhetorical because I realize none of this is trivial, and probably needs me to go away and read up a lot, maybe peek at some open source CNC software to see how they do it.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to genebren

    Trapezoidal is most likely the only profile I can achieve in the PIO (after some more studying). I'll have to compromise something for that, but I don't know what yet. Most likely FIFO size for new commands .

    I'm not too worried about it, because sending new commands is cheap. And the ARM cores could send a new one upon receiving a notification from my stepper library that it's finished a command.
    Currently, it can hold up to 8 commands per stepper motor state machine (32 per PIO).
    If that halves, the cores would have enough time to keep on adding commands in the same pace as each motor completes a command. Triggered by that notification. That would keep the FIFO filled at all times (if required)

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • genebren
    genebren over 1 year ago in reply to Jan Cumps

    Overkill for now, but the keyword was 'past'.  We were building super high-pressure fluidic pumps for commercial/industrial/medical devices (using steppers) and the state of the art for embedded devices were 8051 derivatives, so FPGA allowed us to create well defined trapezoidal movements. 

    Still, you are doing some very amazing work with these PICO Arm parts.  It is a pleasure to follow your projects.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to genebren

    PIO has limited access to memory. It works best if you can give it one or 2 data points. Or a continuous stream of numbers
    Somehow, an FPGA for a stepper or servo seems to be a resource overpower to the nth :) One can control it with a common microcontroller timer. and an interrupt.

    • 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 © 2026 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.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube