Search code examples
armcortex-m

This simple ARM Cortex-M SysTick based task scheduler wont work. Should I manage preemption myself?


So, I am doing a very simple time triggered pattern based on ARM Cortex M3. The idea is:when SysTick is serviced, the task array index is incremented at systick, and so is a function pointer to the task. PendSV handler is called, and calls the task. I am using a Atmel ICE JTAG to debug it. What happens is that it stucks at first task, and does not even increment the counter. It does not go anywhere. Code pattern:

#include <asf.h> // atmel software framework. cmsis and board support package.

#define NTASKS 3

typedef void (*TaskFunction)(void);
void task1(void);
void task2(void);
void task3(void);

TaskFunction run = NULL;

uint32_t count1 = 0; //counter for task1
uint32_t count2 = 0; // 2
uint32_t count3 = 0; // 3

TaskFunction tasks[NTASKS] = {task1, task2, task3};
volatile uint8_t tasknum = 0;

void task1(void)
{
    while(1)
    {
        count1++;
    }
}

void task2(void)
{
    while(1)
    {
        count2++;
    }
}

void task3(void)
{
    while(1)
    {
        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)
{
    run();
}


int main(void)
{
    sysclk_init();
    board_init();
    pmc_enable_all_periph_clk();
    SysTick_Config(1000);
    while(1);
}

Solution

  • This design pattern is fundamentally flawed, I'm afraid.

    At the first SysTick event, task1() will be called from within the PendSV handler, which will consequently not return. Further SysTick events will interrupt the PendSV handler and set the PendSV bit again, but unless the running task ends and the PendSV handler is allowed to return it can't possibly be invoked again.

    The good news is that a proper context switch on the M3 is only a small amount of assembly language - perhaps 10 lines. You need to do some setup too, to get user mode code to use the process stack pointer and so on, and you need to set up a stack per task, but it's not really all that involved.

    If you really want to cancel the running task when a SysTick arrives and launch another, they could all share the same stack; but it would be much easier if this was the process stack so that its stack pointer can be reset from within PendSV without affecting the return from handler mode. You'd also need to do some stack poking to convince PendSV to 'return' to the start of the next task you wanted to run.