fix: wrap-safe micros() timing so the stepper doesn't freeze every ~71 min - #50
Open
ZMan88 wants to merge 1 commit into
Open
fix: wrap-safe micros() timing so the stepper doesn't freeze every ~71 min#50ZMan88 wants to merge 1 commit into
ZMan88 wants to merge 1 commit into
Conversation
…1 min The stepper stores micros() timestamps in time_t and computes timing deltas from them (last_calculation_ in Stepper::calculate_speed_, and last_step_ in TMC2209Stepper::loop). micros() is a uint32_t that overflows every 2^32 us ~= 71.6 min. In a signed/wider time_t the subtraction `now - last_step_` goes negative right after a wrap, so the `dt >= interval` step gate is never true and NO STEP pulses are emitted until micros() climbs back past the stored value — the motor freezes for up to ~71 min, then recovers on its own. The accel/decel ramp in calculate_speed_ is corrupted the same way. Fix by storing these timestamps as uint32_t (and taking the now argument as uint32_t), so the delta arithmetic wraps correctly. This mirrors core esphome/components/stepper, which uses uint32_t for exactly this reason. should_step_() already used a uint32_t local, but the STEP/DIR (PULSES_CONTROL) path in TMC2209Stepper::loop did not go through it. Reproduced on an ESP32-C6 (XIAO) driving a TMC2209 in UART/STEP-DIR mode: the command path ran correctly while the position counter stayed frozen, and a monitor caught the self-recovery at ~33 min into a wrap window.
ZMan88
added a commit
to ZMan88/esphome-window-opener
that referenced
this pull request
Jul 9, 2026
…ypes) Switch the vendored micros()-wrap fix from local defensive casts to the same type-level change submitted upstream as slimcdk/esphome-custom-components#50: store last_calculation_/last_step_ (and the now args) as uint32_t so the delta arithmetic is naturally wrap-safe, mirroring core esphome/components/stepper. The vendored tree is now identical to that PR, so when it merges we can drop the vendoring cleanly. Updates firmware/components/README.md and docs/build-log.md to link the PR. Compiles clean (ESP32-C6 image built). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ZMan88
added a commit
to ZMan88/esphome-window-opener
that referenced
this pull request
Jul 9, 2026
Now that the micros()-wrap fix lives on our fork (submitted upstream as slimcdk/esphome-custom-components#50), point external_components straight at it instead of carrying a local vendored copy: github://ZMan88/esphome-custom-components@bcfdbb4facdf379e7ca4deb0cd5b32bfc254f4da Pinned to the commit (not the branch) so the build is reproducible even after the branch is deleted post-merge. Removes firmware/components/. When #50 merges, repoint at the upstream merge commit. Config validates and compiles clean pulling the source from GitHub (ESP32-C6 image built). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a long-running device the stepper periodically stops moving — commands are accepted but no STEP pulses come out — and then recovers on its own after a while. It looks like a driver/UART glitch but it's a clock bug.
micros()is auint32_tthat overflows every 2³² µs ≈ 71.6 min. The stepper stores those timestamps intime_tand computes deltas from them:Stepper::calculate_speed_—float dt = (now - last_calculation_) * 1e-6f(accel/decel ramp)TMC2209Stepper::loop(PULSES_CONTROL / STEP-DIR) —time_t dt = now - last_step_(the step gate)Because
time_tis signed and wider than 32-bit, right after amicros()wrapnow < last_*_, so the delta goes negative. Thedt >= intervalgate is then never true and no STEP pulses are emitted untilmicros()climbs back up to the stored value — a freeze of up to ~71 min that then self-heals. The ramp math is corrupted the same way.should_step_()already used auint32_tlocal and is wrap-safe, but the STEP/DIR path inTMC2209Stepper::loopdoesn't go through it, so it never benefited.Fix
Store
last_calculation_/last_step_asuint32_tand take thenowargument asuint32_t, so the subtraction wraps correctly. This mirrors coreesphome/components/stepper, which usesuint32_tfor exactly this reason. Minimal, type-level change — no behavioural change except that the wrap no longer freezes stepping.Testing
esphome compile→ ESP32-C6 image created).Files changed:
stepper/stepper.{h,cpp},tmc2209/stepper/tmc2209_stepper.cpp.