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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 12:08:10