中断不仅能由GPIO触发,也可以由硬件定时器触发,接下来为大家演示:
一、新建工程
新建一个名为"timer"的工程并打开,如图所示:



二、完善CMake.txt与main.c文件
CMake.txt:
cpp
idf_component_register(SRCS "main.c"
PRIV_REQUIRES driver freertos driver
INCLUDE_DIRS ".")
main.c:
cpp
#include "driver/gptimer.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// ===================== 硬件参数配置 =====================
#define TIMER_FREQ_HZ 1000000 // 定时器时钟频率:1MHz
#define TIMER_PERIOD_MS 1000 // 定时周期:1000毫秒(1秒)
#define LED_PIN GPIO_NUM_18 // LED引脚
#define GPTIMER_NUM GPTIMER_NUM_0 // 使用0号GPTimer
// 全局变量:定时器句柄、信号量句柄
gptimer_handle_t gptimer = NULL;
SemaphoreHandle_t timer_sem = NULL;
int led_level = 0; // 本地变量维护电平,不依赖读取引脚
// ===================== 定时器中断回调函数 =====================
bool IRAM_ATTR gptimer_isr_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 检查信号量句柄有效性,释放信号量
if (timer_sem != NULL) {
xSemaphoreGiveFromISR(timer_sem, &xHigherPriorityTaskWoken);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
return true;
}
// ===================== LED初始化 =====================
void led_hardware_init(void) {
gpio_config_t gpio_conf = {
.pin_bit_mask = 1ULL << LED_PIN,
.mode = GPIO_MODE_OUTPUT, // 输出模式
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&gpio_conf);
gpio_set_level(LED_PIN, 0); // 初始熄灭LED
}
// ===================== GPTimer初始化 =====================
void hardware_gptimer_init(void) {
// 1. 定时器基础配置
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = TIMER_FREQ_HZ,
.intr_priority = 0,
.flags = {
.intr_shared = false,
.allow_pd = false
}
};
gptimer_new_timer(&timer_config, &gptimer);
// 2. 闹钟配置
gptimer_alarm_config_t alarm_config = {
.alarm_count = TIMER_PERIOD_MS * 1000, // 1秒 = 1000ms * 1000us/ms
.reload_count = 0,
.flags.auto_reload_on_alarm = true,
};
gptimer_set_alarm_action(gptimer, &alarm_config);
// 3. 注册中断回调
gptimer_event_callbacks_t cbs = {
.on_alarm = gptimer_isr_callback,
};
gptimer_register_event_callbacks(gptimer, &cbs, NULL);
// 4. 使能并启动定时器
gptimer_enable(gptimer);
gptimer_start(gptimer);
}
// ===================== 任务逻辑(翻转LED) =====================
void timer_task_handler(void* arg) {
led_hardware_init();
hardware_gptimer_init();
while (1) {
// 等待信号量
if (xSemaphoreTake(timer_sem, portMAX_DELAY) == pdTRUE) {
// 直接翻转本地变量
led_level = !led_level;
// 设置LED电平
gpio_set_level(LED_PIN, led_level);
// 打印翻转后的电平
printf("LED电平翻转:%d\n", led_level);
}
}
}
// ===================== 主函数 =====================
void app_main(void) {
// 创建二进制信号量
timer_sem = xSemaphoreCreateBinary();
// 创建任务
xTaskCreate(
timer_task_handler,
"timer_task",
4096,
NULL,
5,
NULL
);
}
接下来,我们来一一讲解。
这里我们使用的是ESP-IDF的5.5.2版本,此版本弃用了原先的timer,改用了gptimer,所以我们使用gptimer进行讲解。
三、硬件参数配置
cpp
#define TIMER_FREQ_HZ 1000000 // 定时器时钟频率:1MHz
#define TIMER_PERIOD_MS 1000 // 定时周期:1000毫秒(1秒)
#define GPTIMER_NUM GPTIMER_NUM_0 // 使用0号GPTimer
3.1 TIMER_FREQ_HZ
用于设置GPTimer定时器的技术分辨率(工作频率),单位是赫兹(Hz),决定了定时器"每计数一次"对应的实际时间长度。
1MHz白哦是定时器每计数一次,耗时1微秒。
3.2 TIMER_PERIOD_MS
设置定时周期,单位是毫秒,1000ms即为1s。
3.3 GPTIMER_NUM
指定ESP32-S3使用哪一个硬件GPTimer定时器,ESP32-S3内部拥有四个独立的GPTimer硬件定时器,0表示使用第一个。
四、定时器初始化函数
4.1 配置定时器基础参数
cpp
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = TIMER_FREQ_HZ,
.intr_priority = 0,
.flags = {
.intr_shared = false,
.allow_pd = false
}
};
gptimer_new_timer(&timer_config, &gptimer);
这里也是一个结构体,它的成员和我们的取值分别代表以下含义:
- clk_src:配置为GPTIMER_CLK_SRC_APB,定时器的 "动力来源":ESP32-S3 的 APB 总线时钟固定 80MHz,是最稳定的默认选择。
- direction:配置为GPTIMER_COUNT_UP,计数方向:从 0 开始往上数。
- resolution_hz:配置为我们宏定义好的TIMER_FREQ_HZ,代表计数精度。
- intr_priority:配置为0,中断优先级由驱动自动分配。
- flags.intr_shared:属于flags的成员,配置为false,代表中断不共享。
- flags.allow_pd:属于flags的成员,配置为false,代表关闭睡眠省电优化,避免定时器数据丢失。
最后,使用gptimer_new_timer函数,创建定时器实例。第一个参数就是我们的结构体,第二个参数是定义的定时器句柄。
4.2 闹钟配置
"闹钟" 是定时器触发中断的核心:当定时器计数到alarm_count设定的数值时,就会触发一次中断;同时通过auto_reload设置,决定是否循环计数(实现周期性中断)。
cpp
gptimer_alarm_config_t alarm_config = {
.alarm_count = TIMER_PERIOD_MS * 1000, // 1秒 = 1000ms * 1000us/ms
.reload_count = 0,
.flags.auto_reload_on_alarm = true,
};
gptimer_set_alarm_action(gptimer, &alarm_config);
结构体参数与取值如下:
- alarm_count:取值为TIMER_PERIOD_MS*1000。我们之前定义的TIMER_PERIOD_MS为1000ms,同时计时器频率为1MHz,所以后面需要乘上1000。
- reload_count:取值为0,中断触发后 "重新开始数" 的起点:设为 0 表示每次都从 0 开始。
- flags.auto_reload_on_alarm:循环定时开关,设为true代表开启循环,触发中断后自动从 0 重新计数,实现 "1 秒触发一次" 的循环;设为false代表定时器只会触发一次中断就停止。
最后,使用gptimer_set_alarm_action完成配置,第一个参数是定时器句柄,第二个参数是时钟结构体。
4.3 注册中断回调
中断回调是 "中断的动作入口":当定时器触发中断时,会自动调用我们指定的函数,实现 "中断通知任务(释放信号量)" 的核心逻辑。
cpp
gptimer_event_callbacks_t cbs = {
.on_alarm = gptimer_isr_callback,
};
gptimer_register_event_callbacks(gptimer, &cbs, NULL);
这里的成员变量只有on_alarm一个,配置为gptimer_isr_callback,这个是函数名,代表了中断触发后要去找到这个gptimer_isr_callback函数执行。
最后,使用gptimer_register_event_callbacks完成配置,第一个参数是定时器句柄,第二个参数是回调结构体,第三个参数自定义参数,在这里填NULL即可。
4.4 使能并启动定时器
cpp
gptimer_enable(gptimer);
gptimer_start(gptimer);
没什么好说的,就是字面意思。
五、中断回调函数与中断服务函数
这些内容与上节按键中断所讲差不多,这里就不再重复了。

六、编译、烧录、查看现象
点击编译:

点击烧录:


可以看到,LED在以一秒为间隔亮暗变化。
点击打开串口监视器,可以看到芯片一直在输出当前引脚的电平变化:
