mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-05 12:25:03 +02:00
esp32: add implementation of esp_timer based on TG0 LAC timer
Closes: IDF-979
This commit is contained in:
committed by
Angus Gratton
parent
67b0a79167
commit
739eb05bb9
@@ -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}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user