Jump to content
tonyp12

tiny msp430 preemptive multitasking system

Recommended Posts

Tested on G2553 Launchpad with IAR, I recommend G2955 with 1K RAM if you want more than 3 task

#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

Share this post


Link to post
Share on other sites

Hey @@tonyp12

 

I love seeing people implement multi-tasking things. Many full implementations of RTOS's include so many extra things that it becomes difficult to actually see the the implementation of the task switching itself.

 

Nice simple elegant approach. I like it.

Share this post


Link to post
Share on other sites

a ISR always have way to trick the RETI by modifying the SR values on stack.

 

I trick the RETI with a different stack location , task 1 have a stack as normal as first entry in to ISR the PC,SR and R15-R4 are push'ed
?

But for the other tasks I have to preset the PC and SR in the "fake" stack for its first entry in
and also offset the stack pointer as it will see 12 pop's the first time without seeing the 12 push'es yet.

 

I plan to add system_delay() so task can asked to be put on hold, though it will be hard to calculate a actual time if other tasks also do that at the same time.

maybe even enter LPM0 if all task are on timeout for this 4ms interval.

Share this post


Link to post
Share on other sites

You're right, that is room for 16 multi-stacks!!

Run that msp at 16Mhz, intervals will now be 2ms and you have something powerful.
?IRQ's can handle the real-time stuff and just make sure you always code with the thoughts that code can get on hold at any time (though temporary disable WDTIE will give you a lock)

?I just selected 1K+ and did not see that some had even more.
http://www.ti.com/lsds/ti/microcontrollers_16-bit_32-bit/msp/ultra-low_power/msp430g2x_i2x/products.page#p1219=1;4

Share this post


Link to post
Share on other sites

This is interesting stuff, nice post! I took a quick stab at getting this to compile on msp430-gcc. I replaced the __task and __raw with the gcc naked attribute. It gives you warnings but looking at the code it seems to do the right thing.

 

Thanks,

-rick

 

$ diff common.txt common.h
3a4
> #define __task __attribute__((naked))
8a10
>

 
$ diff main.txt main.c 
20c20
<   int* multistack = (int*) __get_SP_register();
---
>   int* multistack = (int*) __read_stack_pointer();
35,36c35,36
< #pragma vector = WDT_VECTOR 
< __raw __interrupt void taskswitcher(void)
---
> __attribute__((interrupt(WDT_VECTOR), naked))
> void taskswitcher(void)
42c42
<   taskstackpnt[taskrun] = __get_SP_register();
---
>   taskstackpnt[taskrun] = (int)__read_stack_pointer();
44c44
<   __set_SP_register(taskstackpnt[taskrun]);
---
>   __write_stack_pointer((void *)taskstackpnt[taskrun]);
49,50c49
< } 
< 
---
> }

Share this post


Link to post
Share on other sites

I may replace the c code in isr with asm if I can not make changes to get it optimized so it only uses 8 words,
now it uses 19 words, but making stuff (int*) instead of array[char value] I think that will get it down to 12 at least.

If all asm  of course there is no need for __raw if you compiler don't support it.

 

btw the asm ("br &taskpnt") can be done in C by: taskpnt[0](); but it does a call and that wastes a stackword for return address.

So unless you want task1 to have an option to exit and return to that spot in main, a exit in task1 now would use the return address from the original call to main

Share this post


Link to post
Share on other sites

 

const int stacksize[tasks] = {28};              // a blank value defaults to 24 stack words
...

  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
  }

 

Can you explain why you are using 28 and 24 for the task stack sizes? Why do you back up the magic # of -26

 

-rick

Share this post


Link to post
Share on other sites

?{28}; is the same thing as {28,0,0} and 28 was just random number to show something
so if you forget to fill in all stack sizes for each task I will give you 24 words (48bytes)

 

A minimum should  be 14 words if your task don't use the stack at all.

 

-26, as I cast the address to (int) it's now a byte space referenced
If I wrapped  it first
(int) (multistack-13); it will be word referenced as it is a int pointer, probably should wrap it for next time,

?So I'm just adjusting the stack pointer to include the PC,SR and the 12 pop's it will see on first entry (only done for task2+)

That is 14 words but Pop is post increment, it works if I do  -13 words
the registers will have random data first time, but as task have not started doing anything yet it should not care.

a POP R15 is actual a: MOV.W @R1+,R15

Share this post


Link to post
Share on other sites

Thanks, the stack size thing seemed arbitrary to me, and I was wondering why. task1 and task2 don't use any stack space. So I was wondering why you bothered to give them any stack beyond the 14 words.

Share this post


Link to post
Share on other sites

preemptive means that the code (e.g.each task that is pretty much its own main.c) does not know it is sharing a single mcu core.
it does not need to say "i take a pause now so next task can go on", you can even run two instances of the same task (though not really useful)
 
A higher power takes over and switch tasks.
Sure there is stuff to add like system_sleep and priority and maybe even new task and end task etc.

 

I plan to show how to use a 512Hz mems osc to NMI pin, so task switching can not be overridden by (maybe mistakenly) disabling GIE.

?wiki: Preemptive multitasking

wiki: Cooperative multitasking
 

P.S I also have a Cooperative multitasking system that uses a Event Machine in main.c and each (up to 16) tasks are all State Machines (switch/case) as that is a good time slicer.
?What I like about that is that I know I have exclusivity while I'm doing my thing so no race conditions.
?And very little overhead so better for battery-operation as task that needs to use a time-constant can sleep from 1ms to 64sec independently keeping in LPM3 95% of the time

Share this post


Link to post
Share on other sites

I've been playing around with this under msp430-gcc. I've got it working pretty well however, I had to make a bunch of changes to make ms430-gcc happy. Unfortunately, I've not been successful getting this to work with msp430-elf-gcc. It seems that the way it uses the stack and ABI aren't playing nicely with this code.

 

I added a systick and sys_delay feature. To make my life simpler, I have the WDT ticking every 1ms running at 8MHz.

 

As soon as we here back from @@tonyp12 I'll post the code.

 

-rick

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...