Introduction: Stepper Speed Control Menu Driven for Arduino
By drmpfpfodApps and pfodDevices
More by the author:
Updated 13th Oct 2023 – V1.1.3 minor code correction
Updated 19th Nov 2022 – V1.1.2 modified for use with ESP32 / ESP8266
This SpeedStepper library is a rewrite of the AccelStepper library to allow speed control of the stepper motor. The SpeedStepper library lets you change the set motor speed and then accelerates/decelerates to the new set speed using the same algorithm as AccelStepper library. The SpeedStepper library also allow you to set plus and minus limit and a 'home' position. There is a goHome command to return to the home position.
Limitations: The SpeedStepper library only drives direction and step outputs and so needs to be connected to a motor driver, such as Easy Driver, to actually drive the stepper motor. The AccelStepper library provides more driving options which could be copied to this library if required.
Three example sketches are provided, each of which can be run without a motor or motor driver. speedStepperPlot sketch issues example speed commands and the goHome command and generates a plot of the resulting speed and position. The speedStepperSetup sketch provides a menu driven setup to set the motor's home and limits and then run the motor and adjust the speed up and down and goHome to finish. The speedStepperProfile sketch shows an example of setting up and executing a speed profile.
While the AccelStepper library provides good position control, speed control was needed for the Prototype Ice-Melting Probe for Collecting Biological Samples on Europa. Here is a video of an earlier version of the prototype, which used weight instead of a motor. Revision 1.1 added speed profiles after a user requested a means for controlling the speed profile of a pump.
This library runs on Arduino Uno and Mega2560, but for the prototype a the larger memory / faster processor SparkFun Redboard Turbo was used.
This instructable is also available online at Stepper Speed Control Library for Arduino
Supplies
To run the example sketches only an Arduino UNO or Mega2560 and the software libraries are needed
For bench testing of the library a
SparkFun Redboard Turbo was used with an
a 200 steps/rev, 12V 350mA stepper motor and
a 12 DC supply 2A or larger, e.g. https://www.sparkfun.com/products/14934.
Arduino IDE V1.8.9 and a computer to run it on.
pfodParser library for the nonBlockingInput and pfodBufferedStream classes
Step 1: Library Functions
The SpeedStepper library runs the stepper motor limited by bounds set by the library. See the SpeedStepper.h file for the various library methods available. Here is an outline of the logic behind them.
The position of the stepper is tracked by counting the number of steps (pulses). The library limits the position to be between the setPlusLimit(int32_t) and setMinusLimit(int32_t) positions. The plus limit is always >= 0 and the minus limit is always <= 0. On startup the motor position is 0 (home) and the limits are set to very large +/- numbers (about +/-1e9 steps). setAcceleration(float) sets how fast the motor will change speed either up or down. As the motor approaches the plus or minus limit it will decelerate at this rate until is stops at the limit. On start up the acceleration is set to 1.0 steps/sec/sec. The acceleration setting is always a +ve number. The sign of the setSpeed(float) setting sets the direction the motor will move.
setSpeed(float) sets the speed to accelerate / decelerate the motor to, from its current speed. The speed that can be set via setSpeed(float) is limited, in absolute value, by settings, setMaxSpeed(float), default 1000 steps/sec and setMinSpeed(float), default 0.003 steps/sec. These defaults are also the absolute hard coded speed limits the library will accept for setMaxSpeed() and setMinSpeed().
If you want to set a max speed > 1000 steps/sec you will need to edit the first line in SpeedStepper.cpp file to change the maxMaxSpeed(1000) to the max speed you want.
In practice the maximum speed is also limited by time between calls to the library's run() method. For 1000 steps / sec the run() method must be called at least every 1ms. See the Latency section below.
Trying to set a speed less than min speed will cause the motor to stop. Each of these setters has a corresponding getter, see the SpeedStepper.h file. For speed, getSetSpeed() returns the speed you set via setSpeed(), while getSpeed() returns the current motor speed which varies as it accelerates/decelerates to you set speed. If the motor is not going in the direction you think of a +ve you can call invertDirectionLogic() to swap the direction the motor moves for +ve speeds.
getCurrentPosition() returns the current motor position compared to 'home' (0). You can override the current motor position setCurrentPosition(int32_t). The new position is limited to be within the set plus/minus limits.
Initially the motor is stopped at position 0. Calling setSpeed(50.0) will cause it to start accelerating in the +ve direction to a maximum speed of 50 steps/min. Calling hardStop() will immediately stop the motor where it is. On the other hand calling the stop() method will set the speed to zero and decelerate the motor to a stop. Calling stopAndSetHome() will stop the motor immediately and set its position to 0. The plus/minus limits values are not changed but now are referred to this new 0 (home) position. Calling goHome() will return the stepper to this 0 (home) position and stop. Calling setSpeed() will cancel going home.
The SpeedStepper library also provides speed profile control via the methods setProfile(SpeedProfileStruct* profileArray, size_t arrayLen), startProfile(), stopProfile(), to interrupt a running profile, and isProfileRunning(). See the speedStepperProfile example sketch.
Step 2: Running the SpeedStepperPlot Example Without a Motor
Install Arduino IDE V1.8.9
Download and install the SpeedStepper library Save the SpeedStepper.zip and then use the Arduino IDE menu item Sketch → Include Library → Add .ZIP library to import the library
Download and install the millisDelay library as well
Open the Examples → SpeedStepper → speedStepperPlot example sketch (Restart the IDE if necessary). This sketch is configured to work with Serial, e.g. UNO and Mega etc. For running on the SparkFun Redboard Turbo see below.
No driver board or stepper motor is need to run this example. These examples use D6 and D7 as outputs. You can change the output pins to any digital output by changing the STEP_PIN and DIR_PIN settings near the top of the sketch.
Upload the sketch to board and then open the Tools → Serial Plotter at 115200 baud to show the plot of the speed (RED) and position (BLUE) The plus limit is set to 360 which causes the speed to ramp to zero from about 100 point on the x-axis. The minus limit is -510. The position stops at ~-390 because the speed has been demanded to 0.0. The at 380 point on the x-axis, the goHome cmd is issued which returns the stepper to position zero.
This speedStepperPlot sketch uses millisDelays to time switching between various speeds and accelerations. In many cases using a SpeedStepperProfile, as in the next example, is simpler.
Step 3: Running the SpeedStepperProfile Example Without a Motor
Open the Examples → SpeedStepper → speedStepperPlot example sketch, this sketch produces the above plot using the Arduino Serial Plotter and is an example of running a prescribed speed profile for example if running a pump.
Stepper Speed Profiles are made up of an array of SpeedProfileStruct, which is defined in the SpeedStepper.h file.
struct SpeedProfileStruct {
float speed; // the target speed at the end of this step
unsigned long deltaTms; // the time to accelerate from current speed (at start of this step) to the target speed
};
Define an array of SpeedProfileStruct containing the target speed for each step and the time, deltaTms, in ms, to reach that target speed from the previous target speed. If the deltaTms is zero or very small, then speed will just jump immediately to the new target speed. Otherwise, the required acceleration will be calculated setAcceleration( ) will be called followed by a call to setSpeed( ) for the new target speed. In all cases the profile will be limited by the existing plus and minus position limits and max/min speed settings. If you want to hold a speed, just repeat the previous speed with time you want it held. Since the new target speed is the same as the current speed, the calculated acceleration will be zero and ,no change is speed occurs.
This SpeedProfileStruct array produced the above plot
const SpeedProfileStruct profile[] = {<br> { 0, 0}, // stop immediately if not already stopped
{ 0, 1000}, // hold zero for 1sec
{ -50, 0}, // jump to -50
{ -200, 2000}, // ramp to -200
{ -200, 6000}, // hold at -200 for 6sec
{ -50, 2000}, // ramp down to -50
{ 0, 0}, // // stop immeadiately
{ 0, 1500}, // hold zero for 1.5sec
{ 50, 0}, // jump to 50
{ 200, 2000}, // ramp to 200
{ 200, 6000}, // hold 200 for 6 sec
{ 50, 2000}, // ramp to 50
{ 0, 0}, // // stop immeadiately
{ 0, 1000} // hold zero // for plotting output
};
const size_t PROFILE_LEN = sizeof(profile) / sizeof(SpeedProfileStruct); // calculate the size of the profile array
The profile is set by calling setProfile(SpeedProfileStruct* profileArray, size_t arrayLen) e.g.
stepper.setProfile(profile, PROFILE_LEN);
Once the profile has been set, call startProfile() to start running it from the current motor speed (usually you will start from stopped). At the end of the profile, the motor will just continue to run at the last target speed. The isProfileRunning() method can be called to see if the profile is still running. If you want to stop the profile early you can call stopProfile() which will abandon the profile and halt the motor.
Step 4: Running the SpeedStepperSetup Example Without a Motor
The example sketch is designed to a bases for your own stepper motor application. It provides a menu driven interface that allows you move to motor to its home position, if not already there and then optionally reset the plus and minus limits and then run the motor within that range. The 'run' menu allows you to increase and decrease the speed, freeze at the current speed, stop and also return to home.
This sketch illustrate a number of software features that keep the loop() responsive so that you can add your own sensor inputs to control the stepper. It takes pains to avoid delays that would interfere with the speed control. (See Delays are Evil)
Install the libraries used for running the SpeedStepperPlot, above, and then also install the pfodParser library. The pfodParser library supplies the NonBlockingInput and pfodBufferedStream classes that are used to handle the user input and menu output with blocking the loop() from running.
Open the Examples → SpeedStepper → speedSpeedSetup example. This sketch is configured to work with Serial, e.g. UNO and Mega etc. For running on the SparkFun Redboard Turbo see below.
No driver board or stepper motor is need to run this example. These examples use D6 and D7 as outputs. You can change the output pins to any digital output by changing the STEP_PIN and DIR_PIN settings near the top of the sketch. Upload the sketch to the board and then open the Tools → Serial Monitor at 115200 to see the SETUP menu.
SETUP<br> pos:0 sp:0.00 +Lim:500000 -Lim:-500 LATENCY: stepper:492us loop:0us
p -- set Home
l -- set limits
h -- goHome
r -- run
>
When the sketch runs the current position of the stepper is taken as it 'home' (0) position. If you need to re-position the stepper to its true 'home' position, enter the p command to display the SET HOME menu
SET HOME<br> pos:0 sp:0.00 +Lim:1073741808 -Lim:-1073741808 LATENCY: stepper:752us loop:3852us
x -- setHome here and exit
+ -- Forward
- -- Reverse
s -- swap Forward/Reverse
-- hardStop
>
As you can see the limits coded in the sketch have been removed so you can re-position the stepper anywhere. You need to take care you don't drive it past the physical limits or you may break something.
Use the + cmd to start moving the stepper forward, if you find it moving in the wrong direction enter a non-command or just an empty line to stop it and then use the scommand to swap the direction of Forward. You should update the sketch to include a call to invertDirectionLogic() in the setup to fix this for the next run.
Use the + / - cmds to position the stepper to the correct zero position. The motor starts off slow and then builds up speed as it proceeds, just use and empty line to stop it. The maximum speed for this and the limits menu is set by the MAX_SETUP_SPEED at the top of the setupMenus.cpp.
Once the motor has been positioned at its 'home' position, use the x cmd to re-set the current position as 0 and return to the SETUP menu.
If you need to set the limits, usually only the on the initial setup, use the l cmd to enter the SET LIMITS menu
SET LIMITS<br> pos:0 sp:0.00 +Lim:1073741808 -Lim:-1073741808 LATENCY: stepper:944us loop:5796us
l -- setLimit here
+ -- Forward
- -- Reverse
h -- goHome
x -- exit
-- hardStop
>
Use the + cmd to more forward to the plus limit and then use the l cmd to set it as the plus limit. The h command can then be used to return to 0 and the – cmd use to move if reveres to the position the motor at the minus limit. Again use the l cmd to set the minus limit. Note the positions of the plus and minus limits and update the setup() method's setPlusLimit and setMinusLimit statements with these values.
When the limits are set use the x cmd to return to the SETUP menu and then you can use the r cmd to open the RUN menu
RUN MENU<br> pos:0 sp:3.31 +Lim:500000 -Lim:-500 LATENCY: stepper:944us loop:5796us
+ -- Speed up
- -- Speed down
h -- goHome
. -- hardStop
-- freeze Speed
>+
pos:4 sp:9.49 +Lim:500000 -Lim:-500 LATENCY: stepper:792us loop:5664us
pos:42 sp:29.15 +Lim:500000 -Lim:-500 LATENCY: stepper:792us loop:5664us
pos:120 sp:49.09 +Lim:500000 -Lim:-500 LATENCY: stepper:792us loop:5664us
pos:238 sp:69.06 +Lim:500000 -Lim:-500 LATENCY: stepper:792us loop:5664us
The + cmd starts accelerating in the forward direction and prints out the position and speed every 2 seconds. When the motor reaches the speed you want you can stop the acceleration with any other key (or an empty input). You can reduce the speed using the – cmd down to stop. If stopped the – cmd will accelerate in reverse.
This RUN menu provides manual control of your project. For auto control you will need to add some other sensors.
Step 5: Latency
The stepper motor control depends on the software commanding every step. To maintain the set speed, your sketch needs to call the stepper.run() method often enough to fire the next step at the right time for the current speed. For control via sensors you need to be able to promptly process new measurements. The position/speed print out includes two LATENCY measurements to let you check your sketch is fast enough.
Stepper Latency (pfodBufferedStream)
The stepper latency measures the maximum delay between successive calls to the stepper.run() method. In order to run the stepper motor at 1000 steps per sec, the stepper latency needs to be less then 1000uS (1ms). The first version of this sketch had a latency of many milliseconds. To overcome this extra calls to the runStepper() method (which calls stepper.run()) where added through the code. This did not completely solve the problem because the menu and output print statements blocked the sketch once the small Serial Tx buffer was full. To avoid this blocking,pfodBufferedStream from the pfodParser library was used to add a 360 byte output print buffer that the print statements could quickly write to. Then pfodBufferedStream releases the bytes at the baud rate specified 115200 in this case. pfodBufferedStream has to option to either block when the buffer is full or just drop the overflow chars. Here it is set to drop any extra chars when the buffer is full so that the sketch is not blocked waiting for Serial to send chars.
Loop Latency (NonBlockingInput)
The loop latency measures the maximum delay between successive calls to the loop() method. This sets how fast you can process new sensor measurements and adjust the motor set speed. How fast then needs to be depends on what you are trying to control.
The delays due to the print statements was removed by using pfodBufferedStream above, but to process the user's input you need to take the just first char of the input and ignore the rest of the line. The NonBlockingInput class in pfodParer library is used to return a non-zero char when there is input, using readInput(), and to clear and discard the following characters, using clearInput(), until no chars are received for 10ms without blocking the loop()
Of course the loop latency will be increased by the extra code you add to read the sensors and calculate the new set speed. Many sensor libraries take the sort cut of just using delay(..) between starting a measurement and retrieving the result. You will need to re-write these libraries to use millisDelay instead, to pick up the measurement after a suitable non-blocking delay.
Step 6: Running SpeedStepperSetup With a Stepper Motor and SparkFun Redboard Turbo
To run the SpeedStepperSetup sketch for real you will need a stepper motor, driver and power supply and in this example SparkFun Redboard Turbo.
The wiring diagram above (pdf version) shows the connections. In the SpeedStepperSetup sketch change the SERIAL define to
#define SERIAL Serial1
Stepper Motor, Power Supply, Driver and Protection
There are many types and sizes of stepper motors. Here a two coil 12V 350mA stepper motor is used for testing. To power this stepper you need a power supply of 12V or more and greater than 350mA.
This library only provides a direction and step output so you need a driver to interface with the stepper motor. The Easy Driver and Big Easy Driver control the current to the motor's coils so you can use a power supply of a higher voltage safely, for example using 6V supply for a 3.3V motor. The Easy Driver can supply between 150mA/coil and 700mA/coil. For higher currents, the Big Easy Driver can supply up to 2A per coil. Read the FAQ's at the bottom of the Easy Drive page.
These examples use D6 and D7 as the Step and Direction outputs. You can change the output pins to any digital output by changing the STEP_PIN and DIR_PIN settings near the top of the sketch.
Programming the Sparkfun Redboard Turbo
Programming the Redboard Turbo is problematic. If it fails to program, first press the reset pushbutton once and reselect the COM port in Arduino Tools menu and retry. If that does not work, double press the reset push button and try again.
Wiring up the Easy Driver
Two coil stepper motors have 4 wires. Use a multimeter to find the pairs that connect to each coil and then wire one coil to the Easy Driver A terminals and the other coil to B terminal. It does not matter which way round you wire them because you can use the s cmd in the setup menu to swap the direction of movement.
The motor power supply is wired to the M+ and GNDSet the logic level of the board with the 3/5V link. Short the link together for 3.3V microprocessor outputs, like the SparkFun Redboard Turbo (if you leave it open it is suitable fo 5V digital signals, e.g. UNO, Mega)Connect the GND, STEP, DIR pins to the microprocessor GND and the step and dir output pins.
No other connections are necessary to drive the motor.
USB to TTL Serial Cable
When moving the SpeedStepperSetup sketch from the Uno/Mega to the Redboard Turbo you might naïvely just replace #define SERIAL Serial with #define SERIAL SerialUSB to suit the Redboard Turbo usb serial connection, however you would find that the resulting stepper latency is about 10ms. That is 10x slower then for the UNO. This due to how the Redboard cpu handles the USB connection. To over come this, connect a USB to TTL Serial Cable to D0/D1 and set
#define SERIAL Serial1
to use the hardware serial connection to control the stepper motor. Using Serial1 gives LATENCY: stepper:345uS loop:2016uS which is 3 time faster then the UNO for the stepper and loop latency
Terminal Program
The Arduino Serial Monitor is a little harder to use to control the stepper motor as you have to enter the char in the cmd line and then press Enter to send it. A quicker more responsive means is to open a terminal window, TeraTerm for PC (or CoolTerm Mac), connected to the USB to TTL cable COM port. Then in that window pressing a cmd key sends it immediately. Pressing Enter just send a empty line.
Setting the motor speed range
As wired up above, the Easy Drive is configured for 1/8th steps, so 1000 step/sec will turn the motor at 1000 / 8 / 200 step/revolution = 0.625 revs per sec or 37.5 rpm maximum. By changing the inputs to MS1/MS2 you can switch between 1/8, ¼, ½ and full steps. For full steps connect both MS1 and MS2 to GND. This will allow speeds of up to 300 rpm. Choosing the appropriate MS1/MS2 settings allows you to adjust for installed gear ratio between the motor and the driven part.
Hardware Protection
While the SpeedStepper library allows you to set position limits on the motor movement, the tacking of the position is done by counting the steps output by the software. If the motor stalls, i.e. the torque is insufficient to drive the motor the next step then the software position will be out of sync with the motor position. Then when you use the 'goHome' command the motor will overshoot the home position. To prevent damage to the hardware you should fit limit switches at the hard limits to disconnect the motor power supply
Setting motor current limit
First, set it to the lowest setting of the potentiometer. i.e. voltage at TP1 is minimum. The potentiometer is delicate, so don't force the potentiometer past the mechanical stops. Set the motor driving at a slow steady speed a slow, then slowly turn the potentiometer until the motor doesn’t skip or jerk between steps.
Conclusion
This project shows how to use the SpeedStepper library in a practical application. While the AccelStepper library provides good position control, speed control was needed for the Prototype Ice-Melting Probe for Collecting Biological Samples on Europa so the AccelStepper library was re-written to provide speed control with end limits and a goHome function.
Participated in the
Make it Move Contest 2020