基于ARM Cortex-M SysTick的简易任务调度器失效,需自行管理抢占吗?
问题分析与解决方案
首先,你的代码里最致命的问题就是每个任务函数里的while(1)死循环。当PendSV_Handler调用run()执行task1时,程序会一直卡在task1的死循环里,永远无法返回PendSV_Handler,更别说回到主循环或者响应下一次SysTick中断了。这就是为什么你看到计数器没法递增、程序完全卡住的核心原因——SysTick中断根本没机会被触发执行。
时间触发的任务调度模式要求每个任务都是非阻塞的:任务只完成一小段工作就退出,而不是一直占用CPU。下面是针对你的问题的具体修复步骤:
1. 移除任务中的死循环
把每个任务里的while(1)去掉,让任务执行完当前工作后自动返回,这样PendSV_Handler才能正常结束,系统才能回到可响应中断的状态,等待下一次SysTick触发任务切换。
修改后的基础任务示例:
void task1(void) { count1++; // 只执行单次计数,然后返回 } void task2(void) { count2++; } void task3(void) { count3++; }
如果你的任务需要持续处理逻辑(比如周期性执行复杂操作),应该用状态机实现,避免阻塞:
typedef enum { TASK1_INIT, TASK1_RUNNING } Task1State; Task1State task1_state = TASK1_INIT; void task1(void) { switch(task1_state) { case TASK1_INIT: // 执行一次性初始化工作 task1_state = TASK1_RUNNING; break; case TASK1_RUNNING: count1++; // 执行单次处理逻辑后退出,下次被调度时继续 break; } }
2. 修正run指针的volatile修饰
run指针在SysTick中断中被修改,在PendSV中断中被读取,必须加上volatile修饰,防止编译器对其进行优化,确保读取到的是最新的任务指针:
volatile TaskFunction run = NULL;
3. 显式配置中断优先级(可选但推荐)
Cortex-M3中,PendSV默认是最低优先级(符合任务切换需求,避免切换被其他中断打断),而SysTick的默认优先级可能不够高。你可以显式配置SysTick优先级高于PendSV,确保SysTick中断能及时触发任务切换:
// 在main函数的SysTick_Config调用前添加: NVIC_SetPriority(SysTick_IRQn, 1); // 数值越小优先级越高 NVIC_SetPriority(PendSV_IRQn, 3); // 让PendSV优先级低于SysTick
完整修改后的代码示例
#include <asf.h> // Atmel软件框架,包含CMSIS与板级支持包 #define NTASKS 3 typedef void (*TaskFunction)(void); void task1(void); void task2(void); void task3(void); volatile TaskFunction run = NULL; uint32_t count1 = 0; // 任务1的计数器 uint32_t count2 = 0; // 任务2的计数器 uint32_t count3 = 0; // 任务3的计数器 TaskFunction tasks[NTASKS] = {task1, task2, task3}; volatile uint8_t tasknum = 0; void task1(void) { count1++; } void task2(void) { count2++; } void task3(void) { count3++; } void SysTick_Handler(void) { tasknum = (tasknum == NTASKS-1)? 0 : tasknum+1; run = tasks[tasknum]; SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; } void PendSV_Handler(void) { if(run != NULL) { run(); } } int main(void) { sysclk_init(); board_init(); pmc_enable_all_periph_clk(); // 配置中断优先级 NVIC_SetPriority(SysTick_IRQn, 1); NVIC_SetPriority(PendSV_IRQn, 3); SysTick_Config(1000); // 假设系统时钟对应1ms触发一次SysTick while(1); }
额外调试建议
- 用Atmel ICE调试时,可以在
SysTick_Handler和PendSV_Handler中设置断点,确认中断是否被依次触发。如果修改后断点能交替命中,说明任务切换逻辑正常工作。 - 观察
count1、count2、count3的值是否周期性递增,验证每个任务都被调度到。
内容的提问来源于stack exchange,提问作者cleveraintwise
相关产品推荐
相关产品推荐

