Repository navigation
Add experimental Teensy 4.0/4.1 support (pd_teensy backend) - #374
Conversation
RampControl.cpp declared log2_timer_freq (and its two derived values) static, while RampGenerator.h declares them extern for use from RampGenerator.cpp, a separate translation unit. A static redeclaration cannot follow a non-static one in the same TU, so this fails to build for any platform whose TICKS_PER_S isn't exactly 16000000 or 21000000 (the two values RampCalculator.h has precomputed fast-path constants for) - e.g. AVR boards not running at 16/21 MHz. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New platform backend for the i.MX RT1062 (Teensy 4.0/4.1), using the chip's 4 QuadTimer (TMR) modules x 4 channels each for up to 16 independently timed steppers. Unlike the SAMD/SAM backends, the step pin is not muxed to the timer's own output pin: the ISR toggles it directly via digitalWriteFast(), so any digital pin works, the same flexibility ESP32 already has. Each step costs two hardware-timed compare-match interrupts (rising edge, falling edge after the pulse width), the same principle this library's AVR backend already uses to time each edge exactly regardless of interrupt latency. The register-level QuadTimer sequence is adapted from luni64/TeensyStep4 (MIT licensed), a stepper library already verified on real Teensy 4.x hardware - not guessed from the reference manual. Includes the same log2 timer-frequency linkage fix as gin66#372, since this backend's 9.375 MHz tick rate doesn't match either of the two existing fast-path TICKS_PER_S constants and needs that generic code path. STATUS: EXPERIMENTAL, NOT YET VERIFIED ON REAL HARDWARE. Written and reviewed without access to a Teensy 4.x board, a compiler for it, or an oscilloscope - see the warning at the top of pd_teensy/pd_config.h. Needs someone with the actual board to build it and check step timing.
60d968a to
74adbf6
Compare
|
Thanks for the patch. I will keep it open until anyone has confirmed, that it works. |
Verified on real Teensy 4.0 hardware for the first time (motor moves, ramps, and reaches target correctly at 2000 Hz / 4000 steps/s^2). Found and fixed two real bugs along the way: - RampCalculator.h/RampGenerator.h: the log2_timer_freq extern declarations lived in RampGenerator.h, included *after* RampCalculator.h - but RampCalculator.h's own inline methods (ramp_config_s::update() etc.) use log2_timer_freq as soon as that header is parsed, before RampGenerator.h's declarations are ever seen. Moved the declarations into RampCalculator.h itself, in the same branch that defines SUPPORT_LOG2_TIMER_FREQ_VARIABLES. This is what made the Teensy backend fail to compile at all (also affects any other platform hitting this fallback branch, e.g. AVR boards not running at 16/21 MHz) - same root cause as the static/extern issue in gin66#372, a second defect in the same never-before-exercised code path. - teensy_queue.cpp: the module-level ISR loop was missing a `dsb` memory barrier after clearing the QuadTimer CSCTRL.TCF1 flags. The Cortex-M7 write buffer does not guarantee that clear has reached the peripheral by the time the ISR returns, which showed up as the motor's step rate plateauing then hanging entirely as interrupt frequency increased during accel - exactly the failure mode observed on hardware. luni64/TeensyStep4's own module ISR carries the same barrier with the same reasoning; this backend had dropped it when adapting that code. Also, now that real hardware is available to tune against: - FAS_TEENSY_TMR_PRESCALE is now a documented, overridable macro (0..7) instead of a hardcoded prescaler, with TICKS_PER_S derived from it so the two can't drift apart. - Step-timing QuadTimer interrupts are now given a higher NVIC priority than the ~4 ms ramp tick (FAS_TEENSY_STEP_ISR_PRIORITY / FAS_TEENSY_RAMP_TICK_PRIORITY, both overridable), so a slow queue refill under load can't delay an already-scheduled step edge. Adds examples/TeensyBringUp, the bring-up sketch used for this testing: moves one stepper back and forth and prints position/speed/ ramp-state/queue-state/enable-pin diagnostics to Serial, useful for verifying this backend on your own board before relying on it. Enable pin behavior was also checked during this session and works as documented (LOW = enabled by default) - the driver board's own power LED, which some test rigs may be watching, is unrelated to the ENA signal and stays lit whenever the driver has power.
|
Video of the hardware test referenced above: single stepper on a DM556 driver board (step/dir/enable), Teensy 4.0, IMG_4199_compressed.mp4 |
TeensyEnableBlinkTest: decisive test of enableOutputs()/disableOutputs() alone (no auto-enable timing, no driver board) - toggles every 2s with an LED wired straight to the enable pin. Used during this session's bring-up to confirm the enable pin control itself works correctly; earlier confusion was from watching a driver board's own power LED (unrelated to the ENA signal) instead of the actual pin. TeensyMaxSpeedFinder: sweeps setSpeedInHz() through increasing levels, each leg moving a fixed step count out and back (net zero), pausing for the operator to visually confirm (via a marked shaft) that no steps were lost before advancing - since there's no encoder feedback, software alone cannot detect missed steps on this backend.
…ardware) Found via real Teensy 4.0 bring-up: any move that starts or reverses from a genuine standstill (no setLinearAcceleration() in use) would "hang" for several minutes. Root cause: log2_timer_freq_div_sqrt_of_2 was computed with log2_multiply(log2_timer_freq, log2_timer_freq) - a copy-paste of the very next line's formula for log2_timer_freq_square_div_2 - squaring the tick frequency instead of dividing it by sqrt(2). calculate_ticks()'s near-standstill branch then produced multi-billion-tick results instead of a normal value. setLinearAcceleration() masks this entirely, since it routes small step counts through a different (cubic) branch that never touches this constant - which is exactly why it took a real motor, run without that option, to expose it. Shared root cause and now shared fix with gin66#372. Adds examples/TeensyMoveAndPause, the sketch that exposed this bug (move, sit genuinely idle for a few seconds so the driver actually disables, move again - unlike TeensyBringUp's back-to-back moves, which never let the queue fully drain and therefore never hit this path).
…sult Distance per leg was a fixed 2000 steps regardless of the speed level being tested - nowhere near enough to actually reach (let alone cruise at) higher speed levels, silently turning them into low triangular blips that never tested the speed they claimed to. Distance is now computed per level from the configured acceleration, so every level actually reaches and briefly cruises at its target speed before returning. Used to measure a real ceiling on a DM556 driver + stepper motor at 25000 pulses/rev, 30V: 100000 Hz clean at 200000 steps/s^2 acceleration, 125000 Hz lost steps at that same acceleration (a motor torque/ acceleration limit) - but every level up to 200000 Hz (the DM556's own rated ceiling) passed clean at a gentler 20000 steps/s^2. Defaults updated to reflect the second, successful sweep.
5 steppers, each moving independently at a different speed/acceleration (deliberately not in lockstep, to make any timing cross-talk between channels/modules easy to notice), spanning both the first QuadTimer module (TMR1, 4 channels - the only one any prior test here exercised) and the second (TMR2, channel 0). Ran for several minutes: every axis's position, speed and ramp state kept advancing independently and correctly the whole time, none showed the frozen-position/stuck-rampState pattern of the bugs found and fixed earlier in this branch.
Was capped at 5 (enough to cross into the 2nd QuadTimer module). Now defaults to all 16 (4 modules x 4 channels), with a shared enable pin across all steppers to keep the pin count within Teensy 4.0's 40 pins (16 unique step + 16 unique dir + 1 shared enable = 33). Ran all 16 simultaneously, each at a different speed/acceleration/ distance so none are ever in lockstep: every axis across all 4 modules (TMR1-TMR4) kept moving, reversing and reaching its target correctly for the full run, with no cross-talk or stuck state anywhere - the first real confirmation that all 4 QuadTimer modules, not just the first two, work correctly under this backend.
TeensyMaxSpeedFinder.ino called setAbsoluteSpeedLimit() unconditionally, but that method only exists when SUPPORT_UNSAFE_ABS_SPEED_LIMIT_SETTING is defined by the platform's pd_config.h. atmelsam (Arduino Due) does not define it, and the CI build matrix compiles every example against every platform, so the atmelsam job failed with "has no member named setAbsoluteSpeedLimit". Unrelated to the RampControl/RampCalculator fixes in this branch.
Same reasoning as the existing StepperDemo/MoveTimed skips just above: nanoatmega168 has only 14336 bytes of usable flash after the bootloader. TeensyMultiStepperTest drives up to 16 simultaneous FastAccelStepper instances (16KB+ compiled), which overflowed that by ~1KB (15304 bytes). It's a Teensy-only stress test anyway and was never meant to run on an 8-bit AVR.
|
Great. Thanks for your (rudimentary) test on HW and github actions pass, too. What still need to be done in readme:
If you have time, you can look at the esp32_hw_based tests and check, if some sequences can be used to test your port.. You can find the log file in that directory. I remember test of one driver/sdk combination takes approx. 20minutes. Unfortunately there is no pulse counter support as for esp32, so only visually verification is possible, if the stepper returns to zero position. |
README and pd_config.h/teensy_queue.{h,cpp} said the Teensy 4.x backend
was "not yet verified on real hardware" - that was true when written,
but it has since been tested on a real Teensy 4.0 with a DM556
industrial driver (single-axis speed sweep to 200 kHz with no missed
steps, and all 16 steppers running simultaneously across all 4
QuadTimer modules with no cross-talk - see CHANGELOG). Update the
remaining stale remarks accordingly; the one caveat that still stands
is pulse width/edge timing, which hasn't been checked on a scope yet.
Also add the Teensy port to the README Contribution section, matching
the existing entries for other platform ports.
|
Thanks for the guidance! Pushed the requested README updates:
Also took a look at If it'd be useful down the line, a Teensy equivalent of the automated harness could work off GPIO edge-counting (e.g. an interrupt pin wired to a spare step output) instead of PCNT, but that's a separate, bigger effort - happy to look into it if you think it's worth it. |
|
Now that this (and #372) are merged into master, would you be open to cutting a new tagged release (version bump in library.properties + a git tag) when you get a chance? That's what makes a new version show up in the Arduino Library Manager / PlatformIO registry - right now master has the Teensy support but anyone installing via Library Manager is still only getting 1.3.3. No rush, just flagging it since master already looks release-ready. |
|
Released as 1.3.4 |
Status: tested on real Teensy 4.0 hardware - full 16-stepper capacity, all 4 QuadTimer modules, and the attached driver's full rated speed
Update: this has now actually been built and run on a real Teensy 4.0, driving steppers through a real driver board (step/dir/enable) and a real motor. Three real bugs turned up during bring-up and are fixed in this branch:
externdeclarations lived in the wrong header, after code that already needed them. This is what made the backend fail to build at all initially.dsb) in the step-timing ISR, matching a barrier luni64/TeensyStep4's own code has for the same reason - without it, the step rate would plateau and then hang as interrupt frequency increased during acceleration.log2_timer_freq_div_sqrt_of_2was computed with a copy-paste of the formula for a different constant right below it (squaring the tick frequency instead of dividing it by sqrt(2)). This made any move that starts or reverses from a genuine standstill compute a multi-billion-tick delay instead of a normal one - the stepper would "hang" for several minutes. It only showed up once testing moved past back-to-back moves (which never let the queue fully drain) to a sketch with a real idle gap between moves (examples/TeensyMoveAndPause, included here).setLinearAcceleration()happens to mask it completely, which is exactly why it wasn't caught by the first round of testing.Speed ceiling, measured, not guessed: using
examples/TeensyMaxSpeedFinder(sweepssetSpeedInHz(), each leg moving out and back by a distance long enough to actually reach and cruise at that speed, operator visually confirms via a marked shaft that no steps were lost) against a DM556 driver + stepper motor at 25000 pulses/rev microstepping, 30 V supply:All 16 steppers, all 4 QuadTimer modules, measured:
examples/TeensyMultiStepperTestruns the full claimed capacity - 16 steppers, 4 per module across all 4 QuadTimer modules (TMR1-TMR4) - each at a different speed/acceleration/travel distance so none are ever in lockstep. Left running continuously reversing direction: every one of the 16 axes, across all 4 modules, kept moving, reversing and reaching its target correctly the whole time - no cross-talk, no stuck state, on any channel or module. This is the first confirmation that modules 3 and 4 (not just the first two) work correctly under this backend, and that all 16 channels' combined interrupt load doesn't cause any of them to miss a deadline.Also added
examples/TeensyBringUp,examples/TeensyMoveAndPause,examples/TeensyEnableBlinkTest,examples/TeensyMaxSpeedFinderandexamples/TeensyMultiStepperTest- the sketches actually used for this bring-up, diagnosis and measurement, useful for anyone else validating this backend.Enable pin (ENA) control is independently confirmed working: a decisive test (
enableOutputs()/disableOutputs()toggled directly, LED wired straight to the pin, no driver board or auto-enable timing involved) shows the pin switching states exactly in sync with the calls. Also checked against the DM556 driver's official datasheet used for this testing: its ENA polarity (LOW = enabled) matches this backend's default.Still open / not yet checked:
_pd_initVars()is still an untested-by-default placeholder - real applications should measure their own ceiling withexamples/TeensyMaxSpeedFinderand setsetAbsoluteSpeedLimit()accordingly, same as done hereSummary
pd_teensybackend for the i.MX RT1062 (Teensy 4.0/4.1): up to 16 steppers via the chip's 4 QuadTimer (TMR) modules x 4 channels each.digitalWriteFast(), so any digital pin works (same flexibility ESP32 already has), instead of being limited to specific muxed pins.TICKS_PER_Sconstant, so it exercises the generic runtime path). Those fixes are duplicated in this branch so it stands alone; happy to rebase onto Fix static/extern linkage conflict for log2 timer-frequency variables #372 once/if that merges, whichever is easier for you.README.md/CHANGELOG.md/library.propertiesupdated, marked experimental.Test plan