为何FreeRTOS在Raspberry Pi Pico(RP2040)中无法抢占任务?
问题
在Raspberry Pi Pico(RP2040)上使用FreeRTOS编写了如下双任务测试程序:
#include "FreeRTOS.h" #include "task.h" #include "timers.h" #include "pico/stdlib.h" #include <stdio.h> void Task1(void* nothing) { while(1) { printf("Task1\n"); sleep_ms(1000); // 不允许抢占 //vTaskDelay(1 * configTICK_RATE_HZ ); // 允许抢占 } } void Task2(void* nothing) { sleep_ms(500); while(1) { printf("Task2\n"); sleep_ms(1000); // 不允许抢占 //vTaskDelay(1 * configTICK_RATE_HZ ); // 允许抢占 } } void main() { stdio_init_all(); sleep_ms(2000); printf("\n\n\n\nHello\n\n"); TaskHandle_t xTask1Handle = NULL; TaskHandle_t xTask2Handle = NULL; BaseType_t ret1 = xTaskCreate(Task1, "Task1", 256, 0, 10, &xTask1Handle); BaseType_t ret2 = xTaskCreate(Task2, "Task2", 256, 0, 10, &xTask2Handle); if (ret1 == pdPASS) printf("Task 1 created OK\n"); else printf("Task 1 failed\n"); if (ret2 == pdPASS) printf("Task 2 created OK\n"); else printf("Task 2 failed\n"); vTaskStartScheduler(); while(1) { } }
程序运行时发现:用sleep_ms()时只有一个任务输出,换成vTaskDelay()则两个任务都能正常运行,看起来FreeRTOS没执行任务抢占。
已经在FreeRTOSConfig.h中开启了抢占配置:
#define configUSE_PREEMPTION 1 #define configUSE_TIME_SLICING 1
请问还需要做哪些设置才能启用任务抢占?
解决方法
替换裸机阻塞函数,改用FreeRTOS调度接口
pico/stdlib.h中的sleep_ms()是裸机休眠函数,执行期间不会触发FreeRTOS调度器——它直接让CPU进入低功耗状态,同优先级任务无法通过时间片轮转切换,高优先级任务虽能抢占,但你的两个任务优先级相同,所以会出现单任务独占的情况。而vTaskDelay()是FreeRTOS专属延迟函数,会让当前任务进入阻塞态主动让出CPU,触发调度器切换到其他就绪任务。检查FreeRTOS时钟节拍核心配置
在FreeRTOSConfig.h中确认以下宏的配置:// 启用SysTick作为FreeRTOS时钟节拍源(RP2040默认依赖此) #define configUSE_SYSTICK 1 // 设置时钟节拍频率,通常为1000Hz(对应每1ms一个tick) #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )若未开启SysTick,FreeRTOS无法生成定时中断触发抢占调度,时间片轮转也无法生效。
确认移植层中断配置正确性
确保RP2040的FreeRTOS移植包正确实现了SysTick中断和PendSV中断(用于任务上下文切换)。部分自定义移植需手动配置中断优先级,要保证FreeRTOS相关中断的优先级高于普通裸机中断,但低于硬件不可屏蔽中断。禁止在任务中使用裸机忙等/阻塞函数
除sleep_ms外,busy_wait_ms这类忙等函数也会持续占用CPU,不会触发调度。任务中所有延迟、阻塞操作都应使用FreeRTOS提供的vTaskDelay()、vTaskDelayUntil()等接口,让调度器有机会切换任务。
内容的提问来源于stack exchange,提问作者Rocketmagnet

