Skip to content

Fix static/extern linkage conflict for log2 timer-frequency variables - #372

Merged
gin66 merged 1 commit into
gin66:masterfrom
ARDUTECH0:fix/log2-timer-freq-linkage
Sep 21, 2026
Merged

gin66 merged 1 commit into
gin66:masterfrom
ARDUTECH0:fix/log2-timer-freq-linkage

Conversation

@ARDUTECH0

@ARDUTECH0 ARDUTECH0 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Two real, related defects in the SUPPORT_LOG2_TIMER_FREQ_VARIABLES fallback path (RampCalculator.h's #else branch, used whenever a platform's TICKS_PER_S isn't exactly 16000000 or 21000000). Every shipped pd_config.h currently uses one of those two values, so this path has never actually been exercised until a Teensy 4.x backend (in a sibling branch, PR #374) needed it for its 9.375 MHz tick rate - both bugs were found there and are fixed here too, standalone.

  1. Linkage: RampControl.cpp declares log2_timer_freq (and its two derived values) static, while RampGenerator.h declares the same names extern for use from RampGenerator.cpp, a different translation unit. A static redeclaration cannot follow a non-static/extern one in the same TU - ill-formed C++, fails to build the moment this path is active.
  2. Header ordering (found after fixing Moving a set distance or angle. #1 and hitting a different compile error): those extern declarations lived in RampGenerator.h, included after RampCalculator.h - but RampCalculator.h's own inline methods use them as soon as that header is parsed, before RampGenerator.h's declarations are ever seen. Moved into RampCalculator.h itself.
  3. Copy-paste bug (found after Moving a set distance or angle. #1 and Stop and SetCurrentPosition methods #2 let it actually build and run on real hardware): log2_timer_freq_div_sqrt_of_2 was computed with log2_multiply(log2_timer_freq, log2_timer_freq) - the same formula as log2_timer_freq_square_div_2 one line below, squaring the frequency instead of dividing it by sqrt(2). This produced absurdly large (multi-billion-tick) results from calculate_ticks()/calculate_ramp_steps()'s non-cubic branch, observed on hardware as the stepper "hanging" for minutes on every ramp start/reversal from standstill. setLinearAcceleration() masks it completely (steps below s_h route through the separate, unaffected cubic branch), which is why a real motor moving under test still exposed this only intermittently depending on configuration.

None of this changes behavior for any currently shipped platform (all use TICKS_PER_S of 16 MHz or 21 MHz, so SUPPORT_LOG2_TIMER_FREQ_VARIABLES is never defined for them today) - but it would affect, for example, an AVR board not running at 16/21 MHz (TICKS_PER_S there is F_CPU), and is required for the Teensy 4.x work in #374.

Test plan

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.
@ARDUTECH0
ARDUTECH0 force-pushed the fix/log2-timer-freq-linkage branch from f1b19d9 to 845d107 Compare September 21, 2026 10:44
ARDUTECH0 added a commit to ARDUTECH0/FastAccelStepper that referenced this pull request Sep 21, 2026
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.
@gin66
gin66 merged commit 69918f9 into gin66:master Sep 21, 2026
117 checks passed
ARDUTECH0 added a commit to ARDUTECH0/FastAccelStepper that referenced this pull request Sep 22, 2026
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.
ARDUTECH0 added a commit to ARDUTECH0/FastAccelStepper that referenced this pull request Sep 22, 2026
…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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants