mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 17:19:09 -04:00
Merge branch 'refactor/gptimer_isr_logs_opt_int' into 'master'
feat(gptimer): make start and stop function idempotent, also refactored the doc structure Closes IDFGH-11157, IDFGH-12474, IDF-12513, and IDFCI-2734 See merge request espressif/esp-idf!36983
This commit is contained in:
commit
e964cc3ad5
@ -43,7 +43,7 @@ extern void spi_flash_enable_interrupts_caches_and_other_cpu(void);
|
||||
__attribute__((unused))
|
||||
static void s_test_cache_disable_period_us(test_adc_iram_ctx_t *ctx, uint32_t period_us);
|
||||
|
||||
#if CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM && CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#if CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM && CONFIG_GPTIMER_ISR_CACHE_SAFE
|
||||
/*---------------------------------------------------------------
|
||||
ADC oneshot work with cache safe ISR
|
||||
---------------------------------------------------------------*/
|
||||
@ -140,7 +140,7 @@ TEST_CASE("ADC oneshot fast work with ISR and Flash", "[adc_oneshot]")
|
||||
TEST_ESP_OK(gptimer_del_timer(timer));
|
||||
TEST_ESP_OK(adc_oneshot_del_unit(oneshot_handle));
|
||||
}
|
||||
#endif //#if CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM && CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#endif //#if CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM && CONFIG_GPTIMER_ISR_CACHE_SAFE
|
||||
|
||||
#if CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE || CONFIG_GDMA_ISR_IRAM_SAFE
|
||||
#include "esp_adc/adc_continuous.h"
|
||||
|
@ -3,7 +3,7 @@ CONFIG_XTAL_FREQ_26=y
|
||||
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_GPTIMER_ISR_IRAM_SAFE=y
|
||||
CONFIG_GPTIMER_ISR_CACHE_SAFE=y
|
||||
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
|
@ -1,6 +1,6 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_GPTIMER_ISR_IRAM_SAFE=y
|
||||
CONFIG_GPTIMER_ISR_CACHE_SAFE=y
|
||||
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
|
@ -1,6 +1,6 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_GPTIMER_ISR_IRAM_SAFE=y
|
||||
CONFIG_GPTIMER_ISR_CACHE_SAFE=y
|
||||
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
|
@ -1,31 +1,45 @@
|
||||
menu "ESP-Driver:GPTimer Configurations"
|
||||
depends on SOC_GPTIMER_SUPPORTED
|
||||
|
||||
config GPTIMER_ISR_HANDLER_IN_IRAM
|
||||
bool "Place GPTimer ISR handler into IRAM"
|
||||
bool "Place GPTimer ISR handler in IRAM to reduce latency"
|
||||
default y
|
||||
select GPTIMER_OBJ_CACHE_SAFE
|
||||
help
|
||||
Place GPTimer ISR handler into IRAM for better performance and fewer cache misses.
|
||||
Place GPTimer ISR handler in IRAM to reduce latency caused by cache miss.
|
||||
|
||||
config GPTIMER_CTRL_FUNC_IN_IRAM
|
||||
bool "Place GPTimer control functions into IRAM"
|
||||
bool "Place GPTimer control functions in IRAM"
|
||||
default n
|
||||
select GPTIMER_OBJ_CACHE_SAFE
|
||||
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.
|
||||
Place GPTimer control functions (like start/stop) in IRAM, to reduce latency caused by cache miss.
|
||||
If enabled, these functions can also be called when cache is disabled.
|
||||
|
||||
config GPTIMER_ISR_IRAM_SAFE
|
||||
bool "GPTimer ISR IRAM-Safe"
|
||||
config GPTIMER_ISR_CACHE_SAFE
|
||||
bool "Allow GPTimer ISR to execute when cache is disabled"
|
||||
select GPTIMER_ISR_HANDLER_IN_IRAM
|
||||
default n
|
||||
help
|
||||
Ensure the GPTimer interrupt is IRAM-Safe by allowing the interrupt handler to be
|
||||
executable when the cache is disabled (e.g. SPI Flash write).
|
||||
Enable this option to allow the GPTimer Interrupt Service Routine (ISR)
|
||||
to execute even when the cache is disabled. This can be useful in scenarios where the cache
|
||||
might be turned off, but the GPTimer functionality is still required to operate correctly.
|
||||
|
||||
config GPTIMER_ENABLE_DEBUG_LOG
|
||||
bool "Enable debug log"
|
||||
config GPTIMER_OBJ_CACHE_SAFE
|
||||
bool
|
||||
default n
|
||||
help
|
||||
whether to enable the debug log message for GPTimer driver.
|
||||
Note that, this option only controls the GPTimer driver log, won't affect other drivers.
|
||||
This will ensure the GPTimer object will not be allocated from a memory region
|
||||
where its cache can be disabled.
|
||||
|
||||
config GPTIMER_ENABLE_DEBUG_LOG
|
||||
bool "Force enable debug log"
|
||||
default n
|
||||
help
|
||||
If enabled, GPTimer component will:
|
||||
1. ignore the global logging settings
|
||||
2. compile all log messages into the binary
|
||||
3. set the runtime log level to VERBOSE
|
||||
Please enable this option by caution, as it will increase the binary size.
|
||||
|
||||
endmenu
|
||||
|
@ -135,7 +135,7 @@ esp_err_t gptimer_get_captured_count(gptimer_handle_t timer, uint64_t *value);
|
||||
/**
|
||||
* @brief Group of supported GPTimer callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
* @note When CONFIG_GPTIMER_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
|
||||
* @note When CONFIG_GPTIMER_ISR_CACHE_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
|
||||
*/
|
||||
typedef struct {
|
||||
gptimer_alarm_cb_t on_alarm; /*!< Timer alarm callback */
|
||||
|
4
components/esp_driver_gptimer/sdkconfig.rename
Normal file
4
components/esp_driver_gptimer/sdkconfig.rename
Normal file
@ -0,0 +1,4 @@
|
||||
# sdkconfig replacement configurations for deprecated options formatted as
|
||||
# CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION
|
||||
|
||||
CONFIG_GPTIMER_ISR_IRAM_SAFE CONFIG_GPTIMER_ISR_CACHE_SAFE
|
@ -1,27 +1,14 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/lock.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "gptimer_priv.h"
|
||||
|
||||
static const char *TAG = "gptimer";
|
||||
#include "esp_memory_utils.h"
|
||||
|
||||
static void gptimer_default_isr(void *args);
|
||||
|
||||
@ -136,9 +123,6 @@ static esp_err_t gptimer_destroy(gptimer_t *timer)
|
||||
|
||||
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)
|
||||
{
|
||||
#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
gptimer_t *timer = NULL;
|
||||
ESP_RETURN_ON_FALSE(config && ret_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
@ -188,7 +172,7 @@ esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *re
|
||||
timer->direction = config->direction;
|
||||
timer->intr_priority = config->intr_priority;
|
||||
timer->flags.intr_shared = config->flags.intr_shared;
|
||||
ESP_LOGD(TAG, "new gptimer (%d,%d) at %p, resolution=%"PRIu32"Hz", group_id, timer_id, timer, timer->resolution_hz);
|
||||
ESP_LOGD(TAG, "new gptimer (%d,%d) at %p, %zu bytes used", group_id, timer_id, timer, heap_caps_get_allocated_size(timer));
|
||||
*ret_timer = timer;
|
||||
return ESP_OK;
|
||||
|
||||
@ -231,7 +215,9 @@ esp_err_t gptimer_del_timer(gptimer_handle_t timer)
|
||||
|
||||
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer_hal_set_counter_value(&timer->hal, value);
|
||||
@ -241,7 +227,9 @@ esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value
|
||||
|
||||
esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, unsigned long long *value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(timer && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (timer == NULL || value == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
*value = timer_hal_capture_and_get_counter_value(&timer->hal);
|
||||
@ -258,7 +246,9 @@ esp_err_t gptimer_get_resolution(gptimer_handle_t timer, uint32_t *out_resolutio
|
||||
|
||||
esp_err_t gptimer_get_captured_count(gptimer_handle_t timer, uint64_t *value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(timer && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (timer == NULL || value == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
*value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id);
|
||||
@ -274,7 +264,7 @@ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer
|
||||
int group_id = group->group_id;
|
||||
int timer_id = timer->timer_id;
|
||||
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#if CONFIG_GPTIMER_ISR_CACHE_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");
|
||||
}
|
||||
@ -308,14 +298,22 @@ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer
|
||||
|
||||
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config) {
|
||||
#if CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
|
||||
ESP_RETURN_ON_FALSE_ISR(esp_ptr_internal(config), ESP_ERR_INVALID_ARG, TAG, "alarm config struct not in internal RAM");
|
||||
// when the function is placed in IRAM, we expect the config struct is also placed in internal RAM
|
||||
// if the cache is disabled, the function can still access the config struct
|
||||
if (esp_ptr_internal(config) == false) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
#endif
|
||||
// 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_ISR(valid_auto_reload, ESP_ERR_INVALID_ARG, TAG, "reload count can't equal to alarm count");
|
||||
if (valid_auto_reload == false) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer->reload_count = config->reload_count;
|
||||
@ -343,6 +341,7 @@ esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_c
|
||||
esp_err_t gptimer_enable(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
// the only acceptable FSM change: init->enable
|
||||
gptimer_fsm_t expected_fsm = GPTIMER_FSM_INIT;
|
||||
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_ENABLE),
|
||||
ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
||||
@ -363,6 +362,7 @@ esp_err_t gptimer_enable(gptimer_handle_t timer)
|
||||
esp_err_t gptimer_disable(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
// the only acceptable FSM change: enable->init
|
||||
gptimer_fsm_t expected_fsm = GPTIMER_FSM_ENABLE;
|
||||
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_INIT),
|
||||
ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
|
||||
@ -382,7 +382,14 @@ esp_err_t gptimer_disable(gptimer_handle_t timer)
|
||||
|
||||
esp_err_t gptimer_start(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// if the timer is already started, do nothing
|
||||
if (atomic_load(&timer->fsm) == GPTIMER_FSM_RUN) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gptimer_fsm_t expected_fsm = GPTIMER_FSM_ENABLE;
|
||||
if (atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_RUN_WAIT)) {
|
||||
@ -396,7 +403,8 @@ esp_err_t gptimer_start(gptimer_handle_t timer)
|
||||
atomic_store(&timer->fsm, GPTIMER_FSM_RUN);
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
} else {
|
||||
ESP_RETURN_ON_FALSE_ISR(false, ESP_ERR_INVALID_STATE, TAG, "timer is not ready for a new start");
|
||||
// return error if the timer is not in the expected state
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
@ -404,7 +412,15 @@ esp_err_t gptimer_start(gptimer_handle_t timer)
|
||||
|
||||
esp_err_t gptimer_stop(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (timer == NULL) {
|
||||
// not printing error message here because the return value already indicates the error well
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// if the timer is not started, do nothing
|
||||
if (atomic_load(&timer->fsm) == GPTIMER_FSM_ENABLE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gptimer_fsm_t expected_fsm = GPTIMER_FSM_RUN;
|
||||
if (atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_ENABLE_WAIT)) {
|
||||
@ -415,7 +431,8 @@ esp_err_t gptimer_stop(gptimer_handle_t timer)
|
||||
atomic_store(&timer->fsm, GPTIMER_FSM_ENABLE);
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
} else {
|
||||
ESP_RETURN_ON_FALSE_ISR(false, ESP_ERR_INVALID_STATE, TAG, "timer is not running");
|
||||
// return error if the timer is not in the expected state
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
@ -1,18 +1,14 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/lock.h>
|
||||
#include "esp_check.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "esp_private/esp_clk_tree_common.h"
|
||||
#include "esp_private/gptimer.h"
|
||||
#include "gptimer_priv.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
static const char *TAG = "gptimer";
|
||||
#include "esp_private/esp_clk_tree_common.h"
|
||||
|
||||
typedef struct gptimer_platform_t {
|
||||
_lock_t mutex; // platform level mutex lock
|
||||
@ -182,3 +178,11 @@ int gptimer_get_group_id(gptimer_handle_t timer, int *group_id)
|
||||
*group_id = timer->group->group_id;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
|
||||
__attribute__((constructor))
|
||||
static void gptimer_override_default_log_level(void)
|
||||
{
|
||||
esp_log_level_set(TAG, ESP_LOG_VERBOSE);
|
||||
}
|
||||
#endif
|
||||
|
@ -1,26 +1,17 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/lock.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "gptimer_priv.h"
|
||||
#include "hal/timer_ll.h"
|
||||
#include "esp_private/etm_interface.h"
|
||||
|
||||
#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
|
||||
static const char *TAG = "gptimer-etm";
|
||||
|
||||
static esp_err_t gptimer_del_etm_event(esp_etm_event_t *event)
|
||||
{
|
||||
free(event);
|
||||
@ -36,17 +27,16 @@ static esp_err_t gptimer_del_etm_task(esp_etm_task_t *task)
|
||||
esp_err_t gptimer_new_etm_event(gptimer_handle_t timer, const gptimer_etm_event_config_t *config, esp_etm_event_handle_t *out_event)
|
||||
{
|
||||
esp_etm_event_t *event = NULL;
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_GOTO_ON_FALSE(timer && config && out_event, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->event_type < GPTIMER_ETM_EVENT_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid event type");
|
||||
ESP_RETURN_ON_FALSE(timer && config && out_event, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(config->event_type < GPTIMER_ETM_EVENT_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid event type");
|
||||
event = heap_caps_calloc(1, sizeof(esp_etm_event_t), ETM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(event, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM event");
|
||||
ESP_RETURN_ON_FALSE(event, ESP_ERR_NO_MEM, TAG, "no memory for ETM event");
|
||||
|
||||
// get the event ID that can be recognized by ETM hardware
|
||||
gptimer_group_t *group = timer->group;
|
||||
int group_id = group->group_id;
|
||||
int timer_id = timer->timer_id;
|
||||
uint32_t event_id = TIMER_LL_ETM_EVENT_TABLE(group_id, timer_id, config->event_type);
|
||||
ESP_GOTO_ON_FALSE(event_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported event type");
|
||||
|
||||
// fill the ETM event object
|
||||
event->event_id = event_id;
|
||||
@ -54,28 +44,21 @@ esp_err_t gptimer_new_etm_event(gptimer_handle_t timer, const gptimer_etm_event_
|
||||
event->del = gptimer_del_etm_event;
|
||||
*out_event = event;
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (event) {
|
||||
gptimer_del_etm_event(event);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t gptimer_new_etm_task(gptimer_handle_t timer, const gptimer_etm_task_config_t *config, esp_etm_task_handle_t *out_task)
|
||||
{
|
||||
esp_etm_task_t *task = NULL;
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_GOTO_ON_FALSE(timer && config && out_task, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->task_type < GPTIMER_ETM_TASK_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid task type");
|
||||
ESP_RETURN_ON_FALSE(timer && config && out_task, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(config->task_type < GPTIMER_ETM_TASK_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid task type");
|
||||
task = heap_caps_calloc(1, sizeof(esp_etm_task_t), ETM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(task, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM task");
|
||||
ESP_RETURN_ON_FALSE(task, ESP_ERR_NO_MEM, TAG, "no memory for ETM task");
|
||||
|
||||
// get the task ID that can be recognized by ETM hardware
|
||||
gptimer_group_t *group = timer->group;
|
||||
int group_id = group->group_id;
|
||||
int timer_id = timer->timer_id;
|
||||
uint32_t task_id = TIMER_LL_ETM_TASK_TABLE(group_id, timer_id, config->task_type);
|
||||
ESP_GOTO_ON_FALSE(task_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported task type");
|
||||
|
||||
// fill the ETM task object
|
||||
task->task_id = task_id;
|
||||
@ -83,10 +66,4 @@ esp_err_t gptimer_new_etm_task(gptimer_handle_t timer, const gptimer_etm_task_co
|
||||
task->del = gptimer_del_etm_task;
|
||||
*out_task = task;
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (task) {
|
||||
gptimer_del_etm_task(task);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -9,17 +9,25 @@
|
||||
#include <stdint.h>
|
||||
#include <stdatomic.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for gptimer driver
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#endif
|
||||
#include "soc/soc_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
#include "esp_pm.h"
|
||||
#include "soc/timer_periph.h"
|
||||
#include "hal/timer_types.h"
|
||||
#include "hal/timer_hal.h"
|
||||
#include "hal/timer_ll.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
#include "esp_private/sleep_retention.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
|
||||
@ -29,13 +37,13 @@ extern "C" {
|
||||
|
||||
// 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 || CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
|
||||
#if CONFIG_GPTIMER_OBJ_CACHE_SAFE
|
||||
#define GPTIMER_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#else
|
||||
#define GPTIMER_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
#endif
|
||||
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#if CONFIG_GPTIMER_ISR_CACHE_SAFE
|
||||
#define GPTIMER_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
|
||||
#else
|
||||
#define GPTIMER_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
|
||||
@ -53,6 +61,10 @@ extern "C" {
|
||||
#define GPTIMER_CLOCK_SRC_ATOMIC()
|
||||
#endif
|
||||
|
||||
///!< Logging settings
|
||||
#define TAG "gptimer"
|
||||
|
||||
///!< Forward declaration
|
||||
typedef struct gptimer_t gptimer_t;
|
||||
|
||||
typedef struct gptimer_group_t {
|
||||
|
@ -1,7 +1,7 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_gptimer.c")
|
||||
|
||||
if(CONFIG_GPTIMER_ISR_IRAM_SAFE)
|
||||
if(CONFIG_GPTIMER_ISR_CACHE_SAFE)
|
||||
list(APPEND srcs "test_gptimer_iram.c")
|
||||
endif()
|
||||
|
||||
|
@ -13,11 +13,11 @@
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_attr.h"
|
||||
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#if CONFIG_GPTIMER_ISR_CACHE_SAFE
|
||||
#define TEST_ALARM_CALLBACK_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define TEST_ALARM_CALLBACK_ATTR
|
||||
#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#endif // CONFIG_GPTIMER_ISR_CACHE_SAFE
|
||||
|
||||
TEST_CASE("gptimer_set_get_raw_count", "[gptimer]")
|
||||
{
|
||||
@ -299,7 +299,7 @@ TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_normal_callback(gptimer_
|
||||
* Also should account for the inaccuracy of the systick during DFS.
|
||||
*/
|
||||
#if CONFIG_PM_ENABLE
|
||||
#define GPTIMER_ONE_SHOT_ALARM_COUNT_DELTA 15000
|
||||
#define GPTIMER_ONE_SHOT_ALARM_COUNT_DELTA 50000
|
||||
#else
|
||||
#define GPTIMER_ONE_SHOT_ALARM_COUNT_DELTA 1000
|
||||
#endif // CONFIG_PM_ENABLE
|
||||
|
@ -13,11 +13,11 @@
|
||||
#include "driver/gpio_etm.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#if CONFIG_GPTIMER_ISR_CACHE_SAFE
|
||||
#define TEST_ALARM_CALLBACK_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define TEST_ALARM_CALLBACK_ATTR
|
||||
#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#endif // CONFIG_GPTIMER_ISR_CACHE_SAFE
|
||||
|
||||
TEST_ALARM_CALLBACK_ATTR
|
||||
static bool on_gptimer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
|
@ -23,7 +23,7 @@ static void IRAM_ATTR test_delay_post_cache_disable(void *args)
|
||||
esp_rom_delay_us(1000);
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_interrupt_iram_safe", "[gptimer]")
|
||||
TEST_CASE("gptimer works with cache disabled", "[gptimer]")
|
||||
{
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_GPTIMER_ISR_IRAM_SAFE=y
|
||||
CONFIG_GPTIMER_ISR_CACHE_SAFE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_NONE=y
|
||||
# place non-ISR FreeRTOS functions in Flash
|
||||
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
|
@ -68,7 +68,7 @@ Other Peripheral Events
|
||||
|
||||
:SOC_SYSTIMER_SUPPORT_ETM: - You can call :cpp:func:`esp_systick_new_etm_alarm_event` to get the ETM event from RTOS Systick, one per CPU core.
|
||||
:SOC_SYSTIMER_SUPPORT_ETM: - Refer to :doc:`/api-reference/system/esp_timer` for how to get the ETM event handle from esp_timer.
|
||||
:SOC_TIMER_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/gptimer` for how to get the ETM event handle from GPTimer.
|
||||
:SOC_TIMER_SUPPORT_ETM: - Refer to :ref:`gptimer-etm-event-and-task` for how to get the ETM event handle from GPTimer.
|
||||
:SOC_GDMA_SUPPORT_ETM: - Refer to :doc:`/api-reference/system/async_memcpy` for how to get the ETM event handle from async memcpy.
|
||||
:SOC_MCPWM_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/mcpwm` for how to get the ETM event handle from MCPWM.
|
||||
:SOC_ANA_CMPR_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/ana_cmpr` for how to get the ETM event handle from analog comparator.
|
||||
@ -98,7 +98,7 @@ Other Peripheral Tasks
|
||||
|
||||
.. list::
|
||||
|
||||
:SOC_TIMER_SUPPORT_ETM: - Refer to :doc:`GPTimer </api-reference/peripherals/gptimer>` for how to get the ETM task handle from GPTimer.
|
||||
:SOC_TIMER_SUPPORT_ETM: - Refer to :ref:`gptimer-etm-event-and-task` for how to get the ETM task handle from GPTimer.
|
||||
:SOC_TEMPERATURE_SENSOR_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/temp_sensor` for how to get the ETM task handle from temperature sensor.
|
||||
:SOC_I2S_SUPPORTS_ETM: - Refer to :doc:`/api-reference/peripherals/i2s` for how to get the ETM task handle from I2S.
|
||||
|
||||
|
@ -3,350 +3,395 @@ General Purpose Timer (GPTimer)
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
GPTimer (General Purpose Timer) is the driver of {IDF_TARGET_NAME} Timer Group peripheral. The hardware timer features high resolution and flexible alarm action. A timer alarm occurs when the internal counter of a timer reaches a specific target value. At that moment, a user-registered per-timer callback function is triggered.
|
||||
This document introduces the features of the General Purpose Timer (GPTimer) driver in ESP-IDF. The table of contents is as follows:
|
||||
|
||||
General-purpose timers are typically used in the following scenarios:
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
- To run freely like a clock, providing high-resolution timestamps anytime and anywhere;
|
||||
- To generate periodic alarms that trigger events at regular intervals;
|
||||
- To generate one-shot alarms that respond at a specific target time.
|
||||
Overview
|
||||
--------
|
||||
|
||||
Functional Overview
|
||||
-------------------
|
||||
GPTimer is a dedicated driver for the {IDF_TARGET_NAME} [`Timer Group peripheral <{IDF_TARGET_TRM_EN_URL}#timg>`__]. This timer can select different clock sources and prescalers to meet the requirements of nanosecond-level resolution. Additionally, it has flexible timeout alarm functions and allows automatic updating of the count value at the alarm moment, achieving very precise timing cycles.
|
||||
|
||||
The following sections of this document cover the typical steps to install and operate a timer:
|
||||
Based on the **high resolution, high count range, and high response** capabilities of the hardware timer, the main application scenarios of this driver include:
|
||||
|
||||
.. list::
|
||||
- Running freely as a calendar clock to provide timestamp services for other modules
|
||||
- Generating periodic alarms to complete periodic tasks
|
||||
- Generating one-shot alarms, which can be used to implement a monotonic software timer list with asynchronous updates of alarm values
|
||||
- Working with the GPIO module to achieve PWM signal output and input capture
|
||||
- etc.
|
||||
|
||||
- :ref:`gptimer-resource-allocation` - covers which parameters should be set up to get a timer handle and how to recycle the resources when GPTimer finishes working.
|
||||
- :ref:`set-and-get-count-value` - covers how to force the timer counting from a start point and how to get the count value at anytime.
|
||||
- :ref:`set-up-alarm-action` - covers the parameters that should be set up to enable the alarm event.
|
||||
- :ref:`gptimer-register-event-callbacks` - covers how to hook user specific code to the alarm event callback function.
|
||||
- :ref:`enable-and-disable-timer` - covers how to enable and disable the timer.
|
||||
- :ref:`start-and-stop-timer` - shows some typical use cases that start the timer with different alarm behavior.
|
||||
:SOC_TIMER_SUPPORT_ETM: - :ref:`gptimer-etm-event-and-task` - describes what the events and tasks can be connected to the ETM channel.
|
||||
- :ref:`gptimer-power-management` - describes how different source clock selections can affect power consumption.
|
||||
- :ref:`gptimer-iram-safe` - describes tips on how to make the timer interrupt and IO control functions work better along with a disabled cache.
|
||||
- :ref:`gptimer-thread-safety` - lists which APIs are guaranteed to be thread safe by the driver.
|
||||
- :ref:`gptimer-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
.. _gptimer-resource-allocation:
|
||||
This section provides a concise overview of how to use the GPTimer driver. Through practical examples, it demonstrates how to initialize and start a timer, configure alarm events, and register callback functions. The typical usage flow is as follows:
|
||||
|
||||
Resource Allocation
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
.. blockdiag::
|
||||
:scale: 100%
|
||||
:caption: GPTimer driver's general usage flow (click to enlarge)
|
||||
:align: center
|
||||
|
||||
Different ESP chips might have different numbers of independent timer groups, and within each group, there could also be several independent timers. [1]_
|
||||
blockdiag {
|
||||
default_fontsize = 14;
|
||||
node_width = 250;
|
||||
node_height = 80;
|
||||
class emphasis [color = pink, style = dashed];
|
||||
|
||||
A GPTimer instance is represented by :cpp:type:`gptimer_handle_t`. The driver behind manages all available hardware resources in a pool, so that you do not need to care about which timer and which group it belongs to.
|
||||
create [label="gptimer_new_timer"];
|
||||
config [label="gptimer_set_alarm_action \n gptimer_register_event_callbacks"];
|
||||
enable [label="gptimer_enable"];
|
||||
start [label="gptimer_start"];
|
||||
running [label="Timer Running", class="emphasis"]
|
||||
stop [label="gptimer_stop"];
|
||||
disable [label="gptimer_disable"];
|
||||
cleanup [label="gptimer_delete_timer"];
|
||||
|
||||
To install a timer instance, there is a configuration structure that needs to be given in advance: :cpp:type:`gptimer_config_t`:
|
||||
create -> config -> enable -> start -> running -> stop -> disable -> cleanup;
|
||||
enable -> start [folded];
|
||||
stop -> disable [folded];
|
||||
}
|
||||
|
||||
- :cpp:member:`gptimer_config_t::clk_src` selects the source clock for the timer. The available clocks are listed in :cpp:type:`gptimer_clock_source_t`, you can only pick one of them. For the effect on power consumption of different clock source, please refer to Section :ref:`gptimer-power-management`.
|
||||
- :cpp:member:`gptimer_config_t::direction` sets the counting direction of the timer, supported directions are listed in :cpp:type:`gptimer_count_direction_t`, you can only pick one of them.
|
||||
- :cpp:member:`gptimer_config_t::resolution_hz` sets the resolution of the internal counter. Each count step is equivalent to **1 / resolution_hz** seconds.
|
||||
- :cpp:member:`gptimer_config::intr_priority` sets the priority of the timer interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority.
|
||||
- :cpp:member:`gptimer_config_t::allow_pd` configures if the driver allows the system to power down the peripheral in light sleep mode. Before entering sleep, the system will backup the GPTimer register context, which will be restored later when the system exit the sleep mode. Powering down the peripheral can save more power, but at the cost of more memory consumed to save the register context. It's a tradeoff between power consumption and memory consumption. This configuration option relies on specific hardware feature, if you enable it on an unsupported chip, you will see error message like ``not able to power down in light sleep``.
|
||||
- Optional :cpp:member:`gptimer_config_t::intr_shared` sets whether or not mark the timer interrupt source as a shared one. For the pros/cons of a shared interrupt, you can refer to :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`.
|
||||
Creating and Starting a Timer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
With all the above configurations set in the structure, the structure can be passed to :cpp:func:`gptimer_new_timer` which will instantiate the timer instance and return a handle of the timer.
|
||||
|
||||
The function can fail due to various errors such as insufficient memory, invalid arguments, etc. Specifically, when there are no more free timers (i.e., all hardware resources have been used up), then :c:macro:`ESP_ERR_NOT_FOUND` will be returned. The total number of available timers is represented by the :c:macro:`SOC_TIMER_GROUP_TOTAL_TIMERS` and its value depends on the ESP chip.
|
||||
|
||||
If a previously created GPTimer instance is no longer required, you should recycle the timer by calling :cpp:func:`gptimer_del_timer`. This allows the underlying HW timer to be used for other purposes. Before deleting a GPTimer handle, please disable it by :cpp:func:`gptimer_disable` in advance or make sure it has not enabled yet by :cpp:func:`gptimer_enable`.
|
||||
|
||||
Creating a GPTimer Handle with Resolution of 1 MHz
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
First, we need to create a timer instance. The following code shows how to create a timer with a resolution of 1 MHz:
|
||||
|
||||
.. code:: c
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
|
||||
.direction = GPTIMER_COUNT_UP, // Counting direction is up
|
||||
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
|
||||
};
|
||||
// Create a timer instance
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
// Enable the timer
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// Start the timer
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
.. _set-and-get-count-value:
|
||||
When creating a timer instance, we need to configure parameters such as the clock source, counting direction, and resolution through :cpp:type:`gptimer_config_t`. These parameters determine how the timer works. Then, call the :cpp:func:`gptimer_new_timer` function to create a new timer instance, which returns a handle pointing to the new instance. The timer handle is essentially a pointer to the timer memory object, of type :cpp:type:`gptimer_handle_t`.
|
||||
|
||||
Set and Get Count Value
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Here are the other configuration parameters of the :cpp:type:`gptimer_config_t` structure and their explanations:
|
||||
|
||||
When the GPTimer is created, the internal counter will be reset to zero by default. The counter value can be updated asynchronously by :cpp:func:`gptimer_set_raw_count`. The maximum count value is dependent on the bit width of the hardware timer, which is also reflected by the SOC macro :c:macro:`SOC_TIMER_GROUP_COUNTER_BIT_WIDTH`. When updating the raw count of an active timer, the timer will immediately start counting from the new value.
|
||||
|
||||
Count value can be retrieved by :cpp:func:`gptimer_get_raw_count`, at any time.
|
||||
|
||||
.. _set-up-alarm-action:
|
||||
|
||||
Set up Alarm Action
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For most of the use cases of GPTimer, you should set up the alarm action before starting the timer, except for the simple wall-clock scenario, where a free running timer is enough. To set up the alarm action, you should configure several members of :cpp:type:`gptimer_alarm_config_t` based on how you make use of the alarm event:
|
||||
|
||||
- :cpp:member:`gptimer_alarm_config_t::alarm_count` sets the target count value that triggers the alarm event. You should also take the counting direction into consideration when setting the alarm value. Specially, :cpp:member:`gptimer_alarm_config_t::alarm_count` and :cpp:member:`gptimer_alarm_config_t::reload_count` cannot be set to the same value when :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` is true, as keeping reload with a target alarm count is meaningless. Please also note, because of the interrupt latency, it's not recommended to set the alarm period smaller than 5 us.
|
||||
|
||||
- :cpp:member:`gptimer_alarm_config_t::reload_count` sets the count value to be reloaded when the alarm event happens. This configuration only takes effect when :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` is set to true.
|
||||
|
||||
- :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` flag sets whether to enable the auto-reload feature. If enabled, the hardware timer will reload the value of :cpp:member:`gptimer_alarm_config_t::reload_count` into counter immediately when an alarm event happens.
|
||||
|
||||
To make the alarm configurations take effect, you should call :cpp:func:`gptimer_set_alarm_action`. Especially, if :cpp:type:`gptimer_alarm_config_t` is set to ``NULL``, the alarm function will be disabled.
|
||||
- :cpp:member:`gptimer_config_t::clk_src` selects the clock source for the timer. Available clock sources are listed in :cpp:type:`gptimer_clock_source_t`, and only one can be selected. Different clock sources vary in resolution, accuracy, and power consumption.
|
||||
- :cpp:member:`gptimer_config_t::direction` sets the counting direction of the timer. Supported directions are listed in :cpp:type:`gptimer_count_direction_t`, and only one can be selected.
|
||||
- :cpp:member:`gptimer_config_t::resolution_hz` sets the resolution of the internal counter. Each tick is equivalent to **1 / resolution_hz** seconds.
|
||||
- :cpp:member:`gptimer_config_t::intr_priority` sets the interrupt priority. If set to ``0``, a default priority interrupt will be allocated; otherwise, the specified priority will be used.
|
||||
- :cpp:member:`gptimer_config_t::flags` is used to fine-tune some behaviors of the driver, including the following options:
|
||||
- :cpp:member:`gptimer_config_t::flags::allow_pd` configures whether the driver allows the system to power down the peripheral in sleep mode. Before entering sleep, the system will back up the GPTimer register context, which will be restored when the system wakes up. Note that powering down the peripheral can save power but will consume more memory to save the register context. You need to balance power consumption and memory usage. This configuration option depends on specific hardware features. If enabled on an unsupported chip, you will see an error message like ``not able to power down in light sleep``.
|
||||
|
||||
.. note::
|
||||
|
||||
If an alarm value is set and the timer has already exceeded this value, the alarm will be triggered immediately.
|
||||
Note that if all hardware timers in the current chip have been allocated, :cpp:func:`gptimer_new_timer` will return the :c:macro:`ESP_ERR_NOT_FOUND` error.
|
||||
|
||||
.. _gptimer-register-event-callbacks:
|
||||
Before starting the timer, it must be enabled. The enable function :cpp:func:`gptimer_enable` can switch the internal state machine of the driver to the active state, which includes some system service requests/registrations, such as applying for a power management lock. The corresponding disable function is :cpp:func:`gptimer_disable`, which releases all system services.
|
||||
|
||||
Register Event Callbacks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
.. note::
|
||||
|
||||
After the timer starts up, it can generate a specific event (e.g., the "Alarm Event") dynamically. If you have some functions that should be called when the event happens, please hook your function to the interrupt service routine by calling :cpp:func:`gptimer_register_event_callbacks`. All supported event callbacks are listed in :cpp:type:`gptimer_event_callbacks_t`:
|
||||
When calling the :cpp:func:`gptimer_enable` and :cpp:func:`gptimer_disable` functions, they need to be used in pairs. This means you cannot call :cpp:func:`gptimer_enable` or :cpp:func:`gptimer_disable` twice in a row. This pairing principle ensures the correct management and release of resources.
|
||||
|
||||
- :cpp:member:`gptimer_event_callbacks_t::on_alarm` sets a callback function for alarm events. As this function is called within the ISR context, you must ensure that the function does not attempt to block (e.g., by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The function prototype is declared in :cpp:type:`gptimer_alarm_cb_t`.
|
||||
The :cpp:func:`gptimer_start` function is used to start the timer. After starting, the timer will begin counting and will automatically overflow and restart from 0 when it reaches the maximum or minimum value (depending on the counting direction).
|
||||
The :cpp:func:`gptimer_stop` function is used to stop the timer. Note that stopping a timer does not clear the current value of the counter. To clear the counter, use the :cpp:func:`gptimer_set_raw_count` function introduced later.
|
||||
The :cpp:func:`gptimer_start` and :cpp:func:`gptimer_stop` functions follow the idempotent principle. This means that if the timer is already started, calling the :cpp:func:`gptimer_start` function again will have no effect. Similarly, if the timer is already stopped, calling the :cpp:func:`gptimer_stop` function again will have no effect.
|
||||
|
||||
You can save your own context to :cpp:func:`gptimer_register_event_callbacks` as well, via the parameter ``user_data``. The user data will be directly passed to the callback function.
|
||||
.. note::
|
||||
|
||||
This function lazy installs the interrupt service for the timer but not enable it. So please call this function before :cpp:func:`gptimer_enable`, otherwise the :c:macro:`ESP_ERR_INVALID_STATE` error will be returned. See Section :ref:`enable-and-disable-timer` for more information.
|
||||
However, note that when the timer is in the **intermediate state** of starting (the start has begun but not yet completed), if another thread calls the :cpp:func:`gptimer_start` or :cpp:func:`gptimer_stop` function, it will return the :c:macro:`ESP_ERR_INVALID_STATE` error to avoid triggering uncertain behavior.
|
||||
|
||||
.. _enable-and-disable-timer:
|
||||
Setting and Getting the Count Value
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Enable and Disable Timer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
When a timer is newly created, its internal counter value defaults to zero. You can set other count values using the :cpp:func:`gptimer_set_raw_count` function. The maximum count value depends on the bit width of the hardware timer (usually no less than ``54 bits``).
|
||||
|
||||
Before doing IO control to the timer, you need to enable the timer first, by calling :cpp:func:`gptimer_enable`. This function:
|
||||
.. note::
|
||||
|
||||
* Switches the timer driver state from **init** to **enable**.
|
||||
* Enables the interrupt service if it has been lazy installed by :cpp:func:`gptimer_register_event_callbacks`.
|
||||
* Acquires a proper power management lock if a specific clock source (e.g., APB clock) is selected. See Section :ref:`gptimer-power-management` for more information.
|
||||
If the timer is already running, :cpp:func:`gptimer_set_raw_count` will make the timer immediately jump to the new value and start counting from the newly set value.
|
||||
|
||||
Calling :cpp:func:`gptimer_disable` does the opposite, that is, put the timer driver back to the **init** state, disable the interrupts service and release the power management lock.
|
||||
|
||||
.. _start-and-stop-timer:
|
||||
|
||||
Start and Stop Timer
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The basic IO operation of a timer is to start and stop. Calling :cpp:func:`gptimer_start` can make the internal counter work, while calling :cpp:func:`gptimer_stop` can make the counter stop working. The following illustrates how to start a timer with or without an alarm event.
|
||||
|
||||
Calling :cpp:func:`gptimer_start` transits the driver state from **enable** to **run**, and vice versa. You need to make sure the start and stop functions are used in pairs, otherwise, the functions may return :c:macro:`ESP_ERR_INVALID_STATE`. Most of the time, this error means that the timer is already stopped or in the "start protection" state (i.e., :cpp:func:`gptimer_start` is called but not finished).
|
||||
|
||||
Start Timer as a Wall Clock
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The :cpp:func:`gptimer_get_raw_count` function is used to get the current count value of the timer. This count value is the accumulated count since the timer started (assuming it started from 0). Note that the returned value has not been converted to any unit; it is a pure count value. You need to convert the count value to time units based on the actual resolution of the timer. The timer's resolution can be obtained using the :cpp:func:`gptimer_get_resolution` function.
|
||||
|
||||
.. code:: c
|
||||
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
// Retrieve the timestamp at any time
|
||||
// Check the timer's resolution
|
||||
uint32_t resolution_hz;
|
||||
ESP_ERROR_CHECK(gptimer_get_resolution(gptimer, &resolution_hz));
|
||||
// Read the current count value
|
||||
uint64_t count;
|
||||
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
|
||||
// (Optional) Convert the count value to time units (seconds)
|
||||
double time = (double)count / resolution_hz;
|
||||
|
||||
Trigger Period Events
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
Triggering Periodic Alarm Events
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code:: c
|
||||
In addition to the timestamp function, the general-purpose timer also supports alarm functions. The following code shows how to set a periodic alarm that triggers once per second:
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
.. code-block:: c
|
||||
:emphasize-lines: 10-32
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
|
||||
.direction = GPTIMER_COUNT_UP, // Counting direction is up
|
||||
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
|
||||
};
|
||||
// Create a timer instance
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// Retrieve the count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
// Do not introduce complex logics in callbacks
|
||||
// Suggest dealing with event data in the main loop, instead of in this callback
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
// General process for handling event callbacks:
|
||||
// 1. Retrieve user context data from user_ctx (passed in from gptimer_register_event_callbacks)
|
||||
// 2. Get alarm event data from edata, such as edata->count_value
|
||||
// 3. Perform user-defined operations
|
||||
// 4. Return whether a high-priority task was awakened during the above operations to notify the scheduler to switch tasks
|
||||
return false;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0, // counter will reload with 0 on alarm event
|
||||
.alarm_count = 1000000, // period = 1s @resolution 1MHz
|
||||
.flags.auto_reload_on_alarm = true, // enable auto-reload
|
||||
.reload_count = 0, // When the alarm event occurs, the timer will automatically reload to 0
|
||||
.alarm_count = 1000000, // Set the actual alarm period, since the resolution is 1us, 1000000 represents 1s
|
||||
.flags.auto_reload_on_alarm = true, // Enable auto-reload function
|
||||
};
|
||||
// Set the timer's alarm action
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
.on_alarm = example_timer_on_alarm_cb, // Call the user callback function when the alarm event occurs
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
// Register timer event callback functions, allowing user context to be carried
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
// Enable the timer
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// Start the timer
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
Trigger One-Shot Event
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
The :cpp:func:`gptimer_set_alarm_action` function is used to configure the timer's alarm action. When the timer count value reaches the specified alarm value, an alarm event will be triggered. Users can choose to automatically reload the preset count value when the alarm event occurs, thereby achieving periodic alarms.
|
||||
|
||||
.. code:: c
|
||||
Here are the necessary members of the :cpp:type:`gptimer_alarm_config_t` structure and their functions. By configuring these parameters, users can flexibly control the timer's alarm behavior to meet different application needs.
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
- :cpp:member:`gptimer_alarm_config_t::alarm_count` sets the target count value that triggers the alarm event. When the timer count value reaches this value, an alarm event will be triggered. When setting the alarm value, consider the counting direction of the timer. If the current count value has **exceeded** the alarm value, the alarm event will be triggered immediately.
|
||||
- :cpp:member:`gptimer_alarm_config_t::reload_count` sets the count value to be reloaded when the alarm event occurs. This configuration only takes effect when the :cpp:member:`gptimer_alarm_config_t::flags::auto_reload_on_alarm` flag is ``true``. The actual alarm period will be determined by ``|alarm_count - reload_count|``. From a practical application perspective, it is not recommended to set the alarm period to less than 5us.
|
||||
|
||||
.. note::
|
||||
|
||||
Specifically, ``gptimer_set_alarm_action(gptimer, NULL);`` means disabling the timer's alarm function.
|
||||
|
||||
The :cpp:func:`gptimer_register_event_callbacks` function is used to register the timer event callback functions. When the timer triggers a specific event (such as an alarm event), the user-defined callback function will be called. Users can perform custom operations in the callback function, such as sending signals, to achieve more flexible event handling mechanisms. Since the callback function is executed in the interrupt context, avoid performing complex operations (including any operations that may cause blocking) in the callback function to avoid affecting the system's real-time performance. The :cpp:func:`gptimer_register_event_callbacks` function also allows users to pass a context pointer to access user-defined data in the callback function.
|
||||
|
||||
The supported event callback functions for GPTimer are as follows:
|
||||
|
||||
- :cpp:type:`gptimer_alarm_cb_t` alarm event callback function, which has a corresponding data structure :cpp:type:`gptimer_alarm_event_data_t` for passing alarm event-related data:
|
||||
- :cpp:member:`gptimer_alarm_event_data_t::alarm_value` stores the alarm value, which is the target count value that triggers the alarm event.
|
||||
- :cpp:member:`gptimer_alarm_event_data_t::count_value` stores the count value when entering the interrupt handler after the alarm occurs. This value may differ from the alarm value due to interrupt handler delays, and the count value may have been automatically reloaded when the alarm occurred.
|
||||
|
||||
.. note::
|
||||
|
||||
Be sure to register the callback function before calling :cpp:func:`gptimer_enable`, otherwise the timer event will not correctly trigger the interrupt service.
|
||||
|
||||
Triggering One-Shot Alarm Events
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Some application scenarios only require triggering a one-shot alarm interrupt. The following code shows how to set a one-shot alarm that triggers after 1 second:
|
||||
|
||||
.. code-block:: c
|
||||
:emphasize-lines: 12-13,24
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
|
||||
.direction = GPTIMER_COUNT_UP, // Counting direction is up
|
||||
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
|
||||
};
|
||||
// Create a timer instance
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// Stop timer the sooner the better
|
||||
// This is just a demonstration of how to stop the timer when the alarm occurs for the first time
|
||||
gptimer_stop(timer);
|
||||
// Retrieve the count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
// General process for handling event callbacks:
|
||||
// 1. Retrieve user context data from user_ctx (passed in from gptimer_register_event_callbacks)
|
||||
// 2. Get alarm event data from edata, such as edata->count_value
|
||||
// 3. Perform user-defined operations
|
||||
// 4. Return whether a high-priority task was awakened during the above operations to notify the scheduler to switch tasks
|
||||
return false;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 1 * 1000 * 1000, // alarm target = 1s @resolution 1MHz
|
||||
.alarm_count = 1000000, // Set the actual alarm period, since the resolution is 1us, 1000000 represents 1s
|
||||
.flags.auto_reload_on_alarm = false; // Disable auto-reload function
|
||||
};
|
||||
// Set the timer's alarm action
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
.on_alarm = example_timer_on_alarm_cb, // Call the user callback function when the alarm event occurs
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
// Register timer event callback functions, allowing user context to be carried
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
// Enable the timer
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// Start the timer
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
Dynamic Alarm Update
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Unlike periodic alarms, the above code disables the auto-reload function when configuring the alarm behavior. This means that after the alarm event occurs, the timer will not automatically reload to the preset count value but will continue counting until it overflows. If you want the timer to stop immediately after the alarm, you can call :cpp:func:`gptimer_stop` in the callback function.
|
||||
|
||||
Alarm value can be updated dynamically inside the ISR handler callback, by changing :cpp:member:`gptimer_alarm_event_data_t::alarm_value`. Then the alarm value will be updated after the callback function returns.
|
||||
Resource Recycling
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code:: c
|
||||
When the timer is no longer needed, you should call the :cpp:func:`gptimer_delete_timer` function to release software and hardware resources. Before deleting, ensure that the timer is already stopped.
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
Advanced Features
|
||||
-----------------
|
||||
|
||||
After understanding the basic usage, we can further explore more features of the GPTimer driver.
|
||||
|
||||
Dynamic Alarm Value Update
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The GPTimer driver supports dynamically updating the alarm value in the interrupt callback function by calling the :cpp:func:`gptimer_set_alarm_action` function, thereby implementing a monotonic software timer list. The following code shows how to reset the next alarm trigger time when the alarm event occurs:
|
||||
|
||||
.. code-block:: c
|
||||
:emphasize-lines: 12-16
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
|
||||
.direction = GPTIMER_COUNT_UP, // Counting direction is up
|
||||
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
|
||||
};
|
||||
// Create a timer instance
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_data;
|
||||
// Retrieve the count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// reconfigure alarm value
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = edata->alarm_value + 1000000, // alarm in next 1s
|
||||
.alarm_count = edata->alarm_value + 1000000, // Next alarm in 1s from the current alarm
|
||||
};
|
||||
// Update the alarm value
|
||||
gptimer_set_alarm_action(timer, &alarm_config);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 1000000, // initial alarm target = 1s @resolution 1MHz
|
||||
.alarm_count = 1000000, // Set the actual alarm period, since the resolution is 1us, 1000000 represents 1s
|
||||
.flags.auto_reload_on_alarm = false, // Disable auto-reload function
|
||||
};
|
||||
// Set the timer's alarm action
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
.on_alarm = example_timer_on_alarm_cb, // Call the user callback function when the alarm event occurs
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
// Register timer event callback functions, allowing user context to be carried
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
// Enable the timer
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// Start the timer
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
|
||||
.. only:: SOC_TIMER_SUPPORT_ETM
|
||||
|
||||
.. _gptimer-etm-event-and-task:
|
||||
|
||||
ETM Event and Task
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
GPTimer's ETM Events and Tasks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
GPTimer is able to generate various events that can interact with the :doc:`ETM </api-reference/peripherals/etm>` module. The supported events are listed in the :cpp:type:`gptimer_etm_event_type_t`. You can call :cpp:func:`gptimer_new_etm_event` to get the corresponding ETM event handle. Likewise, GPTimer exposes several tasks that can be triggered by other ETM events. The supported tasks are listed in the :cpp:type:`gptimer_etm_task_type_t`. You can call :cpp:func:`gptimer_new_etm_task` to get the corresponding ETM task handle.
|
||||
GPTimer can generate various events that can be connected to the :doc:`ETM </api-reference/peripherals/etm>` module. The event types are listed in :cpp:type:`gptimer_etm_event_type_t`. Users can create an ``ETM event`` handle by calling :cpp:func:`gptimer_new_etm_event`.
|
||||
GPTimer also supports some tasks that can be triggered by other events and executed automatically. The task types are listed in :cpp:type:`gptimer_etm_task_type_t`. Users can create an ``ETM task`` handle by calling :cpp:func:`gptimer_new_etm_task`.
|
||||
|
||||
For how to connect the event and task to an ETM channel, please refer to the :doc:`ETM </api-reference/peripherals/etm>` documentation.
|
||||
|
||||
.. _gptimer-power-management:
|
||||
|
||||
.. only:: not SOC_TIMER_SUPPORT_ETM
|
||||
|
||||
.. _gptimer-power-management:
|
||||
For how to connect the timer events and tasks to the ETM channel, please refer to the :doc:`ETM </api-reference/peripherals/etm>` documentation.
|
||||
|
||||
Power Management
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system may adjust or disable the clock source before going to sleep. As a result, the time keeping will be inaccurate.
|
||||
When power management :ref:`CONFIG_PM_ENABLE` is enabled, the system may adjust or disable the clock source before entering sleep mode, causing the GPTimer to lose accuracy.
|
||||
|
||||
The driver can prevent the above issue by creating a power management lock. The lock type is set based on different clock sources. The driver will acquire the lock in :cpp:func:`gptimer_enable`, and release it in :cpp:func:`gptimer_disable`. So that the timer can work correctly in between these two functions, because the clock source won't be disabled or adjusted its frequency during this time.
|
||||
To prevent this, the GPTimer driver creates a power management lock internally. When the :cpp:func:`gptimer_enable` function is called, the lock is activated to ensure the system does not enter sleep mode, thus maintaining the timer's accuracy. To reduce power consumption, you can call the :cpp:func:`gptimer_disable` function to release the power management lock, allowing the system to enter sleep mode. However, this will stop the timer, so you need to restart the timer after waking up.
|
||||
|
||||
.. only:: SOC_TIMER_SUPPORT_SLEEP_RETENTION
|
||||
|
||||
Besides the potential changes to the clock source, when the power management is enabled, the system can also power down the GPTimer hardware before sleep. Set the :cpp:member:`gptimer_config_t::allow_pd` to ``true`` to enable the power down feature. GPTimer registers will be backed up before sleep and restored after wake up. Please note, enabling this option will increase the memory consumption.
|
||||
|
||||
.. _gptimer-iram-safe:
|
||||
|
||||
IRAM Safe
|
||||
^^^^^^^^^
|
||||
|
||||
By default, the GPTimer interrupt will be deferred when the cache is disabled because of writing or erasing the flash. Thus the alarm interrupt will not get executed in time, which is not expected in a real-time application.
|
||||
|
||||
There is a Kconfig option :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` that:
|
||||
|
||||
- Enables the interrupt being serviced even when the cache is disabled
|
||||
- Places all functions that used by the ISR into IRAM [2]_
|
||||
- Places driver object into DRAM (in case it is mapped to PSRAM by accident)
|
||||
|
||||
This allows the interrupt to run while the cache is disabled, but comes at the cost of increased IRAM consumption.
|
||||
|
||||
There is another Kconfig option :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` that can put commonly used IO control functions into IRAM as well. So, these functions can also be executable when the cache is disabled. These IO control functions are as follows:
|
||||
|
||||
- :cpp:func:`gptimer_start`
|
||||
- :cpp:func:`gptimer_stop`
|
||||
- :cpp:func:`gptimer_get_raw_count`
|
||||
- :cpp:func:`gptimer_set_raw_count`
|
||||
- :cpp:func:`gptimer_set_alarm_action`
|
||||
|
||||
.. _gptimer-thread-safety:
|
||||
Besides disabling the clock source, the system can also power down the GPTimer before entering sleep mode to further reduce power consumption. To achieve this, set :cpp:member:`gptimer_config_t::allow_pd` to ``true``. Before the system enters sleep mode, the GPTimer register context will be backed up to memory and restored after the system wakes up. Note that enabling this option reduces power consumption but increases memory usage. Therefore, you need to balance power consumption and memory usage when using this feature.
|
||||
|
||||
Thread Safety
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
All the APIs provided by the driver are guaranteed to be thread safe, which means you can call them from different RTOS tasks without protection by extra locks. The following functions are allowed to run under ISR context.
|
||||
The driver uses critical sections to ensure atomic operations on registers. Key members in the driver handle are also protected by critical sections. The driver's internal state machine uses atomic instructions to ensure thread safety, with state checks preventing certain invalid concurrent operations (e.g., conflicts between `start` and `stop`). Therefore, GPTimer driver APIs can be used in a multi-threaded environment without extra locking.
|
||||
|
||||
- :cpp:func:`gptimer_start`
|
||||
- :cpp:func:`gptimer_stop`
|
||||
- :cpp:func:`gptimer_get_raw_count`
|
||||
- :cpp:func:`gptimer_set_raw_count`
|
||||
- :cpp:func:`gptimer_get_captured_count`
|
||||
- :cpp:func:`gptimer_set_alarm_action`
|
||||
The following functions can also be used in an interrupt context:
|
||||
|
||||
.. _gptimer-kconfig-options:
|
||||
.. list::
|
||||
|
||||
Kconfig Options
|
||||
^^^^^^^^^^^^^^^
|
||||
- :cpp:func:`gptimer_start`
|
||||
- :cpp:func:`gptimer_stop`
|
||||
- :cpp:func:`gptimer_get_raw_count`
|
||||
- :cpp:func:`gptimer_set_raw_count`
|
||||
- :cpp:func:`gptimer_get_captured_count`
|
||||
- :cpp:func:`gptimer_set_alarm_action`
|
||||
|
||||
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` controls where to place the GPTimer control functions (IRAM or flash).
|
||||
- :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` controls where to place the GPTimer ISR handler (IRAM or flash).
|
||||
- :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` controls whether the default ISR handler should be masked when the cache is disabled, see Section :ref:`gptimer-iram-safe` for more information.
|
||||
- :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size.
|
||||
Cache Safety
|
||||
^^^^^^^^^^^^
|
||||
|
||||
When the file system performs Flash read/write operations, the system temporarily disables the Cache function to avoid errors when loading instructions and data from Flash. This causes the GPTimer interrupt handler to be unresponsive during this period, preventing the user callback function from executing in time. If you want the interrupt handler to run normally when the Cache is disabled, you can enable the :ref:`CONFIG_GPTIMER_ISR_CACHE_SAFE` option.
|
||||
|
||||
.. note::
|
||||
|
||||
Note that when this option is enabled, all interrupt callback functions and their context data **must be placed in internal storage**. This is because the system cannot load data and instructions from Flash when the Cache is disabled.
|
||||
|
||||
Performance
|
||||
^^^^^^^^^^^
|
||||
|
||||
To improve the real-time responsiveness of interrupt handling, the GPTimer driver provides the :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` option. Once enabled, the interrupt handler is placed in internal RAM, reducing delays caused by potential cache misses when loading instructions from Flash.
|
||||
|
||||
.. note::
|
||||
|
||||
However, the user callback function and its context data called by the interrupt handler may still reside in Flash. Cache misses are still possible, so users must manually place the callback function and data in internal RAM, for example by using :c:macro:`IRAM_ATTR` and :c:macro:`DRAM_ATTR`.
|
||||
|
||||
As mentioned above, the GPTimer driver allows some functions to be called in an interrupt context. By enabling the :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` option, these functions can also be placed in IRAM, which helps avoid performance loss caused by cache misses and allows them to be used when the Cache is disabled.
|
||||
|
||||
Other Kconfig Options
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- The :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` option forces the GPTimer driver to enable all debug logs, regardless of the global log level settings. Enabling this option helps developers obtain more detailed log information during debugging, making it easier to locate and solve problems.
|
||||
|
||||
Resource Consumption
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use the :doc:`/api-guides/tools/idf-size` tool to check the code and data consumption of the GPTimer driver. The following are the test conditions (using ESP32-C2 as an example):
|
||||
|
||||
- Compiler optimization level set to ``-Os`` to ensure minimal code size.
|
||||
- Default log level set to ``ESP_LOG_INFO`` to balance debug information and performance.
|
||||
- Disable the following driver optimization options:
|
||||
- :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` - Do not place the interrupt handler in IRAM.
|
||||
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` - Do not place control functions in IRAM.
|
||||
- :ref:`CONFIG_GPTIMER_ISR_CACHE_SAFE` - Do not enable Cache safety options.
|
||||
|
||||
**Note that the following data are not exact values and are for reference only; they may differ on different chip models.**
|
||||
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .text | Flash Data | .rodata |
|
||||
+==================+============+=======+======+=======+=======+============+=======+============+=========+
|
||||
| soc | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 8 |
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
| hal | 206 | 0 | 0 | 0 | 0 | 206 | 206 | 0 | 0 |
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
| driver | 4251 | 12 | 12 | 0 | 0 | 4046 | 4046 | 193 | 193 |
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
|
||||
Additionally, each GPTimer handle dynamically allocates about ``90`` bytes of memory from the heap. If the :cpp:member:`gptimer_config_t::flags::allow_pd` option is enabled, each timer will also consume approximately ``30`` extra bytes of memory during sleep to store the register context.
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
|
||||
.. list::
|
||||
|
||||
* :example:`peripherals/timer_group/gptimer` demonstrates how to use the general purpose timer APIs on ESP SOC chip to generate periodic alarm events and manage different alarm actions.
|
||||
:SOC_TIMER_SUPPORT_ETM: * :example:`peripherals/timer_group/gptimer_capture_hc_sr04` demonstrates how to use the general purpose timer and ETM (Event Task Matrix) peripheral to capture internal timer count values and measure the time between two events, specifically to decode the pulse width signals generated from a common HC-SR04 sonar sensor.
|
||||
:not esp32c2: * :example:`peripherals/timer_group/wiegand_interface` uses two timers (one in one-shot mode and another in periodic mode) to trigger the interrupt and change the output state of the GPIO in the interrupt.
|
||||
|
||||
- :example:`peripherals/timer_group/gptimer` demonstrates how to use the general-purpose timer APIs on ESP SOC chips to generate periodic alarm events and trigger different alarm actions.
|
||||
- :example:`peripherals/timer_group/wiegand_interface` uses two timers (one in one-shot alarm mode and the other in periodic alarm mode) to trigger interrupts and change the GPIO output state in the alarm event callback function, simulating the output waveform of the Wiegand protocol.
|
||||
:SOC_TIMER_SUPPORT_ETM: - :example:`peripherals/timer_group/gptimer_capture_hc_sr04` demonstrates how to use the general-purpose timer and Event Task Matrix (ETM) to accurately capture timestamps of ultrasonic sensor events and convert them into distance information.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
@ -358,9 +403,3 @@ API Reference
|
||||
.. only:: SOC_TIMER_SUPPORT_ETM
|
||||
|
||||
.. include-build-file:: inc/gptimer_etm.inc
|
||||
|
||||
.. [1]
|
||||
Different ESP chip series might have different numbers of GPTimer instances. For more details, please refer to **{IDF_TARGET_NAME} Technical Reference Manual** > Chapter **Timer Group (TIMG)** [`PDF <{IDF_TARGET_TRM_EN_URL}#timg>`__]. The driver does forbid you from applying for more timers, but it returns error when all available hardware resources are used up. Please always check the return value when doing resource allocation (e.g., :cpp:func:`gptimer_new_timer`).
|
||||
|
||||
.. [2]
|
||||
:cpp:member:`gptimer_event_callbacks_t::on_alarm` callback and the functions invoked by the callback should also be placed in IRAM, please take care of them by yourself.
|
||||
|
@ -68,7 +68,7 @@ GPIO **边沿** 事件是最常见的事件类型,任何 GPIO 管脚均可触
|
||||
|
||||
:SOC_SYSTIMER_SUPPORT_ETM: - 调用 :cpp:func:`esp_systick_new_etm_alarm_event` 可以从 RTOS Systick 获取 ETM 事件句柄,每个 CPU 核心可以获取一个事件句柄。
|
||||
:SOC_SYSTIMER_SUPPORT_ETM: - 要了解如何从 esp_timer 获取 ETM 事件句柄,请参阅 :doc:`/api-reference/system/esp_timer`。
|
||||
:SOC_TIMER_SUPPORT_ETM: - 要了解如何从 GPTimer 获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/gptimer`。
|
||||
:SOC_TIMER_SUPPORT_ETM: - 要了解如何从 GPTimer 获取 ETM 事件句柄,请参阅 :ref:`gptimer-etm-event-and-task`。
|
||||
:SOC_GDMA_SUPPORT_ETM: - 要了解如何从 async memcpy 获取 ETM 事件句柄,请参阅 :doc:`/api-reference/system/async_memcpy`。
|
||||
:SOC_MCPWM_SUPPORT_ETM: - 要了解如何从 MCPWM 中获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/mcpwm`。
|
||||
:SOC_ANA_CMPR_SUPPORT_ETM: - 要了解如何从模拟比较器获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/ana_cmpr`。
|
||||
@ -98,7 +98,7 @@ GPIO 任务是最常见的任务类型。一个 GPIO 可以采取一个或多个
|
||||
|
||||
.. list::
|
||||
|
||||
:SOC_TIMER_SUPPORT_ETM: - 要了解如何从 GPTimer 获取 ETM 任务句柄,请参阅 :doc:`/api-reference/peripherals/gptimer`。
|
||||
:SOC_TIMER_SUPPORT_ETM: - 要了解如何从 GPTimer 获取 ETM 任务句柄,请参阅 :ref:`gptimer-etm-event-and-task`。
|
||||
:SOC_TEMPERATURE_SENSOR_SUPPORT_ETM: - 要了解如何从温度传感器获取 ETM 任务句柄,请参阅 :doc:`/api-reference/peripherals/temp_sensor`。
|
||||
:SOC_I2S_SUPPORTS_ETM: - 要了解如何从 I2S 获取 ETM 任务句柄,请参阅 :doc:`/api-reference/peripherals/i2s`。
|
||||
|
||||
|
@ -1,355 +1,400 @@
|
||||
通用定时器
|
||||
=====================
|
||||
通用硬件定时器 (GPTimer)
|
||||
========================
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
简介
|
||||
-----------------
|
||||
|
||||
通用定时器是 {IDF_TARGET_NAME} 定时器组外设的驱动程序。{IDF_TARGET_NAME} 硬件定时器分辨率高,具有灵活的报警功能。定时器内部计数器达到特定目标数值的行为被称为定时器报警。定时器报警时将调用用户注册的不同定时器回调函数。
|
||||
本文介绍了 ESP-IDF 中的通用硬件定时器驱动的功能,章节目录如下:
|
||||
|
||||
通用定时器通常在以下场景中使用:
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
- 如同挂钟一般自由运行,随时随地获取高分辨率时间戳;
|
||||
- 生成周期性警报,定期触发事件;
|
||||
- 生成一次性警报,在目标时间内响应。
|
||||
概述
|
||||
----
|
||||
|
||||
功能概述
|
||||
-----------------
|
||||
通用定时器是 {IDF_TARGET_NAME} [`定时器组外设 <{IDF_TARGET_TRM_CN_URL}#timg>`__]的专用驱动程序。该定时器可以选择不同的时钟源和分频系数,能满足纳秒级的分辨率要求。此外,它还具有灵活的超时报警功能,并允许在报警时刻自动更新计数值,从而实现非常精准的定时周期。
|
||||
|
||||
下文介绍了配置和操作定时器的常规步骤:
|
||||
基于硬件定时器的 **高分辨率、高计数范围和高响应** 的能力,该驱动的主要应用场景包括:
|
||||
|
||||
.. list::
|
||||
- 作为自由运行的挂历时钟,给其他模块提供时间戳服务
|
||||
- 产生周期性警报,完成周期性任务
|
||||
- 产生一次性警报,配合警报值的异步更新,可实现单调型的软件定时器链表
|
||||
- 配合 GPIO 模块,可以实现 PWM 信号输出和输入捕获
|
||||
- 其他
|
||||
|
||||
- :ref:`gptimer-resource-allocation` - 获取定时器句柄应设置的参数,以及如何在通用定时器完成工作时回收资源。
|
||||
- :ref:`set-and-get-count-value` - 如何强制定时器从起点开始计数,以及如何随时获取计数值。
|
||||
- :ref:`set-up-alarm-action` - 启动警报事件应设置的参数。
|
||||
- :ref:`gptimer-register-event-callbacks` - 如何将用户的特定代码挂载到警报事件回调函数。
|
||||
- :ref:`enable-and-disable-timer` - 如何使能和禁用定时器。
|
||||
- :ref:`start-and-stop-timer` - 通过不同报警行为启动定时器的典型使用场景。
|
||||
:SOC_TIMER_SUPPORT_ETM: - :ref:`gptimer-etm-event-and-task` - 定时器提供了哪些事件和任务可以连接到 ETM 通道上。
|
||||
- :ref:`gptimer-power-management` - 选择不同的时钟源将会如何影响功耗。
|
||||
- :ref:`gptimer-iram-safe` - 在 cache 禁用的情况下,如何更好地让定时器处理中断事务以及实现 IO 控制功能。
|
||||
- :ref:`gptimer-thread-safety` - 驱动程序保证哪些 API 线程安全。
|
||||
- :ref:`gptimer-kconfig-options` - 支持的 Kconfig 选项,这些选项会对驱动程序行为产生不同影响。
|
||||
快速入门
|
||||
--------
|
||||
|
||||
.. _gptimer-resource-allocation:
|
||||
本节将带你快速了解如何使用 GPTimer 驱动。通过简单的示例,展示如何创建一个定时器并启动它,如何设置警报事件,以及如何注册事件回调函数。一般的使用流程如下:
|
||||
|
||||
资源分配
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
.. blockdiag::
|
||||
:scale: 100%
|
||||
:caption: GPTimer 驱动的一般使用流程(点击图片查看大图)
|
||||
:align: center
|
||||
|
||||
不同的 ESP 芯片可能有不同数量的独立定时器组,每组内也可能有若干个独立定时器。[1]_
|
||||
blockdiag {
|
||||
default_fontsize = 14;
|
||||
node_width = 250;
|
||||
node_height = 80;
|
||||
class emphasis [color = pink, style = dashed];
|
||||
|
||||
通用定时器实例由 :cpp:type:`gptimer_handle_t` 表示。可用硬件资源汇集在资源池内,由后台驱动程序管理,无需考虑硬件所属的定时器以及定时器组。
|
||||
create [label="gptimer_new_timer"];
|
||||
config [label="gptimer_set_alarm_action \n gptimer_register_event_callbacks"];
|
||||
enable [label="gptimer_enable"];
|
||||
start [label="gptimer_start"];
|
||||
running [label="Timer Running", class="emphasis"]
|
||||
stop [label="gptimer_stop"];
|
||||
disable [label="gptimer_disable"];
|
||||
cleanup [label="gptimer_delete_timer"];
|
||||
|
||||
要安装一个定时器实例,需要提前提供配置结构体 :cpp:type:`gptimer_config_t`:
|
||||
create -> config -> enable -> start -> running -> stop -> disable -> cleanup;
|
||||
enable -> start [folded];
|
||||
stop -> disable [folded];
|
||||
}
|
||||
|
||||
- :cpp:member:`gptimer_config_t::clk_src` 选择定时器的时钟源。:cpp:type:`gptimer_clock_source_t` 中列出多个可用时钟,仅可选择其中一个时钟。了解不同时钟源对功耗的影响,请查看章节 :ref:`gptimer-power-management`。
|
||||
- :cpp:member:`gptimer_config_t::direction` 设置定时器的计数方向,:cpp:type:`gptimer_count_direction_t` 中列出多个支持的方向,仅可选择其中一个方向。
|
||||
- :cpp:member:`gptimer_config_t::resolution_hz` 设置内部计数器的分辨率。计数器每滴答一次相当于 **1 / resolution_hz** 秒。
|
||||
- :cpp:member:`gptimer_config::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。
|
||||
- :cpp:member:`gptimer_config::allow_pd` 配置驱动程序是否允许系统在睡眠模式下关闭外设电源。在进入睡眠之前,系统将备份 GPTimer 寄存器上下文,当系统退出睡眠模式时,这些上下文将被恢复。关闭外设可以节省更多功耗,但代价是消耗更多内存来保存寄存器上下文。你需要在功耗和内存消耗之间做权衡。此配置选项依赖于特定的硬件功能,如果在不支持的芯片上启用它,你将看到类似 ``not able to power down in light sleep`` 的错误消息。
|
||||
- 可选地, :cpp:member:`gptimer_config_t::intr_shared` 设置是否将定时器中断源标记为共享源。了解共享中断的优缺点,请参考 :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`。
|
||||
|
||||
完成上述结构配置之后,可以将结构传递给 :cpp:func:`gptimer_new_timer`,用以实例化定时器实例并返回定时器句柄。
|
||||
|
||||
该函数可能由于内存不足、参数无效等错误而失败。具体来说,当没有更多的空闲定时器(即所有硬件资源已用完)时,将返回 :c:macro:`ESP_ERR_NOT_FOUND`。可用定时器总数由 :c:macro:`SOC_TIMER_GROUP_TOTAL_TIMERS` 表示,不同的 ESP 芯片该数值不同。
|
||||
|
||||
如已不再需要之前创建的通用定时器实例,应通过调用 :cpp:func:`gptimer_del_timer` 回收定时器,以便底层硬件定时器用于其他目的。在删除通用定时器句柄之前,请通过 :cpp:func:`gptimer_disable` 禁用定时器,或者通过 :cpp:func:`gptimer_enable` 确认定时器尚未使能。
|
||||
|
||||
创建分辨率为 1 MHz 的通用定时器句柄
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: c
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
.. _set-and-get-count-value:
|
||||
|
||||
设置和获取计数值
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
创建通用定时器时,内部计数器将默认重置为零。计数值可以通过 :cpp:func:`gptimer_set_raw_count` 异步更新。最大计数值取决于硬件定时器的位宽,这也会在 SOC 宏 :c:macro:`SOC_TIMER_GROUP_COUNTER_BIT_WIDTH` 中有所反映。当更新活动定时器的原始计数值时,定时器将立即从新值开始计数。
|
||||
|
||||
计数值可以随时通过 :cpp:func:`gptimer_get_raw_count` 获取。
|
||||
|
||||
.. _set-up-alarm-action:
|
||||
|
||||
设置警报动作
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
对于大多数通用定时器使用场景而言,应在启动定时器之前设置警报动作,但不包括简单的挂钟场景,该场景仅需自由运行的定时器。设置警报动作,需要根据如何使用警报事件来配置 :cpp:type:`gptimer_alarm_config_t` 的不同参数:
|
||||
|
||||
- :cpp:member:`gptimer_alarm_config_t::alarm_count` 设置触发警报事件的目标计数值。设置警报值时还需考虑计数方向。当 :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 为 ``true`` 时,:cpp:member:`gptimer_alarm_config_t::alarm_count` 和 :cpp:member:`gptimer_alarm_config_t::reload_count` 不能设置为相同的值,因为警报值和重载值相同时没有意义。请注意,由于中断延迟,不建议将警报周期设置为小于 5 微秒。
|
||||
|
||||
- :cpp:member:`gptimer_alarm_config_t::reload_count` 代表警报事件发生时要重载的计数值。此配置仅在 :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 设置为 true 时生效。
|
||||
|
||||
- :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 标志设置是否使能自动重载功能。如果使能,硬件定时器将在警报事件发生时立即将 :cpp:member:`gptimer_alarm_config_t::reload_count` 的值重载到计数器中。
|
||||
|
||||
要使警报配置生效,需要调用 :cpp:func:`gptimer_set_alarm_action`。特别是当 :cpp:type:`gptimer_alarm_config_t` 设置为 ``NULL`` 时,报警功能将被禁用。
|
||||
|
||||
.. 注解::
|
||||
|
||||
如果警报值已设置且定时器超过该值,则会立即触发警报。
|
||||
|
||||
.. _gptimer-register-event-callbacks:
|
||||
|
||||
注册事件回调函数
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,请通过 :cpp:func:`gptimer_register_event_callbacks` 将函数挂载到中断服务例程 (ISR)。:cpp:type:`gptimer_event_callbacks_t` 中列出了所有支持的事件回调函数:
|
||||
|
||||
- :cpp:member:`gptimer_event_callbacks_t::on_alarm` 设置警报事件的回调函数。由于此函数在 ISR 上下文中调用,必须确保该函数不会试图阻塞(例如,确保仅从函数内调用具有 ``ISR`` 后缀的 FreeRTOS API)。函数原型在 :cpp:type:`gptimer_alarm_cb_t` 中有所声明。
|
||||
|
||||
也可以通过参数 ``user_data``,将自己的上下文保存到 :cpp:func:`gptimer_register_event_callbacks` 中。用户数据将直接传递给回调函数。
|
||||
|
||||
此功能将为定时器延迟安装中断服务,但不使能中断服务。所以,请在 :cpp:func:`gptimer_enable` 之前调用这一函数,否则将返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。了解详细信息,请查看章节 :ref:`enable-and-disable-timer`。
|
||||
|
||||
.. _enable-and-disable-timer:
|
||||
|
||||
使能和禁用定时器
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
在对定时器进行 IO 控制之前,需要先调用 :cpp:func:`gptimer_enable` 使能定时器。此函数功能如下:
|
||||
|
||||
* 此函数将把定时器驱动程序的状态从 **init** 切换为 **enable**。
|
||||
* 如果 :cpp:func:`gptimer_register_event_callbacks` 已经延迟安装中断服务,此函数将使能中断服务。
|
||||
* 如果选择了特定的时钟源(例如 APB 时钟),此函数将获取适当的电源管理锁。了解更多信息,请查看章节 :ref:`gptimer-power-management`。
|
||||
|
||||
调用 :cpp:func:`gptimer_disable` 会进行相反的操作,即将定时器驱动程序恢复到 **init** 状态,禁用中断服务并释放电源管理锁。
|
||||
|
||||
.. _start-and-stop-timer:
|
||||
|
||||
启动和停止定时器
|
||||
创建和启动定时器
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
启动和停止是定时器的基本 IO 操作。调用 :cpp:func:`gptimer_start` 可以使内部计数器开始工作,而 :cpp:func:`gptimer_stop` 可以使计数器停止工作。下文说明了如何在存在或不存在警报事件的情况下启动定时器。
|
||||
|
||||
调用 :cpp:func:`gptimer_start` 将使驱动程序状态从 enable 转换为 run, 反之亦然。注意确保 start 和 stop 函数成对使用,否则,函数可能返回 :c:macro:`ESP_ERR_INVALID_STATE`。
|
||||
|
||||
将定时器作为挂钟启动
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
首先,我们需要创建一个定时器实例。以下代码展示了如何创建一个分辨率为 1 MHz 的定时器:
|
||||
|
||||
.. code:: c
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // 选择默认的时钟源
|
||||
.direction = GPTIMER_COUNT_UP, // 计数方向为向上计数
|
||||
.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz,即 1 次滴答为 1 微秒
|
||||
};
|
||||
// 创建定时器实例
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
// 使能定时器
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// 启动定时器
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
// Retrieve the timestamp at anytime
|
||||
|
||||
当创建定时器实例时,我们需要通过 :cpp:type:`gptimer_config_t` 配置时钟源、计数方向和分辨率等参数。这些参数将决定定时器的工作方式。然后调用 :cpp:func:`gptimer_new_timer` 函数创建一个新的定时器实例,该函数将返回一个指向新实例的句柄。定时器的句柄实际上是一个指向定时器内存对象的指针,类型为 :cpp:type:`gptimer_handle_t`。
|
||||
|
||||
以下是 :cpp:type:`gptimer_config_t` 结构体的其他配置参数及其解释:
|
||||
|
||||
- :cpp:member:`gptimer_config_t::clk_src` 选择定时器的时钟源。可用时钟源列在 :cpp:type:`gptimer_clock_source_t` 中,只能选择其中一个。不同的时钟源会在分辨率,精度和功耗上有所不同。
|
||||
- :cpp:member:`gptimer_config_t::direction` 设置定时器的计数方向。支持的方向列在 :cpp:type:`gptimer_count_direction_t` 中,只能选择其中一个。
|
||||
- :cpp:member:`gptimer_config_t::resolution_hz` 设置内部计数器的分辨率。计数器每滴答一次相当于 **1 / resolution_hz** 秒。
|
||||
- :cpp:member:`gptimer_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。
|
||||
- :cpp:member:`gptimer_config_t::flags` 通常用来微调驱动的一些行为,包括以下选项:
|
||||
- :cpp:member:`gptimer_config_t::flags::allow_pd` 配置驱动程序是否允许系统在睡眠模式下关闭外设电源。在进入睡眠之前,系统将备份 GPTimer 寄存器上下文,当系统从睡眠唤醒时时,这些上下文将被恢复。请注意,关闭外设可以节省功耗,但会消耗更多内存来保存寄存器上下文。你需要在功耗和内存消耗之间做权衡。此配置选项依赖于特定的硬件功能,如果在不支持的芯片上启用它,你将看到类似 ``not able to power down in light sleep`` 的错误消息。
|
||||
|
||||
.. note::
|
||||
|
||||
请注意,如果当前芯片中所有的硬件定时器都已经被申请使用,那么 :cpp:func:`gptimer_new_timer` 将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。
|
||||
|
||||
定时器在启动前必须要先使能,使能函数 :cpp:func:`gptimer_enable` 可以将驱动的内部状态机切换到激活状态,这里面还会包括一些系统性服务的申请/注册等工作,如申请电源管理锁。与使能函数相对应的是禁用函数 :cpp:func:`gptimer_disable`,它会释放所有的系统性服务。
|
||||
|
||||
.. note::
|
||||
|
||||
调用 :cpp:func:`gptimer_enable` 和 :cpp:func:`gptimer_disable` 函数时,需要成对使用。这意味着,你不能连续调用两次 :cpp:func:`gptimer_enable` 或 :cpp:func:`gptimer_disable` 函数。这种成对调用的原则确保了资源的正确管理和释放。
|
||||
|
||||
:cpp:func:`gptimer_start` 函数用于启动定时器。启动后,定时器将开始计数,并在计数到达最大值或者最小值时(取决于计数方向)自动溢出,从0开始重新计数。
|
||||
:cpp:func:`gptimer_stop` 函数用于停止定时器。请注意,停止一个定时器并不会将计数器当前的值清零。如果想清零计数器,需要使用后面介绍的函数 :cpp:func:`gptimer_set_raw_count`。
|
||||
:cpp:func:`gptimer_start` 和 :cpp:func:`gptimer_stop` 函数遵循幂等原则。这意味着,如果定时器已经启动,再次调用 :cpp:func:`gptimer_start` 函数不会产生任何效果。同样,如果定时器已经停止,再次调用 :cpp:func:`gptimer_stop` 函数也不会产生任何效果。
|
||||
|
||||
.. note::
|
||||
|
||||
但是请注意,当定时器处于启动的 **中间状态** 时(启动开始了,但还没有启动完毕),此时如果另外一个线程调用 :cpp:func:`gptimer_start` 或者 :cpp:func:`gptimer_stop` 函数,则会返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误,避免触发不确定的行为。
|
||||
|
||||
设置和获取计数值
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
一个刚创建的定时器,其内部计数器值默认为 0。你可以通过 :cpp:func:`gptimer_set_raw_count` 设置其他的计数值。最大计数值取决于硬件定时器的位宽(通常不少于 ``54 bit``)。
|
||||
|
||||
.. note::
|
||||
|
||||
如果定时器已经处于启动状态,:cpp:func:`gptimer_set_raw_count` 会让定时器立即跳到新值处开始计数。
|
||||
|
||||
:cpp:func:`gptimer_get_raw_count` 函数用于获取定时器的当前计数值。这个计数值是定时器从启动以来所累积的计数(假设是从 0 开始启动的话),请注意,返回的数值还没有经过任何单位转换,是一个纯粹的计数值。你需要根据定时器的实际分辨率来把计数值转换成时间单位。定时器的分辨率可以通过 :cpp:func:`gptimer_get_resolution` 函数来获取。
|
||||
|
||||
.. code:: c
|
||||
|
||||
// 查看定时器的分辨率
|
||||
uint32_t resolution_hz;
|
||||
ESP_ERROR_CHECK(gptimer_get_resolution(gptimer, &resolution_hz));
|
||||
// 读取当前计数值
|
||||
uint64_t count;
|
||||
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
|
||||
// (可选的)将计数值转换成时间单位 (秒)
|
||||
double time = (double)count / resolution_hz;
|
||||
|
||||
触发周期性事件
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
触发周期性警报事件
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code:: c
|
||||
除了时间戳功能以外,通用定时器还支持警报功能。以下代码展示了如何设置一个周期性警报,每秒触发一次:
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
.. code-block:: c
|
||||
:emphasize-lines: 10-32
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // 选择默认的时钟源
|
||||
.direction = GPTIMER_COUNT_UP, // 计数方向为向上计数
|
||||
.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz,即 1 次滴答为 1 微秒
|
||||
};
|
||||
// 创建定时器实例
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// Retrieve the count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
// Don't introduce complex logics in callbacks
|
||||
// Suggest dealing with event data in the main loop, instead of in this callback
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
// 处理事件回调的一般流程:
|
||||
// 1. 从 user_ctx 中拿到用户上下文数据(需事先从 gptimer_register_event_callbacks 中传入)
|
||||
// 2. 从 edata 中获取警报事件数据,比如 edata->count_value
|
||||
// 3. 执行用户自定义操作
|
||||
// 4. 返回上述操作期间是否有高优先级的任务被唤醒了,以便通知调度器做切换任务
|
||||
return false;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0, // counter will reload with 0 on alarm event
|
||||
.alarm_count = 1000000, // period = 1s @resolution 1MHz
|
||||
.flags.auto_reload_on_alarm = true, // enable auto-reload
|
||||
.reload_count = 0, // 当警报事件发生时,定时器会自动重载到 0
|
||||
.alarm_count = 1000000, // 设置实际的警报周期,因为分辨率是 1us,所以 1000000 代表 1s
|
||||
.flags.auto_reload_on_alarm = true, // 使能自动重载功能
|
||||
};
|
||||
// 设置定时器的警报动作
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
.on_alarm = example_timer_on_alarm_cb, // 当警报事件发生时,调用用户回调函数
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
// 注册定时器事件回调函数,允许携带用户上下文
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
// 使能定时器
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// 启动定时器
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
触发一次性事件
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
:cpp:func:`gptimer_set_alarm_action` 函数用于配置定时器的警报动作。当定时器计数值达到指定的警报值时,将发出警报事件。用户可以选择在警报事件发生时自动重载预设的计数值,从而实现周期性警报。
|
||||
|
||||
.. code:: c
|
||||
以下是 :cpp:type:`gptimer_alarm_config_t` 结构体中的每个必要成员项的作用,通过配置这些参数,用户可以灵活地控制定时器的警报行为,以满足不同的应用需求。
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
- :cpp:member:`gptimer_alarm_config_t::alarm_count` 设置触发警报事件的目标计数值。当定时器计数值达到该值时,将触发警报事件。设置警报值的时候也需要考虑定时器的计数方向,如果当前计数值已经 **越过** 了警报值,那么警报事件会立刻触发。
|
||||
- :cpp:member:`gptimer_alarm_config_t::reload_count` 设置警报事件发生时要自动重载的计数值。此配置仅在 :cpp:member:`gptimer_alarm_config_t::flags::auto_reload_on_alarm` 标志为 ``true`` 时生效。实际的警报周期将会由 ``|alarm_count - reload_count|`` 决定。从实际应用触发,不建议将警报周期设置成小于 5us。
|
||||
|
||||
.. note::
|
||||
|
||||
特别地, ``gptimer_set_alarm_action(gptimer, NULL);`` 表示关闭定时器的警报功能。
|
||||
|
||||
:cpp:func:`gptimer_register_event_callbacks` 函数用于注册定时器事件的回调函数。当定时器触发特定事件(如警报事件)时,将调用用户定义的回调函数。用户可以在回调函数中执行自定义操作,例如发送信号,从而实现更灵活的事件处理机制。由于回调函数是在中断上下文中执行的,因此在回调函数中应该避免执行复杂的操作(包括任何可能导致阻塞的操作),以免影响系统的实时性。:cpp:func:`gptimer_register_event_callbacks` 还允许用户传递一个上下文指针,以便在回调函数中访问用户定义的数据。
|
||||
|
||||
GPTimer 支持的事件回调函数有下面这些:
|
||||
|
||||
- :cpp:type:`gptimer_alarm_cb_t` 警报事件回调函数,它有一个配套的数据结构 :cpp:type:`gptimer_alarm_event_data_t`,用于传递警报事件的相关数据:
|
||||
- :cpp:member:`gptimer_alarm_event_data_t::alarm_value` 保存的是警报值,即触发警报事件的目标计数值。
|
||||
- :cpp:member:`gptimer_alarm_event_data_t::count_value` 保存的是警报发生后,进入中断处理器时的计数值。该值会不同于警报值,因为中断处理器会带来一定的延迟,并且计数值在警报发生时可能已经被自动重载了。
|
||||
|
||||
.. note::
|
||||
|
||||
请务必在调用 :cpp:func:`gptimer_enable` 之前注册回调函数,否则定时器事件将无法正确触发中断服务。
|
||||
|
||||
触发一次性警报事件
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
还有一些应用场景只需要触发一次警报中断,以下代码展示了如何设置一个一次性警报,在 1 秒后触发:
|
||||
|
||||
.. code-block:: c
|
||||
:emphasize-lines: 12-13,24
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // 选择默认的时钟源
|
||||
.direction = GPTIMER_COUNT_UP, // 计数方向为向上计数
|
||||
.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz,即 1 次滴答为 1 微秒
|
||||
};
|
||||
// 创建定时器实例
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// Stop timer the sooner the better
|
||||
// 这里只是演示如何在警报第一次发生时让定时器停止工作
|
||||
gptimer_stop(timer);
|
||||
// Retrieve the count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
// 处理事件回调的一般流程:
|
||||
// 1. 从 user_ctx 中拿到用户上下文数据(需事先从 gptimer_register_event_callbacks 中传入)
|
||||
// 2. 从 edata 中获取警报事件数据,比如 edata->count_value
|
||||
// 3. 执行用户自定义操作
|
||||
// 4. 返回上述操作期间是否有高优先级的任务被唤醒了,以便通知调度器做切换任务
|
||||
return false;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 1 * 1000 * 1000, // alarm target = 1s @resolution 1MHz
|
||||
.alarm_count = 1000000, // 设置实际的警报周期,因为分辨率是 1us,所以 1000000 代表 1s
|
||||
.flags.auto_reload_on_alarm = false; // 关闭自动重载功能
|
||||
};
|
||||
// 设置定时器的警报动作
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
.on_alarm = example_timer_on_alarm_cb, // 当警报事件发生时,调用用户回调函数
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
// 注册定时器事件回调函数,允许携带用户上下文
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
// 使能定时器
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// 启动定时器
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
警报值动态更新
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
与周期性警报不同,上述代码在配置警报行为时关闭了自动重载功能。这意味着,当警报事件发生后,定时器将不会自动重载到预设的计数值,而是继续计数直到溢出。如果希望定时器在警报后立即停止,可以在回调函数中调用 :cpp:func:`gptimer_stop`。
|
||||
|
||||
通过更改 :cpp:member:`gptimer_alarm_event_data_t::alarm_value`,可以在 ISR 程序回调中动态更新警报值。警报值将在回调函数返回后更新。
|
||||
资源回收
|
||||
^^^^^^^^
|
||||
|
||||
.. code:: c
|
||||
当不再需要使用定时器时,应该调用 :cpp:func:`gptimer_delete_timer` 函数来释放软硬件资源。删除前请确保定时器已经处于停止状态。
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
进阶功能
|
||||
--------
|
||||
|
||||
在了解了基本用法后,我们可以进一步探索 GPTimer 驱动的更多玩法。
|
||||
|
||||
动态更新警报值
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
GPTimer 驱动支持在中断回调函数中调用 :cpp:func:`gptimer_set_alarm_action` 函数来动态更新警报值,从而实现单调型的软件定时器链表。以下代码展示了如何在警报事件发生时,重新设置下一次警报的触发时间:
|
||||
|
||||
.. code-block:: c
|
||||
:emphasize-lines: 12-16
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // 选择默认的时钟源
|
||||
.direction = GPTIMER_COUNT_UP, // 计数方向为向上计数
|
||||
.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz,即 1 次滴答为 1 微秒
|
||||
};
|
||||
// 创建定时器实例
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_data;
|
||||
// Retrieve the count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// reconfigure alarm value
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = edata->alarm_value + 1000000, // alarm in next 1s
|
||||
.alarm_count = edata->alarm_value + 1000000, // 下一次警报在当前警报的基础上加 1s
|
||||
};
|
||||
// 更新警报值
|
||||
gptimer_set_alarm_action(timer, &alarm_config);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 1000000, // initial alarm target = 1s @resolution 1MHz
|
||||
.alarm_count = 1000000, // 设置实际的警报周期,因为分辨率是 1us,所以 1000000 代表 1s
|
||||
.flags.auto_reload_on_alarm = false; // 关闭自动重载功能
|
||||
};
|
||||
// 设置定时器的警报动作
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
.on_alarm = example_timer_on_alarm_cb, // 当警报事件发生时,调用用户回调函数
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
// 注册定时器事件回调函数,允许携带用户上下文
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
// 使能定时器
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
// 启动定时器
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
|
||||
.. only:: SOC_TIMER_SUPPORT_ETM
|
||||
|
||||
.. _gptimer-etm-event-and-task:
|
||||
|
||||
ETM 事件与任务
|
||||
^^^^^^^^^^^^^^
|
||||
GPTimer 的 ETM 事件与任务
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
定时器可以产生多种事件,这些事件可以连接到 :doc:`ETM </api-reference/peripherals/etm>` 模块。:cpp:type:`gptimer_etm_event_type_t` 中列出了定时器能够产生的事件类型。用户可以通过调用 :cpp:func:`gptimer_new_etm_event` 来获得相应事件的 ETM event 句柄。同样地,定时器还公开了一些可被其他事件触发然后自动执行的任务。:cpp:type:`gptimer_etm_task_type_t` 中列出了定时器能够支持的任务类型。 用户可以通过调用 :cpp:func:`gptimer_new_etm_task` 来获得相应任务的 ETM task 句柄。
|
||||
GPTimer 可以生成多种事件,这些事件可以连接到 :doc:`ETM </api-reference/peripherals/etm>` 模块。事件类型列在 :cpp:type:`gptimer_etm_event_type_t` 中。用户可以通过调用 :cpp:func:`gptimer_new_etm_event` 来创建 ``ETM event`` 句柄。
|
||||
GPTimer 还支持一些可由其他事件触发并自动执行的任务。任务类型列在 :cpp:type:`gptimer_etm_task_type_t` 中。用户可以通过调用 :cpp:func:`gptimer_new_etm_task` 来创建 ``ETM task`` 句柄。
|
||||
|
||||
关于如何将定时器事件和任务连接到 ETM 通道中,请参阅 :doc:`ETM </api-reference/peripherals/etm>` 文档。
|
||||
有关如何将定时器事件和任务连接到 ETM 通道,请参阅 :doc:`ETM </api-reference/peripherals/etm>` 文档。
|
||||
|
||||
.. _gptimer-power-management:
|
||||
关于低功耗
|
||||
^^^^^^^^^^
|
||||
|
||||
.. only:: not SOC_TIMER_SUPPORT_ETM
|
||||
当启用电源管理 :ref:`CONFIG_PM_ENABLE` 时,系统在进入睡眠模式前可能会调整或禁用时钟源,从而导致 GPTimer 的计时出错。
|
||||
|
||||
.. _gptimer-power-management:
|
||||
|
||||
电源管理
|
||||
^^^^^^^^
|
||||
|
||||
当电源管理 :ref:`CONFIG_PM_ENABLE` 被启用的时候,系统在进入睡眠前可能会调整或禁用时钟源。结果导致 GPTimer 的计时不准确。
|
||||
|
||||
驱动程序可以通过创建一个电源管理锁来防止上述问题。锁的类型会根据不同的时钟源来设置。驱动程序将在 :cpp:func:`gptimer_enable` 中拿锁,并在 :cpp:func:`gptimer_disable` 中释放锁。这意味着,在这两个函数之间,定时器可以正确工作,因为此时时钟源不会被禁用或改变频率。
|
||||
为了防止这种情况发生, GPTimer 驱动内部创建了一个电源管理锁。当调用 :cpp:func:`gptimer_enable` 函数后,该锁将被激活,确保系统不会进入睡眠模式,从而保持定时器的正确工作。如果需要降低功耗,可以调用 :cpp:func:`gptimer_disable` 函数来释放电源管理锁,使系统能够进入睡眠模式。但是,这样做会导致定时器停止计数,因此在唤醒后需要重新启动定时器。
|
||||
|
||||
.. only:: SOC_TIMER_SUPPORT_SLEEP_RETENTION
|
||||
|
||||
除了时钟源的潜在变化外,当启用电源管理时,系统还可以在睡眠前关闭 GPTimer 电源。将 :cpp:member:`gptimer_config_t::allow_pd` 设置为 ``true`` 以启用电源关闭功能。GPTimer 寄存器将在睡眠前备份,并在唤醒后恢复。请注意,启用此选项会增加内存消耗。
|
||||
除了关闭时钟源外,系统在进入睡眠模式时还可以关闭 GPTimer 的电源以进一步降低功耗。要实现这一点,需要将 :cpp:member:`gptimer_config_t::allow_pd` 设置为 ``true``。在系统进入睡眠模式之前, GPTimer 的寄存器上下文会被备份到内存中,并在系统唤醒后恢复。请注意,启用此选项虽然可以降低功耗,但会增加内存的使用量。因此,在使用该功能时需要在功耗和内存消耗之间进行权衡。
|
||||
|
||||
.. _gptimer-iram-safe:
|
||||
关于线程安全
|
||||
^^^^^^^^^^^^
|
||||
|
||||
IRAM 安全
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
驱动使用了临界区保证了对寄存器的原子操作。句柄内部的关键成员也受临界区保护。驱动内部的状态机使用了原子指令保证了线程安全,通过状态检查还能进一步防止一些不合法的并发操作(例如 `start` 和 `stop` 冲突)。因此, GPTimer 驱动的 API 可以在多线程环境下使用,无需自行加锁。
|
||||
|
||||
默认情况下,当 cache 因写入或擦除 flash 等原因而被禁用时,通用定时器的中断服务将会延迟,造成警报中断无法及时执行。在实时应用程序中通常需要避免这一情况发生。
|
||||
|
||||
调用 Kconfig 选项 :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` 可实现如下功能:
|
||||
|
||||
- 即使禁用 cache 也可使能正在运行的中断
|
||||
- 将 ISR 使用的所有函数放入 IRAM [2]_
|
||||
- 将驱动程序对象放入 DRAM(以防意外映射到 PSRAM)
|
||||
|
||||
这将允许中断在 cache 禁用时运行,但会增加 IRAM 使用量。
|
||||
|
||||
调用另一 Kconfig 选项 :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` 也可将常用的 IO 控制功能放入 IRAM,以便这些函数在 cache 禁用时也能执行。常用的 IO 控制功能如下:
|
||||
|
||||
- :cpp:func:`gptimer_start`
|
||||
- :cpp:func:`gptimer_stop`
|
||||
- :cpp:func:`gptimer_get_raw_count`
|
||||
- :cpp:func:`gptimer_set_raw_count`
|
||||
- :cpp:func:`gptimer_set_alarm_action`
|
||||
|
||||
.. _gptimer-thread-safety:
|
||||
|
||||
线程安全
|
||||
^^^^^^^^
|
||||
|
||||
驱动提供的所有 API 都是线程安全的。使用时,可以直接从不同的 RTOS 任务中调用此类函数,无需额外锁保护。以下这些函数还支持在中断上下文中运行。
|
||||
|
||||
- :cpp:func:`gptimer_start`
|
||||
- :cpp:func:`gptimer_stop`
|
||||
- :cpp:func:`gptimer_get_raw_count`
|
||||
- :cpp:func:`gptimer_set_raw_count`
|
||||
- :cpp:func:`gptimer_get_captured_count`
|
||||
- :cpp:func:`gptimer_set_alarm_action`
|
||||
|
||||
.. _gptimer-kconfig-options:
|
||||
|
||||
Kconfig 选项
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` 控制着定时器控制函数的存放位置(IRAM 或 flash)。
|
||||
- :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` 控制着定时器中断处理函数的存放位置(IRAM 或 flash)。
|
||||
- :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` 控制着中断处理函数是否需要在 cache 关闭的时候被屏蔽掉。更多信息,请参阅 :ref:`gptimer-iram-safe`。
|
||||
- :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` 用于启用调试日志输出。启用这一选项将增加固件二进制文件大小。
|
||||
|
||||
应用示例
|
||||
------------------
|
||||
同时,以下这些函数还允许在中断上下文中使用:
|
||||
|
||||
.. list::
|
||||
|
||||
* :example:`peripherals/timer_group/gptimer` 演示了如何在 ESP 芯片上使用通用定时器 API 生成周期性警报事件,触发不同的警报动作。
|
||||
:SOC_TIMER_SUPPORT_ETM: * :example:`peripherals/timer_group/gptimer_capture_hc_sr04` 展示了如何使用通用定时器和事件任务矩阵 (ETM) 外设来捕获内部定时器计数值,并测量两个事件之间的时间,用于解码常见的 HC-SR04 超声波传感器生成的脉冲宽度信号。
|
||||
:not esp32c2: * :example:`peripherals/timer_group/wiegand_interface` 使用两个定时器(一个在单次触发模式下,另一个在周期触发模式下),来触发中断并在中断中改变 GPIO 的输出状态。
|
||||
- :cpp:func:`gptimer_start`
|
||||
- :cpp:func:`gptimer_stop`
|
||||
- :cpp:func:`gptimer_get_raw_count`
|
||||
- :cpp:func:`gptimer_set_raw_count`
|
||||
- :cpp:func:`gptimer_get_captured_count`
|
||||
- :cpp:func:`gptimer_set_alarm_action`
|
||||
|
||||
关于 Cache 安全
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
在文件系统进行 Flash 读写操作时,为了避免 Cache 从 Flash 加载指令和数据时出现错误,系统会暂时禁用 Cache 功能。这会导致 GPTimer 的中断处理程序在此期间无法响应,从而使用户的回调函数无法及时执行。如果希望在 Cache 被禁用期间,中断处理程序仍能正常运行,可以启用 :ref:`CONFIG_GPTIMER_ISR_CACHE_SAFE` 选项。
|
||||
|
||||
.. note::
|
||||
|
||||
请注意,在启用该选项后,所有的中断回调函数及其上下文数据 **必须存放在内部存储空间** 中。因为在 Cache 被禁用时,系统无法从 Flash 中加载数据和指令。
|
||||
|
||||
关于性能
|
||||
^^^^^^^^
|
||||
|
||||
为了提升中断处理的实时响应能力, GPTimer 驱动提供了 :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` 选项。启用该选项后,中断处理程序将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令时可能出现的缓存丢失带来的延迟。
|
||||
|
||||
.. note::
|
||||
|
||||
但是,中断处理程序调用的用户回调函数和用户上下文数据仍然可能位于 Flash 中,缓存缺失的问题还是会存在,这需要用户自己将回调函数和数据放入内部 RAM 中,比如使用 :c:macro:`IRAM_ATTR` 和 :c:macro:`DRAM_ATTR`。
|
||||
|
||||
前文还提到, GPTimer 驱动允许部分函数在中断上下文中使用。:ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` 选项可以将这些函数放入 IRAM 中,一来,可以避免缓存缺失带来的性能损失,二来,这些函数在 Cache 关闭期间也能使用。
|
||||
|
||||
其他 Kconfig 选项
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` 选项允许强制启用 GPTimer 驱动的所有调试日志,无论全局日志级别设置如何。启用此选项可以帮助开发人员在调试过程中获取更详细的日志信息,从而更容易定位和解决问题。
|
||||
|
||||
关于资源消耗
|
||||
^^^^^^^^^^^^
|
||||
|
||||
使用 :doc:`/api-guides/tools/idf-size` 工具可以查看 GPTimer 驱动的代码和数据消耗。以下是测试前提条件(以 ESP32-C2 为例):
|
||||
|
||||
- 编译器优化等级设置为 ``-Os``,以确保代码尺寸最小化。
|
||||
- 默认日志等级设置为 ``ESP_LOG_INFO``,以平衡调试信息和性能。
|
||||
- 关闭以下驱动优化选项:
|
||||
- :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` - 中断处理程序不放入 IRAM。
|
||||
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` - 控制函数不放入 IRAM。
|
||||
- :ref:`CONFIG_GPTIMER_ISR_CACHE_SAFE` - 不启用 Cache 安全选项。
|
||||
|
||||
**注意,以下数据不是精确值,仅供参考,在不同型号的芯片上,数据会有所出入。**
|
||||
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .text | Flash Data | .rodata |
|
||||
+==================+============+=======+======+=======+=======+============+=======+============+=========+
|
||||
| soc | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 8 |
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
| hal | 206 | 0 | 0 | 0 | 0 | 206 | 206 | 0 | 0 |
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
| driver | 4251 | 12 | 12 | 0 | 0 | 4046 | 4046 | 193 | 193 |
|
||||
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
|
||||
|
||||
此外,每一个 GPTimer 句柄会从 heap 中动态申请约 ``90`` 字节的内存。如果还使能了 :cpp:member:`gptimer_config_t::flags::allow_pd` 选项,那么每个定时器还会在睡眠期间额外消耗约 ``30`` 字节的内存用于保存寄存器上下文。
|
||||
|
||||
应用示例
|
||||
--------
|
||||
|
||||
.. list::
|
||||
|
||||
- :example:`peripherals/timer_group/gptimer` 演示了如何在 ESP 芯片上使用通用定时器 API 生成周期性警报事件,触发不同的警报动作。
|
||||
- :example:`peripherals/timer_group/wiegand_interface` 使用两个定时器(一个在单次警报模式下,另一个在周期警报模式下),触发中断并在警报事件的回调函数中改变 GPIO 的输出状态,从而模拟出了 Wiegand 协议的输出波形。
|
||||
:SOC_TIMER_SUPPORT_ETM: - :example:`peripherals/timer_group/gptimer_capture_hc_sr04` 展示了如何使用通用定时器和事件任务矩阵(ETM)来精确捕获超声波传感器事件的时间戳,并据此换算成距离信息。
|
||||
|
||||
API 参考
|
||||
-------------------
|
||||
--------
|
||||
|
||||
.. include-build-file:: inc/gptimer.inc
|
||||
.. include-build-file:: inc/gptimer_types.inc
|
||||
@ -358,9 +403,3 @@ API 参考
|
||||
.. only:: SOC_TIMER_SUPPORT_ETM
|
||||
|
||||
.. include-build-file:: inc/gptimer_etm.inc
|
||||
|
||||
.. [1]
|
||||
不同 ESP 芯片系列的通用定时器实例数量可能不同。了解详细信息,请参考《{IDF_TARGET_NAME} 技术参考手册》 > 章节定时器组 (TIMG) [`PDF <{IDF_TARGET_TRM_CN_URL}#timg>`__]。驱动程序对通道申请数量不做限制,但当硬件资源用尽时,驱动程序将返回错误。在分配资源时,请务必检查返回值(例如 :cpp:func:`gptimer_new_timer`)。
|
||||
|
||||
.. [2]
|
||||
:cpp:member:`gptimer_event_callbacks_t::on_alarm` 回调函数和这一函数调用的函数也需放在 IRAM 中,请自行处理。
|
||||
|
Loading…
x
Reference in New Issue
Block a user