This is a high speed alternative for the AccelStepper library. Supported are avr (ATmega 168/328/P, ATmega2560, ATmega32u4), atmelsam due, Microchip SAMD51, esp32, esp32s2, esp32s3, esp32c3, esp32c6, esp32p4, Raspberry pi pico and pico2, and (experimentally) Teensy 4.0/4.1.
The stepper motors are connected via a driver IC (like A4988) with a 1, 2 or 3-wire connection (step / direction / enable). See Hardware & Pin Connection for the pin restrictions of each platform.
There is an excellent AI-generated description of FastAccelStepper with deep implementation details and diagrams at deepwiki.com/gin66/FastAccelStepper. For memory footprint information across supported architectures, see the Memory Report.
| Platform | Max step rate [kSteps/s] | Steppers | Cmd queue depth | Doc |
|---|---|---|---|---|
| AVR ATmega 168/328 | 50 | 1-2 | 16 | avr.md |
| AVR ATmega32u4 | 50 | 3 | 16 | avr.md |
| AVR ATmega2560 | 50 | 3 | 16 | avr.md |
| ESP32 (IDF 4.x) | 200 | 14 (6 MCPWM/PCNT + 8 RMT) | 32 | esp32.md |
| ESP32 (IDF 5.3+) | 200 [1] | 14 (6 MCPWM/PCNT + 8 RMT) + 32 I2S * | 32 | esp32.md |
| ESP32-S2 | 200 [1] | 4 RMT + 32 I2S * | 32 | esp32.md |
| ESP32-S3 | 200 [1] | 4 MCPWM/PCNT + 4 RMT + 32 I2S * | 32 | esp32.md |
| ESP32-C3 | 200 [1] | 2 RMT + 32 I2S * | 32 | esp32.md |
| ESP32-C6 | 200 [1] | 2 MCPWM/PCNT + 2 RMT + 32 I2S * | 32 | esp32.md |
| ESP32-P4 | 200 [1] | 4 RMT + 32 I2S * [2] | 32 | esp32.md |
| Raspberry Pi Pico | 200 | 4 (8 w/ riscv) | 32 | pico.md |
| Raspberry Pi Pico 2 | 200 | 8 (12 w/ riscv) | 32 | pico.md |
| Atmel SAM Due | 50 | 6 | 32 | sam.md |
| Microchip SAMD51 | 100 | 3-5 | 32 | samd51.md |
| Teensy 4.0/4.1 (exp.) | 200 | 16 | 32 | teensy.md |
Notes:
- 200 kSteps/s applies to the MCPWM/PCNT and RMT drivers. I2S Mux is limited to 40 kSteps/s; I2S Direct channels reach up to 200 kSteps/s.
- The ESP32-P4 hardware provides 2 MCPWM groups and 4 PCNT units, but MCPWM/PCNT is not implemented for P4 yet. ESP32-S2 has PCNT but no MCPWM, ESP32-C3 has neither, so only RMT/I2S are available there.
* The 32 I2S steppers require an external demultiplexer (e.g. 74HC154, or a 74HC138 cascade / 74HC595 shift registers) on the I2S data line. Using I2S slots directly (I2S Direct mode) provides only 1-3 extra steppers.
The full ESP32 driver comparison (MCPWM/PCNT vs RMT vs I2S Mux) is in platforms/esp32.md.
- 1-pin operation for e.g. peristaltic pump => only positive move
- 2-pin operation for e.g. axis control
- 3-pin operation to reduce power dissipation of driver/stepper
- Lower limit of 260s per step @ 16MHz aka one step every four minute (esp32/avr/samd51), 198s for sam due
- fully interrupt/task driven - no periodic function to be called from application loop
- supports acceleration and deceleration with per stepper max speed/acceleration
- Allows the motor to continuously run in the current direction until
stopMove()is called. - speed/acceleration can be varied while stepper is running (call to
moveormoveTois needed in order to apply the new values) - Constant acceleration control: In this mode the motor can be controled by acceleration values and with acceleration=0 will keep current speed
- Linear acceleration increase from/to standstill using cubic speed function -
configurable by
setLinearAcceleration() - Jump start from standstill - configurable by
setJumpStart() - Auto enable mode: stepper motor is enabled before movement and disabled afterwards with configurable delays
- Enable pins can be shared between motors
- Direction pins can be shared between motors
- Configurable delay between direction change and following step
- External callback function can be used to drive the enable pins (e.g. connected to shift register) and, only esp32 derivates: the direction pins
- No float calculation (log2 representation in range -64..64 with 16bit integer representation and 1/512th resolution)
- Provide API to each steppers' command queue. Those commands are tied to timer ticks aka the CPU frequency!
- Command queue can be filled with commands and then started. This allows near synchronous start of several steppers for multi axis applications.
- EXPERIMENTAL multi-axis planner
FasNAxis(src/FasNAxis.h): drives N stepper queues from one polyline so the axes stay time-synchronized. API not stable yet. See the n-axis whitepaper, the NaxesAFAP example, and Future work.
#include "FastAccelStepper.h"
#define dirPinStepper 5
#define enablePinStepper 6
#define stepPinStepper 9
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() {
}More details in Usage and the UsageExample.ino.
Experimental. The
FasNAxisAPI is not stable yet and may change without notice. It runs a polyline as fast as the motors and geometry allow, with no requested speed or time. The call list is FasNAxis.md. A waypoint that carries its own duration is FasTimed. Other open work is listed under Future work.
For coordinated motion, FasNAxis drives N stepper queues from one polyline
so the axes stay time-synchronized. The hot path has no float, division, or
64-bit integers. Full example: examples/NaxesAFAP; the
theory is in the n-axis whitepaper.
FasNAxis is FasNAxis<NAXES, HORIZON = 64, Stepper = FastAccelStepper, Engine = FastAccelStepperEngine> (src/FasNAxis.h). HORIZON caps how many
future polyline points are buffered and planned: a small value slows the track
(a speed cap, never an error — see isSpeedLimitedByLookahead()), a larger one
smooths the ramp at the cost of RAM.
#include "FastAccelStepper.h"
#include "FasNAxis.h" // not pulled in by FastAccelStepper.h
FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper* axes[2] = {NULL, NULL};
FasNAxis<2, 32> planner(FasNAxisConfig{}, engine); // 2 axes, HORIZON = 32
void setup() {
engine.init();
axes[0] = engine.stepperConnectToPin(9);
axes[1] = engine.stepperConnectToPin(10);
for (uint8_t i = 0; i < 2; i++) {
axes[i]->setDirectionPin(5 + i);
axes[i]->setSpeedInHz(4000); // steps/s
axes[i]->setAcceleration(2000); // steps/s²
planner.addAxis(i, axes[i]);
}
planner.setLimitsFromSteppers(); // freeze v_max / a_max per axis
int32_t origin[2] = {0, 0};
planner.setCurrentPosition(origin); // open the path at the current position
}
void loop() {
// Append exact-chord target vertices; backpressures when HORIZON is full.
static const int32_t path[][2] = {
{1000, 0}, {1000, 1000}, {0, 1000}, {0, 0}};
static uint8_t wp = 0;
if (wp < 4) {
if (planner.addWaypoint(path[wp])) {
wp++;
}
if (wp == 4) {
planner.endPath(); // last point is rest
}
}
planner.pump(); // plan and feed the committed path into the queues
}| Topic | Document |
|---|---|
| Installation (Arduino / PlatformIO / ESP-IDF) | installation.md |
| Hardware & pin connection | hardware.md |
| Usage & auto enable | usage.md |
| Move semantics & position wraparound | move_semantics.md |
| Multi-axis applications | multi_axis.md |
| Stepper and engine API | FastAccelStepper_API.md, from header2markdown.sh on FastAccelStepper.h and FastAccelStepperEngine.h |
| FasNAxis calls | FasNAxis.md |
| FasTimed calls | FasTimed.md, example NaxesTimed |
| Driver architecture | driver_architecture.md |
| Ramp generator | ramp.md, ramp_cubic_quadratic.md |
| Planner modes (AFAP vs timed) | planner_modes.md |
| ESP32 I2S driver | esp32_i2s_driver.md |
| Pico PIO program flow | pico_pio.md |
| SAMD51 design notes | samd51/CONTEXT.md |
| n-axis whitepaper | n_axes_whitepaper.md |
| Future work | extras/todo/README.md |
| Physical stepper simulation | physical_stepper_whitepaper.md |
| Test strategy | testing.md |
| Troubleshooting | troubleshooting.md |
| Showcase (videos) | showcase.md |
| Contributors & supporters | contributors.md |
| Memory footprint | memory_report.md |
| Changelog | CHANGELOG.md |
| Directory | Purpose |
|---|---|
fas_arch/ |
Platform abstraction: compiler/framework detection, interrupt macros, Arduino-like polyfills |
pd_avr/ |
AVR pulse driver: timer-based stepper control for ATmega chips |
pd_esp32/ |
ESP32 pulse driver: RMT, MCPWM/PCNT, and I2S-based stepper control |
pd_pico/ |
RP2040/RP2350 pulse driver: PIO-based stepper control |
pd_sam/ |
SAM Due pulse driver: timer-based stepper control |
pd_samd/ |
SAMD51 pulse driver: TCC PWM-based stepper control |
pd_teensy/ |
Teensy 4.0/4.1 pulse driver: QuadTimer-based stepper control (EXPERIMENTAL) |
pd_test/ |
Test platform pulse driver: PC-based testing simulation |
fas_queue/ |
Queue implementation: command queue management |
fas_ramp/ |
Ramp calculation: acceleration/deceleration curves |
fas_naxis/ |
Experimental multi-axis planner (FasNAxis), header-only |
log2/ |
Log2 representation: fixed-point arithmetic for speed calculations |
Platform-specific configuration constants (queue sizes, timing, feature flags)
are defined in pd_*/pd_config.h files, which are included by
fas_arch/common.h during the platform dispatch. See
driver_architecture.md for details.
- Arduino core v3.0.x uses esp-idf v5.0 up to v5.1 and FastAccelStepper will fail to compile. Arduino core 3.1.0 supports ESP-IDF V5.3.0 (based on RC1).
- TODO / roadmap: GitHub project
- Known issues and debugging tips: troubleshooting.md
Open work is one file per item in
extras/todo. The filename prefix is the priority,
three digits wide (050_name.md). Numbers step by 10, so a new item can take
a free number between two existing ones.
The list is library-wide. It currently covers per-platform synchronized
start, a cubic ramp start, delta-step input for FasNAxis, a single-axis
moveTo with an arrival in ticks, and a smooth stop at the end of a path.
Timed chunks are FasTimed. The n-axis design is the
whitepaper.
For FastAccelStepper, the way software is developed is changing fundamentally. It has been a long way from 2020 to today. Back then, I simply needed a fast stepper driver for AVR, and with my embedded know-how I went for it, hand-optimizing the ISR code until I was satisfied. Then I added ESP32 support and found several ways to generate the pulses. Today I no longer write the documentation and implementation code myself; LLMs and coding agents are increasingly doing that work. Almost inadvertently, my role is shifting toward defining requirements, consulting on the architecture, selectively reviewing the generated code, and testing it.
Nowadays, coding agents can understand the existing code base surprisingly well and carry out most of the required modifications. This makes implementing new features significantly faster.
That said, I still take pride in hand-writing the Pico PIO code. There is something deeply satisfying about getting all the required functionality for a step/direction pin into just 50 words of PIO code. That kind of low-level optimization and craftsmanship is something I value.
Perhaps there is also a little nostalgia in this. It feels like one of those moments when a craft that has shaped the way you have worked throughout your lifetime is slowly becoming obsolete — not because it has lost its value, but because there is now a faster and more powerful way of achieving the same result. Like the expert typist who could produce flawless pages at remarkable speed, the old skills don't necessarily become less beautiful; they simply become less necessary.
Those 50 words of PIO code are perhaps one of the last pieces of FastAccelStepper where I can say, quite honestly, I wrote every word myself.
Contributions are welcome. See contributors.md for the list of people who have helped.
Before committing, format the code with bash extras/scripts/format_code.sh and run the PC-based tests with make test in extras/tests/pc_based.
With implementation becoming faster, testing is increasingly becoming the bottleneck for maintaining quality. Any new feature idea should therefore ideally come with a patch and some level of automated testing—preferably in a pc_based or simavr_based form.
The current architecture makes it straightforward to add new multi-axes planners and test them on a PC. Adding a new pulse driver, however, requires testing on real hardware. Any contribution that introduces a new PD instance must therefore include confirmation that it has been tested on the relevant hardware.
It would be great to see the introduction of more cycle-accurate, embeddable emulators that can run in GitHub Actions. simavr was, and still is, extremely valuable to me.
Having said that, I want to point out that contributions using float, double, or 64-bit arithmetic in production code will not make it into the codebase.