No issue with platformio. Check the related issue for the arduino library manager
Arduino core v3.0.x are using esp-idf v5.0 up to v5.1 and FastAccelStepper will fail to compile.
Arduino core 3.1.0 will support ESP-IDF V5.3.0 (based on RC1)
This is a high speed alternative for the AccelStepper library. Supported are avr (ATmega 168/328/P, ATmega2560, ATmega32u4), esp32, esp32s2, esp32s3, esp32c3, esp32c6 and atmelsam due.
The stepper motors should be connected via a driver IC (like A4988) with a 1, 2 or 3-wire connection:
FastAccelStepper offers the following features:
setLinearAcceleration()
setJumpStart()
-2.000.000.000
-2.000.000.001
-2.000.000.002
:
-2.147.483.647
-2.147.483.648
2.147.483.647
2.147.483.646
2.147.483.645
:
2.000.000.000
Comments to pin sharing:
F_CPU
Macro for the relation tick value to time, so it should now not be limited to 16 MHz CPU frequency (untested)F_CPU
Macro for the relation tick value to time, so it should now not be limited to 16 MHz CPU frequency (untested)F_CPU
Macro for the relation tick value to time, so it should now not be limited to 16 MHz CPU frequency (untested)Tested with max two stepper motors with 50 kHz step rate by clazarowitz
The library is in use with A4988, but other driver ICs should work, too.
For the API definition please consult the header file FastAccelStepper.h. Or a generated markdown file
Please check the examples for application and how to use the low level interface. Some info is Issue #86.
The module defines the global variable fas_queue
. Do not use or redefine this variable.
Using the high level interface with ramp up/down as in UsageExample.ino.
#include "FastAccelStepper.h"
#include "AVRStepperPins.h" // Only required for AVR controllers
#define dirPinStepper 5
#define enablePinStepper 6
#define stepPinStepper 9
// If using an AVR device use the definitons provided in AVRStepperPins
// stepPinStepper1A
//
// or even shorter (for 2560 the correct pin on the chosen timer is selected):
// stepPinStepperA
FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;
void setup() {
engine.init();
stepper = engine.stepperConnectToPin(stepPinStepper);
if (stepper) {
stepper->setDirectionPin(dirPinStepper);
stepper->setEnablePin(enablePinStepper);
stepper->setAutoEnable(true);
stepper->setSpeedInHz(500); // 500 steps/s
stepper->setAcceleration(100); // 100 steps/s²
stepper->move(1000);
}
}
void loop() {
}
Few comments to auto enable/disable:
MIN_CMD_TICKS
.More than one stepper can be connected to one auto enable pin. Behaviour is like this:
The library does not consider the case, that Low/High Active enable may be mixed. This means stepper #1 uses the enable pin as High Active and stepper #2 the same pin as Low Active. => This situation will not be identified and will lead to unexpected behaviour
The timer 1 is used with prescaler 1. With the arduino nano running at 16 MHz, timer overflow interrupts are generated every ~4 ms. This timer overflow interrupt is used for adjusting the speed.
The timer compare unit toggles the step pin from Low to High precisely. The transition High to Low is done in the timer compare interrupt routine, thus the High state is only few us.
After stepper movement is completed, the timer compare unit is disconnected from the step pin. Thus the application could change the state freely, while the stepper is not controlled by this library.
Measurement of the acceleration/deacceleration aka timer overflow interrupt yields: one calculation round needs around 300us. Thus it can keep up with the chosen 10 ms planning ahead time.
Similar to ATmega328, but instead of timer 1, timer 4 is used.
For users of platformio, the used timer can be changed to either 1, 3, 4 or 5. For e.g. timer module 3 add to platformio.ini under build_flags
:
build_flags = -DFAS_TIMER_MODULE=3
or better:
build_flags = -Werror -Wall -DFAS_TIMER_MODULE=3
For arduino users, the same can be done by defining the flag before including the FastAccelStepperEngine.h
header (as per info ixil), but apparently to issue #50, this approach does not work for everyone:
e.g.
sketch.ino
----------
#include <Arduino.h>
#define FAS_TIMER_MODULE 3
#include <FastAccelStepper.h>
/* ... */
This stepper driver uses mcpwm modules of the esp32: for the first three stepper motors mcpwm0, and mcpwm1 for the steppers four to six. In addition, the pulse counter module is used starting from unit_0
to unit_5
. This driver uses the pcnt_isr_service
, so unallocated modules can still be used by the application. The mcpwm modules' outputs are fed into the pulse counter by direct gpio-matrix-modification.
For the other stepper motors, the rmt module comes into use.
Only rmt module is supported.
A note to MIN_CMD_TICKS
using mcpwm/pcnt: The current implementation uses one interrupt per command in the command queue. This is much less interrupt rate than for avr. Nevertheless at 200kSteps/s the switch from one command to the next one should be ideally serviced before the next step. This means within 5us. As this cannot be guaranteed, the driver remedies an overrun (at least by design) to deduct the overrun pulses from the next command. The overrun pulses will then be run at the former command's tick rate. For real life stepper application, this should be ok. To be considered for raw access: Do not run many steps at high rate e.g. 200kSteps/s followed by a pause.
What are the differences between mcpwm/pcnt and rmt ?
mcpwm/pcnt | rmt | |
---|---|---|
Interrupt rate/stepper | one interrupt per command | min: one interrupt per command, max: one interrupt per 31 steps at high speed |
Required interrupt response | at high speed: time between two steps | at high speed: time between 31 steps |
Module usage | 1 or 2 mcpcms, up to 6 channels of pcnt | rmt |
esp32 notes | availabe pcnt modules can be connected | no pcnt module used, so can be attached to rmt output as realtime position |
If the interrupt load is not an issue, then rmt is the better choice. With rmt the below (multi-axis application) mentioned loss of synchonicity at high speeds can be avoided. The rmt driver is - besides some rmt modules perks - less complex and way more straightforward.
As of now, allocation of steppers on esp32 are: first all 6 mcpwm/pcnt drivers and then the 8 rmt drivers. In future this may be under application control. Starting with 0.29.2, the module can be directly selected on call of stepperConnectToPin()
. So the allocation gets more flexible.
One specific note for the rmt: If a direction pin toggle is needed directly after a command with steps, then the driver will add before that direction pin toggle another pause of MIN_CMD_TICKS
ticks.
This stepper driver uses rmt module.
The ESP32S3's rmt module is similar to esp32c3 with 4 instead of 2 channels and with different register names.
This stepper driver uses mcpwm/pcnt + rmt modules. Can drive up to 8 motors. Tested with 6 motors (not by me).
This stepper driver uses rmt modules. Can drive up to 4 motors.
This stepper driver uses rmt module and can drive up to 2 motors. Not thoroughly tested, so only experimental support.
Compatibility with ESP32-MINI-1: At least mcpwm and pulse counter modules are listed in the datasheet. So there are chances, that this lib works.
This is supported by clazarowitz
The used formula is just s = 1/2 a t² = v² / (2 a) with s = steps, a = acceleration, v = speed and t = time. In order to determine the speed for a given step, the calculation is v = sqrt(2 a s). The performed square root is an 8 bit table lookup using log2/pow2. Sufficient exact for this purpose.
For the linear acceleration from/to standstill the used formula is s = 1/2 j t³. The variable j is calculated from acceleration and steps of linear acceleration, which is set by setLinearAcceleration()
.
The compare interrupt routines use 16bit tick counters, which translates to approx. 4ms. For longer time between pulses, pauses without step output can be added. With this approach the ramp generation supports up to one step per 268s.
The low level command queue for each stepper allows direct speed control - when high level ramp generation is not operating. This allows precise control of the stepper, if the code, generating the commands, can cope with the stepper speed (beware of any Serial.print in your hot path).
The chosen approach has few limitations for esp32. With acceleration = 1 step/s², the maximum speed is approx. 92 kStep/s. The max. supported speed for esp32 will be reachable only with acceleration >= 5 step/s².
For coordinated movement of two or more axis, the current ramp generation will not provide good results. The planning of steps needs to take into consideration max.speed/acceleration of all steppers and eventually the net speed/acceleration of the resulting movement together with its restrictions. Nice example of multi-axis forward planning can be found within the marlin-project. If this kind of multi-dimensional planning is used, then FastAccelStepper is a good solution to execute the raw commands (without ramp generation) with near-synchronous start of involved steppers. With the tick-exact execution of commands, the synchronization will not be lost as long as the command queues are not running out of commands. And for esp32, second requirement is, that the interrupts can be serviced on time (no pulses issued with previous command's pulse period time)
To keep up the synchronization of two steppers please keep in mind:
Note for esp32 rmt driver:
See project
If you prefer platformio and you are running Linux, then platformio version of the examples are created by executing
ci/build-platformio.sh
This will create a directory pio_dirs, which contains all examples. Can be executed by e.g.
cd pio_dirs/StepperDemo
pio run -e avr --target upload --upload-port /dev/ttyUSB0
A CMakeLists.txt
is provided to use FastAccelStepper as an ESP-IDF component. Clone it into the components/
directory in the root of your project and build as usual. You must have Arduino available as a component. See this for instructions on how to set that up. Tested as ESP-IDF component on PlatformIO Espressif32 Platform v3.3.2.
For any questions/support please contact gagank1, as I do not use esp-idf
The library is tested with different kind of tests:
PC only (sub folder ./tests/pc_based
)
These tests focussing primarily the ramp generator and part of the API
simavr based for avr (sub folder ./tests/simavr_based
)
The simavr is an excellent simulator for avr microcontrollers. This allows to check the avr implementation thoroughly: number of steps generated, virtual stepper position and even timing. Tested code is mainly the StepperDemo, which gets fed in a one line sequence of commands to execute. These tests are focused on avr, but help to check the whole library code, used by esp32, too.
esp32 tests with another pulse counter attached (e.g. test_seq_08
in StepperDemo)
The FastAccelStepper-API supports to attach another free pulse counter to a stepper's step and dir pins. This counter counts in the range of -16383 to 16383 with wrap around to 0. The test condition is, that the library's view of the position should match the independently counted one. These tests are still evolving
Test for pulse generation using examples/Pulses
This has been intensively used to debug the esp32 ISR code
esp32 hw tests
These tests live under sub folder ./tests/esp32_hw_based
manual tests using examples/StepperDemo
These are unstructured tests with listening to the motor and observing the behavior
Short info, what the test sequences, embedded in StepperDemo in the test mode, do:
All those tests have no internal test passed/failed criteria. Those are used for automated tests with ./tests/simavr_based
and ./tests/esp32_hw_based
. The test pass criteria are: They should run smoothly without hiccups and return to start position.
See changelog
mcpwm_isr_register()
. This has been raised as issue at espressifframework-arduinoespressif32 @ 3.10006.210326
and later will lead to compile error for esp32, if using compiler options -Werror -Wall
!!! The problem can be circumvented by applying -Wno-error=incompatible-pointer-types
In case the stepper does not run smoothly, then StepperDemo contains commands to simulate two type of error causes. For avr the commands are r
and e
. For esp32 only e
can be used.
r
: The digitalRead() of arduino is a fancy implementation, which checks, if the pin being read is connected to a timer to generate PWM and if yes, turns this off (actually IMHO a broken implementation: only 1 of the needed 2 bits are cleared, and the activation by force compare is missing). As FastAccelStepper controls the step pin, the digitalRead can disturb the step pin (even though I have expected step loss, only difference in noise can be heard). The error simulation in StepperDemo reads the pins in the main loop(), thus the symptom occurs quite reliably.e
: This blocks repeatedly interrupts for ~100us during 64ms out of 256ms. On AVR to see this problem popping up, the stepper rate has to be <~106 us (avr, one stepper running). >~106us it runs quite smoothly. The 106us = 100us block + ~6us ISR runtime. For ESP32 this has no effect.For avr: cause of long interrupt being blocked can be e.g.:
noInterrupts()/interrupts()
in the application (or used libraries)Especially in interrupt service routines, the digitalRead()/digitalWrite()
must be avoided. Alternative solution is described e.g. here: blog, or digitalWriteFast, or fast versions.
This feature of StepperDemo allows to compare non-smooth running stepper in an application with these error types.
Found on youtube:
As mentioned by kthod861 in Issue #110:
_stepper_cnt
initialization (https://github.com/gin66/FastAccelStepper/pull/204)