0
votes

Here is a small preemptive scheduling algorithm for MSP430G2553 Launchpad from @tonyp12 from https://forum.43oh.com/topic/9450-tiny-msp430-preemptive-multitasking-system/

Even though I know the concept of contet switching, this program confuses me a little bit. Can someone explain what he is doing step by step specifically in the below snippets?

int* multistack = (int*) __get_SP_register();
  int i=0; while(i<tasks-1){
    int j = stacksize[i]; if (!j) j = 24;
    multistack -= j;  
    *(multistack) = (int) taskpnt[++i];         // prefill in PC 
    *(multistack-1) = GIE;                      // prefill in SR
    taskstackpnt[i] = (int) multistack-26;      // needs 12 dummy push words
  } 

and

#pragma vector = WDT_VECTOR 
__raw __interrupt void taskswitcher(void)
{
  asm ("push R15\n push R14\n push R13\n push R12\n"
       "push R11\n push R10\n push R9\n push R8\n"
       "push R7\n push R6\n push R5\n push R4");

  taskstackpnt[taskrun] = __get_SP_register();
  if (++taskrun == tasks) taskrun = 0;
  __set_SP_register(taskstackpnt[taskrun]);
  
  asm ("pop R4\n pop R5\n pop R6\n pop R7\n"
       "pop R8\n pop R9\n pop R10\n pop R11\n"
       "pop R12\n pop R13\n pop R14\n pop R15");
}

Thanks. And here is the complete code:

#include "msp430.h"
#include "common.h"

//=========================(C) Tony Philipsson 2016 =======================
funcpnt const taskpnt[]={ task1, task2, task3,  // <- PUT YOUR TASKS HERE
}; 
const int stacksize[tasks] = {28};              // a blank value defaults to 24 stack words
//=========================================================================
int taskstackpnt[tasks];
unsigned int taskdelay[tasks];
char taskrun;

int main( void )
{
  WDTCTL = WDTPW + WDTHOLD;                     // Stop watchdog timer 
  if (CALBC1_8MHZ != 0xff){               // erased by mistake?
    BCSCTL1 = CALBC1_8MHZ;                   // Set DCO to factory calibrate 1MHz  
    DCOCTL = CALDCO_8MHZ;
  } 
  int* multistack = (int*) __get_SP_register();
  int i=0; while(i<tasks-1){
    int j = stacksize[i]; if (!j) j = 24;
    multistack -= j;  
    *(multistack) = (int) taskpnt[++i];         // prefill in PC 
    *(multistack-1) = GIE;                      // prefill in SR
    taskstackpnt[i] = (int) multistack-26;      // needs 12 dummy push words
  }
  WDTCTL = WDTPW+WDTTMSEL+WDTCNTCL;             // 4ms interval at 8MHz smclk
  IE1 |= WDTIE;
  __bis_SR_register(GIE);
  asm ("br &taskpnt");                          // indirect jmp to first task
}

//============= TASK SWITCHER ISR =============
#pragma vector = WDT_VECTOR 
__raw __interrupt void taskswitcher(void)
{
  asm ("push R15\n push R14\n push R13\n push R12\n"
       "push R11\n push R10\n push R9\n push R8\n"
       "push R7\n push R6\n push R5\n push R4");

  taskstackpnt[taskrun] = __get_SP_register();
  if (++taskrun == tasks) taskrun = 0;
  __set_SP_register(taskstackpnt[taskrun]);

  asm ("pop R4\n pop R5\n pop R6\n pop R7\n"
       "pop R8\n pop R9\n pop R10\n pop R11\n"
       "pop R12\n pop R13\n pop R14\n pop R15");
} 
#include "msp430.h"
#include "common.h"

__task void task1(void){
  P1DIR |= BIT0;
  while(1){
    __delay_cycles(800000);
    P1OUT |= BIT0;
    __delay_cycles(800000);
    P1OUT &=~BIT0;        
  }
}
#include "msp430.h"
#include "common.h"

__task void task2(void){
  P1DIR |= BIT6;
  while(1){
    __delay_cycles(1200000);
    P1OUT |= BIT6;
    __delay_cycles(1200000);
    P1OUT &=~BIT6;        
  }
}
#include "msp430.h"
#include "common.h"
unsigned int fibo(int);

__task void task3(void){
  int temp = 0;
  while(1){
    fibo(++temp);
  }
}
unsigned int fibo(int n){
  if (n < 2)
    return n;
  else
    return (fibo(n-1) + fibo(n-2));
}
#ifndef COMMON_H_
#define COMMON_H_
#define  tasks (sizeof(taskpnt)/2)
__task void task1(void);
__task void task2(void);
__task void task3(void);
typedef __task void (*funcpnt)(void);
#endif
1
This question is too broad. Please explain how much you know about the C language, the MSP430 architecture, and the intrinsic functions used in the above code, and how much of this code you understand, and what you assume it does.CL.

1 Answers

1
votes

The first snipped of code initialize all the stacks for the various tasks.

First saving the PC as the address of the task function (the first instruction):

*(multistack) = (int) taskpnt[++i]; 

then it saves the status register with the GIE enabled (needed for correct task switch function):

*(multistack-1) = GIE;   

These two will be restored automatically by reti when the scheduler interrupt will end.

Also the new stackpointer for the task is saved (including space for registries saving):

taskstackpnt[i] = (int) multistack-26;

The second snippet is the scheduler interrupt itself.

The PC and the SR are saved by hardware during interrupt calling automagically. In the interrupt code the registry are saved for current task:

asm ("push R15\n push R14\n push R13\n push R12\n"
   "push R11\n push R10\n push R9\n push R8\n"
   "push R7\n push R6\n push R5\n push R4");

then the software saves the stack pointer for the current task:

taskstackpnt[taskrun] = __get_SP_register();

and get the next task stack pointer index:

if (++taskrun == tasks) taskrun = 0;

then restore the new task stack pointer:

__set_SP_register(taskstackpnt[taskrun]);

and pop the registries saved in stack:

 asm ("pop R4\n pop R5\n pop R6\n pop R7\n"
   "pop R8\n pop R9\n pop R10\n pop R11\n"
   "pop R12\n pop R13\n pop R14\n pop R15");

The PC and SR for the new task are restored by interrupt's reti.

The new task is ready to go!