应用中经常需要用到 timer,对于 ms 级别的定时需求,FREERTOS 中的 Software Timer 完全就可以满足需求,唯一不足的是 FREERTOS 中的 Software Timer 的优先级比较低且精度依赖于 TICK。
综上,如果有 us 级别的定时需求或者需要准确的定时需求,还是需要用到今天的主角:High Resolution Timer。
对于 High Resolution Timer,各位直接参考 esp_timer demo 即可。使用 High Resolution Timer 很简单,对于文章中的以下部分,纯属自己对于 High Resolution Timer 一些底层方面的理解。
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 Timer 的 callback 每次能被及时触发且时间跨度保持一致。
#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 的临界区来保护链表操作。