Skip to content

fix: use uint32_t for micros() timestamps to prevent 72-minute overflow#42

Open
amasolov wants to merge 1 commit intoslimcdk:masterfrom
amasolov:fix/stepper-micros-overflow
Open

fix: use uint32_t for micros() timestamps to prevent 72-minute overflow#42
amasolov wants to merge 1 commit intoslimcdk:masterfrom
amasolov:fix/stepper-micros-overflow

Conversation

@amasolov
Copy link
Copy Markdown

Summary

Fixes #38 — stepper stops responding permanently after ~71.6 minutes (2³² µs).

Root cause: micros() returns uint32_t which wraps at 2³² µs. The stepper base class stored these values in time_t (int64_t on ESP-IDF v5+). After the wrap, now - last yields a massive negative delta (~-4.29 billion) instead of the correct small positive one, because signed subtraction doesn't wrap like unsigned.

In PULSES_CONTROL mode this is fatal: the negative dt never satisfies the step-interval check, last_step_ is never updated (it's only set inside the if-block), and the motor stops permanently — not just for one cycle.

Bugs fixed:

  1. stepper.hlast_calculation_ and last_step_ changed from time_t to uint32_t
  2. stepper.cppcalculate_speed_() and should_step_() parameter types changed to uint32_t; explicit (uint32_t) cast on delta to ensure unsigned wraparound
  3. tmc2209_stepper.cpploop() uses uint32_t for all timing; added guard against vactual_ == 0 to prevent 1/0.0f = +inf making the step condition permanently false
  4. tmc2300_stepper.cpp — identical fix applied (same bug pattern)

Why uint32_t fixes it

With unsigned 32-bit arithmetic, subtraction naturally handles rollover:

(uint32_t)(0x00000500 - 0xFFFFF000) == 0x00001500  // correct: 5376 µs elapsed

With signed 64-bit (time_t):

(int64_t)(0x00000500 - 0xFFFFF000) == -4294962944  // wrong: negative delta

Status

🚧 Work in progress — compiles cleanly but not yet tested on hardware. Looking for someone with a long-running stepper setup to validate the fix survives past the 72-minute mark.

Test plan

  • Verify compilation with ESP-IDF and Arduino frameworks
  • Run stepper in PULSES_CONTROL mode for >75 minutes — confirm no stall
  • Run stepper in SERIAL_CONTROL mode for >75 minutes — confirm no stall
  • Verify normal operation (acceleration, deceleration, direction changes) is unaffected

Made with Cursor

micros() returns uint32_t which wraps at 2^32 µs (~71.6 min). The
stepper base class stored these values in time_t (int64_t on ESP-IDF
v5+). After the wrap, (now - last) yields a massive negative delta
instead of the correct small positive one, because signed subtraction
does not wrap like unsigned.

In PULSES_CONTROL mode this is fatal: the negative dt never satisfies
the step-interval check, last_step_ is never updated, and the motor
stops permanently.

Changes:
- stepper.h: last_calculation_ and last_step_ → uint32_t
- stepper.cpp: calculate_speed_() and should_step_() parameter → uint32_t
- tmc2209_stepper.cpp: loop() now uses uint32_t for timing; guard
  against vactual_ == 0 to avoid division by zero (+inf)
- tmc2300_stepper.cpp: identical fix for the TMC2300 variant

Fixes slimcdk#38

Made-with: Cursor
Copy link
Copy Markdown

@EvoSems EvoSems left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having similar issues with high dose steps

@slimcdk
Copy link
Copy Markdown
Owner

slimcdk commented Mar 27, 2026

Thank you very much for looking into this, @amasolov. I haven't had the time to review this yet and may not for the next couple of weeks. Considering the PR number, it may be the answer (to everything).

If anyone wants to test this PR, it is possible to reference it in your ESPHome config by adding @pull/42/head to the source url.

external_components:
  - source: github://slimcdk/esphome-custom-components@pull/42/head
    components: [tmc2209_hub, tmc2209, stepper]
    refresh: 0s

@EvoSems can you clarify on what you mean? Do you experience those issues from current master branch or from this fix?

@amasolov
Copy link
Copy Markdown
Author

Hardware tested: Full open/close cycle executed successfully at 93 minutes uptime (past the 2^32 µs rollover). Motor responded normally to move_to_position commands. Tested on ESP32 rev3.1 with TMC2209 in PULSES_CONTROL mode (StealthChop, 4 microsteps, 200 steps/s).

@amasolov
Copy link
Copy Markdown
Author

Sorry it took so long to get it tested. Had to disassemble several things to do so.

@amasolov
Copy link
Copy Markdown
Author

It's still working for me after ~18 hours.

@EvoSems
Copy link
Copy Markdown

EvoSems commented Apr 1, 2026

Thank you very much for looking into this, @amasolov. I haven't had the time to review this yet and may not for the next couple of weeks. Considering the PR number, it may be the answer (to everything).

If anyone wants to test this PR, it is possible to reference it in your ESPHome config by adding @pull/42/head to the source url.

external_components:
  - source: github://slimcdk/esphome-custom-components@pull/42/head
    components: [tmc2209_hub, tmc2209, stepper]
    refresh: 0s

@EvoSems can you clarify on what you mean? Do you experience those issues from current master branch or from this fix?

Hi mate. sorry I have been away from home.
I have gone over my code and didn't realise I hade a safety timeout that I didn't see. I have no time to test at the Moment if this is my fix, but I am pretty sure that was my issue.

@jkbco
Copy link
Copy Markdown

jkbco commented Apr 4, 2026

Thanks a lot for this! I can confirm that running with the fix, it's been working fine for more than a week without issues. I can confirm that both IDF and Arduino compilation, pulse and serial, as well as normal operation works.

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.

Stops responding after around 1460000 steps

4 participants