Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Unreleased:
- fix: RampControl.cpp declared the log2 timer-frequency variables `static`, conflicting with their `extern` declaration in RampGenerator.h. That combination is ill-formed C++ (a static redeclaration cannot follow a non-static/extern one in the same translation unit) and breaks the build for any platform whose TICKS_PER_S isn't exactly 16000000 or 21000000 - e.g. an AVR board not running at 16/21 MHz - the moment SUPPORT_LOG2_TIMER_FREQ_VARIABLES is exercised
- Teensy 4.0/4.1 support (EXPERIMENTAL): new pd_teensy backend, up to 16 steppers via the i.MX RT1062's QuadTimer modules, any digital pin usable as step/dir (see README "Teensy 4.0/4.1" section). Verified on real Teensy 4.0 hardware: single stepper through a DM556 driver board reaching the driver's full 200 kHz rated speed with zero missed steps, and all 16 steppers across all 4 QuadTimer modules running simultaneously with no cross-talk
- fix: teensy_queue.cpp's step-timing ISR was missing a Cortex-M7 `dsb` memory barrier after clearing the QuadTimer interrupt flags, matching one luni64/TeensyStep4's own code has for the same reason - without it, step rate would plateau then the motor would hang as interrupt frequency increased during acceleration
- fix (shared with #372): RampControl.cpp declared the log2 timer-frequency variables `static`, conflicting with their `extern` declaration. Ill-formed C++, breaks the build for any platform whose TICKS_PER_S isn't exactly 16000000 or 21000000 - needed for this port's 9.375 MHz tick rate
- fix (shared with #372): those extern declarations also lived in the wrong header (RampGenerator.h, included after the header that already needs them) - moved into RampCalculator.h
- fix (shared with #372): `log2_timer_freq_div_sqrt_of_2` was computed with a copy-paste of the formula for `log2_timer_freq_square_div_2` (squaring the frequency instead of dividing by sqrt(2)). Caused the stepper to "hang" for minutes on any ramp start/reversal from standstill, unless setLinearAcceleration() happened to be in use (which masks it) - found via real hardware testing on this port

1.3.3:
- moveTimed(0, duration): pause uses last direction XOR prepare_revert (default false does not toggle DIR; true pauses in the opposite direction)
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Arduino core 3.1.0 will support ESP-IDF V5.3.0 (based on RC1)
## Overview

This is a high speed alternative for the [AccelStepper library](http://www.airspayce.com/mikem/arduino/AccelStepper/).
Supported are avr (ATmega 168/328/P, ATmega2560, ATmega32u4), atmelsam due, Microchip SAMD51, esp32, esp32s2, esp32s3, esp32c3, esp32c6, esp32p4, Raspberry pi pico and pico2.
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.

For memory footprint information across supported architectures, see [Memory Report](https://github.com/gin66/FastAccelStepper/blob/master/extras/doc/memory_report.md).

Expand Down Expand Up @@ -96,6 +96,7 @@ The `src/` directory is organized into the following subdirectories:
| `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 |
Expand Down Expand Up @@ -291,6 +292,14 @@ Tested with max two stepper motors with 50 kHz step rate by clazarowitz
* SAMD21 is not supported (compile error)
* requires an Arduino SAMD51 core (uses the variant pin table); tested with the Adafruit SAMD core

### Teensy 4.0/4.1 (EXPERIMENTAL)

* up to 16 stepper motors: 4 QuadTimer (TMR) modules x 4 channels each
* step/dir pins are plain GPIO (toggled via `digitalWriteFast()`), not restricted to specific muxed pins - any digital pin works
* two ISR calls per step (rising + falling edge), each hardware-timed by a QuadTimer compare match, the same principle this library's AVR backend uses
* Steppers' command queue depth: 32
* tested on a real Teensy 4.0 with a DM556 industrial stepper driver: a single axis was speed-swept up to 200 kHz with no missed steps (verified by marking the shaft, since there is no pulse-counter/encoder feedback to check this in software), and all 16 steppers were run simultaneously across all 4 QuadTimer modules with no cross-talk between channels. Pulse width/edge timing has not been checked on a scope or logic analyzer yet - if you have one, please verify and report back what you find

## Usage

The library is in use with A4988, but other driver ICs should work, too.
Expand Down Expand Up @@ -706,4 +715,5 @@ As mentioned by kthod861 in [Issue #110](https://github.com/gin66/FastAccelStepp
- Thanks Sam W for pull request (https://github.com/gin66/FastAccelStepper/pull/351) samx3-due: Fix null result from `tryAllocateQueue()`
- Thanks drayde for pull request (https://github.com/gin66/FastAccelStepper/pull/366) Fix ESP-IDF 6 RMT header include
- Thanks Loriland for the Microchip SAMD51 port (https://github.com/gin66/FastAccelStepper/issues/317)
- Thanks ARDUTECH0 for the experimental Teensy 4.0/4.1 port (https://github.com/gin66/FastAccelStepper/pull/374)

109 changes: 109 additions & 0 deletions examples/TeensyBringUp/TeensyBringUp.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include "FastAccelStepper.h"

// Minimal bring-up/test sketch for the EXPERIMENTAL Teensy 4.0/4.1 backend
// (src/pd_teensy). This backend has NOT been verified on real hardware -
// see the warning at the top of src/pd_teensy/pd_config.h. Use this sketch
// to check it on your board before relying on it in a real project.
//
// What to check:
// 1. Does it compile and upload at all?
// 2. Does the stepper actually move back and forth, the right number of
// steps (watch the position printed to Serial, or count physical steps)?
// 3. Put a scope/logic analyzer on the STEP pin: pulse HIGH time should be
// close to FAS_TEENSY_PULSE_WIDTH_US (default 3us, see pd_config.h), and
// the step-to-step period should ramp smoothly (S-curve corners from
// setLinearAcceleration(), see below) without jitter or missed edges,
// especially near the top speed configured below.
// 4. Try raising setSpeedInHz()/setAcceleration() to find where it actually
// breaks (missed steps, hangs) - the 50k steps/s ceiling in
// _pd_initVars() is an untested guess, not a measurement.
//
// Any digital pin works for step/dir/enable on this backend (unlike
// SAMD/SAM, it's not restricted to specific muxed pins) - change the pin
// numbers below to whatever you've actually wired up.

#define stepPinStepper 2
#define dirPinStepper 3
#define enablePinStepper 4

FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;

void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000) {
// wait for the USB serial monitor, but don't hang forever if none is
// attached
}
Serial.println("START - FastAccelStepper Teensy 4.x bring-up test");

engine.init();

stepper = engine.stepperConnectToPin(stepPinStepper);
if (stepper) {
Serial.println("HAVE STEPPER");
stepper->setDirectionPin(dirPinStepper);
stepper->setEnablePin(enablePinStepper);
stepper->setAutoEnable(true);

stepper->setSpeedInHz(2000); // start conservative, raise later
stepper->setAcceleration(4000);
stepper->setLinearAcceleration(50); // small S-curve corners to check

Serial.print("TICKS_PER_S=");
Serial.println((uint32_t)TICKS_PER_S);
Serial.print("max_speed getMaxSpeedInHz()=");
Serial.println(stepper->getMaxSpeedInHz());
Serial.print("enablePin state right after setEnablePin() (expect HIGH=1, disabled): ");
Serial.println(digitalRead(enablePinStepper));
} else {
while (true) {
Serial.println("NO STEPPER - check stepPinStepper is a valid pin");
delay(1000);
}
}
}

uint32_t last_report_ms = 0;

void loop() {
if (!stepper->isRunning()) {
int32_t pos = stepper->getCurrentPosition();
Serial.print("Reached standstill at position ");
Serial.println(pos);

int32_t target = (pos <= 0) ? 3200 : -3200;
Serial.print("Moving to ");
Serial.println(target);
MoveResultCode res = stepper->moveTo(target);
if (!moveIsOk(res)) {
Serial.print("moveTo() error: ");
Serial.println(toString(res));
}
}

// Periodic status while running, so you can watch position/speed advance
// in the Serial Monitor without needing a scope for a first sanity check.
uint32_t now = millis();
if (stepper->isRunning() && (now - last_report_ms >= 200)) {
last_report_ms = now;
Serial.print("pos=");
Serial.print(stepper->getCurrentPosition());
Serial.print(" target=");
Serial.print(stepper->targetPos());
Serial.print(" stepsToStop=");
Serial.print(stepper->stepsToStop());
Serial.print(" speed(mHz)=");
Serial.print(stepper->getCurrentSpeedInMilliHz());
Serial.print(" rampState=");
Serial.print(stepper->rampState());
Serial.print(" queueRunning=");
Serial.print(stepper->isQueueRunning());
Serial.print(" queueEmpty=");
Serial.print(stepper->isQueueEmpty());
Serial.print(" enablePin=");
Serial.print(digitalRead(enablePinStepper));
Serial.print(" queueEntries=");
Serial.println(stepper->queueEntries());
}
}
70 changes: 70 additions & 0 deletions examples/TeensyEnableBlinkTest/TeensyEnableBlinkTest.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "FastAccelStepper.h"

// Minimal, decisive test of FastAccelStepper's enable-pin control,
// completely decoupled from auto-enable timing and from any driver board -
// wire an LED directly to enablePinStepper (through a resistor to GND) to
// watch it.
//
// This calls the library's own public enableOutputs()/disableOutputs()
// directly, alternating every 2 seconds, printing what it did and what
// digitalRead() sees right after. If the LED doesn't change in sync with
// these prints, the problem is confirmed to be in the library's pin
// control - if it does, the library is fine and something else (timing,
// wiring, or how another sketch used it) was the real cause.

#define stepPinStepper 2
#define dirPinStepper 3
#define enablePinStepper 4

FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;

void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000) {
}
Serial.println("START - enable pin blink test");

engine.init();
stepper = engine.stepperConnectToPin(stepPinStepper);
if (!stepper) {
while (true) {
Serial.println("NO STEPPER - check stepPinStepper is a valid pin");
delay(1000);
}
}
stepper->setDirectionPin(dirPinStepper);
stepper->setEnablePin(enablePinStepper);
// NOT calling setAutoEnable() - we're driving enable manually here so
// there's no ambiguity about when it fires.

Serial.print("Right after setEnablePin(), digitalRead(pin)=");
Serial.println(digitalRead(enablePinStepper));
Serial.println(
"Expect HIGH=1 here (disabled is the safe default) - the LED should "
"be ON now if wired pin->resistor->LED->GND.");
}

bool enabled = false;
uint32_t lastToggleMs = 0;

void loop() {
uint32_t now = millis();
if (now - lastToggleMs >= 2000) {
lastToggleMs = now;
enabled = !enabled;
bool ok;
if (enabled) {
ok = stepper->enableOutputs();
Serial.print("enableOutputs() -> ");
} else {
ok = stepper->disableOutputs();
Serial.print("disableOutputs() -> ");
}
Serial.print(ok ? "true" : "false");
Serial.print(" digitalRead(pin) now = ");
Serial.print(digitalRead(enablePinStepper));
Serial.println(enabled ? " (expect LOW=0, LED should turn OFF)"
: " (expect HIGH=1, LED should turn ON)");
}
}
Loading
Loading