esp32: add implementation of esp_timer based on TG0 LAC timer

Closes: IDF-979
This commit is contained in:
Konstantin Kondrashov
2020-02-06 14:00:18 +08:00
committed by Angus Gratton
parent 67b0a79167
commit 739eb05bb9
63 changed files with 1261 additions and 504 deletions
+3 -2
View File
@@ -19,7 +19,6 @@ else()
"crosscore_int.c"
"dport_access.c"
"dport_panic_highint_hdl.S"
"esp_timer_esp32.c"
"esp_himem.c"
"hw_random.c"
"int_wdt.c"
@@ -33,14 +32,16 @@ else()
"spiram_psram.c"
"system_api_esp32.c"
"task_wdt.c")
set(include_dirs "include")
set(requires driver efuse soc) #unfortunately rom/uart uses SOC registers directly
# driver is a public requirement because esp_sleep.h uses gpio_num_t & touch_pad_t
# app_update is added here because cpu_start.c uses esp_ota_get_app_description() function.
# esp_timer is added here because cpu_start.c uses esp_timer
set(priv_requires app_trace app_update bootloader_support log mbedtls nvs_flash pthread
spi_flash vfs espcoredump esp_common perfmon)
spi_flash vfs espcoredump esp_common perfmon esp_timer)
set(fragments linker.lf ld/esp32_fragments.lf)
idf_component_register(SRCS "${srcs}"
+2 -3
View File
@@ -454,8 +454,7 @@ menu "ESP32-specific"
When brownout reset occurs, reduce PHY TX power to keep the code running
# Note about the use of "FRC1" name: currently FRC1 timer is not used for
# high resolution timekeeping anymore. Instead the esp_timer API, implemented
# using FRC2 timer, is used.
# high resolution timekeeping anymore. Instead the esp_timer API is used.
# FRC1 name in the option name is kept for compatibility.
choice ESP32_TIME_SYSCALL
prompt "Timers used for gettimeofday function"
@@ -732,7 +731,7 @@ menu "Power Management"
config PM_USE_RTC_TIMER_REF
bool "Use RTC timer to prevent time drift (EXPERIMENTAL)"
depends on PM_ENABLE && (ESP32_TIME_SYSCALL_USE_RTC || ESP32_TIME_SYSCALL_USE_RTC_FRC1)
depends on PM_ENABLE && ESP_TIMER_IMPL_FRC2 && (ESP32_TIME_SYSCALL_USE_RTC || ESP32_TIME_SYSCALL_USE_RTC_FRC1)
default n
help
When APB clock frequency changes, high-resolution timer (esp_timer)
-417
View File
@@ -1,417 +0,0 @@
// 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_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 "esp32/clk.h"
#include "esp_private/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).
* Due to the 0xffffffff cannot recognize the real overflow or the scenario that
* ISR happens follow set_alarm, so change the ALARM_OVERFLOW_VAL to resolve this problem.
* Set it to 0xefffffffUL. The remain 0x10000000UL(about 3 second) is enough to handle ISR.
*/
#define DEFAULT_ALARM_OVERFLOW_VAL 0xefffffffUL
/* Provision to set lower overflow value for unit testing. Lowering the
* overflow value helps check for race conditions which occur near overflow
* moment.
*/
#ifndef ESP_TIMER_DYNAMIC_OVERFLOW_VAL
#define ALARM_OVERFLOW_VAL DEFAULT_ALARM_OVERFLOW_VAL
#else
static uint32_t s_alarm_overflow_val = DEFAULT_ALARM_OVERFLOW_VAL;
#define ALARM_OVERFLOW_VAL (s_alarm_overflow_val)
#endif
static const char* TAG = "esp_timer_impl";
// Interrupt handle returned 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 handler
// will not increment s_time_base_us if this flag is set.
static bool s_mask_overflow;
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
// If DFS is enabled, upon the first frequency change this value is set to the
// difference between esp_timer value and RTC timer value. On every subsequent
// frequency change, s_time_base_us is adjusted to maintain the same difference
// between esp_timer and RTC timer. (All mentioned values are in microseconds.)
static uint64_t s_rtc_time_diff = 0;
#endif
// Spinlock used to protect access to static variables above and to the hardware
// registers.
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
//Use FRC_TIMER_LOAD_VALUE(1) instead of UINT32_MAX, convenience to change FRC TIMER for future
#define TIMER_IS_AFTER_OVERFLOW(a) (ALARM_OVERFLOW_VAL < (a) && (a) <= FRC_TIMER_LOAD_VALUE(1))
// Check if timer overflow has happened (but was not handled by ISR yet)
static inline bool IRAM_ATTR timer_overflow_happened(void)
{
return ((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) != 0 &&
((REG_READ(FRC_TIMER_ALARM_REG(1)) == ALARM_OVERFLOW_VAL && TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))) && !s_mask_overflow) ||
(!TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_ALARM_REG(1))) && TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))))));
}
static inline void IRAM_ATTR timer_count_reload(void)
{
//this function should be only called the real overflow happened. And the count cannot be very approach to 0xffffffff.
assert(TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))));
/* Restart the timer count by current time count minus ALARM_OVERFLOW_VAL(0xefffffff), it may cause error, if current tick is near boundary.
* But even if the error happen 100% per overflow(the distance of each real overflow is about 50 second),
* the error is 0.0125us*N per 50s(the FRC time clock is 80MHz), the N is the ticks run by the line following,
* Normally, N is less than 10, assume N is 10, so the error accumulation is only 6.48ms per month.
* In fact, if the CPU frequency is large than 80MHz. The error accumulation will be more less than 6.48ms per month.
* so It can be adopted.
*/
REG_WRITE(FRC_TIMER_LOAD_REG(1), REG_READ(FRC_TIMER_COUNT_REG(1)) - ALARM_OVERFLOW_VAL);
}
void esp_timer_impl_lock(void)
{
portENTER_CRITICAL(&s_time_update_lock);
}
void esp_timer_impl_unlock(void)
{
portEXIT_CRITICAL(&s_time_update_lock);
}
uint64_t IRAM_ATTR esp_timer_impl_get_time(void)
{
uint32_t timer_val;
uint64_t time_base;
uint32_t ticks_per_us;
bool overflow;
do {
/* Read all values needed to calculate current time */
timer_val = REG_READ(FRC_TIMER_COUNT_REG(1));
time_base = s_time_base_us;
overflow = timer_overflow_happened();
ticks_per_us = s_timer_ticks_per_us;
/* Read them again and compare */
/* In this function, do not call timer_count_reload() when overflow is true.
* Because there's remain count enough to allow FRC_TIMER_COUNT_REG grow
*/
if (REG_READ(FRC_TIMER_COUNT_REG(1)) > timer_val &&
time_base == *((volatile uint64_t*) &s_time_base_us) &&
ticks_per_us == *((volatile uint32_t*) &s_timer_ticks_per_us) &&
overflow == timer_overflow_happened()) {
break;
}
/* If any value has changed (other than the counter increasing), read again */
} while(true);
uint64_t result = time_base
+ timer_val / ticks_per_us;
return result;
}
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
// Use calculated alarm value if it is less than ALARM_OVERFLOW_VAL.
// Note that if by the time we update ALARM_REG, COUNT_REG value is higher,
// interrupt will not happen for another ALARM_OVERFLOW_VAL timer ticks,
// so need to check if alarm value is too close in the future (e.g. <2 us away).
int32_t offset = s_timer_ticks_per_us * 2;
do {
// Adjust current time if overflow has happened
if (timer_overflow_happened() ||
((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0))) {
// 1. timer_overflow_happened() checks overflow with the interrupt flag.
// 2. During several loops, the counter can be higher than the alarm and even step over ALARM_OVERFLOW_VAL boundary (the interrupt flag is not set).
timer_count_reload();
s_time_base_us += s_timer_us_per_overflow;
}
s_mask_overflow = false;
int64_t cur_count = REG_READ(FRC_TIMER_COUNT_REG(1));
// Alarm time relative to the moment when counter was 0
int64_t time_after_timebase_us = (int64_t)timestamp - s_time_base_us;
// Calculate desired timer compare value (may exceed 2^32-1)
int64_t compare_val = time_after_timebase_us * s_timer_ticks_per_us;
compare_val = MAX(compare_val, cur_count + offset);
uint32_t alarm_reg_val = ALARM_OVERFLOW_VAL;
if (compare_val < ALARM_OVERFLOW_VAL) {
alarm_reg_val = (uint32_t) compare_val;
}
REG_WRITE(FRC_TIMER_ALARM_REG(1), alarm_reg_val);
int64_t delta = (int64_t)alarm_reg_val - (int64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
if (delta <= 0) {
/*
When the timestamp is a bit less than the current counter then the alarm = current_counter + offset.
But due to CPU_freq in some case can be equal APB_freq the offset time can not exceed the overhead
(the alarm will be less than the counter) and it leads to the infinity loop.
To exclude this behavior to the offset was added the delta to have the opportunity to go through it.
*/
offset += abs((int)delta) + s_timer_ticks_per_us * 2;
} else {
break;
}
} while (1);
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
static void IRAM_ATTR timer_alarm_isr(void *arg)
{
portENTER_CRITICAL_ISR(&s_time_update_lock);
// Timekeeping: adjust s_time_base_us if counter has passed ALARM_OVERFLOW_VAL
if (timer_overflow_happened()) {
timer_count_reload();
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);
if ((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0)) {
/*
This check excludes the case when the alarm can be less than the counter.
Without this check, it is possible because DPORT uses 4-lvl, and users can use the 5 Hi-interrupt,
they can interrupt this function between FRC_TIMER_INT_CLR and setting the alarm = ALARM_OVERFLOW_VAL
that lead to the counter will go ahead leaving the alarm behind.
*/
timer_count_reload();
s_time_base_us += s_timer_us_per_overflow;
}
portEXIT_CRITICAL_ISR(&s_time_update_lock);
// Call the upper layer handler
(*s_alarm_handler)(arg);
}
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
{
portENTER_CRITICAL_ISR(&s_time_update_lock);
/* Bail out if the timer is not initialized yet */
if (s_timer_interrupt_handle == NULL) {
portEXIT_CRITICAL_ISR(&s_time_update_lock);
return;
}
uint32_t new_ticks_per_us = apb_ticks_per_us / TIMER_DIV;
uint32_t alarm = REG_READ(FRC_TIMER_ALARM_REG(1));
uint32_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
uint64_t ticks_to_alarm = alarm - count;
uint64_t new_ticks = (ticks_to_alarm * new_ticks_per_us) / s_timer_ticks_per_us;
uint32_t new_alarm_val;
if (alarm > count && new_ticks <= ALARM_OVERFLOW_VAL) {
new_alarm_val = new_ticks;
} else {
new_alarm_val = ALARM_OVERFLOW_VAL;
if (alarm != ALARM_OVERFLOW_VAL) {
s_mask_overflow = true;
}
}
REG_WRITE(FRC_TIMER_ALARM_REG(1), new_alarm_val);
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
s_time_base_us += count / s_timer_ticks_per_us;
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
// Due to the extra time required to read RTC time, don't attempt this
// adjustment when switching to a higher frequency (which usually
// happens in an interrupt).
if (new_ticks_per_us < s_timer_ticks_per_us) {
uint64_t rtc_time = esp_clk_rtc_time();
uint64_t new_rtc_time_diff = s_time_base_us - rtc_time;
if (s_rtc_time_diff != 0) {
uint64_t correction = new_rtc_time_diff - s_rtc_time_diff;
s_time_base_us -= correction;
} else {
s_rtc_time_diff = new_rtc_time_diff;
}
}
#endif // CONFIG_PM_DFS_USE_RTC_TIMER_REF
s_timer_ticks_per_us = new_ticks_per_us;
s_timer_us_per_overflow = ALARM_OVERFLOW_VAL / new_ticks_per_us;
portEXIT_CRITICAL_ISR(&s_time_update_lock);
}
void esp_timer_impl_advance(int64_t time_us)
{
assert(time_us > 0 && "negative adjustments not supported yet");
portENTER_CRITICAL(&s_time_update_lock);
uint64_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
/* Trigger an ISR to handle past alarms and set new one.
* ISR handler will run once we exit the critical section.
*/
REG_WRITE(FRC_TIMER_ALARM_REG(1), 0);
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
s_time_base_us += count / s_timer_ticks_per_us + time_us;
portEXIT_CRITICAL(&s_time_update_lock);
}
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 = ALARM_OVERFLOW_VAL / 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(void)
{
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 IRAM_ATTR esp_timer_impl_get_min_period_us(void)
{
return 50;
}
#ifdef ESP_TIMER_DYNAMIC_OVERFLOW_VAL
uint32_t esp_timer_impl_get_overflow_val(void)
{
return s_alarm_overflow_val;
}
void esp_timer_impl_set_overflow_val(uint32_t overflow_val)
{
s_alarm_overflow_val = overflow_val;
/* update alarm value */
esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000);
}
#endif // ESP_TIMER_DYNAMIC_OVERFLOW_VAL
+2 -2
View File
@@ -33,7 +33,7 @@
#include "esp_private/pm_impl.h"
#include "esp_private/pm_trace.h"
#include "esp_private/esp_timer_impl.h"
#include "esp_private/esp_timer_private.h"
#include "esp32/pm.h"
#include "esp_sleep.h"
@@ -301,7 +301,7 @@ static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_p
uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80);
/* Update APB frequency value used by the timer */
if (old_apb_ticks_per_us != apb_ticks_per_us) {
esp_timer_impl_update_apb_freq(apb_ticks_per_us);
esp_timer_private_update_apb_freq(apb_ticks_per_us);
}
/* Calculate new tick divisor */
+5 -5
View File
@@ -17,7 +17,7 @@
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_sleep.h"
#include "esp_private/esp_timer_impl.h"
#include "esp_private/esp_timer_private.h"
#include "esp_log.h"
#include "esp32/clk.h"
#include "esp_newlib.h"
@@ -281,11 +281,11 @@ esp_err_t esp_light_sleep_start(void)
{
static portMUX_TYPE light_sleep_lock = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&light_sleep_lock);
/* We will be calling esp_timer_impl_advance inside DPORT access critical
/* We will be calling esp_timer_private_advance inside DPORT access critical
* section. Make sure the code on the other CPU is not holding esp_timer
* lock, otherwise there will be deadlock.
*/
esp_timer_impl_lock();
esp_timer_private_lock();
s_config.rtc_ticks_at_sleep_start = rtc_time_get();
uint64_t frc_time_at_start = esp_timer_get_time();
DPORT_STALL_OTHER_CPU_START();
@@ -347,11 +347,11 @@ esp_err_t esp_light_sleep_start(void)
* monotonic.
*/
if (time_diff > 0) {
esp_timer_impl_advance(time_diff);
esp_timer_private_advance(time_diff);
}
esp_set_time_from_rtc();
esp_timer_impl_unlock();
esp_timer_private_unlock();
DPORT_STALL_OTHER_CPU_END();
if (!wdt_was_enabled) {
rtc_wdt_disable();
-1
View File
@@ -13,7 +13,6 @@ if(IDF_TARGET STREQUAL "esp32")
add_dependencies(${COMPONENT_LIB} esp32_test_logo)
idf_build_set_property(COMPILE_DEFINITIONS "-DESP_TIMER_DYNAMIC_OVERFLOW_VAL" APPEND)
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ld_include_test_dport_xt_highint5")
endif()
-848
View File
@@ -1,848 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/param.h>
#include "unity.h"
#include "soc/frc_timer_reg.h"
#include "esp_timer.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "test_utils.h"
#include "esp_private/esp_timer_impl.h"
#include "esp_freertos_hooks.h"
#ifdef CONFIG_ESP_TIMER_PROFILING
#define WITH_PROFILING 1
#endif
extern uint32_t esp_timer_impl_get_overflow_val(void);
extern void esp_timer_impl_set_overflow_val(uint32_t overflow_val);
static uint32_t s_old_overflow_val;
static void setup_overflow(void)
{
s_old_overflow_val = esp_timer_impl_get_overflow_val();
/* Overflow every 0.1 sec.
* Chosen so that it is 0 modulo s_timer_ticks_per_us (which is 80),
* to prevent roundoff error on each overflow.
*/
esp_timer_impl_set_overflow_val(8000000);
}
static void teardown_overflow(void)
{
esp_timer_impl_set_overflow_val(s_old_overflow_val);
}
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];
setup_overflow();
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));
}
teardown_overflow();
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_impl_set_alarm stress test", "[esp_timer]")
{
const int test_time_sec = 10;
void set_alarm_task(void* arg)
{
SemaphoreHandle_t done = (SemaphoreHandle_t) arg;
uint64_t start = esp_timer_impl_get_time();
uint64_t now = start;
int count = 0;
const int delays[] = {50, 5000, 10000000};
const int delays_count = sizeof(delays)/sizeof(delays[0]);
while (now - start < test_time_sec * 1000000) {
now = esp_timer_impl_get_time();
esp_timer_impl_set_alarm(now + delays[count % delays_count]);
++count;
}
xSemaphoreGive(done);
vTaskDelete(NULL);
}
SemaphoreHandle_t done = xSemaphoreCreateCounting(portNUM_PROCESSORS, 0);
setup_overflow();
xTaskCreatePinnedToCore(&set_alarm_task, "set_alarm_0", 4096, done, UNITY_FREERTOS_PRIORITY, NULL, 0);
#if portNUM_PROCESSORS == 2
xTaskCreatePinnedToCore(&set_alarm_task, "set_alarm_1", 4096, done, UNITY_FREERTOS_PRIORITY, NULL, 1);
#endif
TEST_ASSERT(xSemaphoreTake(done, test_time_sec * 2 * 1000 / portTICK_PERIOD_MS));
#if portNUM_PROCESSORS == 2
TEST_ASSERT(xSemaphoreTake(done, test_time_sec * 2 * 1000 / portTICK_PERIOD_MS));
#endif
teardown_overflow();
vSemaphoreDelete(done);
}
TEST_CASE("esp_timer produces correct delay", "[esp_timer]")
{
void timer_func(void* arg)
{
int64_t* p_end = (int64_t*) arg;
*p_end = ref_clock_get();
}
int64_t t_end;
esp_timer_handle_t timer1;
esp_timer_create_args_t args = {
.callback = &timer_func,
.arg = &t_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]);
ref_clock_init();
setup_overflow();
for (size_t i = 0; i < delays_count; ++i) {
t_end = 0;
int64_t t_start = ref_clock_get();
TEST_ESP_OK(esp_timer_start_once(timer1, delays_ms[i] * 1000));
vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS);
TEST_ASSERT(t_end != 0);
int32_t ms_diff = (t_end - t_start) / 1000;
printf("%d %d\n", delays_ms[i], ms_diff);
TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff);
}
teardown_overflow();
ref_clock_deinit();
TEST_ESP_OK( esp_timer_dump(stdout) );
esp_timer_delete(timer1);
}
TEST_CASE("periodic esp_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];
int64_t t_start;
SemaphoreHandle_t done;
} test_args_t;
void timer_func(void* arg)
{
test_args_t* p_args = (test_args_t*) arg;
int64_t t_end = ref_clock_get();
int32_t ms_diff = (t_end - p_args->t_start) / 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));
xSemaphoreGive(p_args->done);
}
}
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));
ref_clock_init();
setup_overflow();
args.timer = timer1;
args.t_start = ref_clock_get();
args.done = xSemaphoreCreateBinary();
TEST_ESP_OK(esp_timer_start_periodic(timer1, delay_ms * 1000));
TEST_ASSERT(xSemaphoreTake(args.done, delay_ms * NUM_INTERVALS * 2));
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]);
}
teardown_overflow();
ref_clock_deinit();
TEST_ESP_OK( esp_timer_dump(stdout) );
TEST_ESP_OK( esp_timer_delete(timer1) );
vSemaphoreDelete(args.done);
#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;
int64_t t_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];
int ms_since_start = (ref_clock_get() - p_args->t_start) / 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);
ref_clock_init();
int64_t now = ref_clock_get();
test_args_t args1 = {
.timer_index = 1,
.intervals = {10, 40, 20, 40, 30},
.common = &common,
.pass = true,
.done = done,
.t_start = now
};
test_args_t args2 = {
.timer_index = 2,
.intervals = {20, 20, 60, 30, 40},
.common = &common,
.pass = true,
.done = done,
.t_start = now
};
test_args_t args3 = {
.timer_index = 3,
.intervals = {30, 30, 60, 30, 10},
.common = &common,
.pass = true,
.done = done,
.t_start = 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);
ref_clock_deinit();
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) );
setup_overflow();
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));
}
teardown_overflow();
vSemaphoreDelete(semaphore);
}
TEST_CASE("esp_timer_get_time call takes less than 1us", "[esp_timer]")
{
int64_t begin = esp_timer_get_time();
volatile int64_t end;
const int iter_count = 10000;
for (int i = 0; i < iter_count; ++i) {
end = esp_timer_get_time();
}
int ns_per_call = (int) ((end - begin) * 1000 / iter_count);
TEST_PERFORMANCE_LESS_THAN(ESP_TIMER_GET_TIME_PER_CALL, "%dns", ns_per_call);
}
static int64_t IRAM_ATTR __attribute__((noinline)) get_clock_diff(void)
{
uint64_t hs_time = esp_timer_get_time();
uint64_t ref_time = ref_clock_get();
return hs_time - ref_time;
}
TEST_CASE("esp_timer_get_time returns monotonic values", "[esp_timer]")
{
typedef struct {
SemaphoreHandle_t done;
bool pass;
int test_cnt;
int error_cnt;
int64_t max_error;
int64_t avg_diff;
int64_t dummy;
} test_state_t;
void timer_test_task(void* arg) {
test_state_t* state = (test_state_t*) arg;
state->pass = true;
/* make sure both functions are in cache */
state->dummy = get_clock_diff();
/* calculate the difference between the two clocks */
portDISABLE_INTERRUPTS();
int64_t delta = get_clock_diff();
portENABLE_INTERRUPTS();
int64_t start_time = ref_clock_get();
int error_repeat_cnt = 0;
while (ref_clock_get() - start_time < 10000000) { /* 10 seconds */
/* Get values of both clocks again, and check that they are close to 'delta'.
* We don't disable interrupts here, because esp_timer_get_time doesn't lock
* interrupts internally, so we check if it can get "broken" by a well placed
* interrupt.
*/
int64_t diff = get_clock_diff() - delta;
/* Allow some difference due to rtos tick interrupting task between
* getting 'hs_now' and 'now'.
*/
if (abs(diff) > 100) {
error_repeat_cnt++;
state->error_cnt++;
} else {
error_repeat_cnt = 0;
}
if (error_repeat_cnt > 2) {
printf("diff=%lld\n", diff);
state->pass = false;
}
state->avg_diff += diff;
state->max_error = MAX(state->max_error, abs(diff));
state->test_cnt++;
}
state->avg_diff /= state->test_cnt;
xSemaphoreGive(state->done);
vTaskDelete(NULL);
}
ref_clock_init();
setup_overflow();
test_state_t states[portNUM_PROCESSORS] = {0};
SemaphoreHandle_t done = xSemaphoreCreateCounting(portNUM_PROCESSORS, 0);
for (int i = 0; i < portNUM_PROCESSORS; ++i) {
states[i].done = done;
xTaskCreatePinnedToCore(&timer_test_task, "test", 4096, &states[i], 6, NULL, i);
}
for (int i = 0; i < portNUM_PROCESSORS; ++i) {
TEST_ASSERT_TRUE( xSemaphoreTake(done, portMAX_DELAY) );
printf("CPU%d: %s test_cnt=%d error_cnt=%d avg_diff=%d |max_error|=%d\n",
i, states[i].pass ? "PASS" : "FAIL",
states[i].test_cnt, states[i].error_cnt,
(int) states[i].avg_diff, (int) states[i].max_error);
}
vSemaphoreDelete(done);
teardown_overflow();
ref_clock_deinit();
for (int i = 0; i < portNUM_PROCESSORS; ++i) {
TEST_ASSERT(states[i].pass);
}
}
TEST_CASE("Can dump esp_timer stats", "[esp_timer]")
{
esp_timer_dump(stdout);
}
TEST_CASE("Can delete timer from callback", "[esp_timer]")
{
typedef struct {
SemaphoreHandle_t notify_from_timer_cb;
esp_timer_handle_t timer;
} test_arg_t;
void timer_func(void* varg)
{
test_arg_t arg = *(test_arg_t*) varg;
esp_timer_delete(arg.timer);
printf("Timer %p is deleted\n", arg.timer);
xSemaphoreGive(arg.notify_from_timer_cb);
}
test_arg_t args = {
.notify_from_timer_cb = xSemaphoreCreateBinary(),
};
esp_timer_create_args_t timer_args = {
.callback = &timer_func,
.arg = &args,
.name = "self_deleter"
};
esp_timer_create(&timer_args, &args.timer);
esp_timer_start_once(args.timer, 10000);
TEST_ASSERT_TRUE(xSemaphoreTake(args.notify_from_timer_cb, 1000 / portTICK_PERIOD_MS));
printf("Checking heap at %p\n", args.timer);
TEST_ASSERT_TRUE(heap_caps_check_integrity_addr((intptr_t) args.timer, true));
vSemaphoreDelete(args.notify_from_timer_cb);
}
typedef struct {
SemaphoreHandle_t delete_start;
SemaphoreHandle_t delete_done;
SemaphoreHandle_t test_done;
esp_timer_handle_t timer;
} timer_delete_test_args_t;
static void timer_delete_task(void* arg)
{
timer_delete_test_args_t* args = (timer_delete_test_args_t*) arg;
xSemaphoreTake(args->delete_start, portMAX_DELAY);
printf("Deleting the timer\n");
esp_timer_delete(args->timer);
printf("Timer deleted\n");
xSemaphoreGive(args->delete_done);
vTaskDelete(NULL);
}
static void timer_delete_test_callback(void* arg)
{
timer_delete_test_args_t* args = (timer_delete_test_args_t*) arg;
printf("Timer callback called\n");
xSemaphoreGive(args->delete_start);
xSemaphoreTake(args->delete_done, portMAX_DELAY);
printf("Callback complete\n");
xSemaphoreGive(args->test_done);
}
TEST_CASE("Can delete timer from a separate task, triggered from callback", "[esp_timer]")
{
timer_delete_test_args_t args = {
.delete_start = xSemaphoreCreateBinary(),
.delete_done = xSemaphoreCreateBinary(),
.test_done = xSemaphoreCreateBinary(),
};
esp_timer_create_args_t timer_args = {
.callback = &timer_delete_test_callback,
.arg = &args
};
esp_timer_handle_t timer;
TEST_ESP_OK(esp_timer_create(&timer_args, &timer));
args.timer = timer;
xTaskCreate(timer_delete_task, "deleter", 4096, &args, 5, NULL);
esp_timer_start_once(timer, 100);
TEST_ASSERT(xSemaphoreTake(args.test_done, pdMS_TO_TICKS(1000)));
vSemaphoreDelete(args.delete_done);
vSemaphoreDelete(args.delete_start);
vSemaphoreDelete(args.test_done);
}
TEST_CASE("esp_timer_impl_advance moves time base correctly", "[esp_timer]")
{
ref_clock_init();
int64_t t0 = esp_timer_get_time();
const int64_t diff_us = 1000000;
esp_timer_impl_advance(diff_us);
int64_t t1 = esp_timer_get_time();
int64_t t_delta = t1 - t0;
printf("diff_us=%lld t1-t0=%lld\n", diff_us, t_delta);
TEST_ASSERT_INT_WITHIN(1000, diff_us, (int) t_delta);
ref_clock_deinit();
}
TEST_CASE("after esp_timer_impl_advance, timers run when expected", "[esp_timer]")
{
typedef struct {
int64_t cb_time;
} test_state_t;
void timer_func(void* varg) {
test_state_t* arg = (test_state_t*) varg;
arg->cb_time = ref_clock_get();
}
ref_clock_init();
test_state_t state = { 0 };
esp_timer_create_args_t timer_args = {
.callback = &timer_func,
.arg = &state
};
esp_timer_handle_t timer;
TEST_ESP_OK(esp_timer_create(&timer_args, &timer));
const int64_t interval = 10000;
const int64_t advance = 2000;
printf("test 1\n");
int64_t t_start = ref_clock_get();
esp_timer_start_once(timer, interval);
esp_timer_impl_advance(advance);
vTaskDelay(2 * interval / 1000 / portTICK_PERIOD_MS);
TEST_ASSERT_INT_WITHIN(portTICK_PERIOD_MS * 1000, interval - advance, state.cb_time - t_start);
printf("test 2\n");
state.cb_time = 0;
t_start = ref_clock_get();
esp_timer_start_once(timer, interval);
esp_timer_impl_advance(interval);
vTaskDelay(1);
TEST_ASSERT(state.cb_time > t_start);
ref_clock_deinit();
}
static esp_timer_handle_t timer1;
static SemaphoreHandle_t sem;
static void IRAM_ATTR test_tick_hook(void)
{
static int i;
const int iterations = 16;
if (++i <= iterations) {
if (i & 0x1) {
TEST_ESP_OK(esp_timer_start_once(timer1, 5000));
} else {
TEST_ESP_OK(esp_timer_stop(timer1));
}
} else {
xSemaphoreGiveFromISR(sem, 0);
}
}
TEST_CASE("Can start/stop timer from ISR context", "[esp_timer]")
{
void timer_func(void* arg)
{
printf("timer cb\n");
}
esp_timer_create_args_t create_args = {
.callback = &timer_func,
};
TEST_ESP_OK(esp_timer_create(&create_args, &timer1));
sem = xSemaphoreCreateBinary();
esp_register_freertos_tick_hook(test_tick_hook);
TEST_ASSERT(xSemaphoreTake(sem, portMAX_DELAY));
esp_deregister_freertos_tick_hook(test_tick_hook);
TEST_ESP_OK( esp_timer_delete(timer1) );
vSemaphoreDelete(sem);
}
#if !defined(CONFIG_FREERTOS_UNICORE) && defined(CONFIG_ESP32_DPORT_WORKAROUND)
#include "soc/dport_reg.h"
#include "soc/frc_timer_reg.h"
#include "esp_ipc.h"
static bool task_stop;
static bool time_jumped;
static void task_check_time(void *p)
{
int64_t t1 = 0, t2 = 0;
while (task_stop == false) {
t1 = t2;
t2 = esp_timer_get_time();
if (t1 > t2) {
int64_t shift_us = t2 - t1;
time_jumped = true;
printf("System clock jumps back: %lli us\n", shift_us);
}
vTaskDelay(1);
}
vTaskDelete(NULL);
}
static void timer_callback(void* arg)
{
}
static void dport_task(void *pvParameters)
{
while (task_stop == false) {
DPORT_STALL_OTHER_CPU_START();
ets_delay_us(3);
DPORT_STALL_OTHER_CPU_END();
}
vTaskDelete(NULL);
}
TEST_CASE("esp_timer_impl_set_alarm does not set an alarm below the current time", "[esp_timer][timeout=62]")
{
const int max_timers = 2;
time_jumped = false;
task_stop = false;
xTaskCreatePinnedToCore(task_check_time, "task_check_time", 4096, NULL, 5, NULL, 0);
// dport_task is used here to interrupt the esp_timer_impl_set_alarm function.
// To interrupt it we can use an interrupt with 4 or 5 levels which will run on CPU0.
// Instead, an interrupt we use the dport workaround which has 4 interrupt level for stall CPU0.
xTaskCreatePinnedToCore(dport_task, "dport_task", 4096, NULL, 5, NULL, 1);
const esp_timer_create_args_t periodic_timer_args = {
.callback = &timer_callback,
};
esp_timer_handle_t periodic_timer[max_timers];
printf("timers created\n");
esp_timer_create(&periodic_timer_args, &periodic_timer[0]);
esp_timer_start_periodic(periodic_timer[0], 9000);
esp_timer_create(&periodic_timer_args, &periodic_timer[1]);
esp_timer_start_periodic(periodic_timer[1], 9000);
vTaskDelay(60 * 1000 / portTICK_PERIOD_MS);
task_stop = true;
esp_timer_stop(periodic_timer[0]);
esp_timer_delete(periodic_timer[0]);
esp_timer_stop(periodic_timer[1]);
esp_timer_delete(periodic_timer[1]);
printf("timers deleted\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
TEST_ASSERT(time_jumped == false);
}
static esp_timer_handle_t oneshot_timer;
static void oneshot_timer_callback(void* arg)
{
esp_timer_start_once(oneshot_timer, 5000);
}
static const esp_timer_create_args_t oneshot_timer_args = {
.callback = &oneshot_timer_callback,
};
TEST_CASE("esp_timer_impl_set_alarm and using start_once do not lead that the System time jumps back", "[esp_timer][timeout=62]")
{
time_jumped = false;
task_stop = false;
xTaskCreatePinnedToCore(task_check_time, "task_check_time", 4096, NULL, 5, NULL, 0);
// dport_task is used here to interrupt the esp_timer_impl_set_alarm function.
// To interrupt it we can use an interrupt with 4 or 5 levels which will run on CPU0.
// Instead, an interrupt we use the dport workaround which has 4 interrupt level for stall CPU0.
xTaskCreatePinnedToCore(dport_task, "dport_task", 4096, NULL, 5, NULL, 1);
const esp_timer_create_args_t periodic_timer_args = {
.callback = &timer_callback,
};
esp_timer_handle_t periodic_timer;
esp_timer_create(&periodic_timer_args, &periodic_timer);
esp_timer_start_periodic(periodic_timer, 5000);
esp_timer_create(&oneshot_timer_args, &oneshot_timer);
esp_timer_start_once(oneshot_timer, 9990);
printf("timers created\n");
vTaskDelay(60 * 1000 / portTICK_PERIOD_MS);
task_stop = true;
esp_timer_stop(oneshot_timer);
esp_timer_delete(oneshot_timer);
printf("timers deleted\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
TEST_ASSERT(time_jumped == false);
}
#endif // !defined(CONFIG_FREERTOS_UNICORE) && defined(CONFIG_ESP32_DPORT_WORKAROUND)
TEST_CASE("Test case when esp_timer_impl_set_alarm needs set timer < now_time", "[esp_timer]")
{
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
esp_timer_impl_advance(50331648); // 0xefffffff/80 = 50331647
ets_delay_us(2);
portDISABLE_INTERRUPTS();
esp_timer_impl_set_alarm(50331647);
uint32_t alarm_reg = REG_READ(FRC_TIMER_ALARM_REG(1));
uint32_t count_reg = REG_READ(FRC_TIMER_COUNT_REG(1));
portENABLE_INTERRUPTS();
const uint32_t offset = 80 * 2; // s_timer_ticks_per_us
printf("alarm_reg = 0x%x, count_reg 0x%x\n", alarm_reg, count_reg);
TEST_ASSERT(alarm_reg <= (count_reg + offset));
}
TEST_CASE("Test esp_timer_impl_set_alarm when the counter is near an overflow value", "[esp_timer]")
{
for (int i = 0; i < 1024; ++i) {
uint32_t count_reg = 0xeffffe00 + i;
REG_WRITE(FRC_TIMER_LOAD_REG(1), count_reg);
printf("%d) count_reg = 0x%x\n", i, count_reg);
esp_timer_impl_set_alarm(1); // timestamp is expired
}
}
-238
View File
@@ -1,238 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include "unity.h"
#include "esp32/rom/ets_sys.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_spi_flash.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
}
/* WiFi/BT coexistence will sometimes arm/disarm
timers from an ISR where flash may be disabled. */
IRAM_ATTR TEST_CASE("ETSTimers arm & disarm run from IRAM", "[ets_timer]")
{
void timer_func(void* arg)
{
volatile bool *b = (volatile bool *)arg;
*b = true;
}
volatile bool flag = false;
ETSTimer timer1;
const int INTERVAL = 5;
ets_timer_setfn(&timer1, &timer_func, (void *)&flag);
/* arm a disabled timer, then disarm a live timer */
spi_flash_guard_get()->start(); // Disables flash cache
ets_timer_arm(&timer1, INTERVAL, false);
// redundant call is deliberate (test code path if already armed)
ets_timer_arm(&timer1, INTERVAL, false);
ets_timer_disarm(&timer1);
spi_flash_guard_get()->end(); // Re-enables flash cache
TEST_ASSERT_FALSE(flag); // didn't expire yet
/* do the same thing but wait for the timer to expire */
spi_flash_guard_get()->start();
ets_timer_arm(&timer1, INTERVAL, false);
spi_flash_guard_get()->end();
vTaskDelay(2 * INTERVAL / portTICK_PERIOD_MS);
TEST_ASSERT_TRUE(flag);
spi_flash_guard_get()->start();
ets_timer_disarm(&timer1);
spi_flash_guard_get()->end();
}