当前位置: 首页 > 工具软件 > Timer > 使用案例 >

Espressif 玩转 High Resolution Timer

郑乐池
2023-12-01

应用中经常需要用到 timer,对于 ms 级别的定时需求,FREERTOS 中的 Software Timer 完全就可以满足需求,唯一不足的是 FREERTOS 中的 Software Timer优先级比较低且精度依赖于 TICK

  • 对于优先级,在 menuconfig 中默认设置为 1,仅仅比 IDLE TASK 的优先级高,所以在有高优先级的 TASK 持续占据 CPU 时,就会影响 Software Timer 的精度;
  • 对于精度,在 menuconfig 中最大可以设置为 1000 HZ,也就是说 Software Timer 的精度最大只能维持在 ms 级别。

综上,如果有 us 级别的定时需求或者需要准确的定时需求,还是需要用到今天的主角:High Resolution Timer

High Resolution Timer

对于 High Resolution Timer,各位直接参考 esp_timer demo 即可。使用 High Resolution Timer 很简单,对于文章中的以下部分,纯属自己对于 High Resolution Timer 一些底层方面的理解。

Overview

High Resolution Timer 基于一个 64 bits 的硬件 timer 实现。

High Resolution Timer 默认在 ESP_TIMER_TASK 中实现 callback 。而 ESP_TIMER_TASK 的优先级是固定的,没有在 menuconfig 开放配置选项。ESP_TIMER_TASK 在接口 esp_timer_init() 创建,该接口在 bootloader 中被调用,所以在应用层不需要再次调用 esp_timer_init()

esp_err_t esp_timer_init(void)
{
    esp_err_t err;
    if (is_initialized()) {
        return ESP_ERR_INVALID_STATE;
    }

    int ret = xTaskCreatePinnedToCore(&timer_task, "esp_timer",
            ESP_TASK_TIMER_STACK, NULL, ESP_TASK_TIMER_PRIO, &s_timer_task, PRO_CPU_NUM);
    if (ret != pdPASS) {
        err = ESP_ERR_NO_MEM;
        goto out;
    }

    err = esp_timer_impl_init(&timer_alarm_handler);
    if (err != ESP_OK) {
        goto out;
    }

    return ESP_OK;

out:
    if (s_timer_task) {
        vTaskDelete(s_timer_task);
        s_timer_task = NULL;
    }

    return ESP_ERR_NO_MEM;
}

可以看到,TASK 的名称为 esp_timer,堆栈深度为 ESP_TASK_TIMER_STACK,优先级为 ESP_TASK_TIMER_PRIO,被固定在核 PRO_CPU_NUM 上运行。而 ESP_TASK_TIMER_PRIO 最终被设置为 FREERTOS 的最高优先级 configMAX_PRIORITIES - 3 即为 22。该优先级比一些系统的 BLE、Wi-Fi 之类的 TASK 的优先级还要高,这就保证了 High Resolution Timercallback 每次能被及时触发且时间跨度保持一致。

#define ESP_TASK_PRIO_MAX (configMAX_PRIORITIES)
...
#define ESP_TASK_TIMER_PRIO           (ESP_TASK_PRIO_MAX - 3)

High Resolution Timer 可以创建多个 timer,这得益于它采用链表的方式来挂载 timer。通过分析接口 esp_timer_start_once()esp_timer_start_periodic() 可知,每次创建 timer,实际上就是将一个 struct esp_timer 放入到链表中。

esp_err_t IRAM_ATTR esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us)
{
    if (timer == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    if (!is_initialized() || timer_armed(timer)) {
        return ESP_ERR_INVALID_STATE;
    }
    int64_t alarm = esp_timer_get_time() + timeout_us;
    esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
    timer_list_lock(dispatch_method);
    timer->alarm = alarm;
    timer->period = 0;
#if WITH_PROFILING
    timer->times_armed++;
#endif
    esp_err_t err = timer_insert(timer, false);
    timer_list_unlock(dispatch_method);
    return err;
}

esp_err_t IRAM_ATTR esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us)
{
    if (timer == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    if (!is_initialized() || timer_armed(timer)) {
        return ESP_ERR_INVALID_STATE;
    }
    period_us = MAX(period_us, esp_timer_impl_get_min_period_us());
    int64_t alarm = esp_timer_get_time() + period_us;
    esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
    timer_list_lock(dispatch_method);
    timer->alarm = alarm;
    timer->period = period_us;
#if WITH_PROFILING
    timer->times_armed++;
    timer->times_skipped = 0;
#endif
    esp_err_t err = timer_insert(timer, false);
    timer_list_unlock(dispatch_method);
    return err;
}

从代码上分析可知,为了保证操作链表的可靠性,在操作链表的过程中使用了 timer_list_lock()timer_list_unlock() 来保护,实际上就是使用了 FREERTOS临界区来保护链表操作。

Note

  1. 如上所述,High Resolution Timer 默认在 ESP_TIMER_TASK 中实现 callback 。而 ESP_TIMER_TASK 的优先级默认为 22。所以,如果在 High Resolution Timercallback 中做过多耗时的操作的话,势必会影响到正常的 BLE 和 Wi-Fi 的通信,可能会造成 BLE 和 Wi-Fi 的断连
  2. esp_timer_start_once()esp_timer_start_periodic() 接口中的行参 period_us64 bits 的,所以在 32 bitsEspressif MCU 中使用时,一定要转换成 64 bits 的数据类型,不然可能会造成数据溢出而提前 callback 的情况
 类似资料: