From 857a29872da6c5c2d081ea9cb38ff05b7db3e008 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 8 Aug 2017 04:21:19 +0800 Subject: [PATCH] esp_timer: add high resolution software timer API --- components/esp32/Kconfig | 26 ++ components/esp32/cpu_start.c | 4 +- components/esp32/esp_timer.c | 449 +++++++++++++++++++++++++ components/esp32/esp_timer.h | 229 +++++++++++++ components/esp32/esp_timer_esp32.c | 241 +++++++++++++ components/esp32/esp_timer_impl.h | 68 ++++ components/esp32/ets_timer_legacy.c | 126 +++++++ components/esp32/include/esp_task.h | 2 + components/esp32/lib | 2 +- components/esp32/test/test_esp_timer.c | 324 ++++++++++++++++++ components/esp32/test/test_ets_timer.c | 194 +++++++++++ 11 files changed, 1663 insertions(+), 2 deletions(-) create mode 100644 components/esp32/esp_timer.c create mode 100644 components/esp32/esp_timer.h create mode 100644 components/esp32/esp_timer_esp32.c create mode 100644 components/esp32/esp_timer_impl.h create mode 100644 components/esp32/ets_timer_legacy.c create mode 100644 components/esp32/test/test_esp_timer.c create mode 100644 components/esp32/test/test_ets_timer.c diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index d7c297024..c52402eb4 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -179,6 +179,20 @@ config IPC_TASK_STACK_SIZE It can be shrunk if you are sure that you do not use any custom IPC functionality. +config TIMER_TASK_STACK_SIZE + int "High-resolution timer task stack size" + default 4096 + range 2048 65536 + help + Configure the stack size of esp_timer/ets_timer task. This task is used + to dispatch callbacks of timers created using ets_timer and esp_timer + APIs. If you are seing stack overflow errors in timer task, increase + this value. + + Note that this is not the same as FreeRTOS timer task. To configure + FreeRTOS timer task size, see "FreeRTOS timer task stack size" option + in "FreeRTOS" menu. + choice NEWLIB_STDOUT_LINE_ENDING prompt "Line ending for UART output" default NEWLIB_STDOUT_LINE_ENDING_CRLF @@ -598,6 +612,18 @@ config NO_BLOBS If enabled, this disables the linking of binary libraries in the application build. Note that after enabling this Wi-Fi/Bluetooth will not work. +config ESP_TIMER_PROFILING + bool "Enable esp_timer profiling features" + depends on MAKING_ESP_TIMER_A_PUBLIC_API + default n + help + If enabled, esp_timer_dump will dump information such as number of times + the timer was started, number of times the timer has triggered, and the + total time it took for the callback to run. + This option has some effect on timer performance and the amount of memory + used for timer storage, and should only be used for debugging/testing + purposes. + endmenu menu Wi-Fi diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index f0fba5d94..7fec14f3c 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -63,6 +63,7 @@ #include "esp_core_dump.h" #include "esp_app_trace.h" #include "esp_clk.h" +#include "esp_timer.h" #include "trax.h" #define STRINGIFY(s) STRINGIFY2(s) @@ -245,7 +246,6 @@ void start_cpu0_default(void) esp_brownout_init(); #endif rtc_gpio_force_hold_dis_all(); - esp_setup_time_syscalls(); esp_vfs_dev_uart_register(); esp_reent_init(_GLOBAL_REENT); #ifndef CONFIG_CONSOLE_UART_NONE @@ -258,6 +258,8 @@ void start_cpu0_default(void) _GLOBAL_REENT->_stdout = (FILE*) &__sf_fake_stdout; _GLOBAL_REENT->_stderr = (FILE*) &__sf_fake_stderr; #endif + esp_timer_init(); + esp_setup_time_syscalls(); #if CONFIG_ESP32_APPTRACE_ENABLE esp_err_t err = esp_apptrace_init(); if (err != ESP_OK) { diff --git a/components/esp32/esp_timer.c b/components/esp32/esp_timer.c new file mode 100644 index 000000000..75374c0f4 --- /dev/null +++ b/components/esp32/esp_timer.c @@ -0,0 +1,449 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "esp_types.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_timer.h" +#include "esp_task.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/xtensa_api.h" +#include "sdkconfig.h" + +#include "esp_timer_impl.h" + +#ifdef CONFIG_ESP_TIMER_PROFILING +#define WITH_PROFILING 1 +#endif + +#ifndef NDEBUG +// Enable built-in checks in queue.h in debug builds +#define INVARIANTS +#endif +#include "rom/queue.h" + +#define TIMER_EVENT_QUEUE_SIZE 16 + +struct esp_timer { + uint64_t alarm; + uint64_t period; + esp_timer_cb_t callback; + void* arg; +#if WITH_PROFILING + const char* name; + size_t times_triggered; + size_t times_armed; + uint64_t total_callback_run_time; +#endif // WITH_PROFILING + LIST_ENTRY(esp_timer) list_entry; +}; + +static bool is_initialized(); +static esp_err_t timer_insert(esp_timer_handle_t timer); +static esp_err_t timer_remove(esp_timer_handle_t timer); +static bool timer_armed(esp_timer_handle_t timer); +static void timer_list_lock(); +static void timer_list_unlock(); + +#if WITH_PROFILING +static void timer_insert_inactive(esp_timer_handle_t timer); +static void timer_remove_inactive(esp_timer_handle_t timer); +#endif // WITH_PROFILING + +static const char* TAG = "esp_timer"; + +// list of currently armed timers +static LIST_HEAD(esp_timer_list, esp_timer) s_timers = + LIST_HEAD_INITIALIZER(s_timers); +#if WITH_PROFILING +// list of unarmed timers, used only to be able to dump statistics about +// all the timers +static LIST_HEAD(esp_inactive_timer_list, esp_timer) s_inactive_timers = + LIST_HEAD_INITIALIZER(s_timers); +#endif +// task used to dispatch timer callbacks +static TaskHandle_t s_timer_task; +// counting semaphore used to notify the timer task from ISR +static SemaphoreHandle_t s_timer_semaphore; +// lock protecting s_timers and s_inactive_timers +static portMUX_TYPE s_timer_lock = portMUX_INITIALIZER_UNLOCKED; + + + +esp_err_t esp_timer_create(const esp_timer_create_args_t* args, + esp_timer_handle_t* out_handle) +{ + if (!is_initialized()) { + return ESP_ERR_INVALID_STATE; + } + if (args->callback == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_timer_handle_t result = (esp_timer_handle_t) calloc(1, sizeof(*result)); + if (result == NULL) { + return ESP_ERR_NO_MEM; + } + result->callback = args->callback; + result->arg = args->arg; +#if WITH_PROFILING + result->name = args->name; + timer_insert_inactive(result); +#endif + *out_handle = result; + return ESP_OK; +} + +esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us) +{ + if (!is_initialized() || timer_armed(timer)) { + return ESP_ERR_INVALID_STATE; + } + timer->alarm = esp_timer_get_time() + timeout_us; + timer->period = 0; +#if WITH_PROFILING + timer->times_armed++; +#endif + return timer_insert(timer); +} + +esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us) +{ + if (!is_initialized() || timer_armed(timer)) { + return ESP_ERR_INVALID_STATE; + } + period_us = MAX(period_us, esp_timer_impl_get_min_period_us()); + timer->alarm = esp_timer_get_time() + period_us; + timer->period = period_us; +#if WITH_PROFILING + timer->times_armed++; +#endif + return timer_insert(timer); +} + +esp_err_t esp_timer_stop(esp_timer_handle_t timer) +{ + if (!is_initialized() || !timer_armed(timer)) { + return ESP_ERR_INVALID_STATE; + } + return timer_remove(timer); +} + +esp_err_t esp_timer_delete(esp_timer_handle_t timer) +{ + if (timer_armed(timer)) { + return ESP_ERR_INVALID_STATE; + } +#if WITH_PROFILING + timer_remove_inactive(timer); +#endif + if (timer == NULL) { + return ESP_ERR_INVALID_ARG; + } + free(timer); + return ESP_OK; +} + +static esp_err_t timer_insert(esp_timer_handle_t timer) +{ + timer_list_lock(); +#if WITH_PROFILING + timer_remove_inactive(timer); +#endif + esp_timer_handle_t it, last = NULL; + if (LIST_FIRST(&s_timers) == NULL) { + LIST_INSERT_HEAD(&s_timers, timer, list_entry); + } else { + LIST_FOREACH(it, &s_timers, list_entry) { + if (timer->alarm < it->alarm) { + LIST_INSERT_BEFORE(it, timer, list_entry); + break; + } + last = it; + } + if (it == NULL) { + assert(last); + LIST_INSERT_AFTER(last, timer, list_entry); + } + } + if (timer == LIST_FIRST(&s_timers)) { + esp_timer_impl_set_alarm(timer->alarm); + } + timer_list_unlock(); + return ESP_OK; +} + +static esp_err_t timer_remove(esp_timer_handle_t timer) +{ + timer_list_lock(); + LIST_REMOVE(timer, list_entry); + timer->alarm = 0; + timer->period = 0; +#if WITH_PROFILING + timer_insert_inactive(timer); +#endif + timer_list_unlock(); + return ESP_OK; +} + +#if WITH_PROFILING + +static void timer_insert_inactive(esp_timer_handle_t timer) +{ + /* May be locked or not, depending on where this is called from. + * Lock recursively. + */ + timer_list_lock(); + esp_timer_handle_t head = LIST_FIRST(&s_inactive_timers); + if (head == NULL) { + LIST_INSERT_HEAD(&s_inactive_timers, timer, list_entry); + } else { + /* Insert as head element as this is the fastest thing to do. + * Removal is O(1) anyway. + */ + LIST_INSERT_BEFORE(head, timer, list_entry); + } + timer_list_unlock(); +} + +static void timer_remove_inactive(esp_timer_handle_t timer) +{ + timer_list_lock(); + LIST_REMOVE(timer, list_entry); + timer_list_unlock(); +} + +#endif // WITH_PROFILING + +static bool timer_armed(esp_timer_handle_t timer) +{ + return timer->alarm > 0; +} + +static void timer_list_lock() +{ + portENTER_CRITICAL(&s_timer_lock); +} + +static void timer_list_unlock() +{ + portEXIT_CRITICAL(&s_timer_lock); +} + +static void timer_process_alarm(esp_timer_dispatch_t dispatch_method) +{ + /* unused, provision to allow running callbacks from ISR */ + (void) dispatch_method; + + timer_list_lock(); + uint64_t now = esp_timer_impl_get_time(); + esp_timer_handle_t it = LIST_FIRST(&s_timers); + while (it != NULL && + it->alarm < now) { + LIST_REMOVE(it, list_entry); + if (it->period > 0) { + it->alarm += it->period; + timer_insert(it); + } else { + it->alarm = 0; +#if WITH_PROFILING + timer_insert_inactive(it); +#endif + } +#if WITH_PROFILING + uint64_t callback_start = now; +#endif + timer_list_unlock(); + (*it->callback)(it->arg); + timer_list_lock(); + now = esp_timer_impl_get_time(); +#if WITH_PROFILING + it->times_triggered++; + it->total_callback_run_time += now - callback_start; +#endif + it = LIST_FIRST(&s_timers); + } + esp_timer_handle_t first = LIST_FIRST(&s_timers); + if (first) { + esp_timer_impl_set_alarm(first->alarm); + } + timer_list_unlock(); +} + +static void timer_task(void* arg) +{ + while (true){ + int res = xSemaphoreTake(s_timer_semaphore, portMAX_DELAY); + assert(res == pdTRUE); + timer_process_alarm(ESP_TIMER_TASK); + } +} + +static void IRAM_ATTR timer_alarm_handler(void* arg) +{ + int need_yield; + if (xSemaphoreGiveFromISR(s_timer_semaphore, &need_yield) != pdPASS) { + ESP_EARLY_LOGD(TAG, "timer queue overflow"); + return; + } + if (need_yield == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +static bool is_initialized() +{ + return s_timer_task != NULL; +} + + +esp_err_t esp_timer_init(void) +{ + if (is_initialized()) { + return ESP_ERR_INVALID_STATE; + } + + s_timer_semaphore = xSemaphoreCreateCounting(TIMER_EVENT_QUEUE_SIZE, 0); + if (!s_timer_semaphore) { + return ESP_ERR_NO_MEM; + } + + int ret = xTaskCreatePinnedToCore(&timer_task, "esp_timer", + ESP_TASK_TIMER_STACK, NULL, ESP_TASK_TIMER_PRIO, &s_timer_task, PRO_CPU_NUM); + if (ret != pdPASS) { + vSemaphoreDelete(s_timer_semaphore); + s_timer_semaphore = NULL; + return ESP_ERR_NO_MEM; + } + + esp_err_t err = esp_timer_impl_init(&timer_alarm_handler); + if (err != ESP_OK) { + vTaskDelete(s_timer_task); + s_timer_task = NULL; + vSemaphoreDelete(s_timer_semaphore); + s_timer_semaphore = NULL; + return err; + } + + return ESP_OK; +} + +esp_err_t esp_timer_deinit(void) +{ + if (!is_initialized()) { + return ESP_ERR_INVALID_STATE; + } + + /* Check if there are any active timers */ + if (!LIST_EMPTY(&s_timers)) { + return ESP_ERR_INVALID_STATE; + } + + /* We can only check if there are any timers which are not deleted if + * profiling is enabled. + */ +#if WITH_PROFILING + if (!LIST_EMPTY(&s_inactive_timers)) { + return ESP_ERR_INVALID_STATE; + } +#endif + + esp_timer_impl_deinit(); + + vTaskDelete(s_timer_task); + s_timer_task = NULL; + vSemaphoreDelete(s_timer_semaphore); + s_timer_semaphore = NULL; + return ESP_OK; +} + +static void print_timer_info(esp_timer_handle_t t, char** dst, size_t* dst_size) +{ + size_t cb = snprintf(*dst, *dst_size, +#if WITH_PROFILING + "%-12s %12lld %12lld %9d %9d %12lld\n", + t->name, t->period, t->alarm, + t->times_armed, t->times_triggered, t->total_callback_run_time); + /* keep this in sync with the format string, used in esp_timer_dump */ +#define TIMER_INFO_LINE_LEN 78 +#else + "timer@%p %12lld %12lld\n", t, t->period, t->alarm); +#define TIMER_INFO_LINE_LEN 46 +#endif + *dst += cb; + *dst_size -= cb; +} + + +esp_err_t esp_timer_dump(FILE* stream) +{ + /* Since timer lock is a critical section, we don't want to print directly + * to stdout, since that may cause a deadlock if stdout is interrupt-driven + * (via the UART driver). Allocate sufficiently large chunk of memory first, + * print to it, then dump this memory to stdout. + */ + + esp_timer_handle_t it; + + /* First count the number of timers */ + size_t timer_count = 0; + timer_list_lock(); + LIST_FOREACH(it, &s_timers, list_entry) { + ++timer_count; + } +#if WITH_PROFILING + LIST_FOREACH(it, &s_inactive_timers, list_entry) { + ++timer_count; + } +#endif + timer_list_unlock(); + + /* Allocate the memory for this number of timers. Since we have unlocked, + * we may find that there are more timers. There's no bulletproof solution + * for this (can't allocate from a critical section), but we allocate + * slightly more and the output will be truncated if that is not enough. + */ + size_t buf_size = TIMER_INFO_LINE_LEN * (timer_count + 3); + char* print_buf = calloc(1, buf_size + 1); + if (print_buf == NULL) { + return ESP_ERR_NO_MEM; + } + + /* Print to the buffer */ + timer_list_lock(); + char* pos = print_buf; + LIST_FOREACH(it, &s_timers, list_entry) { + print_timer_info(it, &pos, &buf_size); + } +#if WITH_PROFILING + LIST_FOREACH(it, &s_inactive_timers, list_entry) { + print_timer_info(it, &pos, &buf_size); + } +#endif + timer_list_unlock(); + + /* Print the buffer */ + fputs(print_buf, stream); + + free(print_buf); + return ESP_OK; +} + +uint64_t IRAM_ATTR esp_timer_get_time() +{ + return esp_timer_impl_get_time(); +} diff --git a/components/esp32/esp_timer.h b/components/esp32/esp_timer.h new file mode 100644 index 000000000..e187cacd5 --- /dev/null +++ b/components/esp32/esp_timer.h @@ -0,0 +1,229 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/** + * @file esp_timer.h + * @brief microsecond-precision 64-bit timer API, replacement for ets_timer + * + * Not a public header yet. To be moved into include/ directory when it is made + * public. + * + * esp_timer APIs allow components to receive callbacks when a hardware timer + * reaches certain value. The timer provides microsecond accuracy and + * up to 64 bit range. Note that while the timer itself provides microsecond + * accuracy, callbacks are dispatched from an auxiliary task. Some time is + * needed to notify this task from timer ISR, and then to invoke the callback. + * If more than one callback needs to be dispatched at any particular time, + * each subsequent callback will be dispatched only when the previous callback + * returns. Therefore, callbacks should not do much work; instead, they should + * use RTOS notification mechanisms (queues, semaphores, event groups, etc.) to + * pass information to other tasks. + * + * It should be possible to request the callback to be called + * directly from the ISR. This reduces the latency, but has potential impact on + * all other callbacks which need to be dispatched. This option should only be + * used for simple callback functions, which do not take longer than a few + * microseconds to run. + * + * Implementation note: on the ESP32, esp_timer APIs use the "legacy" FRC2 + * timer. Timer callbacks are called from a task running on the PRO CPU. + */ + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Opaque type representing a single esp_timer + */ +typedef struct esp_timer* esp_timer_handle_t; + +/** + * @brief Timer callback function type + * @param arg pointer to opaque user-specific data + */ +typedef void (*esp_timer_cb_t)(void* arg); + + +/** + * @brief Method for dispatching timer callback + */ +typedef enum { + ESP_TIMER_TASK, //!< Callback is called from timer task + + /* Not supported for now, provision to allow callbacks to run directly + * from an ISR: + + ESP_TIMER_ISR, //!< Callback is called from timer ISR + + */ +} esp_timer_dispatch_t; + +/** + * @brief Timer configuration passed to esp_timer_create + */ +typedef struct { + esp_timer_cb_t callback; //!< Function to call when timer expires + void* arg; //!< Argument to pass to the callback + esp_timer_dispatch_t dispatch_method; //!< Call the callback from task or from ISR + const char* name; //!< Timer name, used in esp_timer_dump function +} esp_timer_create_args_t; + +/** + * @brief Initialize esp_timer library + * + * @note This function is called from startup code. Applications do not need + * to call this function before using other esp_timer APIs. + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if allocation has failed + * - ESP_ERR_INVALID_STATE if already initialized + * - other errors from interrupt allocator + */ +esp_err_t esp_timer_init(); + +/** + * @brief De-initialize esp_timer library + * + * @note Normally this function should not be called from applications + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if not yet initialized + */ +esp_err_t esp_timer_deinit(); + +/** + * @brief Create an esp_timer instance + * + * @note When done using the timer, delete it with esp_timer_delete function. + * + * @param create_args Pointer to a structure with timer creation arguments. + * Not saved by the library, can be allocated on the stack. + * @param[out] out_handle Output, pointer to esp_timer_handle_t variable which + * will hold the created timer handle. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if some of the create_args are not valid + * - ESP_ERR_INVALID_STATE if esp_timer library is not initialized yet + * - ESP_ERR_NO_MEM if memory allocation fails + */ +esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args, + esp_timer_handle_t* out_handle); + +/** + * @brief Start one-shot timer + * + * Timer should not be running when this function is called. + * + * @param timer timer handle created using esp_timer_create + * @param timeout_us timer timeout, in microseconds relative to the current moment + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the handle is invalid + * - ESP_ERR_INVALID_STATE if the timer is already running + */ +esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us); + +/** + * @brief Start a periodic timer + * + * Timer should not be running when this function is called. This function will + * start the timer which will trigger every 'period' microseconds. + * + * @param timer timer handle created using esp_timer_create + * @param period timer period, in microseconds + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the handle is invalid + * - ESP_ERR_INVALID_STATE if the timer is already running + */ +esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period); + +/** + * @brief Stop the timer + * + * This function stops the timer previously started using esp_timer_start_once + * or esp_timer_start_periodic. + * + * @param timer timer handle created using esp_timer_create + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the timer is not running + */ +esp_err_t esp_timer_stop(esp_timer_handle_t timer); + +/** + * @brief Delete an esp_timer instance + * + * The timer must be stopped before deleting. A one-shot timer which has expired + * does not need to be stopped. + * + * @param timer timer handle allocated using esp_timer_create + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the timer is not running + */ +esp_err_t esp_timer_delete(esp_timer_handle_t timer); + +/** + * @brief Get time in microseconds since boot + * @return number of microseconds since esp_timer_init was called (this normally + * happens early during application startup). + */ +uint64_t esp_timer_get_time(); + +/** + * @brief Dump the list of timers to a stream + * + * If CONFIG_ESP_TIMER_PROFILING option is enabled, this prints the list of all + * the existing timers. Otherwise, only the list active timers is printed. + * + * The format is: + * + * name period alarm times_armed times_triggered total_callback_run_time + * + * where: + * + * name — timer name (if CONFIG_ESP_TIMER_PROFILING is defined), or timer pointer + * period — period of timer, in microseconds, or 0 for one-shot timer + * alarm - time of the next alarm, in microseconds since boot, or 0 if the timer + * is not started + * + * The following fields are printed if CONFIG_ESP_TIMER_PROFILING is defined: + * + * times_armed — number of times the timer was armed via esp_timer_start_X + * times_triggered - number of times the callback was called + * total_callback_run_time - total time taken by callback to execute, across all calls + * + * @param stream stream (such as stdout) to dump the information to + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if can not allocate temporary buffer for the output + */ +esp_err_t esp_timer_dump(FILE* stream); + + +#ifdef __cplusplus +} +#endif + diff --git a/components/esp32/esp_timer_esp32.c b/components/esp32/esp_timer_esp32.c new file mode 100644 index 000000000..864167b2b --- /dev/null +++ b/components/esp32/esp_timer_esp32.c @@ -0,0 +1,241 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_err.h" +#include "esp_timer.h" +#include "esp_system.h" +#include "esp_task.h" +#include "esp_attr.h" +#include "esp_intr_alloc.h" +#include "esp_log.h" +#include "esp_timer_impl.h" +#include "soc/frc_timer_reg.h" +#include "soc/rtc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +/** + * @file esp_timer_esp32.c + * @brief Implementation of chip-specific part of esp_timer + * + * This implementation uses FRC2 (legacy) timer of the ESP32. This timer is + * a 32-bit up-counting timer, with a programmable compare value (called 'alarm' + * hereafter). When the timer reaches compare value, interrupt is raised. + * The timer can be configured to produce an edge or a level interrupt. + * + * In this implementation the timer is used for two purposes: + * 1. To generate interrupts at certain moments — the upper layer of esp_timer + * uses this to trigger callbacks of esp_timer objects. + * + * 2. To keep track of time relative to application start. This facility is + * used both by the upper layer of esp_timer and by time functions, such as + * gettimeofday. + * + * Whenever an esp_timer timer is armed (configured to fire once or + * periodically), timer_insert function of the upper layer calls + * esp_timer_impl_set_alarm to enable the interrupt at the required moment. + * This implementation sets up the timer interrupt to fire at the earliest of + * two moments: + * a) the time requested by upper layer + * b) the time when the timer count reaches 0xffffffff (i.e. is about to overflow) + * + * Whenever the interrupt fires and timer overflow is detected, interrupt hander + * increments s_time_base_us variable, which is used for timekeeping. + * + * When the interrupt fires, the upper layer is notified, and it dispatches + * the callbacks (if any timers have expired) and sets new alarm value (if any + * timers are still active). + * + * At any point in time, esp_timer_impl_get_time will return the current timer + * value (expressed in microseconds) plus s_time_base_us. To account for the + * case when the timer counter has overflown, but the interrupt has not fired + * yet (for example, because interupts are temporarily disabled), + * esp_timer_impl_get_time will also check timer overflow flag, and will add + * s_timer_us_per_overflow to the returned value. + * + */ + +/* Timer is clocked from APB. To allow for integer scaling factor between ticks + * and microseconds, divider 1 is used. 16 or 256 would not work for APB + * frequencies such as 40 or 26 or 2 MHz. + */ +#define TIMER_DIV 1 +#define TIMER_DIV_CFG FRC_TIMER_PRESCALER_1 + +/* ALARM_OVERFLOW_VAL is used as timer alarm value when there are not timers + * enabled which need to fire within the next timer overflow period. This alarm + * is used to perform timekeeping (i.e. to track timer overflows). + */ +#define ALARM_OVERFLOW_VAL UINT32_MAX + +static const char* TAG = "esp_timer_impl"; + +// Interrupt handle retuned by the interrupt allocator +static intr_handle_t s_timer_interrupt_handle; + +// Function from the upper layer to be called when the interrupt happens. +// Registered in esp_timer_impl_init. +static intr_handler_t s_alarm_handler; + +// Time in microseconds from startup to the moment +// when timer counter was last equal to 0. This variable is updated each time +// when timer overflows, and when APB frequency switch is performed. +static uint64_t s_time_base_us; + +// Number of timer ticks per microsecond. Calculated from APB frequency. +static uint32_t s_timer_ticks_per_us; + +// Period between timer overflows, in microseconds. +// Equal to 2^32 / s_timer_ticks_per_us. +static uint32_t s_timer_us_per_overflow; + +// When frequency switch happens, timer counter is reset to 0, s_time_base_us +// is updated, and alarm value is re-calculated based on the new APB frequency. +// However because the frequency switch can happen before the final +// interrupt handler is invoked, interrupt handler may see a different alarm +// value than the one which caused an interrupt. This can cause interrupt handler +// to consider that the interrupt has happened due to timer overflow, incrementing +// s_time_base_us. To avoid this, frequency switch hook sets this flag if +// it needs to set timer alarm value to ALARM_OVERFLOW_VAL. Interrupt hanler +// will not increment s_time_base_us if this flag is set. +static bool s_mask_overflow; + +// Spinlock used to protect access to static variables above and to the hardware +// registers. +portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED; + +// Check if timer overflow has happened (but was not handled by ISR yet) +static inline bool IRAM_ATTR timer_overflow_happened() +{ + return (REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) != 0 && + REG_READ(FRC_TIMER_ALARM_REG(1)) == ALARM_OVERFLOW_VAL && + !s_mask_overflow; +} + +uint64_t IRAM_ATTR esp_timer_impl_get_time() +{ + portENTER_CRITICAL(&s_time_update_lock); + uint32_t timer_val = REG_READ(FRC_TIMER_COUNT_REG(1)); + uint64_t result = s_time_base_us; + if (timer_overflow_happened()) { + result += s_timer_us_per_overflow; + } + uint32_t ticks_per_us = s_timer_ticks_per_us; + portEXIT_CRITICAL(&s_time_update_lock); + return result + timer_val / ticks_per_us; +} + +void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp) +{ + portENTER_CRITICAL(&s_time_update_lock); + // Alarm time relative to the moment when counter was 0 + uint64_t time_after_timebase_us = timestamp - s_time_base_us; + // Adjust current time if overflow has happened + bool overflow = timer_overflow_happened(); + if (overflow) { + assert(time_after_timebase_us > s_timer_us_per_overflow); + time_after_timebase_us -= s_timer_us_per_overflow; + } + // Calculate desired timer compare value (may exceed 2^32-1) + uint64_t compare_val = time_after_timebase_us * s_timer_ticks_per_us; + uint32_t alarm_reg_val = ALARM_OVERFLOW_VAL; + // Use calculated alarm value if it is less than 2^32-1 + if (compare_val < ALARM_OVERFLOW_VAL) { + uint64_t cur_count = REG_READ(FRC_TIMER_COUNT_REG(1)); + // If we by the time we update ALARM_REG, COUNT_REG value is higher, + // interrupt will not happen for another 2^32 timer ticks, so need to + // check if alarm value is too close in the future (e.g. <1 us away). + uint32_t offset = s_timer_ticks_per_us; + if (compare_val < cur_count + offset) { + compare_val = cur_count + offset; + if (compare_val > UINT32_MAX) { + compare_val = ALARM_OVERFLOW_VAL; + } + } + alarm_reg_val = (uint32_t) compare_val; + } + REG_WRITE(FRC_TIMER_ALARM_REG(1), alarm_reg_val); + portEXIT_CRITICAL(&s_time_update_lock); +} + +static void IRAM_ATTR timer_alarm_isr(void *arg) +{ + portENTER_CRITICAL(&s_time_update_lock); + // Timekeeping: adjust s_time_base_us if counter has passed ALARM_OVERFLOW_VAL + if (timer_overflow_happened()) { + s_time_base_us += s_timer_us_per_overflow; + } + s_mask_overflow = false; + // Clear interrupt status + REG_WRITE(FRC_TIMER_INT_REG(1), FRC_TIMER_INT_CLR); + // Set alarm to the next overflow moment. Later, upper layer function may + // call esp_timer_impl_set_alarm to change this to an earlier value. + REG_WRITE(FRC_TIMER_ALARM_REG(1), ALARM_OVERFLOW_VAL); + portEXIT_CRITICAL(&s_time_update_lock); + // Call the upper layer handler + (*s_alarm_handler)(arg); +} + + +esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler) +{ + s_alarm_handler = alarm_handler; + + esp_err_t err = esp_intr_alloc(ETS_TIMER2_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM, + &timer_alarm_isr, NULL, &s_timer_interrupt_handle); + + if (err != ESP_OK) { + ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err); + return err; + } + + uint32_t apb_freq = rtc_clk_apb_freq_get(); + s_timer_ticks_per_us = apb_freq / 1000000 / TIMER_DIV; + assert(s_timer_ticks_per_us > 0 + && apb_freq % TIMER_DIV == 0 + && "APB frequency does not result in a valid ticks_per_us value"); + s_timer_us_per_overflow = FRC_TIMER_LOAD_VALUE(1) / s_timer_ticks_per_us; + s_time_base_us = 0; + + REG_WRITE(FRC_TIMER_ALARM_REG(1), ALARM_OVERFLOW_VAL); + REG_WRITE(FRC_TIMER_LOAD_REG(1), 0); + REG_WRITE(FRC_TIMER_CTRL_REG(1), + TIMER_DIV_CFG | FRC_TIMER_ENABLE | FRC_TIMER_LEVEL_INT); + REG_WRITE(FRC_TIMER_INT_REG(1), FRC_TIMER_INT_CLR); + ESP_ERROR_CHECK( esp_intr_enable(s_timer_interrupt_handle) ); + + return ESP_OK; +} + +void esp_timer_impl_deinit() +{ + esp_intr_disable(s_timer_interrupt_handle); + + REG_WRITE(FRC_TIMER_CTRL_REG(1), 0); + REG_WRITE(FRC_TIMER_ALARM_REG(1), 0); + REG_WRITE(FRC_TIMER_LOAD_REG(1), 0); + + esp_intr_free(s_timer_interrupt_handle); + s_timer_interrupt_handle = NULL; +} + +// FIXME: This value is safe for 80MHz APB frequency. +// Should be modified to depend on clock frequency. + +uint64_t esp_timer_impl_get_min_period_us() +{ + return 50; +} diff --git a/components/esp32/esp_timer_impl.h b/components/esp32/esp_timer_impl.h new file mode 100644 index 000000000..f5d73f5a3 --- /dev/null +++ b/components/esp32/esp_timer_impl.h @@ -0,0 +1,68 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/** + * @file esp_timer_impl.h + * + * @brief Interface between common and platform-specific parts of esp_timer. + * + * The functions in this header file are implemented for each supported SoC. + * High level functions defined in esp_timer.c call the functions here to + * interact with the hardware. + */ + +#include +#include "esp_err.h" +#include "esp_intr_alloc.h" + +/** + * @brief Initialize platform specific layer of esp_timer + * @param alarm_handler function to call on timer interrupt + * @return ESP_OK, ESP_ERR_NO_MEM, or one of the errors from interrupt allocator + */ +esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler); + +/** + * @brief Deinitialize platform specific layer of esp_timer + */ +void esp_timer_impl_deinit(); + +/** + * @brief Set up the timer interrupt to fire at a particular time + * + * If the alarm time is too close in the future, implementation should set the + * alarm to the earliest time possible. + * + * @param timestamp time in microseconds when interrupt should fire (relative to + * boot time, i.e. as returned by esp_timer_impl_get_time) + */ +void esp_timer_impl_set_alarm(uint64_t timestamp); + +/** + * @brief Get time, in microseconds, since esp_timer_impl_init was called + * @return timestamp in microseconds + */ +uint64_t esp_timer_impl_get_time(); + +/** + * @brief Get minimal timer period, in microseconds + * Periods shorter than the one returned may not be possible to achieve due to + * interrupt latency and context switch time. Short period of periodic timer may + * cause the system to spend all the time servicing the interrupt and timer + * callback, preventing other tasks from running. + * @return minimal period of periodic timer, in microseconds + */ +uint64_t esp_timer_impl_get_min_period_us(); diff --git a/components/esp32/ets_timer_legacy.c b/components/esp32/ets_timer_legacy.c new file mode 100644 index 000000000..648b8ca55 --- /dev/null +++ b/components/esp32/ets_timer_legacy.c @@ -0,0 +1,126 @@ +// Copyright 2010-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* + * ets_timer module implements a set of legacy timer APIs which are + * used by the WiFi driver. This is done on top of the newer esp_timer APIs. + * Applications should not use ets_timer functions, as they may change without + * notice. + */ + +#include +#include "esp_types.h" +#include "esp_log.h" +#include "esp_attr.h" +#include "esp_intr_alloc.h" +#include "rom/ets_sys.h" +#include "soc/frc_timer_reg.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/xtensa_api.h" +#include "sdkconfig.h" +#include "esp_timer.h" +#include "esp_timer_impl.h" + +/* We abuse 'timer_arg' field of ETSTimer structure to hold a pointer to esp_timer */ +#define ESP_TIMER(p_ets_timer) ((esp_timer_handle_t) (p_ets_timer)->timer_arg) + +/* We abuse 'timer_expire' field of ETSTimer structure to hold a magic value + * signifying that the contents of the timer was zeroed out. + */ +#define TIMER_INITIALIZED_FIELD(p_ets_timer) ((p_ets_timer)->timer_expire) +#define TIMER_INITIALIZED_VAL 0x12121212 + +static bool timer_initialized(ETSTimer *ptimer) +{ + return TIMER_INITIALIZED_FIELD(ptimer) == TIMER_INITIALIZED_VAL; +} + +void ets_timer_setfn(ETSTimer *ptimer, ETSTimerFunc *pfunction, void *parg) +{ + if (!timer_initialized(ptimer)) { + memset(ptimer, 0, sizeof(*ptimer)); + TIMER_INITIALIZED_FIELD(ptimer) = TIMER_INITIALIZED_VAL; + } + + if (ESP_TIMER(ptimer) == NULL) { + const esp_timer_create_args_t create_args = { + .callback = pfunction, + .arg = parg, + .name = "ETSTimer", + .dispatch_method = ESP_TIMER_TASK + }; + + ESP_ERROR_CHECK( esp_timer_create(&create_args, (esp_timer_handle_t*)&(ptimer->timer_arg)) ); + } +} + + +void ets_timer_arm_us(ETSTimer *ptimer, uint32_t time_us, bool repeat_flag) +{ + assert(timer_initialized(ptimer)); + esp_timer_stop(ESP_TIMER(ptimer)); // no error check + if (!repeat_flag) { + ESP_ERROR_CHECK( esp_timer_start_once(ESP_TIMER(ptimer), time_us) ); + } else { + ESP_ERROR_CHECK( esp_timer_start_periodic(ESP_TIMER(ptimer), time_us) ); + } +} + +void ets_timer_arm(ETSTimer *ptimer, uint32_t time_ms, bool repeat_flag) +{ + uint64_t time_us = 1000LL * (uint64_t) time_ms; + assert(timer_initialized(ptimer)); + esp_timer_stop(ESP_TIMER(ptimer)); // no error check + if (!repeat_flag) { + ESP_ERROR_CHECK( esp_timer_start_once(ESP_TIMER(ptimer), time_us) ); + } else { + ESP_ERROR_CHECK( esp_timer_start_periodic(ESP_TIMER(ptimer), time_us) ); + } +} + +void ets_timer_done(ETSTimer *ptimer) +{ + if (timer_initialized(ptimer)) { + esp_timer_delete(ESP_TIMER(ptimer)); + ptimer->timer_arg = NULL; + TIMER_INITIALIZED_FIELD(ptimer) = 0; + } +} + +void ets_timer_disarm(ETSTimer *ptimer) +{ + if (timer_initialized(ptimer)) { + esp_timer_stop(ESP_TIMER(ptimer)); + } +} + + +void ets_timer_init(void) +{ + +} + +void ets_timer_deinit(void) +{ + +} + +void os_timer_setfn(ETSTimer *ptimer, ETSTimerFunc *pfunction, void *parg) __attribute__((alias("ets_timer_setfn"))); +void os_timer_disarm(ETSTimer *ptimer) __attribute__((alias("ets_timer_disarm"))); +void os_timer_arm_us(ETSTimer *ptimer,uint32_t u_seconds,bool repeat_flag) __attribute__((alias("ets_timer_arm_us"))); +void os_timer_arm(ETSTimer *ptimer,uint32_t milliseconds,bool repeat_flag) __attribute__((alias("ets_timer_arm"))); +void os_timer_done(ETSTimer *ptimer) __attribute__((alias("ets_timer_done"))); + diff --git a/components/esp32/include/esp_task.h b/components/esp32/include/esp_task.h index aa694d3aa..f90e14a0c 100644 --- a/components/esp32/include/esp_task.h +++ b/components/esp32/include/esp_task.h @@ -43,6 +43,8 @@ /* idf task */ +#define ESP_TASK_TIMER_PRIO (ESP_TASK_PRIO_MAX - 3) +#define ESP_TASK_TIMER_STACK CONFIG_TIMER_TASK_STACK_SIZE #define ESP_TASKD_EVENT_PRIO (ESP_TASK_PRIO_MAX - 5) #define ESP_TASKD_EVENT_STACK CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE #define ESP_TASK_TCPIP_PRIO (ESP_TASK_PRIO_MAX - 7) diff --git a/components/esp32/lib b/components/esp32/lib index bf540beef..64b0ff419 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit bf540beef3382f216375328052c1776ee9edfb44 +Subproject commit 64b0ff4199614a8a5066fe3a05d446acb52575e6 diff --git a/components/esp32/test/test_esp_timer.c b/components/esp32/test/test_esp_timer.c new file mode 100644 index 000000000..a5b23a51f --- /dev/null +++ b/components/esp32/test/test_esp_timer.c @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include "unity.h" +#include "../esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +TEST_CASE("esp_timer orders timers correctly", "[esp_timer]") +{ + void dummy_cb(void* arg) + { + } + + uint64_t timeouts[] = { 10000, 1000, 10000, 5000, 20000, 1000 }; + size_t indices[] = { 3, 0, 4, 2, 5, 1 }; + const size_t num_timers = sizeof(timeouts)/sizeof(timeouts[0]); + esp_timer_handle_t handles[num_timers]; + char* names[num_timers]; + for (size_t i = 0; i < num_timers; ++i) { + asprintf(&names[i], "timer%d", i); + esp_timer_create_args_t args = { + .callback = &dummy_cb, + .name = names[i] + }; + TEST_ESP_OK(esp_timer_create(&args, &handles[i])); + TEST_ESP_OK(esp_timer_start_once(handles[i], timeouts[i] * 100)); + } + char* stream_str[1024]; + FILE* stream = fmemopen(stream_str, sizeof(stream_str), "r+"); + TEST_ESP_OK(esp_timer_dump(stream)); + for (size_t i = 0; i < num_timers; ++i) { + TEST_ESP_OK(esp_timer_stop(handles[i])); + TEST_ESP_OK(esp_timer_delete(handles[i])); + free(names[i]); + } + fflush(stream); + fseek(stream, 0, SEEK_SET); + for (size_t i = 0; i < num_timers; ++i) { + char line[128]; + TEST_ASSERT_NOT_NULL(fgets(line, sizeof(line), stream)); +#if WITH_PROFILING + int timer_id; + sscanf(line, "timer%d", &timer_id); + TEST_ASSERT_EQUAL(indices[timer_id], i); +#else + intptr_t timer_ptr; + sscanf(line, "timer@0x%x", &timer_ptr); + for (size_t j = 0; j < num_timers; ++j) { + if (indices[j] == i) { + TEST_ASSERT_EQUAL_PTR(handles[j], timer_ptr); + break; + } + } +#endif + + } + fclose(stream); +} + + +TEST_CASE("esp_timer produces correct delay", "[esp_timer]") +{ + void timer_func(void* arg) + { + struct timeval* ptv = (struct timeval*) arg; + gettimeofday(ptv, NULL); + } + + volatile struct timeval tv_end = {0}; + esp_timer_handle_t timer1; + esp_timer_create_args_t args = { + .callback = &timer_func, + .arg = (struct timeval*) &tv_end, + .name = "timer1" + }; + TEST_ESP_OK(esp_timer_create(&args, &timer1)); + + const int delays_ms[] = {20, 100, 200, 250}; + const size_t delays_count = sizeof(delays_ms)/sizeof(delays_ms[0]); + + for (size_t i = 0; i < delays_count; ++i) { + tv_end = (struct timeval) {0}; + struct timeval tv_start; + gettimeofday(&tv_start, NULL); + + TEST_ESP_OK(esp_timer_start_once(timer1, delays_ms[i] * 1000)); + + vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS); + TEST_ASSERT(tv_end.tv_sec != 0 || tv_end.tv_usec != 0); + int32_t ms_diff = (tv_end.tv_sec - tv_start.tv_sec) * 1000 + + (tv_end.tv_usec - tv_start.tv_usec) / 1000; + printf("%d %d\n", delays_ms[i], ms_diff); + + TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff); + } + + TEST_ESP_OK( esp_timer_dump(stdout) ); + + esp_timer_delete(timer1); +} + +TEST_CASE("periodic ets_timer produces correct delays", "[esp_timer]") +{ + // no, we can't make this a const size_t (§6.7.5.2) +#define NUM_INTERVALS 16 + + typedef struct { + esp_timer_handle_t timer; + size_t cur_interval; + int intervals[NUM_INTERVALS]; + struct timeval tv_start; + } test_args_t; + + void timer_func(void* arg) + { + test_args_t* p_args = (test_args_t*) arg; + struct timeval tv_now; + gettimeofday(&tv_now, NULL); + int32_t ms_diff = (tv_now.tv_sec - p_args->tv_start.tv_sec) * 1000 + + (tv_now.tv_usec - p_args->tv_start.tv_usec) / 1000; + printf("timer #%d %dms\n", p_args->cur_interval, ms_diff); + p_args->intervals[p_args->cur_interval++] = ms_diff; + // Deliberately make timer handler run longer. + // We check that this doesn't affect the result. + ets_delay_us(10*1000); + if (p_args->cur_interval == NUM_INTERVALS) { + printf("done\n"); + TEST_ESP_OK(esp_timer_stop(p_args->timer)); + } + } + + const int delay_ms = 100; + test_args_t args = {0}; + esp_timer_handle_t timer1; + esp_timer_create_args_t create_args = { + .callback = &timer_func, + .arg = &args, + .name = "timer1" + }; + TEST_ESP_OK(esp_timer_create(&create_args, &timer1)); + + args.timer = timer1; + gettimeofday(&args.tv_start, NULL); + TEST_ESP_OK(esp_timer_start_periodic(timer1, delay_ms * 1000)); + + vTaskDelay(delay_ms * (NUM_INTERVALS + 1)); + + TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, args.cur_interval); + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * delay_ms, args.intervals[i]); + } + + TEST_ESP_OK( esp_timer_dump(stdout) ); + + TEST_ESP_OK( esp_timer_delete(timer1) ); +#undef NUM_INTERVALS +} + +TEST_CASE("multiple timers are ordered correctly", "[esp_timer]") +{ +#define N 5 + + typedef struct { + const int order[N * 3]; + size_t count; + } test_common_t; + + typedef struct { + int timer_index; + const int intervals[N]; + size_t intervals_count; + esp_timer_handle_t timer; + test_common_t* common; + bool pass; + SemaphoreHandle_t done; + struct timeval* tv_start; + } test_args_t; + + void timer_func(void* arg) + { + test_args_t* p_args = (test_args_t*) arg; + // check order + size_t count = p_args->common->count; + int expected_index = p_args->common->order[count]; + struct timeval tv_timer; + gettimeofday(&tv_timer, NULL); + int ms_since_start = (tv_timer.tv_sec - p_args->tv_start->tv_sec) * 1000 + + (tv_timer.tv_usec - p_args->tv_start->tv_usec) / 1000; + printf("Time %dms, at count %d, expected timer %d, got timer %d\n", + ms_since_start, count, expected_index, p_args->timer_index); + if (expected_index != p_args->timer_index) { + p_args->pass = false; + esp_timer_stop(p_args->timer); + xSemaphoreGive(p_args->done); + return; + } + p_args->common->count++; + if (++p_args->intervals_count == N) { + esp_timer_stop(p_args->timer); + xSemaphoreGive(p_args->done); + return; + } + int next_interval = p_args->intervals[p_args->intervals_count]; + printf("starting timer %d interval #%d, %d ms\n", + p_args->timer_index, p_args->intervals_count, next_interval); + esp_timer_start_once(p_args->timer, next_interval * 1000); + } + + + test_common_t common = { + .order = {1, 2, 3, 2, 1, 3, 1, 2, 1, 3, 2, 1, 3, 3, 2}, + .count = 0 + }; + + SemaphoreHandle_t done = xSemaphoreCreateCounting(3, 0); + + struct timeval tv_now; + gettimeofday(&tv_now, NULL); + + test_args_t args1 = { + .timer_index = 1, + .intervals = {10, 40, 20, 40, 30}, + .common = &common, + .pass = true, + .done = done, + .tv_start = &tv_now + }; + + test_args_t args2 = { + .timer_index = 2, + .intervals = {20, 20, 60, 30, 40}, + .common = &common, + .pass = true, + .done = done, + .tv_start = &tv_now + }; + + test_args_t args3 = { + .timer_index = 3, + .intervals = {30, 30, 60, 30, 10}, + .common = &common, + .pass = true, + .done = done, + .tv_start = &tv_now + }; + + + esp_timer_create_args_t create_args = { + .callback = &timer_func, + .arg = &args1, + .name = "1" + }; + TEST_ESP_OK(esp_timer_create(&create_args, &args1.timer)); + + create_args.name = "2"; + create_args.arg = &args2; + TEST_ESP_OK(esp_timer_create(&create_args, &args2.timer)); + + create_args.name = "3"; + create_args.arg = &args3; + TEST_ESP_OK(esp_timer_create(&create_args, &args3.timer)); + + esp_timer_start_once(args1.timer, args1.intervals[0] * 1000); + esp_timer_start_once(args2.timer, args2.intervals[0] * 1000); + esp_timer_start_once(args3.timer, args3.intervals[0] * 1000); + + for (int i = 0; i < 3; ++i) { + int result = xSemaphoreTake(done, 1000 / portTICK_PERIOD_MS); + TEST_ASSERT_TRUE(result == pdPASS); + } + + TEST_ASSERT_TRUE(args1.pass); + TEST_ASSERT_TRUE(args2.pass); + TEST_ASSERT_TRUE(args3.pass); + + TEST_ESP_OK( esp_timer_dump(stdout) ); + + TEST_ESP_OK( esp_timer_delete(args1.timer) ); + TEST_ESP_OK( esp_timer_delete(args2.timer) ); + TEST_ESP_OK( esp_timer_delete(args3.timer) ); + +#undef N +} + +/* Create two timers, start them around the same time, and search through + * timeout delta values to reproduce the case when timeouts occur close to + * each other, testing the "multiple timers triggered" code path in timer_process_alarm. + */ +TEST_CASE("esp_timer for very short intervals", "[esp_timer]") +{ + SemaphoreHandle_t semaphore = xSemaphoreCreateCounting(2, 0); + + void timer_func(void* arg) { + SemaphoreHandle_t done = (SemaphoreHandle_t) arg; + xSemaphoreGive(done); + printf("."); + } + + esp_timer_create_args_t timer_args = { + .callback = &timer_func, + .arg = (void*) semaphore, + .name = "foo" + }; + + esp_timer_handle_t timer1, timer2; + ESP_ERROR_CHECK( esp_timer_create(&timer_args, &timer1) ); + ESP_ERROR_CHECK( esp_timer_create(&timer_args, &timer2) ); + const int timeout_ms = 10; + for (int timeout_delta_us = -150; timeout_delta_us < 150; timeout_delta_us++) { + printf("delta=%d", timeout_delta_us); + ESP_ERROR_CHECK( esp_timer_start_once(timer1, timeout_ms * 1000) ); + ESP_ERROR_CHECK( esp_timer_start_once(timer2, timeout_ms * 1000 + timeout_delta_us) ); + TEST_ASSERT_EQUAL(pdPASS, xSemaphoreTake(semaphore, timeout_ms * 2)); + TEST_ASSERT_EQUAL(pdPASS, xSemaphoreTake(semaphore, timeout_ms * 2)); + printf("\n"); + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_timer_stop(timer1)); + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_timer_stop(timer2)); + } + + vSemaphoreDelete(semaphore); +} diff --git a/components/esp32/test/test_ets_timer.c b/components/esp32/test/test_ets_timer.c new file mode 100644 index 000000000..f2094388d --- /dev/null +++ b/components/esp32/test/test_ets_timer.c @@ -0,0 +1,194 @@ +#include +#include +#include +#include +#include "unity.h" +#include "rom/ets_sys.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +TEST_CASE("ets_timer produces correct delay", "[ets_timer]") +{ + void timer_func(void* arg) + { + struct timeval* ptv = (struct timeval*) arg; + gettimeofday(ptv, NULL); + } + + ETSTimer timer1 = {0}; + + const int delays_ms[] = {20, 100, 200, 250}; + const size_t delays_count = sizeof(delays_ms)/sizeof(delays_ms[0]); + + for (size_t i = 0; i < delays_count; ++i) { + struct timeval tv_end = {0}; + + ets_timer_setfn(&timer1, &timer_func, &tv_end); + struct timeval tv_start; + gettimeofday(&tv_start, NULL); + + ets_timer_arm(&timer1, delays_ms[i], false); + + vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS); + int32_t ms_diff = (tv_end.tv_sec - tv_start.tv_sec) * 1000 + + (tv_end.tv_usec - tv_start.tv_usec) / 1000; + printf("%d %d\n", delays_ms[i], ms_diff); + + TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff); + } + + ets_timer_disarm(&timer1); +} + +TEST_CASE("periodic ets_timer produces correct delays", "[ets_timer]") +{ + // no, we can't make this a const size_t (§6.7.5.2) +#define NUM_INTERVALS 16 + + typedef struct { + ETSTimer* timer; + size_t cur_interval; + int intervals[NUM_INTERVALS]; + struct timeval tv_start; + } test_args_t; + + void timer_func(void* arg) + { + test_args_t* p_args = (test_args_t*) arg; + struct timeval tv_now; + gettimeofday(&tv_now, NULL); + int32_t ms_diff = (tv_now.tv_sec - p_args->tv_start.tv_sec) * 1000 + + (tv_now.tv_usec - p_args->tv_start.tv_usec) / 1000; + printf("timer #%d %dms\n", p_args->cur_interval, ms_diff); + p_args->intervals[p_args->cur_interval++] = ms_diff; + // Deliberately make timer handler run longer. + // We check that this doesn't affect the result. + ets_delay_us(10*1000); + if (p_args->cur_interval == NUM_INTERVALS) { + printf("done\n"); + ets_timer_disarm(p_args->timer); + } + } + + const int delay_ms = 100; + ETSTimer timer1 = {0}; + test_args_t args = {0}; + + args.timer = &timer1; + gettimeofday(&args.tv_start, NULL); + ets_timer_setfn(&timer1, &timer_func, &args); + ets_timer_arm(&timer1, delay_ms, true); + vTaskDelay(delay_ms * (NUM_INTERVALS + 1)); + + TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, args.cur_interval); + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * delay_ms, args.intervals[i]); + } + +#undef NUM_INTERVALS +} + +TEST_CASE("multiple ETSTimers are ordered correctly", "[ets_timer]") +{ +#define N 5 + + typedef struct { + const int order[N * 3]; + size_t count; + } test_common_t; + + typedef struct { + int timer_index; + const int intervals[N]; + size_t intervals_count; + ETSTimer* timer; + test_common_t* common; + bool pass; + SemaphoreHandle_t done; + } test_args_t; + + void timer_func(void* arg) + { + test_args_t* p_args = (test_args_t*) arg; + // check order + size_t count = p_args->common->count; + int expected_index = p_args->common->order[count]; + printf("At count %d, expected timer %d, got timer %d\n", + count, expected_index, p_args->timer_index); + if (expected_index != p_args->timer_index) { + p_args->pass = false; + ets_timer_disarm(p_args->timer); + xSemaphoreGive(p_args->done); + return; + } + p_args->common->count++; + if (++p_args->intervals_count == N) { + ets_timer_disarm(p_args->timer); + xSemaphoreGive(p_args->done); + return; + } + int next_interval = p_args->intervals[p_args->intervals_count]; + printf("timer %d interval #%d, %d ms\n", + p_args->timer_index, p_args->intervals_count, next_interval); + ets_timer_arm(p_args->timer, next_interval, false); + } + + ETSTimer timer1; + ETSTimer timer2; + ETSTimer timer3; + + test_common_t common = { + .order = {1, 2, 3, 2, 1, 3, 1, 2, 1, 3, 2, 1, 3, 3, 2}, + .count = 0 + }; + + SemaphoreHandle_t done = xSemaphoreCreateCounting(3, 0); + + test_args_t args1 = { + .timer_index = 1, + .intervals = {10, 40, 20, 40, 30}, + .timer = &timer1, + .common = &common, + .pass = true, + .done = done + }; + + test_args_t args2 = { + .timer_index = 2, + .intervals = {20, 20, 60, 30, 40}, + .timer = &timer2, + .common = &common, + .pass = true, + .done = done + }; + + test_args_t args3 = { + .timer_index = 3, + .intervals = {30, 30, 60, 30, 10}, + .timer = &timer3, + .common = &common, + .pass = true, + .done = done + }; + + ets_timer_setfn(&timer1, &timer_func, &args1); + ets_timer_setfn(&timer2, &timer_func, &args2); + ets_timer_setfn(&timer3, &timer_func, &args3); + + ets_timer_arm(&timer1, args1.intervals[0], false); + ets_timer_arm(&timer2, args2.intervals[0], false); + ets_timer_arm(&timer3, args3.intervals[0], false); + + for (int i = 0; i < 3; ++i) { + int result = xSemaphoreTake(done, 180 / portTICK_PERIOD_MS); + TEST_ASSERT_TRUE(result == pdPASS); + } + + TEST_ASSERT_TRUE(args1.pass); + TEST_ASSERT_TRUE(args2.pass); + TEST_ASSERT_TRUE(args3.pass); + + +#undef N +}