diff --git a/esp_timer/.cproject b/esp_timer/.cproject new file mode 100644 index 0000000..08175ca --- /dev/null +++ b/esp_timer/.cproject @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/esp_timer/.gitignore b/esp_timer/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/esp_timer/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/esp_timer/.project b/esp_timer/.project new file mode 100644 index 0000000..2146423 --- /dev/null +++ b/esp_timer/.project @@ -0,0 +1,20 @@ + + + esp_timer + + + + + + org.eclipse.cdt.core.cBuilder + clean,full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + com.espressif.idf.core.idfNature + + diff --git a/esp_timer/CMakeLists.txt b/esp_timer/CMakeLists.txt new file mode 100644 index 0000000..b4cf7f1 --- /dev/null +++ b/esp_timer/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(esp_timer_example) diff --git a/esp_timer/README.md b/esp_timer/README.md new file mode 100644 index 0000000..6346949 --- /dev/null +++ b/esp_timer/README.md @@ -0,0 +1,159 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | + +# High Resolution Timer Example (`esp_timer`) + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The [High Resolution Timer (`esp_timer`)](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html) APIs allow an application to create multiple timers using a single hardware timer, and hides complexity associated with managing multiple timers, invoking callbacks, accounting for APB frequency changes (if dynamic frequency scaling is enabled), and maintaining correct time after light sleep. + +This example illustrates the usage of the [`esp_timer` API](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html#api-reference) to create one-shot and periodic software timers. + +The `esp_timer` API also provides the `esp_timer_get_time()` function which returns the time since boot in microseconds. This can be useful for fine-grained timing in tasks and ISRs thus is also demonstrated in this example. + +## How to use example + +### Hardware Required + +This example should be able to run on any commonly available ESP development board. + +### Configure the project + +``` +idf.py menuconfig +``` + +Under `Component config > Common ESP-related` are the following `esp_timer` related configurations + +* `High-resolution timer task stack size` can be increased if timer callbacks require a larger stack +* `Enable esp_timer profiling features` will cause `esp_timer_dump()` to include more information. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +The example should have the following log output: + +``` +... +I (294) example: Started timers, time since boot: 9662 us +periodic 500000 509644 1 0 0 0 +one-shot 0 5009654 1 0 0 0 +I (794) example: Periodic timer called, time since boot: 509694 us +I (1294) example: Periodic timer called, time since boot: 1009671 us +I (1794) example: Periodic timer called, time since boot: 1509671 us +I (2294) example: Periodic timer called, time since boot: 2009671 us +periodic 500000 2509644 1 4 0 542 +one-shot 0 5009654 1 0 0 0 +I (2794) example: Periodic timer called, time since boot: 2509671 us +I (3294) example: Periodic timer called, time since boot: 3009671 us +I (3794) example: Periodic timer called, time since boot: 3509671 us +I (4294) example: Periodic timer called, time since boot: 4009671 us +periodic 500000 4509644 1 8 0 1026 +one-shot 0 5009654 1 0 0 0 +I (4794) example: Periodic timer called, time since boot: 4509671 us +I (5294) example: Periodic timer called, time since boot: 5009669 us +I (5294) example: One-shot timer called, time since boot: 5009788 us +I (5294) example: Restarted periodic timer with 1s period, time since boot: 5012675 us +I (6294) example: Periodic timer called, time since boot: 6012692 us +periodic 1000000 7012666 2 11 0 1391 +one-shot 0 0 1 1 0 11472 +I (7294) example: Periodic timer called, time since boot: 7012692 us +I (8294) example: Periodic timer called, time since boot: 8012692 us +periodic 1000000 9012666 2 13 0 1639 +one-shot 0 0 1 1 0 11472 +I (9294) example: Periodic timer called, time since boot: 9012692 us +I (10294) example: Periodic timer called, time since boot: 10012692 us +I (10314) example: Entering light sleep for 0.5s, time since boot: 10024351 us +I (10314) example: Woke up from light sleep, time since boot: 10525143 us +... +``` + +## Example Breakdown + +### 1. Creating and starting timers + +The example starts by creating a periodic and a one shot timer using the `esp_timer_create()` function. Once created, the two timers are started using the `esp_timer_start_periodic()` and `esp_timer_start_once()` functions. + +``` +I (265) example: Starting timers, time since boot: 2479 us +``` + +### 2. Getting initial timer dump + +These two repeating lines are the output of `esp_timer_dump()` function. There is one line for each of the timers created. This function can be useful for debugging purposes. Note that such debugging information is available because the example sets `CONFIG_ESP_TIMER_PROFILING` option in sdkconfig. Without this option, less information will be available. See documentation of `esp_timer_dump()` in ESP-IDF programming guide for more details. + +``` +timer period next time times times times callback +name to fire started fired skipped run time (us) +------------------------------------------------------------------------------------------------- + +periodic 500000 502455 1 0 0 0 +one-shot 0 5002469 1 0 0 0 +``` + +### 3. Periodic timer keeps running at 500ms period: + +``` +I (765) example: Periodic timer called, time since boot: 502506 us +I (1265) example: Periodic timer called, time since boot: 1002478 us +I (1765) example: Periodic timer called, time since boot: 1502478 us +I (2265) example: Periodic timer called, time since boot: 2002478 us +periodic 500000 2502455 1 4 0 511 +one-shot 0 5002469 1 0 0 0 +I (2765) example: Periodic timer called, time since boot: 2502478 us +I (3265) example: Periodic timer called, time since boot: 3002478 us +I (3765) example: Periodic timer called, time since boot: 3502478 us +I (4265) example: Periodic timer called, time since boot: 4002478 us +periodic 500000 4502455 1 8 0 971 +one-shot 0 5002469 1 0 0 0 +I (4765) example: Periodic timer called, time since boot: 4502478 us +I (5265) example: Periodic timer called, time since boot: 5002476 us +``` + +### 4. One-shot timer runs + +The one-shot timer runs and changes the period of the periodic timer. Now the periodic timer runs with a period of 1 second: + +``` +I (5265) example: One-shot timer called, time since boot: 5002586 us +I (5265) example: Restarted periodic timer with 1s period, time since boot: 5005475 us +I (6265) example: Periodic timer called, time since boot: 6005492 us +periodic 1000000 7005469 2 11 0 1316 +one-shot 0 0 1 1 0 11474 +I (7265) example: Periodic timer called, time since boot: 7005492 us +I (8265) example: Periodic timer called, time since boot: 8005492 us +periodic 1000000 9005469 2 13 0 1550 +one-shot 0 0 1 1 0 11474 +I (9265) example: Periodic timer called, time since boot: 9005492 us +I (10265) example: Periodic timer called, time since boot: 10005492 us +``` + +### 5. Continuation through light sleep + +To illustrate that timekeeping continues correctly after light sleep, the example enters light sleep for 0.5 seconds. This sleep does not impact timer period, and the timer is executed 1 second after the previous iteration. Note that the timers can not execute during light sleep, since the CPU is not running at that time. Such timers would execute immediately after light sleep, and then continue running with their normal period. + +``` +I (10275) example: Entering light sleep for 0.5s, time since boot: 10011559 us +I (10275) example: Woke up from light sleep, time since boot: 10512007 us +I (10765) example: Periodic timer called, time since boot: 11005492 us +I (11765) example: Periodic timer called, time since boot: 12005492 us +``` + +### 6. Finally, timers are deleted. + +``` +I (12275) example: Stopped and deleted timers +``` \ No newline at end of file diff --git a/esp_timer/main/CMakeLists.txt b/esp_timer/main/CMakeLists.txt new file mode 100644 index 0000000..6e9e5b7 --- /dev/null +++ b/esp_timer/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "esp_timer_example_main.c" + INCLUDE_DIRS ".") diff --git a/esp_timer/main/esp_timer_example_main.c b/esp_timer/main/esp_timer_example_main.c new file mode 100644 index 0000000..6aa5093 --- /dev/null +++ b/esp_timer/main/esp_timer_example_main.c @@ -0,0 +1,102 @@ +/* esp_timer (high resolution timer) example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_sleep.h" +#include "sdkconfig.h" + +static void periodic_timer_callback(void* arg); +static void oneshot_timer_callback(void* arg); + +static const char* TAG = "example"; + +void app_main(void) +{ + /* Create two timers: + * 1. a periodic timer which will run every 0.5s, and print a message + * 2. a one-shot timer which will fire after 5s, and re-start periodic + * timer with period of 1s. + */ + + const esp_timer_create_args_t periodic_timer_args = { + .callback = &periodic_timer_callback, + /* name is optional, but may help identify the timer when debugging */ + .name = "periodic" + }; + + esp_timer_handle_t periodic_timer; + ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); + /* The timer has been created but is not running yet */ + + const esp_timer_create_args_t oneshot_timer_args = { + .callback = &oneshot_timer_callback, + /* argument specified here will be passed to timer callback function */ + .arg = (void*) periodic_timer, + .name = "one-shot" + }; + esp_timer_handle_t oneshot_timer; + ESP_ERROR_CHECK(esp_timer_create(&oneshot_timer_args, &oneshot_timer)); + + /* Start the timers */ + ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000)); + ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000)); + ESP_LOGI(TAG, "Started timers, time since boot: %lld us", esp_timer_get_time()); + + /* Print debugging information about timers to console every 2 seconds */ + for (int i = 0; i < 5; ++i) { + ESP_ERROR_CHECK(esp_timer_dump(stdout)); + usleep(2000000); + } + + /* Timekeeping continues in light sleep, and timers are scheduled + * correctly after light sleep. + */ + int64_t t1 = esp_timer_get_time(); + ESP_LOGI(TAG, "Entering light sleep for 0.5s, time since boot: %lld us", t1); + + ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(500000)); + esp_light_sleep_start(); + + int64_t t2 = esp_timer_get_time(); + ESP_LOGI(TAG, "Woke up from light sleep, time since boot: %lld us", t2); + + assert(llabs((t2 - t1) - 500000) < 1000); + + /* Let the timer run for a little bit more */ + usleep(2000000); + + /* Clean up and finish the example */ + ESP_ERROR_CHECK(esp_timer_stop(periodic_timer)); + ESP_ERROR_CHECK(esp_timer_delete(periodic_timer)); + ESP_ERROR_CHECK(esp_timer_delete(oneshot_timer)); + ESP_LOGI(TAG, "Stopped and deleted timers"); +} + +static void periodic_timer_callback(void* arg) +{ + int64_t time_since_boot = esp_timer_get_time(); + ESP_LOGI(TAG, "Periodic timer called, time since boot: %lld us", time_since_boot); +} + +static void oneshot_timer_callback(void* arg) +{ + int64_t time_since_boot = esp_timer_get_time(); + ESP_LOGI(TAG, "One-shot timer called, time since boot: %lld us", time_since_boot); + esp_timer_handle_t periodic_timer_handle = (esp_timer_handle_t) arg; + /* To start the timer which is running, need to stop it first */ + ESP_ERROR_CHECK(esp_timer_stop(periodic_timer_handle)); + ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer_handle, 1000000)); + time_since_boot = esp_timer_get_time(); + ESP_LOGI(TAG, "Restarted periodic timer with 1s period, time since boot: %lld us", + time_since_boot); +} diff --git a/esp_timer/pytest_esp_timer.py b/esp_timer/pytest_esp_timer.py new file mode 100644 index 0000000..74e5fe5 --- /dev/null +++ b/esp_timer/pytest_esp_timer.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import logging + +import pytest +from pytest_embedded import Dut + +STARTING_TIMERS_REGEX = r'Started timers, time since boot: (\d+) us' + +# name, period, next_alarm, times_started, times_fired, times_skipped, cb_exec_time +TIMER_DUMP_LINE_REGEX = r'([\w-]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)' + +PERIODIC_TIMER_REGEX = r'Periodic timer called, time since boot: (\d+) us' + +LIGHT_SLEEP_ENTER_REGEX = r'Entering light sleep for 0\.5s, time since boot: (\d+) us' +LIGHT_SLEEP_EXIT_REGEX = r'Woke up from light sleep, time since boot: (\d+) us' + +ONE_SHOT_REGEX = r'One\-shot timer called, time since boot: (\d+) us' + +RESTART_REGEX = r'Restarted periodic timer with 1s period, time since boot: (\d+) us' + +STOP_REGEX = r'Stopped and deleted timers' + +INITIAL_TIMER_PERIOD = 500000 +FINAL_TIMER_PERIOD = 1000000 +LIGHT_SLEEP_TIME = 500000 +ONE_SHOT_TIMER_PERIOD = 5000000 + + +@pytest.mark.supported_targets +@pytest.mark.temp_skip_ci(targets=['esp32c6', 'esp32h2'], reason='c6/h2 support TBD') +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'rtc', + ], + indirect=True +) +def test_esp_timer(dut: Dut) -> None: + + match = dut.expect(STARTING_TIMERS_REGEX) + start_time = int(match.group(1)) + logging.info('Start time: {} us'.format(start_time)) + + match = dut.expect(TIMER_DUMP_LINE_REGEX, timeout=2) + assert match.group(1).decode('utf8') == 'periodic' and int(match.group(2)) == INITIAL_TIMER_PERIOD + match = dut.expect(TIMER_DUMP_LINE_REGEX, timeout=2) + assert match.group(1).decode('utf8') == 'one-shot' and int(match.group(2)) == 0 + + for i in range(0, 5): + match = dut.expect(PERIODIC_TIMER_REGEX, timeout=2) + cur_time = int(match.group(1)) + diff = start_time + (i + 1) * INITIAL_TIMER_PERIOD - cur_time + logging.info('Callback #{}, time: {} us, diff: {} us'.format(i, cur_time, diff)) + assert abs(diff) < 100 + + match = dut.expect(ONE_SHOT_REGEX, timeout=3) + one_shot_timer_time = int(match.group(1)) + diff = start_time + ONE_SHOT_TIMER_PERIOD - one_shot_timer_time + logging.info('One-shot timer, time: {} us, diff: {}'.format(one_shot_timer_time, diff)) + assert abs(diff) < 350 + + match = dut.expect(RESTART_REGEX, timeout=3) + start_time = int(match.group(1)) + logging.info('Timer restarted, time: {} us'.format(start_time)) + + for i in range(0, 5): + match = dut.expect(PERIODIC_TIMER_REGEX, timeout=2) + cur_time = int(match.group(1)) + diff = start_time + (i + 1) * FINAL_TIMER_PERIOD - cur_time + logging.info('Callback #{}, time: {} us, diff: {} us'.format(i, cur_time, diff)) + assert abs(diff) < 100 + + match = dut.expect(LIGHT_SLEEP_ENTER_REGEX, timeout=2) + sleep_enter_time = int(match.group(1)) + match = dut.expect(LIGHT_SLEEP_EXIT_REGEX, timeout=2) + sleep_exit_time = int(match.group(1)) + sleep_time = sleep_exit_time - sleep_enter_time + + logging.info('Enter sleep: {}, exit sleep: {}, slept: {}'.format( + sleep_enter_time, sleep_exit_time, sleep_time)) + + assert abs(sleep_time - LIGHT_SLEEP_TIME) < 1000 + + for i in range(5, 7): + match = dut.expect(PERIODIC_TIMER_REGEX, timeout=2) + cur_time = int(match.group(1)) + diff = abs(start_time + (i + 1) * FINAL_TIMER_PERIOD - cur_time) + logging.info('Callback #{}, time: {} us, diff: {} us'.format(i, cur_time, diff)) + assert diff < 100 + + dut.expect(STOP_REGEX, timeout=2) diff --git a/esp_timer/sdkconfig.ci b/esp_timer/sdkconfig.ci new file mode 100644 index 0000000..b161456 --- /dev/null +++ b/esp_timer/sdkconfig.ci @@ -0,0 +1,3 @@ +# For ESP32-S3 and ESP32-C3, keep secondary console disabled +# This is to avoid any timing impact on test behavior +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y diff --git a/esp_timer/sdkconfig.ci.rtc b/esp_timer/sdkconfig.ci.rtc new file mode 100644 index 0000000..5ccfc53 --- /dev/null +++ b/esp_timer/sdkconfig.ci.rtc @@ -0,0 +1,5 @@ +# For ESP32-S3 and ESP32-C3, keep secondary console disabled +# This is to avoid any timing impact on test behavior +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y + +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC=y diff --git a/esp_timer/sdkconfig.defaults b/esp_timer/sdkconfig.defaults new file mode 100644 index 0000000..3cf720d --- /dev/null +++ b/esp_timer/sdkconfig.defaults @@ -0,0 +1,5 @@ +CONFIG_ESP_TIMER_PROFILING=y + +# NEWLIB_NANO_FORMAT is enabled by default on ESP32-C2 +# This example needs 64-bit integer formatting, this is why this option is disabled +CONFIG_NEWLIB_NANO_FORMAT=n