ESP32-S3开发教程6:硬件定时器

中断不仅能由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);

这里也是一个结构体,它的成员和我们的取值分别代表以下含义:

  1. clk_src:配置为GPTIMER_CLK_SRC_APB,定时器的 "动力来源":ESP32-S3 的 APB 总线时钟固定 80MHz,是最稳定的默认选择。
  2. direction:配置为GPTIMER_COUNT_UP,计数方向:从 0 开始往上数。
  3. resolution_hz:配置为我们宏定义好的TIMER_FREQ_HZ,代表计数精度。
  4. intr_priority:配置为0,中断优先级由驱动自动分配。
  5. flags.intr_shared:属于flags的成员,配置为false,代表中断不共享。
  6. 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);

结构体参数与取值如下:

  1. alarm_count:取值为TIMER_PERIOD_MS*1000。我们之前定义的TIMER_PERIOD_MS为1000ms,同时计时器频率为1MHz,所以后面需要乘上1000。
  2. reload_count:取值为0,中断触发后 "重新开始数" 的起点:设为 0 表示每次都从 0 开始。
  3. 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在以一秒为间隔亮暗变化。

点击打开串口监视器,可以看到芯片一直在输出当前引脚的电平变化:

相关推荐
Godspeed Zhao2 小时前
现代智能汽车中的无线技术97——NearLink(4)
stm32·单片机·汽车
小灰灰搞电子3 小时前
ESP32+ESP-IDF 使用MQTT协议连接阿里云物联网平台源码分享
物联网·阿里云·esp32
z20348315203 小时前
如何用状态机解决按键状态识别问题(一)
c语言·单片机
之歆6 小时前
Heartbeat 高可用集群完全指南
单片机·嵌入式硬件
浩子智控7 小时前
提升linux串口通信实时性的编程实践
linux·单片机·嵌入式硬件
Tyrion.Mon7 小时前
5脚188数码管驱动
单片机
国科安芯1 天前
高可靠性电源方案的高温降额设计与热管理策略——基于ASP3605的温域特性实证研究
单片机·嵌入式硬件·安全威胁分析·安全性测试
白太岁1 天前
操作系统开发:(9) 从硬件复位到程序执行:如何编写符合硬件动作的启动文件与链接脚本
c语言·汇编·嵌入式硬件·系统架构
JialBro1 天前
【嵌入式】直流无刷电机FOC控制算法全解析
算法·嵌入式·直流·foc·新手·控制算法·无刷电机