gptimer: new driver for previous timer group

This commit is contained in:
morris 2022-01-02 16:14:17 +08:00
parent 79cc650d75
commit 5deb83b12d
24 changed files with 1771 additions and 1239 deletions

View File

@ -2,8 +2,11 @@ idf_build_get_property(target IDF_TARGET)
set(srcs
"gpio.c"
"gptimer.c"
"timer_legacy.c"
"i2c.c"
"ledc.c"
"legacy_new_driver_coexist.c"
"periph_ctrl.c"
"rtc_io.c"
"rtc_module.c"
@ -14,7 +17,6 @@ set(srcs
"spi_master.c"
"spi_slave.c"
"spi_bus_lock.c"
"timer.c"
"uart.c")
set(includes "include" "${target}/include" "deprecated")

View File

@ -188,4 +188,30 @@ menu "Driver configurations"
(e.g. SPI Flash write).
endmenu # GDMA Configuration
menu "GPTimer Configuration"
config GPTIMER_CTRL_FUNC_IN_IRAM
bool "Place GPTimer control functions into IRAM"
default n
help
Place GPTimer control functions (like start/stop) into IRAM,
so that these functions can be IRAM-safe and able to be called in the other IRAM interrupt context.
Enabling this option can improve driver performance as well.
config GPTIMER_ISR_IRAM_SAFE
bool "GPTimer ISR IRAM-Safe"
default n
help
This will ensure the GPTimer interrupt handle is IRAM-Safe, allow to avoid flash
cache misses, and also be able to run whilst the cache is disabled.
(e.g. SPI Flash write)
config GPTIMER_SUPPRESS_DEPRECATE_WARN
bool "Suppress leagcy driver deprecated warning"
default n
help
Wether to suppress the deprecation warnings when using legacy timer group driver (driver/timer.h).
If you want to continue using the legacy driver, and don't want to see related deprecation warnings,
you can enable this option.
endmenu # GPTimer Configuration
endmenu # Driver configurations

View File

@ -7,147 +7,19 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_attr.h"
#include "soc/soc_caps.h"
#include "esp_intr_alloc.h"
#include "hal/timer_types.h"
#include "driver/timer_types_legacy.h"
#if !CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN
#warning "legacy timer group driver is deprecated, please migrate to driver/gptimer.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Frequency of the clock on the input of the timer groups
* @note This macro is not correct for Timer Groups with multiple clock sources (e.g. APB, XTAL)
* So please don't use it in your application, we keep it here only for backward compatible
*/
#define TIMER_BASE_CLK (APB_CLK_FREQ)
/**
* @brief Selects a Timer-Group out of 2 available groups
*/
typedef enum {
TIMER_GROUP_0 = 0, /*!< Hw timer group 0 */
#if SOC_TIMER_GROUPS > 1
TIMER_GROUP_1 = 1, /*!< Hw timer group 1 */
#endif
TIMER_GROUP_MAX /*!< Maximum number of Hw timer groups */
} timer_group_t;
/**
* @brief Select a hardware timer from timer groups
*/
typedef enum {
TIMER_0 = 0, /*!<Select timer0 of GROUPx*/
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
TIMER_1 = 1, /*!<Select timer1 of GROUPx*/
#endif
TIMER_MAX,
} timer_idx_t;
/**
* @brief Interrupt types of the timer.
*/
typedef enum {
TIMER_INTR_T0 = 1 << 0, /*!< interrupt of timer 0 */
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
TIMER_INTR_T1 = 1 << 1, /*!< interrupt of timer 1 */
TIMER_INTR_WDT = 1 << 2, /*!< interrupt of watchdog */
#else
TIMER_INTR_WDT = 1 << 1, /*!< interrupt of watchdog */
#endif
TIMER_INTR_NONE = 0
} timer_intr_t;
FLAG_ATTR(timer_intr_t)
/**
* @brief Decides the direction of counter
*/
typedef enum {
TIMER_COUNT_DOWN = GPTIMER_COUNT_DOWN, /*!< Descending Count from cnt.high|cnt.low*/
TIMER_COUNT_UP = GPTIMER_COUNT_UP, /*!< Ascending Count from Zero*/
TIMER_COUNT_MAX /*!< Maximum number of timer count directions */
} timer_count_dir_t;
/**
* @brief Decides whether timer is on or paused
*/
typedef enum {
TIMER_PAUSE, /*!<Pause timer counter*/
TIMER_START, /*!<Start timer counter*/
} timer_start_t;
/**
* @brief Decides whether to enable alarm mode
*/
typedef enum {
TIMER_ALARM_DIS = 0, /*!< Disable timer alarm*/
TIMER_ALARM_EN = 1, /*!< Enable timer alarm*/
TIMER_ALARM_MAX
} timer_alarm_t;
/**
* @brief Select interrupt type if running in alarm mode.
*/
typedef enum {
TIMER_INTR_LEVEL = 0, /*!< Interrupt mode: level mode*/
TIMER_INTR_MAX
} timer_intr_mode_t;
/**
* @brief Select if Alarm needs to be loaded by software or automatically reload by hardware.
*/
typedef enum {
TIMER_AUTORELOAD_DIS = 0, /*!< Disable auto-reload: hardware will not load counter value after an alarm event*/
TIMER_AUTORELOAD_EN = 1, /*!< Enable auto-reload: hardware will load counter value after an alarm event*/
TIMER_AUTORELOAD_MAX,
} timer_autoreload_t;
/**
* @brief Select timer source clock.
*/
typedef enum {
TIMER_SRC_CLK_APB = GPTIMER_CLK_SRC_APB, /*!< Select APB as the source clock*/
#if SOC_TIMER_GROUP_SUPPORT_XTAL
TIMER_SRC_CLK_XTAL = GPTIMER_CLK_SRC_XTAL, /*!< Select XTAL as the source clock*/
#endif
} timer_src_clk_t;
/**
* @brief Data structure with timer's configuration settings
*/
typedef struct {
timer_alarm_t alarm_en; /*!< Timer alarm enable */
timer_start_t counter_en; /*!< Counter enable */
timer_intr_mode_t intr_type; /*!< Interrupt mode */
timer_count_dir_t counter_dir; /*!< Counter direction */
timer_autoreload_t auto_reload; /*!< Timer auto-reload */
timer_src_clk_t clk_src; /*!< Selects source clock. */
uint32_t divider; /*!< Counter clock divider */
} timer_config_t;
/**
* @brief Interrupt handle callback function. User need to retrun a bool value
* in callback.
*
* @return
* - True Do task yield at the end of ISR
* - False Not do task yield at the end of ISR
*
* @note If you called FreeRTOS functions in callback, you need to return true or false based on
* the retrun value of argument `pxHigherPriorityTaskWoken`.
* For example, `xQueueSendFromISR` is called in callback, if the return value `pxHigherPriorityTaskWoken`
* of any FreeRTOS calls is pdTRUE, return true; otherwise return false.
*/
typedef bool (*timer_isr_t)(void *);
/**
* @brief Interrupt handle, used in order to free the isr after use.
* Aliases to an int handle for now.
*/
typedef intr_handle_t timer_isr_handle_t;
/**
* @brief Read the counter value of hardware timer.
*

View File

@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "soc/soc_caps.h"
#include "hal/timer_types.h"
#include "esp_intr_alloc.h"
#include "esp_attr.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Frequency of the clock on the input of the timer groups
* @note This macro is not correct for Timer Groups with multiple clock sources (e.g. APB, XTAL)
* So please don't use it in your application, we keep it here only for backward compatible
*/
#define TIMER_BASE_CLK _Pragma ("GCC warning \"'TIMER_BASE_CLK' macro is deprecated\"") APB_CLK_FREQ
/**
* @brief Timer-Group ID
*/
typedef enum {
TIMER_GROUP_0 = 0, /*!< Hw timer group 0 */
#if SOC_TIMER_GROUPS > 1
TIMER_GROUP_1 = 1, /*!< Hw timer group 1 */
#endif
TIMER_GROUP_MAX /*!< Maximum number of Hw timer groups */
} timer_group_t;
/**
* @brief Timer ID
*/
typedef enum {
TIMER_0 = 0, /*!< Select timer0 of GROUPx*/
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
TIMER_1 = 1, /*!< Select timer1 of GROUPx*/
#endif
TIMER_MAX,
} timer_idx_t;
/**
* @brief Interrupt types of the timer.
*/
typedef enum {
TIMER_INTR_T0 = 1 << 0, /*!< interrupt of timer 0 */
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
TIMER_INTR_T1 = 1 << 1, /*!< interrupt of timer 1 */
TIMER_INTR_WDT = 1 << 2, /*!< interrupt of watchdog */
#else
TIMER_INTR_WDT = 1 << 1, /*!< interrupt of watchdog */
#endif
TIMER_INTR_NONE = 0
} timer_intr_t;
FLAG_ATTR(timer_intr_t)
/**
* @brief Timer count direction
*/
typedef enum {
TIMER_COUNT_DOWN = GPTIMER_COUNT_DOWN, /*!< Descending Count from cnt.high|cnt.low*/
TIMER_COUNT_UP = GPTIMER_COUNT_UP, /*!< Ascending Count from Zero*/
TIMER_COUNT_MAX /*!< Maximum number of timer count directions */
} timer_count_dir_t;
/**
* @brief Timer start/stop command
*/
typedef enum {
TIMER_PAUSE, /*!< Pause timer counter*/
TIMER_START, /*!< Start timer counter*/
} timer_start_t;
/**
* @brief Timer alarm command
*/
typedef enum {
TIMER_ALARM_DIS = 0, /*!< Disable timer alarm*/
TIMER_ALARM_EN = 1, /*!< Enable timer alarm*/
TIMER_ALARM_MAX
} timer_alarm_t;
/**
* @brief Timer interrupt type
*/
typedef enum {
TIMER_INTR_LEVEL = 0, /*!< Interrupt mode: level mode*/
TIMER_INTR_MAX
} timer_intr_mode_t;
/**
* @brief Timer autoreload command
*/
typedef enum {
TIMER_AUTORELOAD_DIS = 0, /*!< Disable auto-reload: hardware will not load counter value after an alarm event*/
TIMER_AUTORELOAD_EN = 1, /*!< Enable auto-reload: hardware will load counter value after an alarm event*/
TIMER_AUTORELOAD_MAX,
} timer_autoreload_t;
/**
* @brief Timer group clock source
*/
typedef enum {
TIMER_SRC_CLK_APB = GPTIMER_CLK_SRC_APB, /*!< Select APB as the source clock*/
#if SOC_TIMER_GROUP_SUPPORT_XTAL
TIMER_SRC_CLK_XTAL = GPTIMER_CLK_SRC_XTAL, /*!< Select XTAL as the source clock*/
#endif
} timer_src_clk_t;
/**
* @brief Interrupt handler callback function
*
* @return
* - True Do task yield at the end of ISR
* - False Not do task yield at the end of ISR
*
* @note If you called FreeRTOS functions in callback, you need to return true or false based on
* the retrun value of argument `pxHigherPriorityTaskWoken`.
* For example, `xQueueSendFromISR` is called in callback, if the return value `pxHigherPriorityTaskWoken`
* of any FreeRTOS calls is pdTRUE, return true; otherwise return false.
*/
typedef bool (*timer_isr_t)(void *);
/**
* @brief Interrupt handle, used in order to free the isr after use.
*/
typedef intr_handle_t timer_isr_handle_t;
/**
* @brief Timer configurations
*/
typedef struct {
timer_alarm_t alarm_en; /*!< Timer alarm enable */
timer_start_t counter_en; /*!< Counter enable */
timer_intr_mode_t intr_type; /*!< Interrupt mode */
timer_count_dir_t counter_dir; /*!< Counter direction */
timer_autoreload_t auto_reload; /*!< Timer auto-reload */
timer_src_clk_t clk_src; /*!< Selects source clock. */
uint32_t divider; /*!< Counter clock divider */
} timer_config_t;
#ifdef __cplusplus
}
#endif

530
components/driver/gptimer.c Normal file
View File

@ -0,0 +1,530 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG // uncomment this line to enable debug logs
#include <stdlib.h>
#include <sys/lock.h>
#include "freertos/FreeRTOS.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_pm.h"
#include "driver/gptimer.h"
#include "hal/timer_types.h"
#include "hal/timer_hal.h"
#include "hal/timer_ll.h"
#include "soc/timer_periph.h"
#include "soc/soc_memory_types.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/esp_clk.h"
// If ISR handler is allowed to run whilst cache is disabled,
// Make sure all the code and related variables used by the handler are in the SRAM
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
#define GPTIMER_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
#define GPTIMER_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define GPTIMER_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
#define GPTIMER_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif //CONFIG_GPTIMER_ISR_IRAM_SAFE
#if CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
#define GPTIMER_CTRL_FUNC_ATTR IRAM_ATTR
#else
#define GPTIMER_CTRL_FUNC_ATTR
#endif // CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
#define GPTIMER_PM_LOCK_NAME_LEN_MAX 16
static const char *TAG = "gptimer";
typedef struct gptimer_platform_t gptimer_platform_t;
typedef struct gptimer_group_t gptimer_group_t;
typedef struct gptimer_t gptimer_t;
struct gptimer_platform_t {
_lock_t mutex; // platform level mutex lock
gptimer_group_t *groups[SOC_TIMER_GROUPS]; // timer group pool
int group_ref_counts[SOC_TIMER_GROUPS]; // reference count used to protect group install/uninstall
};
struct gptimer_group_t {
int group_id;
portMUX_TYPE spinlock; // to protect per-group register level concurrent access
gptimer_t *timers[SOC_TIMER_GROUP_TIMERS_PER_GROUP];
};
typedef enum {
GPTIMER_FSM_STOP,
GPTIMER_FSM_START,
} gptimer_lifecycle_fsm_t;
struct gptimer_t {
gptimer_group_t *group;
int timer_id;
unsigned int resolution_hz;
unsigned long long reload_count;
unsigned long long alarm_count;
gptimer_count_direction_t direction;
timer_hal_context_t hal;
gptimer_lifecycle_fsm_t fsm; // access to fsm should be protect by spinlock, as fsm is also accessed from ISR handler
intr_handle_t intr;
_lock_t mutex; // to protect other resource allocation, like interrupt handle
portMUX_TYPE spinlock; // to protect per-timer resources concurent accessed by task and ISR handler
gptimer_alarm_cb_t on_alarm;
void *user_ctx;
esp_pm_lock_handle_t pm_lock; // power management lock
#if CONFIG_PM_ENABLE
char pm_lock_name[GPTIMER_PM_LOCK_NAME_LEN_MAX]; // pm lock name
#endif
struct {
uint32_t intr_shared: 1;
uint32_t auto_reload_on_alarm: 1;
uint32_t alarm_en: 1;
} flags;
};
// gptimer driver platform, it's always a singleton
static gptimer_platform_t s_platform;
static gptimer_group_t *gptimer_acquire_group_handle(int group_id);
static void gptimer_release_group_handle(gptimer_group_t *group);
static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz);
static esp_err_t gptimer_install_interrupt(gptimer_t *timer);
IRAM_ATTR static void gptimer_default_isr(void *args);
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)
{
esp_err_t ret = ESP_OK;
gptimer_group_t *group = NULL;
gptimer_t *timer = NULL;
int group_id = -1;
int timer_id = -1;
ESP_GOTO_ON_FALSE(config && ret_timer, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(config->resolution_hz, ESP_ERR_INVALID_ARG, err, TAG, "invalid timer resolution:%d", config->resolution_hz);
timer = heap_caps_calloc(1, sizeof(gptimer_t), GPTIMER_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(timer, ESP_ERR_NO_MEM, err, TAG, "no mem for gptimer");
for (int i = 0; (i < SOC_TIMER_GROUPS) && (timer_id < 0); i++) {
group = gptimer_acquire_group_handle(i);
ESP_GOTO_ON_FALSE(group, ESP_ERR_NO_MEM, err, TAG, "no mem for group (%d)", group_id);
// loop to search free timer in the group
portENTER_CRITICAL(&group->spinlock);
for (int j = 0; j < SOC_TIMER_GROUP_TIMERS_PER_GROUP; j++) {
if (!group->timers[j]) {
group_id = i;
timer_id = j;
group->timers[j] = timer;
break;
}
}
portEXIT_CRITICAL(&group->spinlock);
if (timer_id < 0) {
gptimer_release_group_handle(group);
group = NULL;
}
}
ESP_GOTO_ON_FALSE(timer_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free timer");
timer->timer_id = timer_id;
timer->group = group;
// initialize HAL layer
timer_hal_init(&timer->hal, group_id, timer_id);
// stop counter, alarm, auto-reload
timer_ll_enable_counter(timer->hal.dev, timer_id, false);
timer_ll_enable_auto_reload(timer->hal.dev, timer_id, false);
timer_ll_enable_alarm(timer->hal.dev, timer_id, false);
// select clock source, set clock resolution
ESP_GOTO_ON_ERROR(gptimer_select_periph_clock(timer, config->clk_src, config->resolution_hz), err, TAG, "set periph clock failed");
// initialize counter value to zero
timer_hal_set_counter_value(&timer->hal, 0);
// set counting direction
timer_ll_set_count_direction(timer->hal.dev, timer_id, config->direction);
// interrupt register is shared by all timers in the same group
portENTER_CRITICAL(&group->spinlock);
timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer_id), false); // disable interrupt
timer_ll_clear_intr_status(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer_id)); // clear pending interrupt event
portEXIT_CRITICAL(&group->spinlock);
// initialize other members of timer
timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
timer->fsm = GPTIMER_FSM_STOP;
timer->direction = config->direction;
timer->flags.intr_shared = config->flags.intr_shared;
_lock_init(&timer->mutex);
ESP_LOGD(TAG, "new gptimer (%d,%d) at %p, resolution=%uHz", group_id, timer_id, timer, timer->resolution_hz);
*ret_timer = timer;
return ESP_OK;
err:
if (timer) {
if (timer->pm_lock) {
esp_pm_lock_delete(timer->pm_lock);
}
free(timer);
}
if (group) {
gptimer_release_group_handle(group);
}
return ret;
}
esp_err_t gptimer_del_timer(gptimer_handle_t timer)
{
gptimer_group_t *group = NULL;
bool valid_state = true;
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
portENTER_CRITICAL(&timer->spinlock);
if (timer->fsm != GPTIMER_FSM_STOP) {
valid_state = false;
}
portEXIT_CRITICAL(&timer->spinlock);
ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "can't delete timer as it's not in stop state");
group = timer->group;
int group_id = group->group_id;
int timer_id = timer->timer_id;
if (timer->intr) {
esp_intr_free(timer->intr);
ESP_LOGD(TAG, "uninstall interrupt service for timer (%d,%d)", group_id, timer_id);
}
if (timer->pm_lock) {
esp_pm_lock_delete(timer->pm_lock);
ESP_LOGD(TAG, "uninstall APB_FREQ_MAX lock for timer (%d,%d)", group_id, timer_id);
}
_lock_close(&timer->mutex);
free(timer);
ESP_LOGD(TAG, "del timer (%d,%d)", group_id, timer_id);
portENTER_CRITICAL(&group->spinlock);
group->timers[timer_id] = NULL;
portEXIT_CRITICAL(&group->spinlock);
// timer has a reference on group, release it now
gptimer_release_group_handle(group);
return ESP_OK;
}
GPTIMER_CTRL_FUNC_ATTR
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
portENTER_CRITICAL_SAFE(&timer->spinlock);
timer_hal_set_counter_value(&timer->hal, value);
portEXIT_CRITICAL_SAFE(&timer->spinlock);
return ESP_OK;
}
GPTIMER_CTRL_FUNC_ATTR
esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, unsigned long long *value)
{
ESP_RETURN_ON_FALSE(timer && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
portENTER_CRITICAL_SAFE(&timer->spinlock);
*value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id);
portEXIT_CRITICAL_SAFE(&timer->spinlock);
return ESP_OK;
}
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data)
{
gptimer_group_t *group = NULL;
ESP_RETURN_ON_FALSE(timer && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
group = timer->group;
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
if (cbs->on_alarm) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_alarm), ESP_ERR_INVALID_ARG, TAG, "on_alarm callback not in IRAM");
}
if (user_data) {
ESP_RETURN_ON_FALSE(esp_ptr_in_dram(user_data) ||
esp_ptr_in_diram_dram(user_data) ||
esp_ptr_in_rtc_dram_fast(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in DRAM");
}
#endif
// lazy install interrupt service
ESP_RETURN_ON_ERROR(gptimer_install_interrupt(timer), TAG, "install interrupt service failed");
// enable/disable GPTimer interrupt events
portENTER_CRITICAL_SAFE(&group->spinlock);
timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id), cbs->on_alarm); // enable timer interrupt
portEXIT_CRITICAL_SAFE(&group->spinlock);
timer->on_alarm = cbs->on_alarm;
timer->user_ctx = user_data;
return ESP_OK;
}
GPTIMER_CTRL_FUNC_ATTR
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
if (config) {
// When auto_reload is enabled, alarm_count should not be equal to reload_count
bool valid_auto_reload = !config->flags.auto_reload_on_alarm || config->alarm_count != config->reload_count;
ESP_RETURN_ON_FALSE(valid_auto_reload, ESP_ERR_INVALID_ARG, TAG, "reload count can't equal to alarm count");
timer->reload_count = config->reload_count;
timer->alarm_count = config->alarm_count;
timer->flags.auto_reload_on_alarm = config->flags.auto_reload_on_alarm;
timer->flags.alarm_en = true;
portENTER_CRITICAL_SAFE(&timer->spinlock);
timer_ll_set_reload_value(timer->hal.dev, timer->timer_id, config->reload_count);
timer_ll_set_alarm_value(timer->hal.dev, timer->timer_id, config->alarm_count);
portEXIT_CRITICAL_SAFE(&timer->spinlock);
} else {
timer->flags.auto_reload_on_alarm = false;
timer->flags.alarm_en = false;
}
portENTER_CRITICAL_SAFE(&timer->spinlock);
timer_ll_enable_auto_reload(timer->hal.dev, timer->timer_id, timer->flags.auto_reload_on_alarm);
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en);
portEXIT_CRITICAL_SAFE(&timer->spinlock);
return ESP_OK;
}
GPTIMER_CTRL_FUNC_ATTR
esp_err_t gptimer_start(gptimer_handle_t timer)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// acquire power manager lock
if (timer->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(timer->pm_lock), TAG, "acquire APB_FREQ_MAX lock failed");
}
// interrupt interupt service
if (timer->intr) {
ESP_RETURN_ON_ERROR(esp_intr_enable(timer->intr), TAG, "enable interrupt service failed");
}
portENTER_CRITICAL_SAFE(&timer->spinlock);
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, true);
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en);
timer->fsm = GPTIMER_FSM_START;
portEXIT_CRITICAL_SAFE(&timer->spinlock);
return ESP_OK;
}
GPTIMER_CTRL_FUNC_ATTR
esp_err_t gptimer_stop(gptimer_handle_t timer)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// disable counter, alarm, autoreload
portENTER_CRITICAL_SAFE(&timer->spinlock);
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, false);
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, false);
timer->fsm = GPTIMER_FSM_STOP;
portEXIT_CRITICAL_SAFE(&timer->spinlock);
// disable interrupt service
if (timer->intr) {
ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt service failed");
}
// release power manager lock
if (timer->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_release(timer->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
}
return ESP_OK;
}
static gptimer_group_t *gptimer_acquire_group_handle(int group_id)
{
// esp_log_level_set(TAG, ESP_LOG_DEBUG);
bool new_group = false;
gptimer_group_t *group = NULL;
// prevent install timer group concurrently
_lock_acquire(&s_platform.mutex);
if (!s_platform.groups[group_id]) {
group = heap_caps_calloc(1, sizeof(gptimer_group_t), GPTIMER_MEM_ALLOC_CAPS);
if (group) {
new_group = true;
s_platform.groups[group_id] = group;
// initialize timer group members
group->group_id = group_id;
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
// enable APB access timer registers
periph_module_enable(timer_group_periph_signals.groups[group_id].module);
}
} else {
group = s_platform.groups[group_id];
}
// someone acquired the group handle means we have a new object that refer to this group
s_platform.group_ref_counts[group_id]++;
_lock_release(&s_platform.mutex);
if (new_group) {
ESP_LOGD(TAG, "new group (%d) @%p", group_id, group);
}
return group;
}
static void gptimer_release_group_handle(gptimer_group_t *group)
{
int group_id = group->group_id;
bool do_deinitialize = false;
_lock_acquire(&s_platform.mutex);
s_platform.group_ref_counts[group_id]--;
if (s_platform.group_ref_counts[group_id] == 0) {
assert(s_platform.groups[group_id]);
do_deinitialize = true;
s_platform.groups[group_id] = NULL;
// Theoretically we need to disable the peripheral clock for the timer group
// However, next time when we enable the peripheral again, the registers will be reset to default value, including the watchdog registers inside the group
// Then the watchdog will go into reset state, e.g. the flash boot watchdog is enabled again and reset the system very soon
// periph_module_disable(timer_group_periph_signals.groups[group_id].module);
}
_lock_release(&s_platform.mutex);
if (do_deinitialize) {
free(group);
ESP_LOGD(TAG, "del group (%d)", group_id);
}
}
static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz)
{
unsigned int counter_src_hz = 0;
esp_err_t ret = ESP_OK;
int timer_id = timer->timer_id;
switch (src_clk) {
case GPTIMER_CLK_SRC_APB:
counter_src_hz = esp_clk_apb_freq();
#if CONFIG_PM_ENABLE
sprintf(timer->pm_lock_name, "gptimer_%d_%d", timer->group->group_id, timer_id); // e.g. gptimer_0_0
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, timer->pm_lock_name, &timer->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create APB_FREQ_MAX lock failed");
ESP_LOGD(TAG, "install APB_FREQ_MAX lock for timer (%d,%d)", timer->group->group_id, timer_id);
#endif
break;
#if SOC_TIMER_GROUP_SUPPORT_XTAL
case GPTIMER_CLK_SRC_XTAL:
counter_src_hz = esp_clk_xtal_freq();
break;
#endif
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "clock source %d is not support", src_clk);
break;
}
timer_ll_set_clock_source(timer->hal.dev, timer_id, src_clk);
unsigned int prescale = counter_src_hz / resolution_hz; // potential resolution loss here
timer_ll_set_clock_prescale(timer->hal.dev, timer_id, prescale);
timer->resolution_hz = counter_src_hz / prescale; // this is the real resolution
if (timer->resolution_hz != resolution_hz) {
ESP_LOGW(TAG, "resolution lost, expect %ul, real %ul", resolution_hz, timer->resolution_hz);
}
return ret;
}
static esp_err_t gptimer_install_interrupt(gptimer_t *timer)
{
esp_err_t ret = ESP_OK;
gptimer_group_t *group = timer->group;
int group_id = group->group_id;
int timer_id = timer->timer_id;
bool new_isr = false;
if (!timer->intr) {
_lock_acquire(&timer->mutex);
if (!timer->intr) {
// if user wants to control the interrupt allocation more precisely, we can expose more flags in `gptimer_config_t`
int extra_isr_flags = timer->flags.intr_shared ? ESP_INTR_FLAG_SHARED : 0;
ret = esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_id].timer_irq_id[timer_id], extra_isr_flags | GPTIMER_INTR_ALLOC_FLAGS,
(uint32_t)timer_ll_get_intr_status_reg(timer->hal.dev), TIMER_LL_EVENT_ALARM(timer_id),
gptimer_default_isr, timer, &timer->intr);
new_isr = (ret == ESP_OK);
}
_lock_release(&timer->mutex);
}
if (new_isr) {
ESP_LOGD(TAG, "install interrupt service for timer (%d,%d)", group_id, timer_id);
}
return ret;
}
// Put the default ISR handler in the IRAM for better performance
IRAM_ATTR static void gptimer_default_isr(void *args)
{
bool need_yield = false;
gptimer_t *timer = (gptimer_t *)args;
gptimer_group_t *group = timer->group;
gptimer_alarm_cb_t on_alarm_cb = timer->on_alarm;
uint32_t intr_status = timer_ll_get_intr_status(timer->hal.dev);
if (intr_status & TIMER_LL_EVENT_ALARM(timer->timer_id)) {
// Note: when alarm event happends, the alarm will be disabled automatically by hardware
gptimer_alarm_event_data_t edata = {
.count_value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id),
.alarm_value = timer->alarm_count,
};
portENTER_CRITICAL_ISR(&group->spinlock);
timer_ll_clear_intr_status(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id));
// for auto-reload, we need to re-enable the alarm manually
if (timer->flags.auto_reload_on_alarm) {
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, true);
}
portEXIT_CRITICAL_ISR(&group->spinlock);
if (on_alarm_cb) {
if (on_alarm_cb(timer, &edata, timer->user_ctx)) {
need_yield = true;
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///// The Following APIs are for internal use only (e.g. unit test) /////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle)
{
ESP_RETURN_ON_FALSE(timer && ret_intr_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
*ret_intr_handle = timer->intr;
return ESP_OK;
}
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock)
{
ESP_RETURN_ON_FALSE(timer && ret_pm_lock, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
*ret_pm_lock = timer->pm_lock;
return ESP_OK;
}
/**
* @brief This function will be called during start up, to check that gptimer driver is not running along with the legacy timer group driver
*/
__attribute__((constructor))
static void check_gptimer_driver_conflict(void)
{
extern int timer_group_driver_init_count;
timer_group_driver_init_count++;
if (timer_group_driver_init_count > 1) {
ESP_EARLY_LOGE(TAG, "CONFLICT! The gptimer driver can't work along with the legacy timer group driver");
abort();
}
}

View File

@ -0,0 +1,197 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "hal/timer_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of General Purpose Timer handle
*/
typedef struct gptimer_t *gptimer_handle_t;
/**
* @brief GPTimer event data
*/
typedef struct {
uint64_t count_value; /*!< Current count value */
uint64_t alarm_value; /*!< Current alarm value */
} gptimer_alarm_event_data_t;
/**
* @brief Timer alarm callback prototype
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @param[in] edata Alarm event data, fed by driver
* @param[in] user_ctx User data, passed from `gptimer_config_t`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*gptimer_alarm_cb_t) (gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
/**
* @brief Group of supported GPTimer callbacks
* @note The callbacks are all running under ISR environment
*/
typedef struct {
gptimer_alarm_cb_t on_alarm; /*!< Timer alarm callback */
} gptimer_event_callbacks_t;
/**
* @brief General Purpose Timer configuration
*/
typedef struct {
gptimer_clock_source_t clk_src; /*!< GPTimer clock source */
gptimer_count_direction_t direction; /*!< Count direction */
uint32_t resolution_hz; /*!< Counter resolution (working frequency) in Hz,
hence, the step size of each count tick equals to (1 / resolution_hz) seconds */
struct {
uint32_t intr_shared: 1; /*!< Set true, the timer interrupt number can be shared with other peripherals */
} flags;
} gptimer_config_t;
/**
* @brief General Purpose Timer alarm configuration
*/
typedef struct {
uint64_t alarm_count; /*!< Alarm target count value */
uint64_t reload_count; /*!< Alarm reload count value, effect only when `auto_reload_on_alarm` is set to true */
struct {
uint32_t auto_reload_on_alarm: 1; /*!< Reload the count value by hardware, immediately at the alarm event */
} flags;
} gptimer_alarm_config_t;
/**
* @brief Create a new General Purpose Timer, and return the handle
*
* @note Once a timer is created, it is placed in the stopped state and will not start until `gptimer_start()` is called.
*
* @param[in] config GPTimer configuration
* @param[out] ret_timer Returned timer handle
* @return
* - ESP_OK: Create GPTimer successfully
* - ESP_ERR_INVALID_ARG: Create GPTimer failed because of invalid argument
* - ESP_ERR_NO_MEM: Create GPTimer failed because out of memory
* - ESP_ERR_NOT_FOUND: Create GPTimer failed because all hardware timers are used up and no more free one
* - ESP_FAIL: Create GPTimer failed because of other error
*/
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer);
/**
* @brief Delete the GPTimer handle
*
* @note A timer must be in a stop state before it can be deleted.
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @return
* - ESP_OK: Delete GPTimer successfully
* - ESP_ERR_INVALID_ARG: Delete GPTimer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Delete GPTimer failed because the timer has not stopped
* - ESP_FAIL: Delete GPTimer failed because of other error
*/
esp_err_t gptimer_del_timer(gptimer_handle_t timer);
/**
* @brief Set GPTimer raw count value
*
* @note When updating the raw count of an active timer, the timer will immediately start counting from the new value.
* @note This function is allowed to run within ISR context
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @param[in] value Count value to be set
* @return
* - ESP_OK: Set GPTimer raw count value successfully
* - ESP_ERR_INVALID_ARG: Set GPTimer raw count value failed because of invalid argument
* - ESP_FAIL: Set GPTimer raw count value failed because of other error
*/
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value);
/**
* @brief Get GPTimer raw count value
*
* @note With the raw count value and the resolution set in the `gptimer_config_t`, you can convert the count value into seconds.
* @note This function is allowed to run within ISR context
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @param[out] value Returned GPTimer count value
* @return
* - ESP_OK: Get GPTimer raw count value successfully
* - ESP_ERR_INVALID_ARG: Get GPTimer raw count value failed because of invalid argument
* - ESP_FAIL: Get GPTimer raw count value failed because of other error
*/
esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value);
/**
* @brief Set callbacks for GPTimer
*
* @note The user registered callbacks are expected to be runnable within ISR context
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @param[in] cbs Group of callback functions
* @param[in] user_data User data, which will be passed to callback functions directly
* @return
* - ESP_OK: Set event callbacks successfully
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
* - ESP_FAIL: Set event callbacks failed because of other error
*/
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data);
/**
* @brief Set alarm event actions for GPTimer.
*
* @note This function is allowed to run within ISR context
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @param[in] config Alarm configuration, especially, set config to NULL means disabling the alarm function
* @return
* - ESP_OK: Set alarm action for GPTimer successfully
* - ESP_ERR_INVALID_ARG: Set alarm action for GPTimer failed because of invalid argument
* - ESP_FAIL: Set alarm action for GPTimer failed because of other error
*/
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config);
/**
* @brief Start GPTimer
*
* @note This function is allowed to run within ISR context
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @return
* - ESP_OK: Start GPTimer successfully
* - ESP_ERR_INVALID_ARG: Start GPTimer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Start GPTimer failed because the timer is not in stop state
* - ESP_FAIL: Start GPTimer failed because of other error
*/
esp_err_t gptimer_start(gptimer_handle_t timer);
/**
* @brief Stop GPTimer
*
* @note This function is allowed to run within ISR context
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @return
* - ESP_OK: Stop GPTimer successfully
* - ESP_ERR_INVALID_ARG: Stop GPTimer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Stop GPTimer failed because the timer is not in start state
* - ESP_FAIL: Stop GPTimer failed because of other error
*/
esp_err_t gptimer_stop(gptimer_handle_t timer);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// DO NOT USE THESE APIS IN YOUR APPLICATIONS
// The following APIs are for internal use, public to other IDF components, but not for users' applications.
#pragma once
#include "esp_err.h"
#include "esp_intr_alloc.h"
#include "esp_pm.h"
#include "driver/gptimer.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get GPTimer interrupt handle
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @param[out] ret_intr_handle Timer's internal interrupt handle
* @return
* - ESP_OK: Get GPTimer interrupt handle successfully
* - ESP_ERR_INVALID_ARG: Get GPTimer interrupt handle failed because of invalid argument
* - ESP_FAIL: Get GPTimer interrupt handle failed because of other error
*/
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle);
/**
* @brief Get GPTimer power management lock
*
* @param[in] timer Timer handle created by `gptimer_new_timer()`
* @param[out] ret_pm_lock Timer's internal power management lock
* @return
* - ESP_OK: Get GPTimer power management lock successfully
* - ESP_ERR_INVALID_ARG: Get GPTimer power management lock failed because of invalid argument
* - ESP_FAIL: Get GPTimer power management lock failed because of other error
*/
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief This count is used to prevent the coexistence of
* the legacy timer group driver (deprecated/driver/timer.h) and the new gptimer driver (driver/gptimer.h).
*/
int timer_group_driver_init_count = 0;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(gptimer_test)
if(CONFIG_GPTIMER_ISR_IRAM_SAFE)
add_custom_target(check_test_app_sections ALL
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
--rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/
--elf-file ${CMAKE_BINARY_DIR}/gptimer_test.elf
find-refs
--from-sections=.iram0.text
--to-sections=.flash.text,.flash.rodata
--exit-code
DEPENDS ${elf}
)
endif()

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
| ----------------- | ----- | -------- | -------- | -------- |

View File

@ -0,0 +1,30 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import glob
import os
import ttfw_idf
from tiny_test_fw import Utility
@ttfw_idf.idf_component_unit_test(env_tag='COMPONENT_UT_GENERIC', target=['esp32', 'esp32s2', 'esp32s3', 'esp32c3'])
def test_component_ut_gptimer(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None
# Get the names of all configs (sdkconfig.ci.* files)
config_files = glob.glob(os.path.join(os.path.dirname(__file__), 'sdkconfig.ci.*'))
config_names = [os.path.basename(s).replace('sdkconfig.ci.', '') for s in config_files]
# Run test once with binaries built for each config
for name in config_names:
Utility.console_log(f'Checking config "{name}"... ', end='')
dut = env.get_dut('gptimer', 'components/driver/test_apps/gptimer', app_config_name=name)
dut.start_app()
stdout = dut.expect('Press ENTER to see the list of tests', full_stdout=True)
dut.write('*')
stdout = dut.expect("Enter next test, or 'enter' to see menu", full_stdout=True, timeout=30)
ttfw_idf.ComponentUTResult.parse_result(stdout,ttfw_idf.TestFormat.UNITY_BASIC)
env.close_dut(dut.name)
Utility.console_log(f'Test config "{name}" done')
if __name__ == '__main__':
test_component_ut_gptimer()

View File

@ -0,0 +1,8 @@
set(srcs "test_app_main.c"
"test_gptimer.c"
"test_gptimer_iram.c")
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES driver unity spi_flash)
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_gptimer" "-u test_app_include_gptimer_iram")

View File

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_heap_caps.h"
// Some resources are lazy allocated in GPTimer driver, the threshold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (-300)
static size_t before_free_8bit;
static size_t before_free_32bit;
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void app_main(void)
{
// ____ ____ _____ _ _____ _
// / ___| _ \_ _(_)_ __ ___ ___ _ __ |_ _|__ ___| |_
// | | _| |_) || | | | '_ ` _ \ / _ \ '__| | |/ _ \/ __| __|
// | |_| | __/ | | | | | | | | | __/ | | | __/\__ \ |_
// \____|_| |_| |_|_| |_| |_|\___|_| |_|\___||___/\__|
printf(" ____ ____ _____ _ _____ _\r\n");
printf(" / ___| _ \\_ _(_)_ __ ___ ___ _ __ |_ _|__ ___| |_\r\n");
printf("| | _| |_) || | | | '_ ` _ \\ / _ \\ '__| | |/ _ \\/ __| __|\r\n");
printf("| |_| | __/ | | | | | | | | | __/ | | | __/\\__ \\ |_\r\n");
printf(" \\____|_| |_| |_|_| |_| |_|\\___|_| |_|\\___||___/\\__|\r\n");
unity_run_menu();
}

View File

@ -0,0 +1,487 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "driver/gptimer.h"
#include "soc/soc_caps.h"
#include "esp_attr.h"
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
#define TEST_ALARM_CALLBACK_ATTR IRAM_ATTR
#else
#define TEST_ALARM_CALLBACK_ATTR
#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE
void test_app_include_gptimer(void)
{
}
TEST_CASE("gptimer_set_get_raw_count", "[gptimer]")
{
gptimer_config_t config = {
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1 * 1000 * 1000,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&config, &timers[i]));
}
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gptimer_new_timer(&config, &timers[0]));
unsigned long long get_value = 0;
printf("check gptimer initial count value\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &get_value));
TEST_ASSERT_EQUAL(0, get_value);
}
unsigned long long set_values[] = {100, 500, 666};
for (size_t j = 0; j < sizeof(set_values) / sizeof(set_values[0]); j++) {
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
printf("set raw count to %llu for gptimer %d\r\n", set_values[j], i);
TEST_ESP_OK(gptimer_set_raw_count(timers[i], set_values[j]));
}
vTaskDelay(pdMS_TO_TICKS(10));
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &get_value));
printf("get raw count of gptimer %d: %llu\r\n", i, get_value);
TEST_ASSERT_EQUAL(set_values[j], get_value);
}
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}
TEST_CASE("gptimer_wallclock_with_various_clock_sources", "[gptimer]")
{
gptimer_clock_source_t test_clk_srcs[] = {
GPTIMER_CLK_SRC_APB,
#if SOC_TIMER_GROUP_SUPPORT_XTAL
GPTIMER_CLK_SRC_XTAL,
#endif // SOC_TIMER_GROUP_SUPPORT_XTAL
};
// test with various clock sources
for (size_t i = 0; i < sizeof(test_clk_srcs) / sizeof(test_clk_srcs[0]); i++) {
gptimer_config_t timer_config = {
.clk_src = test_clk_srcs[i],
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1 * 1000 * 1000,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
}
printf("start timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_start(timers[i]));
}
vTaskDelay(pdMS_TO_TICKS(20)); // 20ms = 20_000 ticks
unsigned long long value = 0;
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
TEST_ASSERT_UINT_WITHIN(1000, 20000, value);
}
printf("stop timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_stop(timers[i]));
}
printf("check whether timers have stopped\r\n");
vTaskDelay(pdMS_TO_TICKS(20));
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
printf("get raw count of gptimer %d: %llu\r\n", i, value);
TEST_ASSERT_UINT_WITHIN(1000, 20000, value);
}
printf("restart timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_start(timers[i]));
}
vTaskDelay(pdMS_TO_TICKS(20));
printf("stop timers again\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_stop(timers[i]));
}
printf("check whether timers have stopped\r\n");
vTaskDelay(pdMS_TO_TICKS(20));
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
printf("get raw count of gptimer %d: %llu\r\n", i, value);
TEST_ASSERT_UINT_WITHIN(2000, 40000, value);
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}
}
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_stop_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
xTaskHandle task_handle = (xTaskHandle)user_data;
BaseType_t high_task_wakeup;
gptimer_stop(timer);
esp_rom_printf("count=%lld @alarm\n", edata->count_value);
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("gptimer_stop_on_alarm", "[gptimer]")
{
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
gptimer_config_t timer_config = {
.resolution_hz = 1 * 1000 * 1000,
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
}
gptimer_event_callbacks_t cbs = {
.on_alarm = test_gptimer_alarm_stop_callback,
};
gptimer_alarm_config_t alarm_config = {};
printf("start timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
alarm_config.alarm_count = 100000 * (i + 1);
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
TEST_ESP_OK(gptimer_start(timers[i]));
printf("alarm value for gptimer %d: %llu\r\n", i, alarm_config.alarm_count);
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
}
printf("check whether the timers have stopped in the ISR\r\n");
vTaskDelay(pdMS_TO_TICKS(20));
unsigned long long value = 0;
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
printf("get raw count of gptimer %d: %llu\r\n", i, value);
TEST_ASSERT_UINT_WITHIN(40, 100000 * (i + 1), value);
}
printf("restart timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
alarm_config.alarm_count = 100000 * (i + 1);
// reset counter value to zero
TEST_ESP_OK(gptimer_set_raw_count(timers[i], 0));
TEST_ESP_OK(gptimer_start(timers[i]));
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
}
printf("check whether the timers have stopped in the ISR\r\n");
vTaskDelay(pdMS_TO_TICKS(20));
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
printf("get raw count of gptimer %d: %llu\r\n", i, value);
TEST_ASSERT_UINT_WITHIN(40, 100000 * (i + 1), value);
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_reload_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
xTaskHandle task_handle = (xTaskHandle)user_data;
BaseType_t high_task_wakeup;
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
// check if the count value has been reloaded
TEST_ASSERT_UINT_WITHIN(20, 100, edata->count_value);
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("gptimer_auto_reload_on_alarm", "[gptimer]")
{
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
gptimer_config_t timer_config = {
.resolution_hz = 1 * 1000 * 1000,
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
}
gptimer_event_callbacks_t cbs = {
.on_alarm = test_gptimer_alarm_reload_callback,
};
gptimer_alarm_config_t alarm_config = {
.reload_count = 100,
.alarm_count = 100000,
.flags.auto_reload_on_alarm = true,
};
printf("start timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
TEST_ESP_OK(gptimer_start(timers[i]));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
// delete should fail if timer is not stopped
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, gptimer_del_timer(timers[i]));
TEST_ESP_OK(gptimer_stop(timers[i]));
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_normal_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
xTaskHandle task_handle = (xTaskHandle)user_data;
BaseType_t high_task_wakeup;
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
// check the count value at alarm event
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("gptimer_one_shot_alarm", "[gptimer]")
{
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
gptimer_config_t timer_config = {
.resolution_hz = 1 * 1000 * 1000,
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
}
gptimer_event_callbacks_t cbs = {
.on_alarm = test_gptimer_alarm_normal_callback,
};
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = 100000, // 100ms
};
printf("start timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
TEST_ESP_OK(gptimer_start(timers[i]));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
// no alarm event should trigger again, as auto-reload is not enabled and alarm value hasn't changed in the isr
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
// the alarm is stopped, but the counter should still work
uint64_t value = 0;
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
TEST_ASSERT_UINT_WITHIN(1000, 1100000, value); // 1100000 = 100ms alarm + 1s delay
TEST_ESP_OK(gptimer_stop(timers[i]));
}
printf("restart timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_start(timers[i]));
// alarm should be triggered immediately as the counter value has across the target alarm value already
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, 0));
TEST_ESP_OK(gptimer_stop(timers[i]));
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_update_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
xTaskHandle task_handle = (xTaskHandle)user_data;
BaseType_t high_task_wakeup;
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
gptimer_alarm_config_t alarm_config = {
.alarm_count = edata->count_value + 100000, // alarm in next 100ms again
};
gptimer_set_alarm_action(timer, &alarm_config);
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("gptimer_update_alarm_dynamically", "[gptimer]")
{
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
gptimer_config_t timer_config = {
.resolution_hz = 1 * 1000 * 1000,
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
}
gptimer_event_callbacks_t cbs = {
.on_alarm = test_gptimer_alarm_update_callback,
};
gptimer_alarm_config_t alarm_config = {
.alarm_count = 100000, // initial alarm count, 100ms
};
printf("start timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
TEST_ESP_OK(gptimer_start(timers[i]));
// check the alarm event for multiple times
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
TEST_ESP_OK(gptimer_stop(timers[i]));
// check there won't be more interrupts triggered than expected
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
}
printf("restart timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_start(timers[i]));
// check the alarm event for multiple times
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
TEST_ESP_OK(gptimer_stop(timers[i]));
// check there won't be more interrupts triggered than expected
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_count_down_reload_alarm_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
xTaskHandle task_handle = (xTaskHandle)user_data;
BaseType_t high_task_wakeup;
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
// check if the count value has been reloaded
TEST_ASSERT_UINT_WITHIN(20, 200000, edata->count_value);
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("gptimer_count_down_reload", "[gptimer]")
{
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
gptimer_config_t timer_config = {
.resolution_hz = 1 * 1000 * 1000,
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_DOWN,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
TEST_ESP_OK(gptimer_set_raw_count(timers[i], 200000));
}
gptimer_event_callbacks_t cbs = {
.on_alarm = test_gptimer_count_down_reload_alarm_callback,
};
gptimer_alarm_config_t alarm_config = {
.reload_count = 200000, // 200ms
.alarm_count = 0,
.flags.auto_reload_on_alarm = true,
};
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
TEST_ESP_OK(gptimer_start(timers[i]));
// check twice, as it's a period event
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ESP_OK(gptimer_stop(timers[i]));
}
printf("restart gptimer with previous configuration\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_start(timers[i]));
// check twice, as it's a period event
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ESP_OK(gptimer_stop(timers[i]));
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_overflow_reload_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
xTaskHandle task_handle = (xTaskHandle)user_data;
BaseType_t high_task_wakeup;
// Note: esp_rom_printf can't print value with 64 bit length, so the following print result is meaningless, but as an incidator for test that the alarm has fired
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("gptimer_overflow", "[gptimer]")
{
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
gptimer_config_t timer_config = {
.resolution_hz = 1 * 1000 * 1000,
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
};
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
}
#if SOC_TIMER_GROUP_COUNTER_BIT_WIDTH == 64
uint64_t reload_at = UINT64_MAX - 100000;
#else
uint64_t reload_at = (1ULL << SOC_TIMER_GROUP_COUNTER_BIT_WIDTH) - 100000;
#endif
gptimer_event_callbacks_t cbs = {
.on_alarm = test_gptimer_overflow_reload_callback,
};
gptimer_alarm_config_t alarm_config = {
.reload_count = reload_at,
.alarm_count = 100000, // 100ms
.flags.auto_reload_on_alarm = true,
};
// The counter should start from [COUNTER_MAX-100000] and overflows to [0] and continue, then reached to alarm value [100000], reloaded to [COUNTER_MAX-100000] automatically
// thus the period should be 200ms
printf("start timers\r\n");
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
// we start from the reload value
TEST_ESP_OK(gptimer_set_raw_count(timers[i], reload_at));
TEST_ESP_OK(gptimer_start(timers[i]));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(400)));
TEST_ESP_OK(gptimer_stop(timers[i]));
}
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_del_timer(timers[i]));
}
}

View File

@ -0,0 +1,107 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "unity.h"
#include "driver/gptimer.h"
#include "esp_spi_flash.h"
#include "soc/soc_caps.h"
void test_app_include_gptimer_iram(void)
{
}
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
typedef struct {
size_t buf_size;
uint8_t *buf;
size_t flash_addr;
size_t repeat_count;
SemaphoreHandle_t done_sem;
} read_task_arg_t;
typedef struct {
size_t delay_time_us;
size_t repeat_count;
} block_task_arg_t;
static bool IRAM_ATTR on_gptimer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
block_task_arg_t *arg = (block_task_arg_t *)user_ctx;
esp_rom_delay_us(arg->delay_time_us);
arg->repeat_count++;
return false;
}
static void flash_read_task(void *varg)
{
read_task_arg_t *arg = (read_task_arg_t *)varg;
for (size_t i = 0; i < arg->repeat_count; i++) {
TEST_ESP_OK(spi_flash_read(arg->flash_addr, arg->buf, arg->buf_size));
}
xSemaphoreGive(arg->done_sem);
vTaskDelete(NULL);
}
TEST_CASE("gptimer_iram_interrupt_safe", "[gptimer]")
{
gptimer_handle_t gptimer = NULL;
const size_t size = 128;
uint8_t *buf = malloc(size);
TEST_ASSERT_NOT_NULL(buf);
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(done_sem);
read_task_arg_t read_arg = {
.buf_size = size,
.buf = buf,
.flash_addr = 0,
.repeat_count = 1000,
.done_sem = done_sem,
};
block_task_arg_t block_arg = {
.repeat_count = 0,
.delay_time_us = 100,
};
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1 * 1000 * 1000,
};
TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer));
gptimer_event_callbacks_t cbs = {
.on_alarm = on_gptimer_alarm_cb,
};
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = 120,
.flags.auto_reload_on_alarm = true,
};
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, &block_arg));
TEST_ESP_OK(gptimer_start(gptimer));
xTaskCreatePinnedToCore(flash_read_task, "read_flash", 2048, &read_arg, 3, NULL, portNUM_PROCESSORS - 1);
// wait for task done
xSemaphoreTake(done_sem, portMAX_DELAY);
printf("alarm callback runs %d times\r\n", block_arg.repeat_count);
TEST_ASSERT_GREATER_THAN(1000, block_arg.repeat_count);
// delete gptimer
TEST_ESP_OK(gptimer_stop(gptimer));
TEST_ESP_OK(gptimer_del_timer(gptimer));
vSemaphoreDelete(done_sem);
free(buf);
// leave time for IDLE task to recycle deleted task
vTaskDelay(2);
}
#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE

View File

@ -0,0 +1,5 @@
CONFIG_COMPILER_DUMP_RTL_FILES=y
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_ISR_IRAM_SAFE=y
# disable log as most of log access rodata string on error, causing RTL check failure
CONFIG_LOG_DEFAULT_LEVEL_NONE=y

View File

@ -0,0 +1,5 @@
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

View File

@ -0,0 +1,2 @@
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT=n

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -10,14 +10,14 @@
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "freertos/FreeRTOS.h"
#include "driver/timer.h"
#include "esp_private/periph_ctrl.h"
#include "driver/timer_types_legacy.h"
#include "hal/timer_hal.h"
#include "hal/timer_ll.h"
#include "hal/check.h"
#include "soc/timer_periph.h"
#include "soc/rtc.h"
#include "soc/timer_group_reg.h"
#include "esp_private/periph_ctrl.h"
static const char *TIMER_TAG = "timer_group";
@ -230,6 +230,42 @@ static void IRAM_ATTR timer_isr_default(void *arg)
}
}
esp_err_t timer_enable_intr(timer_group_t group_num, timer_idx_t timer_num)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), true);
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
return ESP_OK;
}
esp_err_t timer_disable_intr(timer_group_t group_num, timer_idx_t timer_num)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), false);
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
return ESP_OK;
}
esp_err_t timer_isr_register(timer_group_t group_num, timer_idx_t timer_num,
void (*fn)(void *), void *arg, int intr_alloc_flags, timer_isr_handle_t *handle)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
ESP_RETURN_ON_FALSE(fn != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_PARAM_ADDR_ERROR);
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
timer_hal_context_t *hal = &p_timer_obj[group_num][timer_num]->hal;
return esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_num].timer_irq_id[timer_num],
intr_alloc_flags,
(uint32_t)timer_ll_get_intr_status_reg(hal->dev),
TIMER_LL_EVENT_ALARM(timer_num), fn, arg, handle);
}
esp_err_t timer_isr_callback_add(timer_group_t group_num, timer_idx_t timer_num, timer_isr_t isr_handler, void *args, int intr_alloc_flags)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
@ -261,20 +297,6 @@ esp_err_t timer_isr_callback_remove(timer_group_t group_num, timer_idx_t timer_n
return ESP_OK;
}
esp_err_t timer_isr_register(timer_group_t group_num, timer_idx_t timer_num,
void (*fn)(void *), void *arg, int intr_alloc_flags, timer_isr_handle_t *handle)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
ESP_RETURN_ON_FALSE(fn != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_PARAM_ADDR_ERROR);
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
timer_hal_context_t *hal = &p_timer_obj[group_num][timer_num]->hal;
return esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_num].timer_irq_id[timer_num],
intr_alloc_flags,
(uint32_t)timer_ll_get_intr_status_reg(hal->dev),
TIMER_LL_EVENT_ALARM(timer_num), fn, arg, handle);
}
esp_err_t timer_init(timer_group_t group_num, timer_idx_t timer_num, const timer_config_t *config)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
@ -369,28 +391,6 @@ esp_err_t timer_group_intr_disable(timer_group_t group_num, timer_intr_t disable
return ESP_OK;
}
esp_err_t timer_enable_intr(timer_group_t group_num, timer_idx_t timer_num)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), true);
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
return ESP_OK;
}
esp_err_t timer_disable_intr(timer_group_t group_num, timer_idx_t timer_num)
{
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), false);
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
return ESP_OK;
}
/* This function is deprecated */
timer_intr_t IRAM_ATTR timer_group_intr_get_in_isr(timer_group_t group_num)
{
@ -411,17 +411,17 @@ uint32_t IRAM_ATTR timer_group_get_intr_status_in_isr(timer_group_t group_num)
return intr_status;
}
void IRAM_ATTR timer_group_clr_intr_status_in_isr(timer_group_t group_num, timer_idx_t timer_num)
{
timer_ll_clear_intr_status(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num));
}
/* This function is deprecated */
void IRAM_ATTR timer_group_intr_clr_in_isr(timer_group_t group_num, timer_idx_t timer_num)
{
timer_group_clr_intr_status_in_isr(group_num, timer_num);
}
void IRAM_ATTR timer_group_clr_intr_status_in_isr(timer_group_t group_num, timer_idx_t timer_num)
{
timer_ll_clear_intr_status(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num));
}
void IRAM_ATTR timer_group_enable_alarm_in_isr(timer_group_t group_num, timer_idx_t timer_num)
{
timer_ll_enable_alarm(p_timer_obj[group_num][timer_num]->hal.dev, timer_num, true);
@ -474,9 +474,17 @@ esp_err_t IRAM_ATTR timer_spinlock_give(timer_group_t group_num)
return ESP_OK;
}
STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_T0, TIMG_T0_INT_CLR);
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_T1, TIMG_T1_INT_CLR);
#endif
STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_WDT, TIMG_WDT_INT_CLR);
/**
* @brief This function will be called during start up, to check that this legacy timer group driver is not running along with the gptimer driver
*/
__attribute__((constructor))
static void check_legacy_timer_driver_conflict(void)
{
extern int timer_group_driver_init_count;
timer_group_driver_init_count++;
if (timer_group_driver_init_count > 1) {
ESP_EARLY_LOGE(TIMER_TAG, "CONFLICT! The legacy timer group driver can't work along with the gptimer driver");
abort();
}
ESP_EARLY_LOGW(TIMER_TAG, "legacy timer group driver is deprecated, please migrate to use driver/gptimer.h");
}

View File

@ -14,6 +14,7 @@ if(NOT BOOTLOADER_BUILD)
"spi_slave_hal.c"
"spi_slave_hal_iram.c"
"timer_hal.c"
"timer_hal_iram.c"
"ledc_hal.c"
"ledc_hal_iram.c"
"i2c_hal.c"

View File

@ -22,3 +22,5 @@ entries:
if IDF_TARGET_ESP32 = n:
spi_flash_hal_gpspi (noflash)
systimer_hal (noflash)
if GPTIMER_CTRL_FUNC_IN_IRAM = y:
timer_hal_iram (noflash)

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -12,13 +12,3 @@ void timer_hal_init(timer_hal_context_t *hal, uint32_t group_num, uint32_t timer
hal->dev = TIMER_LL_GET_HW(group_num);
hal->timer_id = timer_num;
}
void timer_hal_set_counter_value(timer_hal_context_t *hal, uint64_t load_val)
{
// save current reload value
uint64_t old_reload = timer_ll_get_reload_value(hal->dev, hal->timer_id);
timer_ll_set_reload_value(hal->dev, hal->timer_id, load_val);
timer_ll_trigger_soft_reload(hal->dev, hal->timer_id);
// restore the previous reload value
timer_ll_set_reload_value(hal->dev, hal->timer_id, old_reload);
}

View File

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/timer_hal.h"
#include "hal/timer_ll.h"
void timer_hal_set_counter_value(timer_hal_context_t *hal, uint64_t load_val)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// - `timer_ll_set_reload_value()` will only indicate the `reload_value`
// - `timer_ll_set_reload_value()` + ``timer_ll_trigger_soft_reload()` can update the HW counter value by software
// Therefore, after updating the HW counter value, we need to restore the previous `reload_value`.
// Attention: The following process should be protected by a lock in the driver layer.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// save current reload value
uint64_t old_reload = timer_ll_get_reload_value(hal->dev, hal->timer_id);
timer_ll_set_reload_value(hal->dev, hal->timer_id, load_val);
timer_ll_trigger_soft_reload(hal->dev, hal->timer_id);
// restore the previous reload value
timer_ll_set_reload_value(hal->dev, hal->timer_id, old_reload);
}