You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何xTimerPeriodInTicks会影响ESP32+FreeRTOS代码的运行耗时?

ESP32 + FreeRTOS 函数执行周期测量异常问题解析

问题背景

我正在撰写报告,需在ESP32平台结合FreeRTOS测量多个独立函数的执行周期,当前目标为onesecwindow.fifo_iir_filter_datapoint()函数。

初始定时器实现代码

float geninputarray[SAMPLING_RATE]; //stores the values of an artificial signal we can use to test the algorithm
fifo_buffer onesecwindow = fifo_buffer(SAMPLING_RATE);


esp_err_t init_input_generator() {
    dsps_tone_gen_f32(geninputarray, SAMPLING_RATE, 4, 0.04, 0); //10Hz
    TimerHandle_t input_signal_timer =
        xTimerCreate("input_timer", pdMS_TO_TICKS(10), pdTRUE, (void *)0, input_generator_callback);
    xTimerStart(input_signal_timer, 0);
    return ESP_OK;
}

void input_generator_callback(TimerHandle_t xTimer)
{
    xTaskCreatePinnedToCore(
        input_counter,
        "input_generator",
        4096,
        NULL,
        tskIDLE_PRIORITY + 11,
        NULL,
        PRO_CPU_NUM);
}

void input_counter(TimerHandle_t xTimer)
{
    stepcounter = (stepcounter + 1) % SAMPLING_RATE;
    insert_new_data(geninputarray[stepcounter]);
    vTaskDelete(nullptr); // to gracefully end the task as returning is not allowed
}

esp_err_t insert_new_data(float datapoint)
{
    onesecwindow.fifo_write(datapoint); // writes the new datapoint into the onesec window
    unsigned int start_time = dsp_get_cpu_cycle_count();
    onesecwindow.fifo_iir_filter_datapoint();
    unsigned int end_time = dsp_get_cpu_cycle_count();
    printf("%i\n", (end_time-start_time));
}

异常现象

增大定时器周期时,测量耗时显著变长,且存在周期性异常值:

  • 10ms周期输出示例:1567, 630, 607, 624, 591, 619, 649, 1606, 607
  • 40ms周期输出示例:1904, 600, 1894, 616, 1928, 1928, 607, 1897, 628

重构后的循环任务代码

改用vTaskDelay替代定时器,但问题依然存在:

#include <esp_dsp.h> //Official ESP-DSP library
float geninputarray[SAMPLING_RATE]; //stores the values of an artificial signal we can use to test the algorithm
fifo_buffer onesecwindow = fifo_buffer(SAMPLING_RATE);


esp_err_t init_input_generator() {
    dsps_tone_gen_f32(geninputarray, SAMPLING_RATE, 4, 0.04, 0); //10Hz
    
    xTaskCreatePinnedToCore(
        input_counter, // the actual function to be called
        "input_generator",
        4096,
        NULL,
        tskIDLE_PRIORITY + 5,
        NULL,
        APP_CPU_NUM);
    return ESP_OK;
}

void input_counter(TimerHandle_t xTimer)
{
    while (true)
    {
        stepcounter = (stepcounter + 1) % SAMPLING_RATE;
        insert_new_data(geninputarray[stepcounter]);
        vTaskDelay(4);
    }
}

esp_err_t insert_new_data(float datapoint)
{
    onesecwindow.fifo_write(datapoint); // writes the new datapoint into the onesec window
    unsigned int start_time = dsp_get_cpu_cycle_count();
    onesecwindow.fifo_iir_filter_datapoint();
    unsigned int end_time = dsp_get_cpu_cycle_count();
    printf("%i\n", (end_time-start_time));
}

任务始终绑定同一核心、使用相同优先级,无法理解为何定时器周期/任务延迟时长会影响测量结果,求技术解惑。


问题根源分析

1. 缓存失效导致的执行波动

ESP32的ICache/DCache会在任务长时间休眠时,将fifo_iir_filter_datapoint()的代码或相关数据逐出缓存。当任务再次唤醒执行时,需要从Flash/SRAM重新加载缓存,这会导致第一次执行函数的耗时大幅增加。定时器周期/延迟越长,缓存失效的概率越高,异常耗时出现的频率和幅度也会变大。

2. 系统任务的调度干扰

即使任务绑定核心且优先级固定,FreeRTOS内核仍会执行系统级任务(如IDLE任务、定时器服务任务、ESP-IDF后台任务),这些任务可能与测量任务竞争CPU资源。当定时器周期变长时,测量任务的执行时机更可能与系统任务的调度窗口重叠,导致测量耗时被拉长。

3. 初始代码的额外开销

第一种实现中,每次定时器触发都创建新任务,xTaskCreatePinnedToCore本身存在系统开销,且任务创建/删除过程会引入额外的CPU调度动作,干扰测量结果的准确性。


解决方案

1. 锁定缓存,避免失效

将目标函数标记为缓存常驻,使用ESP-IDF的IRAM_ATTR宏,确保函数加载到IRAM中,不会被ICache逐出:

void IRAM_ATTR fifo_buffer::fifo_iir_filter_datapoint() {
    // 原函数实现
}

2. 优化任务结构与预热

  • 保持测量任务长期运行,避免频繁创建/删除任务;
  • 在正式测量前预热函数,提前加载缓存,消除首次执行的额外耗时:
void input_counter(void *arg)
{
    // 预热:连续执行5次目标函数,确保缓存加载完成
    for(int i=0; i<5; i++){
        onesecwindow.fifo_iir_filter_datapoint();
    }
    
    while (true)
    {
        stepcounter = (stepcounter + 1) % SAMPLING_RATE;
        onesecwindow.fifo_write(geninputarray[stepcounter]);
        
        // 仅测量目标函数耗时
        unsigned int start_time = dsp_get_cpu_cycle_count();
        onesecwindow.fifo_iir_filter_datapoint();
        unsigned int end_time = dsp_get_cpu_cycle_count();
        printf("%i\n", (end_time-start_time));
        
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

3. 隔离系统干扰

  • 将测量任务设置为最高优先级,确保执行过程不被抢占:
xTaskCreatePinnedToCore(
    input_counter,
    "input_generator",
    4096,
    NULL,
    configMAX_PRIORITIES - 1, // 最高优先级
    NULL,
    PRO_CPU_NUM);
  • 暂时禁用不必要的系统功能(如WiFi、蓝牙),减少后台任务干扰。

4. 稳定CPU频率

如果使用dsp_get_cpu_cycle_count()测量,需先锁定CPU频率,避免动态调频影响周期计数的准确性:

esp_clk_cpu_freq_set(ESP_CPU_FREQ_240M); // 锁定CPU到240MHz

内容的提问来源于stack exchange,提问作者TomK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 07:35:21