esp_timer: add high resolution software timer API

This commit is contained in:
Ivan Grokhotkov 2017-08-08 04:21:19 +08:00
parent a66df0826e
commit 857a29872d
11 changed files with 1663 additions and 2 deletions

View file

@ -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

View file

@ -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) {

View 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();
}

View 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

View 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;
}

View 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();

View 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")));

View file

@ -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

View 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);
}

View 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
}