esp_timer: add high resolution software timer API
This commit is contained in:
parent
a66df0826e
commit
857a29872d
11 changed files with 1663 additions and 2 deletions
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
449
components/esp32/esp_timer.c
Normal file
449
components/esp32/esp_timer.c
Normal file
|
@ -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 <sys/param.h>
|
||||
#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();
|
||||
}
|
229
components/esp32/esp_timer.h
Normal file
229
components/esp32/esp_timer.h
Normal file
|
@ -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.
|
||||
*
|
||||
* <to be implemented> 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. </to be implemented>
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#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
|
||||
|
241
components/esp32/esp_timer_esp32.c
Normal file
241
components/esp32/esp_timer_esp32.c
Normal file
|
@ -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;
|
||||
}
|
68
components/esp32/esp_timer_impl.h
Normal file
68
components/esp32/esp_timer_impl.h
Normal file
|
@ -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 <stdint.h>
|
||||
#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();
|
126
components/esp32/ets_timer_legacy.c
Normal file
126
components/esp32/ets_timer_legacy.c
Normal file
|
@ -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 <string.h>
|
||||
#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")));
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bf540beef3382f216375328052c1776ee9edfb44
|
||||
Subproject commit 64b0ff4199614a8a5066fe3a05d446acb52575e6
|
324
components/esp32/test/test_esp_timer.c
Normal file
324
components/esp32/test/test_esp_timer.c
Normal file
|
@ -0,0 +1,324 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#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);
|
||||
}
|
194
components/esp32/test/test_ets_timer.c
Normal file
194
components/esp32/test/test_ets_timer.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#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
|
||||
}
|
Loading…
Reference in a new issue