通用定时器
简介
通用定时器是 ESP32 定时器组外设的驱动程序。ESP32 硬件定时器分辨率高,具有灵活的报警功能。定时器内部计数器达到特定目标数值的行为被称为定时器报警。定时器报警时将调用用户注册的不同定时器回调函数。
通用定时器通常在以下场景中使用:
如同挂钟一般自由运行,随时随地获取高分辨率时间戳;
生成周期性警报,定期触发事件;
生成一次性警报,在目标时间内响应。
功能概述
下文介绍了配置和操作定时器的常规步骤:
资源分配 - 获取定时器句柄应设置的参数,以及如何在通用定时器完成工作时回收资源。
设置和获取计数值 - 如何强制定时器从起点开始计数,以及如何随时获取计数值。
设置警报动作 - 启动警报事件应设置的参数。
注册事件回调函数 - 如何将用户的特定代码挂载到警报事件回调函数。
使能和禁用定时器 - 如何使能和禁用定时器。
启动和停止定时器 - 通过不同报警行为启动定时器的典型使用场景。
电源管理 - 选择不同的时钟源将会如何影响功耗。
IRAM 安全 - 在 cache 禁用的情况下,如何更好地让定时器处理中断事务以及实现 IO 控制功能。
线程安全 - 驱动程序保证哪些 API 线程安全。
Kconfig 选项 - 支持的 Kconfig 选项,这些选项会对驱动程序行为产生不同影响。
资源分配
不同的 ESP 芯片可能有不同数量的独立定时器组,每组内也可能有若干个独立定时器。1
通用定时器实例由 gptimer_handle_t
表示。后台驱动会在资源池中管理所有可用的硬件资源,这样您便无需考虑硬件所属的定时器以及定时器组。
要安装一个定时器实例,需要提前提供配置结构体 gptimer_config_t
:
gptimer_config_t::clk_src
选择定时器的时钟源。gptimer_clock_source_t
中列出多个可用时钟,仅可选择其中一个时钟。了解不同时钟源对功耗的影响,请查看章节 电源管理。gptimer_config_t::direction
设置定时器的计数方向,gptimer_count_direction_t
中列出多个支持的方向,仅可选择其中一个方向。gptimer_config_t::resolution_hz
设置内部计数器的分辨率。计数器每滴答一次相当于 1 / resolution_hz 秒。选用
gptimer_config_t::intr_shared
设置是否将定时器中断源标记为共享源。了解共享中断的优缺点,请参考 Interrupt Handling。
完成上述结构配置之后,可以将结构传递给 gptimer_new_timer()
,用以实例化定时器实例并返回定时器句柄。
该函数可能由于内存不足、参数无效等错误而失败。具体来说,当没有更多的空闲定时器(即所有硬件资源已用完)时,将返回 ESP_ERR_NOT_FOUND
。可用定时器总数由 SOC_TIMER_GROUP_TOTAL_TIMERS
表示,不同的 ESP 芯片该数值不同。
如已不再需要之前创建的通用定时器实例,应通过调用 gptimer_del_timer()
回收定时器,以便底层硬件定时器用于其他目的。在删除通用定时器句柄之前,请通过 gptimer_disable()
禁用定时器,或者通过 gptimer_enable()
确认定时器尚未使能。
创建分辨率为 1 MHz 的通用定时器句柄
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_set_raw_count()
异步更新。最大计数值取决于硬件定时器的位宽,这也会在 SOC 宏 SOC_TIMER_GROUP_COUNTER_BIT_WIDTH
中有所反映。当更新活动定时器的原始计数值时,定时器将立即从新值开始计数。
计数值可以随时通过 gptimer_get_raw_count()
获取。
设置警报动作
对于大多数通用定时器使用场景而言,应在启动定时器之前设置警报动作,但不包括简单的挂钟场景,该场景仅需自由运行的定时器。设置警报动作,需要根据如何使用警报事件来配置 gptimer_alarm_config_t
的不同参数:
gptimer_alarm_config_t::alarm_count
设置触发警报事件的目标计数值。设置警报值时还需考虑计数方向。尤其是当gptimer_alarm_config_t::auto_reload_on_alarm
为 true 时,gptimer_alarm_config_t::alarm_count
和gptimer_alarm_config_t::reload_count
不能设置为相同的值,因为警报值和重载值相同时没有意义。gptimer_alarm_config_t::reload_count
代表警报事件发生时要重载的计数值。此配置仅在gptimer_alarm_config_t::auto_reload_on_alarm
设置为 true 时生效。gptimer_alarm_config_t::auto_reload_on_alarm
标志设置是否使能自动重载功能。如果使能,硬件定时器将在警报事件发生时立即将gptimer_alarm_config_t::reload_count
的值重载到计数器中。
要使警报配置生效,需要调用 gptimer_set_alarm_action()
。特别是当 gptimer_alarm_config_t
设置为 NULL
时,报警功能将被禁用。
备注
如果警报值已设置且定时器超过该值,则会立即触发警报。
注册事件回调函数
定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,请通过 gptimer_register_event_callbacks()
将函数挂载到中断服务例程 (ISR)。gptimer_event_callbacks_t
中列出了所有支持的事件回调函数:
gptimer_event_callbacks_t::on_alarm
设置警报事件的回调函数。由于此函数在 ISR 上下文中调用,必须确保该函数不会试图阻塞(例如,确保仅从函数内调用具有ISR
后缀的 FreeRTOS API)。函数原型在gptimer_alarm_cb_t
中有所声明。
您也可以通过参数 user_data
将自己的上下文保存到 gptimer_register_event_callbacks()
中。用户数据将直接传递给回调函数。
此功能将为定时器延迟安装中断服务,但不使能中断服务。所以,请在 gptimer_enable()
之前调用这一函数,否则将返回 ESP_ERR_INVALID_STATE
错误。了解详细信息,请查看章节 使能和禁用定时器。
使能和禁用定时器
在对定时器进行 IO 控制之前,需要先调用 gptimer_enable()
使能定时器。此函数功能如下:
此函数将把定时器驱动程序的状态从 init 切换为 enable。
如果
gptimer_register_event_callbacks()
已经延迟安装中断服务,此函数将使能中断服务。如果选择了特定的时钟源(例如 APB 时钟),此函数将获取适当的电源管理锁。了解更多信息,请查看章节 电源管理。
调用 gptimer_disable()
会进行相反的操作,即将定时器驱动程序恢复到 init 状态,禁用中断服务并释放电源管理锁。
启动和停止定时器
启动和停止是定时器的基本 IO 操作。调用 gptimer_start()
可以使内部计数器开始工作,而 gptimer_stop()
可以使计数器停止工作。下文说明了如何在存在或不存在警报事件的情况下启动定时器。
将定时器作为挂钟启动
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
// Retrieve the timestamp at anytime
uint64_t count;
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
触发周期性事件
typedef struct {
uint64_t event_count;
} example_queue_element_t;
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;
}
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
};
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
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
触发一次性事件
typedef struct {
uint64_t event_count;
} example_queue_element_t;
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;
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = 1 * 1000 * 1000, // alarm target = 1s @resolution 1MHz
};
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
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
警报值动态更新
通过更改 gptimer_alarm_event_data_t::alarm_value
,可以在 ISR 程序回调中动态更新警报值。警报值将在回调函数返回后更新。
typedef struct {
uint64_t event_count;
} example_queue_element_t;
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
};
gptimer_set_alarm_action(timer, &alarm_config);
// return whether we need to yield at the end of ISR
return high_task_awoken == pdTRUE;
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = 1000000, // initial alarm target = 1s @resolution 1MHz
};
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
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config));
电源管理
当使能电源管理时(即 CONFIG_PM_ENABLE 已打开),系统将在进入 Light-sleep 模式之前调整 APB 频率,从而可能会改变通用定时器的计数步骤周期,导致计时不准确。
然而,驱动程序可以通过获取类型为 ESP_PM_APB_FREQ_MAX
的电源管理锁来阻止系统更改 APB 频率。每当驱动程序创建一个通用定时器实例,且该实例选择 GPTIMER_CLK_SRC_APB
作为其时钟源的时,驱动程序会确保在通过 gptimer_enable()
使能定时器时,已经获取了电源管理锁。同样,当为该定时器调用 gptimer_disable()
时,驱动程序会释放电源管理锁。
如果选择 GPTIMER_CLK_SRC_XTAL
等其他时钟源,那么驱动程序不会安装电源管理锁。只要时钟源仍可提供足够的分辨率,XTAL 时钟源就更适合低功耗应用。
IRAM 安全
默认情况下,当 cache 因写入或擦除 flash 等原因而被禁用时,通用定时器的中断服务将会延迟,造成警报中断无法及时执行。在实时应用程序中通常需要避免这一情况发生。
调用 Kconfig 选项 CONFIG_GPTIMER_ISR_IRAM_SAFE 可实现如下功能:
即使禁用 cache 也可使能正在运行的中断
将 ISR 使用的所有函数放入 IRAM 2
将驱动程序对象放入 DRAM(以防意外映射到 PSRAM)
这将允许中断在 cache 禁用时运行,但会增加 IRAM 使用量。
调用另一 Kconfig 选项 CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM 也可将常用的 IO 控制功能放入 IRAM,以便这些函数在 cache 禁用时也能执行。常用的 IO 控制功能如下:
线程安全
驱动程序会保证工厂函数 gptimer_new_timer()
的线程安全,这意味着您可以从不同的 RTOS 任务中调用这一函数,而无需额外的锁保护。
由于驱动程序通过使用临界区来防止这些函数在任务和 ISR 中同时被调用,所以以下函数能够在 ISR 上下文中运行。
将 gptimer_handle_t
作为第一个位置参数的其他函数不被视作线程安全,也就是说应该避免从多个任务中调用这些函数。
Kconfig 选项
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM 控制放置通用定时器控制函数(IRAM 或 flash)的位置。了解更多信息,请参考章节 IRAM 安全。
CONFIG_GPTIMER_ISR_IRAM_SAFE 控制默认 ISR 程序在 cache 禁用时是否可以运行。了解更多信息,请参考章节 IRAM 安全。
CONFIG_GPTIMER_ENABLE_DEBUG_LOG 用于启用调试日志输出。启用这一选项将增加固件二进制文件大小。
应用示例
示例 peripherals/timer_group/gptimer 中列出了通用定时器的典型用例。
API 参考
Header File
Functions
-
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)
Create a new General Purpose Timer, and return the handle.
备注
The newly created timer is put in the init state.
- 参数
config – [in] GPTimer configuration
ret_timer – [out] Returned timer handle
- 返回
ESP_OK: Create GPTimer successfully
ESP_ERR_INVALID_ARG: Create GPTimer failed because of invalid argument
ESP_ERR_NO_MEM: Create GPTimer failed because out of memory
ESP_ERR_NOT_FOUND: Create GPTimer failed because all hardware timers are used up and no more free one
ESP_FAIL: Create GPTimer failed because of other error
-
esp_err_t gptimer_del_timer(gptimer_handle_t timer)
Delete the GPTimer handle.
备注
A timer can’t be in the enable state when this function is invoked. See also
gptimer_disable()
for how to disable a timer.- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
- 返回
ESP_OK: Delete GPTimer successfully
ESP_ERR_INVALID_ARG: Delete GPTimer failed because of invalid argument
ESP_ERR_INVALID_STATE: Delete GPTimer failed because the timer is not in init state
ESP_FAIL: Delete GPTimer failed because of other error
-
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value)
Set GPTimer raw count value.
备注
When updating the raw count of an active timer, the timer will immediately start counting from the new value.
备注
This function is allowed to run within ISR context
备注
This function is allowed to be executed when Cache is disabled, by enabling
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
value – [in] Count value to be set
- 返回
ESP_OK: Set GPTimer raw count value successfully
ESP_ERR_INVALID_ARG: Set GPTimer raw count value failed because of invalid argument
ESP_FAIL: Set GPTimer raw count value failed because of other error
-
esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value)
Get GPTimer raw count value.
备注
With the raw count value and the resolution set in the
gptimer_config_t
, you can convert the count value into seconds.备注
This function is allowed to run within ISR context
备注
This function is allowed to be executed when Cache is disabled, by enabling
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
value – [out] Returned GPTimer count value
- 返回
ESP_OK: Get GPTimer raw count value successfully
ESP_ERR_INVALID_ARG: Get GPTimer raw count value failed because of invalid argument
ESP_FAIL: Get GPTimer raw count value failed because of other error
-
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data)
Set callbacks for GPTimer.
备注
User registered callbacks are expected to be runnable within ISR context
备注
The first call to this function needs to be before the call to
gptimer_enable
备注
User can deregister a previously registered callback by calling this function and setting the callback member in the
cbs
structure to NULL.- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
cbs – [in] Group of callback functions
user_data – [in] User data, which will be passed to callback functions directly
- 返回
ESP_OK: Set event callbacks successfully
ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
ESP_ERR_INVALID_STATE: Set event callbacks failed because the timer is not in init state
ESP_FAIL: Set event callbacks failed because of other error
-
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config)
Set alarm event actions for GPTimer.
备注
This function is allowed to run within ISR context, so that user can set new alarm action immediately in the ISR callback.
备注
This function is allowed to be executed when Cache is disabled, by enabling
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
config – [in] Alarm configuration, especially, set config to NULL means disabling the alarm function
- 返回
ESP_OK: Set alarm action for GPTimer successfully
ESP_ERR_INVALID_ARG: Set alarm action for GPTimer failed because of invalid argument
ESP_FAIL: Set alarm action for GPTimer failed because of other error
-
esp_err_t gptimer_enable(gptimer_handle_t timer)
Enable GPTimer.
备注
This function will transit the timer state from init to enable.
备注
This function will enable the interrupt service, if it’s lazy installed in
gptimer_register_event_callbacks()
.备注
This function will acquire a PM lock, if a specific source clock (e.g. APB) is selected in the
gptimer_config_t
, whileCONFIG_PM_ENABLE
is enabled.备注
Enable a timer doesn’t mean to start it. See also
gptimer_start()
for how to make the timer start counting.- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
- 返回
ESP_OK: Enable GPTimer successfully
ESP_ERR_INVALID_ARG: Enable GPTimer failed because of invalid argument
ESP_ERR_INVALID_STATE: Enable GPTimer failed because the timer is already enabled
ESP_FAIL: Enable GPTimer failed because of other error
-
esp_err_t gptimer_disable(gptimer_handle_t timer)
Disable GPTimer.
备注
This function will do the opposite work to the
gptimer_enable()
备注
Disable a timer doesn’t mean to stop it. See also
gptimer_stop()
for how to make the timer stop counting.- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
- 返回
ESP_OK: Disable GPTimer successfully
ESP_ERR_INVALID_ARG: Disable GPTimer failed because of invalid argument
ESP_ERR_INVALID_STATE: Disable GPTimer failed because the timer is not enabled yet
ESP_FAIL: Disable GPTimer failed because of other error
-
esp_err_t gptimer_start(gptimer_handle_t timer)
Start GPTimer (internal counter starts counting)
备注
This function should be called when the timer is in the enable state (i.e. after calling
gptimer_enable()
)备注
This function is allowed to run within ISR context
备注
This function will be placed into IRAM if
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
is on, so that it’s allowed to be executed when Cache is disabled- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
- 返回
ESP_OK: Start GPTimer successfully
ESP_ERR_INVALID_ARG: Start GPTimer failed because of invalid argument
ESP_ERR_INVALID_STATE: Start GPTimer failed because the timer is not enabled yet
ESP_FAIL: Start GPTimer failed because of other error
-
esp_err_t gptimer_stop(gptimer_handle_t timer)
Stop GPTimer (internal counter stops counting)
备注
This function should be called when the timer is in the enable state (i.e. after calling
gptimer_enable()
)备注
This function is allowed to run within ISR context
备注
This function will be placed into IRAM if
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
is on, so that it’s allowed to be executed when Cache is disabled- 参数
timer – [in] Timer handle created by
gptimer_new_timer()
- 返回
ESP_OK: Stop GPTimer successfully
ESP_ERR_INVALID_ARG: Stop GPTimer failed because of invalid argument
ESP_ERR_INVALID_STATE: Stop GPTimer failed because the timer is not enabled yet
ESP_FAIL: Stop GPTimer failed because of other error
Structures
-
struct gptimer_alarm_event_data_t
GPTimer alarm event data.
-
struct gptimer_event_callbacks_t
Group of supported GPTimer callbacks.
备注
The callbacks are all running under ISR environment
备注
When CONFIG_GPTIMER_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
Public Members
-
gptimer_alarm_cb_t on_alarm
Timer alarm callback
-
gptimer_alarm_cb_t on_alarm
-
struct gptimer_config_t
General Purpose Timer configuration.
Public Members
-
gptimer_clock_source_t clk_src
GPTimer clock source
-
gptimer_count_direction_t direction
Count direction
-
uint32_t resolution_hz
Counter resolution (working frequency) in Hz, hence, the step size of each count tick equals to (1 / resolution_hz) seconds
Set true, the timer interrupt number can be shared with other peripherals
-
struct gptimer_config_t::[anonymous] flags
GPTimer config flags
-
gptimer_clock_source_t clk_src
-
struct gptimer_alarm_config_t
General Purpose Timer alarm configuration.
Public Members
-
uint64_t alarm_count
Alarm target count value
-
uint64_t reload_count
Alarm reload count value, effect only when
auto_reload_on_alarm
is set to true
-
uint32_t auto_reload_on_alarm
Reload the count value by hardware, immediately at the alarm event
-
struct gptimer_alarm_config_t::[anonymous] flags
Alarm config flags
-
uint64_t alarm_count
Type Definitions
-
typedef struct gptimer_t *gptimer_handle_t
Type of General Purpose Timer handle.
-
typedef bool (*gptimer_alarm_cb_t)(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
Timer alarm callback prototype.
- Param timer
[in] Timer handle created by
gptimer_new_timer()
- Param edata
[in] Alarm event data, fed by driver
- Param user_ctx
[in] User data, passed from
gptimer_register_event_callbacks()
- Return
Whether a high priority task has been waken up by this function
Header File
Type Definitions
-
typedef soc_periph_gptimer_clk_src_t gptimer_clock_source_t
GPTimer clock source.
备注
User should select the clock source based on the power and resolution requirement
Enumerations
-
enum gptimer_count_direction_t
GPTimer count direction.
Values:
-
enumerator GPTIMER_COUNT_DOWN
Decrease count value
-
enumerator GPTIMER_COUNT_UP
Increase count value
-
enumerator GPTIMER_COUNT_DOWN
- 1
不同 ESP 芯片系列的通用定时器实例数量可能不同。了解详细信息,请参考《ESP32 技术参考手册》 > 章节定时器组 (TIMG) [PDF]。驱动程序不会禁止您申请更多的定时器,但是当所有可用的硬件资源用完时将会返回错误。在分配资源时,请务必检查返回值(例如
gptimer_new_timer()
)。- 2
gptimer_event_callbacks_t::on_alarm
回调函数和这一函数调用的函数也需放在 IRAM 中,请自行处理。