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

STM32 FreeRTOS中ISR切换带vTaskDelay的任务不流畅问题求助

问题:带vTaskDelay的FreeRTOS任务切换优化

我有两个任务T1和T2,希望通过ISR触发任务切换,但两个任务都包含私有vTaskDelay调用,目前切换不够流畅。尝试将vTaskDelay拆分为小段循环检查切换标志,但效果未达预期。

原代码示例

void T1(void){
    while(1){
         if (tsk == 1) {
             // doSth
             vTaskDelay(2000); // 2秒延迟
             // doSth
         }
    }
}

void T2(void){
    while(1){
         if (tsk == 2) {
             // doSth
             vTaskDelay(1000); // 1秒延迟
             // doSth
         }
    }
}

void ISR(){
    if (READGPIO){
        // 在1和2之间切换tsk的值
    }
}

尝试过的优化(效果不佳)

// 将T1中的vTaskDelay拆分为小段循环
for (int i = 0; i < 200; i++) {
    vTaskDelay(10);
    if (tsk == 1) break;;
}

问题根源分析

  1. 阻塞期间无法响应切换:原代码中任务调用vTaskDelay时会进入阻塞状态,即使ISR切换了tsk标志,任务也必须等待当前延迟周期结束才能检查标志,导致切换延迟。
  2. 轮询逻辑错误:拆分延迟后的循环中,break条件写反(应该检查tsk != 1时退出),导致无法正确响应切换。
  3. 轮询效率低:小段延迟轮询会占用不必要的CPU时间,且如果任务优先级较低,可能被其他高优先级任务抢占,进一步影响切换流畅度。

可行优化方案

1. 用FreeRTOS事件通知替代全局变量轮询

使用任务通知(Task Notification)让ISR直接唤醒目标任务,替代全局变量tsk的轮询逻辑,任务在等待时处于阻塞状态,收到通知可立即响应:

// 定义任务句柄
TaskHandle_t xTask1Handle = NULL;
TaskHandle_t xTask2Handle = NULL;

void T1(void *pvParameters){
    xTask1Handle = xTaskGetCurrentTaskHandle();
    while(1){
        // 等待激活通知,无限阻塞直到收到信号
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        
        // 执行任务逻辑
        // doSth
        
        // 分段延迟并检查是否需要中断
        TickType_t xLastWakeTime = xTaskGetTickCount();
        const TickType_t xSegmentDelay = pdMS_TO_TICKS(10);
        for(int i=0; i<200; i++){
            vTaskDelayUntil(&xLastWakeTime, xSegmentDelay);
            // 检查是否收到停止通知(非阻塞式检查)
            if(ulTaskNotifyTake(pdFALSE, 0) != 0){
                break;
            }
        }
        
        // 执行后续逻辑
        // doSth
    }
}

void T2(void *pvParameters){
    xTask2Handle = xTaskGetCurrentTaskHandle();
    while(1){
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        
        // doSth
        
        TickType_t xLastWakeTime = xTaskGetTickCount();
        const TickType_t xSegmentDelay = pdMS_TO_TICKS(10);
        for(int i=0; i<100; i++){
            vTaskDelayUntil(&xLastWakeTime, xSegmentDelay);
            if(ulTaskNotifyTake(pdFALSE, 0) != 0){
                break;
            }
        }
        
        // doSth
    }
}

void ISR(){
    if(READGPIO){
        static BaseType_t xActiveTask = 1;
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        
        // 向当前激活任务发送停止通知,向目标任务发送激活通知
        if(xActiveTask == 1){
            xTaskNotifyFromISR(xTask1Handle, 1, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
            xTaskNotifyFromISR(xTask2Handle, 1, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
        }else{
            xTaskNotifyFromISR(xTask2Handle, 1, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
            xTaskNotifyFromISR(xTask1Handle, 1, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
        }
        xActiveTask = (xActiveTask == 1) ? 2 : 1;
        
        // 如果唤醒了更高优先级的任务,立即触发上下文切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

2. 优化任务结构,去掉冗余轮询

重构任务逻辑,让每个任务只在收到激活信号时执行,无需持续轮询全局变量,减少CPU空耗。

3. 使用vTaskDelayUntil保证周期精度(可选)

如果任务是周期性执行,用vTaskDelayUntil替代vTaskDelay,可以避免延迟累积误差,同时在分段延迟时保持周期稳定性。

关键注意事项

  • ISR中必须使用FreeRTOS的FromISR系列API(如xTaskNotifyFromISR),避免在中断上下文调用普通任务API。
  • 任务优先级设置要合理:确保切换操作能及时抢占当前执行的任务,避免高优先级任务阻塞切换响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 13:42:51