逐步前行头像
关注

ESP32-S3-SYSTIMER(系统定时器)

一、SYSTIMER

  1、SYSTIMER

     SYSTIMER(System Timer,系统定时器)是ESP32-S3内置的高精度、全局硬件定时器,属于芯片底层基础定时外设。
     SYSTIMER专为系统计时、高精度延时、时间戳统计、周期性中断设计,具备52位超长计数、微秒级高精度、独立时钟域、不掉电计时等特性,是ESP-IDF系统时钟、任务延时、软件定时器的底层硬件依托。
     SYSTIMER核心作用是为整个芯片系统提供统一的全局时间基准,所有上层延时函数、时间统计、RTOS节拍、软件定时任务均基于该硬件定时器实现,是嵌入式系统时序运行的核心基石。

  2、特性

     • 由两个 52 位计数器和三个 52 位比较器组成。
     • 软件操作使用 APB_CLK 时钟。
     • 计数采用 CNT_CLK 时钟,两次计数周期的平均频率为 16 MHz。
     • CNT_CLK 的时钟源为 XTAL_CLK (40 MHz)。
     • 支持 52 位报警值 (t) 和 26 位报警周期 (δt)。
     • 支持两种报警模式:
       单次报警模式:根据设定的目标报警值 (t),生成一次性报警。
       周期报警模式:根据设定的报警周期 (δt),生成周期性报警。
     • 三个比较器可根据设置的报警值 (t) 或报警周期 (δt) 生成三个独立中断。
     • 支持软件配置基准计数值。例如,支持从 Light-sleep 唤醒之后,系统定时器通过软件加载 RTC 定时器记录的睡眠时间,并进行补偿。
     • CPU 处于停止状态或处于在线调试状态时,系统定时器可选择停止运行或继续运行。

  3、组成结构

     系统定时器内置两个计数器(UNIT0 和 UNIT1)以及三个比较器(COMP 0、COMP1 和 COMP2)。
     比较器用于监控计数器的计数值是否达到报警值。
    在这里插入图片描述

二、结构原理

  1、时钟源

     计数器 / 比较器时钟域:XTAL_CLK(外部晶振),驱动计数器、Alarm 比较器,决定计时精度。
     寄存器访问时钟域:APB_CLK(外设总线时钟),驱动软件读写寄存器,不影响计时精度。
    在这里插入图片描述

  2、系统定时器生成报警的过程

     XTAL_CLK → DIV分频器 → Timer Counter计数器 → Comparator比较器 → 中断矩阵。
    在这里插入图片描述

三、核心功能

  1、两种工作模式

     一次性定时器:到期执行一次回调后自动停止。
     周期性定时器:循环触发,需手动esp_timer_stop停止。

  2、两种回调分发模式(二选一)

     ESP_TIMER_TASK(默认):高优先级专用任务执行回调。
       回调可 printf、调用阻塞 API;
     多定时器串行执行,存在调度延迟。
     ESP_TIMER_ISR(中断分发):直接在硬件中断执行回调。
       延迟极低,硬实时;
     中断上下文限制(禁止 printf、阻塞函数),需开启 Kconfig ONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH。

  3、全局时间戳接口 esp_timer_get_time()

     返回开机至今微秒数,快速无锁,任务 / 中断均可调用,适合耗时统计。

  4、睡眠适配

     LightSleep:定时器暂停,唤醒后自动补偿休眠时长,可配置skip_unhandled_events跳过堆积回调。
     DeepSleep:定时器完全复位,唤醒从零计时。

  5、调试工具:

     esp_timer_dump() 打印所有定时器运行统计(触发次数、跳过次数、回调耗时)。

四、SYSTIMER API

  1、头文件/组件

       头文件:
         #include “esp_timer.h”
       组件:
         PRIV_REQUIRES esp_timer

  2、创建 / 销毁

       esp_err_t esp_timer_create(const esp_timer_create_args_t *args, esp_timer_handle_t *out_handle);
       esp_err_t esp_timer_delete(esp_timer_handle_t timer);

  3、启停控制

		// 一次性启动
		esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us);
		// 周期性启动
		esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us);
		// 停止运行
		esp_err_t esp_timer_stop(esp_timer_handle_t timer);
		// 重启(运行中定时器更新延时)
		esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t new_timeout_us);

  4、时间获取

		// 获取开机微秒时间戳
		int64_t esp_timer_get_time(void);
		// 获取下一次定时唤醒时间
		int64_t esp_timer_get_next_alarm(void);

  5、调试

		// 打印全部定时器统计信息
		esp_err_t esp_timer_dump(FILE *stream);

  6、关键结构体

		typedef struct {
		    esp_timer_cb_t callback;    // 定时到期回调函数
		    void *arg;                  // 回调入参
		    esp_timer_dispatch_t dispatch_method; // TASK/ISR
		    const char *name;           // 定时器名称(调试dump用)
		    bool skip_unhandled_events; // 浅睡眠跳过堆积回调
		} esp_timer_create_args_t;

  7、标准开发流程

     1.头文件与组件依赖

       #include “esp_timer.h”
       main/CMakeLists.txt 必须添加:
       PRIV_REQUIRES esp_timer

     2.定义定时器配置结构体

       esp_timer_create_args_t(绑定回调、分发模式、名称)。

     3.创建定时器句柄

       esp_timer_create() 。

     5.启动定时器

       单次:esp_timer_start_once(句柄, 延时us)。
       周期:esp_timer_start_periodic(句柄, 周期us)。

     6.生命周期管理

       暂停:esp_timer_stop()。
       重启:esp_timer_restart()。
       销毁:esp_timer_delete()(必须先 stop)。

五、示例代码

  1、timestamp(时间戳 & 耗时测量)

    1.示例代码

			#include <stdio.h>
			#include <string.h>
			#include "freertos/FreeRTOS.h"
			#include "freertos/task.h"
			#include "esp_timer.h"
			#include "esp_log.h"
			
			static const char *TAG = "ESP_TIMER_TS";
			
			void app_main(void)
			{
			    ESP_LOGI(TAG, "===== esp_timer 时间戳 & 耗时测量 =====");
			
			    // 1. 获取开机时间戳(单位:微秒 us)
			    int64_t t1 = esp_timer_get_time();
			    ESP_LOGI(TAG, "开机时间戳 t1 = %lld us", t1);
			
			    // 模拟代码执行
			    vTaskDelay(pdMS_TO_TICKS(100));
			
			    int64_t t2 = esp_timer_get_time();
			    ESP_LOGI(TAG, "开机时间戳 t2 = %lld us", t2);
			
			    // 2. 计算耗时
			    int64_t diff = t2 - t1;
			    ESP_LOGI(TAG, "vTaskDelay(100ms) 实际耗时 = %lld us = %.3f ms",
			             diff, diff / 1000.0f);
			
			    // 3. 多项耗时对比测试
			    ESP_LOGI(TAG, "----- 各项操作耗时 -----");
			
			    // 空循环 10000 次
			    {
			        volatile uint32_t dummy = 0;
			        int64_t s = esp_timer_get_time();
			        for (uint32_t i = 0; i < 10000; i++) dummy++;
			        int64_t e = esp_timer_get_time();
			        ESP_LOGI(TAG, "空循环10000次 : %lld us", e - s);
			    }
			
			    // memcpy 1KB
			    {
			        uint8_t src[1024], dst[1024];
			        memset(src, 0xAA, sizeof(src));
			        int64_t s = esp_timer_get_time();
			        memcpy(dst, src, 1024);
			        int64_t e = esp_timer_get_time();
			        ESP_LOGI(TAG, "memcpy 1KB    : %lld us", e - s);
			    }
			
			    // printf 一次
			    {
			        int64_t s = esp_timer_get_time();
			        printf("benchmark test\n");
			        int64_t e = esp_timer_get_time();
			        ESP_LOGI(TAG, "一次printf    : %lld us", e - s);
			    }
			
			    ESP_LOGI(TAG, "===== 测量完成 =====");
			
			    // 循环打印当前时间
			    uint32_t cnt = 0;
			    while (1)
			    {
			        ESP_LOGI(TAG, "[%lu] now = %lld us",
			                 (unsigned long)cnt++, esp_timer_get_time());
			        vTaskDelay(pdMS_TO_TICKS(1000));
			    }
			}

    2.执行结果

      在这里插入图片描述

  2、oneshot(一次性定时器,TASK 模式)

    1.示例代码

			#include <stdio.h>
			#include "freertos/FreeRTOS.h"
			#include "freertos/task.h"
			#include "esp_timer.h"
			#include "esp_log.h"
			
			static const char *TAG = "ESP_TIMER_ONESHOT";
			
			#define DELAY_US    (500 * 1000)   // 500ms 后触发
			
			static esp_timer_handle_t s_oneshot_timer = NULL;
			
			/* ===== 回调函数(任务上下文,可直接 printf/ESP_LOG) ===== */
			static void oneshot_cb(void *arg)
			{
			    (void)arg;
			    int64_t now = esp_timer_get_time();
			    ESP_LOGI(TAG, "一次性定时器触发! time = %lld us", now);
			
			    // 用完销毁
			    esp_timer_delete(s_oneshot_timer);
			    s_oneshot_timer = NULL;
			    ESP_LOGI(TAG, "定时器已销毁");
			}
			
			void app_main(void)
			{
			    ESP_LOGI(TAG, "===== esp_timer 一次性定时器 =====");
			    ESP_LOGI(TAG, "延时 %d us (%d ms) 后触发", DELAY_US, DELAY_US / 1000);
			
			    // 1. 配置定时器参数
			    const esp_timer_create_args_t timer_args = {
			        .callback = oneshot_cb,
			        .arg = NULL,
			        .dispatch_method = ESP_TIMER_TASK,   // 任务上下文(默认)
			        .name = "oneshot_500ms",
			        .skip_unhandled_events = false,
			    };
			
			    // 2. 创建定时器
			    ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_oneshot_timer));
			
			    // 3. 启动一次性定时
			    ESP_ERROR_CHECK(esp_timer_start_once(s_oneshot_timer, DELAY_US));
			
			    ESP_LOGI(TAG, "一次性定时器已启动");
			
			    // 打印当前时间
			    while (1)
			    {
			        ESP_LOGI(TAG, "main loop running... now = %lld us",
			                 esp_timer_get_time());
			        vTaskDelay(pdMS_TO_TICKS(200));
			    }
			}

    2.执行结果

      在这里插入图片描述

  3、periodic(周期性定时器,TASK 模式)

     1.示例代码

			#include <stdio.h>
			#include "freertos/FreeRTOS.h"
			#include "freertos/task.h"
			#include "esp_timer.h"
			#include "esp_log.h"
			
			static const char *TAG = "ESP_TIMER_PERIOD";
			
			#define PERIOD_US   (200 * 1000)   // 周期 200ms
			
			static esp_timer_handle_t s_period_timer = NULL;
			static uint32_t s_trigger_cnt = 0;
			
			/* ===== 回调函数(任务上下文,可直接打印日志) ===== */
			static void period_cb(void *arg)
			{
			    (void)arg;
			    s_trigger_cnt++;
			    int64_t now = esp_timer_get_time();
			    ESP_LOGI(TAG, "[%lu] 周期触发  time = %lld us",
			             (unsigned long)s_trigger_cnt, now);
			}
			
			void app_main(void)
			{
			    ESP_LOGI(TAG, "===== esp_timer 周期性定时器 =====");
			    ESP_LOGI(TAG, "周期 = %d us (%d ms)", PERIOD_US, PERIOD_US / 1000);
			
			    // 1. 配置定时器参数
			    const esp_timer_create_args_t timer_args = {
			        .callback = period_cb,
			        .arg = NULL,
			        .dispatch_method = ESP_TIMER_TASK,   // 任务上下文
			        .name = "period_200ms",
			        .skip_unhandled_events = false,
			    };
			
			    // 2. 创建定时器
			    ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_period_timer));
			
			    // 3. 启动周期性定时
			    ESP_ERROR_CHECK(esp_timer_start_periodic(s_period_timer, PERIOD_US));
			
			    ESP_LOGI(TAG, "周期定时器已启动");
			
			    // 5秒后停止演示
			    vTaskDelay(pdMS_TO_TICKS(5000));
			
			    ESP_LOGI(TAG, "----- 停止定时器 -----");
			    ESP_ERROR_CHECK(esp_timer_stop(s_period_timer));
			    ESP_LOGI(TAG, "定时器已停止,总触发次数:%lu", (unsigned long)s_trigger_cnt);
			
			    // 2秒后重启
			    vTaskDelay(pdMS_TO_TICKS(2000));
			    ESP_LOGI(TAG, "----- 重启定时器 -----");
			    ESP_ERROR_CHECK(esp_timer_start_periodic(s_period_timer, PERIOD_US));
			
			    while (1) {
			        vTaskDelay(pdMS_TO_TICKS(1000));
			    }
			}
			

     2.执行结果

      在这里插入图片描述

  4、isr_mode(ISR 中断分发模式)

     meunconfig配置:
       CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y

     1.示例代码

			#include <stdio.h>
			#include "freertos/FreeRTOS.h"
			#include "freertos/task.h"
			#include "freertos/queue.h"
			#include "esp_timer.h"
			#include "esp_log.h"
			
			static const char *TAG = "ESP_TIMER_ISR";
			
			#define PERIOD_US   (100 * 1000)   // 100ms
			
			static esp_timer_handle_t s_isr_timer = NULL;
			static QueueHandle_t s_queue = NULL;
			
			/* ===== 回调函数(中断上下文!禁止 printf/ESP_LOG) ===== */
			static void isr_mode_cb(void *arg)
			{
			    (void)arg;
			    BaseType_t yield = pdFALSE;
			    uint32_t sig = 1;
			
			    // 只能使用 FromISR 系列 API
			    xQueueSendFromISR(s_queue, &sig, &yield);
			
			    if (yield) {
			        portYIELD_FROM_ISR();
			    }
			}
			
			/* ===== 处理任务(任务上下文,允许打印) ===== */
			static void process_task(void *arg)
			{
			    (void)arg;
			    uint32_t sig;
			    uint32_t cnt = 0;
			
			    while (1)
			    {
			        if (xQueueReceive(s_queue, &sig, portMAX_DELAY))
			        {
			            cnt++;
			            int64_t now = esp_timer_get_time();
			            ESP_LOGI(TAG, "[%lu] ISR模式触发  time = %lld us",
			                     (unsigned long)cnt, now);
			        }
			    }
			}
			
			void app_main(void)
			{
			    ESP_LOGI(TAG, "===== esp_timer ISR 中断分发模式 =====");
			    ESP_LOGI(TAG, "周期 = %d us (%d ms)", PERIOD_US, PERIOD_US / 1000);
			
			    // 1. 创建队列
			    s_queue = xQueueCreate(4, sizeof(uint32_t));
			
			    // 2. 配置定时器(ISR 模式)
			    const esp_timer_create_args_t timer_args = {
			        .callback = isr_mode_cb,
			        .arg = NULL,
			        .dispatch_method = ESP_TIMER_ISR,    // 中断分发模式
			        .name = "isr_100ms",
			        .skip_unhandled_events = false,
			    };
			
			    // 3. 创建定时器
			    ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_isr_timer));
			
			    // 4. 启动
			    ESP_ERROR_CHECK(esp_timer_start_periodic(s_isr_timer, PERIOD_US));
			
			    // 5. 创建处理任务
			    xTaskCreate(process_task, "isr_proc", 2048, NULL, 5, NULL);
			
			    ESP_LOGI(TAG, "ISR模式定时器已启动");
			
			    // 调试:打印所有定时器信息
			    vTaskDelay(pdMS_TO_TICKS(1000));
			    ESP_LOGI(TAG, "----- 定时器调试信息 -----");
			    esp_timer_dump(stdout);
			
			    while (1) {
			        vTaskDelay(pdMS_TO_TICKS(1000));
			    }
			}

     2.执行结果

      在这里插入图片描述

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Black_Leopard/article/details/166595730

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--