Skip to content

Conversation

@robot-controller
Copy link
Contributor

@robot-controller robot-controller commented Oct 31, 2025

Description

The ESP Timer API is very useful for scheduling delayed and / or periodic timer. In some cases however, multiple timers should execute in a synchronized manner with respect to each other, with high precision in terms of relative timing between timers.

The current implementation only allows scheduling timers relative to the current moment of scheduling. This makes it more difficult to achieve precise relative timing between two or more timers, because the timer alarm time is defined partially by when the timer is scheduled.

In this PR, three new functions have been introduced to facilitate scheduling timers based on absolute time (as returned by esp_timer_get_time()): esp_timer_start_once_at(), esp_timer_start_periodic_at(), and esp_timer_restart_at(). They correspond to esp_timer_start_once(), esp_timer_start_periodic(), and esp_timer_restart(), respectively.

esp_timer_start_once_at() accepts a parameter alarm_us defining the timestamp at which the timer should fire instead of the relative timeout_us of esp_timer_start_once().

esp_timer_start_periodic_at() accepts a parameter first_alarm_us defining the timestamp at which the timer should fire for the first time in addition to the arguments of esp_timer_start_periodic().

esp_timer_restart_at() accepts a parameter alarm_us defining the timestamp at which the timer should fire next. Differently to esp_timer_restart(), that implies that the second parameter is unused if the timer to be restarted is a one-shot timer. For periodic timers, that parameter defines the period at which the timer will fire after alarm_us.

Testing

The added functions have been added to the esp_timer example to showcase its functionality. This example also served to test the new functions.


Checklist

Before submitting a Pull Request, please ensure the following:

  • 🚨 This PR does not introduce breaking changes.
  • All CI checks (GH Actions) pass.
  • Documentation is updated as needed.
  • Tests are updated or added as necessary.
  • Code is well-commented, especially in complex areas.
  • Git history is clean — commits are squashed to the minimum necessary.

Note

Adds absolute-timing support to ESP Timer via new APIs to start/restart at an absolute timestamp, with corresponding docs, example updates, and tests.

  • API (esp_timer):
    • Add esp_timer_start_once_at, esp_timer_start_periodic_at, esp_timer_restart_at for absolute-time scheduling.
    • Refactor existing start/restart paths to shared *_at_impl helpers; validate non-zero, future alarms.
  • Implementation:
    • Introduce internal helpers: esp_timer_start_once_at_impl, esp_timer_start_periodic_at_impl, esp_timer_restart_at_impl.
    • Update insertion/removal logic to set absolute alarm and handle new-period semantics.
  • Docs:
    • Document relative vs absolute timing and new APIs in docs/en/api-reference/system/esp_timer.rst.
  • Example (examples/system/esp_timer):
    • Expand example to create timed periodic and one-shot timers; demonstrate *_at APIs and esp_timer_restart_at.
    • Update README with new sample output and explanations.
  • Tests:
    • Extend pytest_esp_timer.py to assert absolute-timed callbacks and restart behavior; update expected logs.

Written by Cursor Bugbot for commit 80516c2. This will update automatically on new commits. Configure here.

@CLAassistant
Copy link

CLAassistant commented Oct 31, 2025

CLA assistant check
All committers have signed the CLA.

@github-actions
Copy link

github-actions bot commented Oct 31, 2025

Messages
📖 You might consider squashing your 5 commits (simplifying branch history).

👋 Hello robot-controller, we appreciate your contribution to this project!


📘 Please review the project's Contributions Guide for key guidelines on code, documentation, testing, and more.

🖊️ Please also make sure you have read and signed the Contributor License Agreement for this project.

Click to see more instructions ...


This automated output is generated by the PR linter DangerJS, which checks if your Pull Request meets the project's requirements and helps you fix potential issues.

DangerJS is triggered with each push event to a Pull Request and modify the contents of this comment.

Please consider the following:
- Danger mainly focuses on the PR structure and formatting and can't understand the meaning behind your code or changes.
- Danger is not a substitute for human code reviews; it's still important to request a code review from your colleagues.
- Addressing info messages (📖) is strongly recommended; they're less critical but valuable.
- To manually retry these Danger checks, please navigate to the Actions tab and re-run last Danger workflow.

Review and merge process you can expect ...


We do welcome contributions in the form of bug reports, feature requests and pull requests via this public GitHub repository.

This GitHub project is public mirror of our internal git repository

1. An internal issue has been created for the PR, we assign it to the relevant engineer.
2. They review the PR and either approve it or ask you for changes or clarifications.
3. Once the GitHub PR is approved, we synchronize it into our internal git repository.
4. In the internal git repository we do the final review, collect approvals from core owners and make sure all the automated tests are passing.
- At this point we may do some adjustments to the proposed change, or extend it by adding tests or documentation.
5. If the change is approved and passes the tests it is merged into the default branch.
5. On next sync from the internal git repository merged change will appear in this public GitHub repository.

Generated by 🚫 dangerJS against 80516c2

cursor[bot]

This comment was marked as outdated.

@github-actions github-actions bot changed the title Support absolute timings for ESP Timer (High Resolution Timer) Support absolute timings for ESP Timer (High Resolution Timer) (IDFGH-16718) Oct 31, 2025
@espressif-bot espressif-bot added the Status: Opened Issue is new label Oct 31, 2025
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

if (alarm_us != 0) {
timer->alarm = alarm_us;
}

Copy link

Choose a reason for hiding this comment

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

Bug: Deadlock in esp_timer_restart_at_impl with timer_lock

The esp_timer_restart_at_impl function causes a deadlock by calling timer_remove while already holding the non-recursive timer_list_lock. It also inconsistently ignores an alarm_us value of 0, preventing absolute alarm times at boot, unlike other _at timer functions.

Fix in Cursor Fix in Web

Copy link
Collaborator

@KonstantinKondrashov KonstantinKondrashov left a comment

Choose a reason for hiding this comment

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

LGTM!
It would be good to add a few notes in the doc (esp_timer.rst) and add some tests in pytest_esp_timer.py.

return ESP_ERR_INVALID_STATE;
}

return esp_timer_restart_at_impl(timer, period_us, alarm_us);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like we have to check alarm_us, != 0, and greater than the current time, and return an error.
For all 3 functions.

if (!is_initialized()) {
return ESP_ERR_INVALID_STATE;
}
return esp_timer_start_once_at_impl(timer, alarm_us);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add the check of alarm_us


esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_start_once_at(esp_timer_handle_t timer, uint64_t alarm_us)
{
if (timer == NULL || alarm_us <= esp_timer_impl_get_time() || alarm_us == 0) {
Copy link

Choose a reason for hiding this comment

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

Bug: Timer Race: Time Slips, Schedules Break.

Race condition in esp_timer_start_once_at: the validation alarm_us <= esp_timer_impl_get_time() occurs before acquiring the timer lock, but time can advance between this check and the actual timer insertion inside the critical section. This allows timers with alarm times in the past to be scheduled if sufficient time elapses between validation and insertion, potentially causing immediate or missed callbacks.

Fix in Cursor Fix in Web

esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_start_periodic_at(
esp_timer_handle_t timer, uint64_t period_us, uint64_t first_alarm_us)
{
if (timer == NULL || first_alarm_us <= esp_timer_impl_get_time() || first_alarm_us == 0) {
Copy link

Choose a reason for hiding this comment

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

Bug: Time Warp: Past Timers Get Scheduled

Race condition in esp_timer_start_periodic_at: the validation first_alarm_us <= esp_timer_impl_get_time() occurs before acquiring the timer lock, but time can advance between this check and the actual timer insertion inside the critical section. This allows timers with alarm times in the past to be scheduled if sufficient time elapses between validation and insertion, potentially causing immediate or missed callbacks.

Fix in Cursor Fix in Web


esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_restart_at(esp_timer_handle_t timer, uint64_t period_us, uint64_t alarm_us)
{
if (timer == NULL || alarm_us <= esp_timer_impl_get_time() || alarm_us == 0) {
Copy link

Choose a reason for hiding this comment

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

Bug: Time-Sensitive Race: Timers Schedule Unexpectedly

Race condition in esp_timer_restart_at: the validation alarm_us <= esp_timer_impl_get_time() occurs before acquiring the timer lock, but time can advance between this check and the actual timer insertion inside the critical section. This allows timers with alarm times in the past to be scheduled if sufficient time elapses between validation and insertion, potentially causing immediate or missed callbacks.

Fix in Cursor Fix in Web

@robot-controller
Copy link
Contributor Author

Thank you for the review! The new commits add the missing checks for alarm times as well as updates to the tests and the docs to reflect the new functionality.

@KonstantinKondrashov
Copy link
Collaborator

Thanks for the update. LGTM.

@KonstantinKondrashov
Copy link
Collaborator

sha=80516c24ac5edd659977e8836951a4b4e9ee4c05

@KonstantinKondrashov KonstantinKondrashov added the PR-Sync-Merge Pull request sync as merge commit label Nov 11, 2025
@espressif-bot espressif-bot added Status: Reviewing Issue is being reviewed and removed Status: Opened Issue is new labels Nov 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR-Sync-Merge Pull request sync as merge commit Status: Reviewing Issue is being reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants