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
|
It can be shrunk if you are sure that you do not use any custom
|
||||||
IPC functionality.
|
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
|
choice NEWLIB_STDOUT_LINE_ENDING
|
||||||
prompt "Line ending for UART output"
|
prompt "Line ending for UART output"
|
||||||
default NEWLIB_STDOUT_LINE_ENDING_CRLF
|
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
|
If enabled, this disables the linking of binary libraries in the application build. Note
|
||||||
that after enabling this Wi-Fi/Bluetooth will not work.
|
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
|
endmenu
|
||||||
|
|
||||||
menu Wi-Fi
|
menu Wi-Fi
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
#include "esp_core_dump.h"
|
#include "esp_core_dump.h"
|
||||||
#include "esp_app_trace.h"
|
#include "esp_app_trace.h"
|
||||||
#include "esp_clk.h"
|
#include "esp_clk.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
#include "trax.h"
|
#include "trax.h"
|
||||||
|
|
||||||
#define STRINGIFY(s) STRINGIFY2(s)
|
#define STRINGIFY(s) STRINGIFY2(s)
|
||||||
|
@ -245,7 +246,6 @@ void start_cpu0_default(void)
|
||||||
esp_brownout_init();
|
esp_brownout_init();
|
||||||
#endif
|
#endif
|
||||||
rtc_gpio_force_hold_dis_all();
|
rtc_gpio_force_hold_dis_all();
|
||||||
esp_setup_time_syscalls();
|
|
||||||
esp_vfs_dev_uart_register();
|
esp_vfs_dev_uart_register();
|
||||||
esp_reent_init(_GLOBAL_REENT);
|
esp_reent_init(_GLOBAL_REENT);
|
||||||
#ifndef CONFIG_CONSOLE_UART_NONE
|
#ifndef CONFIG_CONSOLE_UART_NONE
|
||||||
|
@ -258,6 +258,8 @@ void start_cpu0_default(void)
|
||||||
_GLOBAL_REENT->_stdout = (FILE*) &__sf_fake_stdout;
|
_GLOBAL_REENT->_stdout = (FILE*) &__sf_fake_stdout;
|
||||||
_GLOBAL_REENT->_stderr = (FILE*) &__sf_fake_stderr;
|
_GLOBAL_REENT->_stderr = (FILE*) &__sf_fake_stderr;
|
||||||
#endif
|
#endif
|
||||||
|
esp_timer_init();
|
||||||
|
esp_setup_time_syscalls();
|
||||||
#if CONFIG_ESP32_APPTRACE_ENABLE
|
#if CONFIG_ESP32_APPTRACE_ENABLE
|
||||||
esp_err_t err = esp_apptrace_init();
|
esp_err_t err = esp_apptrace_init();
|
||||||
if (err != ESP_OK) {
|
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 */
|
/* 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_PRIO (ESP_TASK_PRIO_MAX - 5)
|
||||||
#define ESP_TASKD_EVENT_STACK CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE
|
#define ESP_TASKD_EVENT_STACK CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE
|
||||||
#define ESP_TASK_TCPIP_PRIO (ESP_TASK_PRIO_MAX - 7)
|
#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