Jump to content
43oh

tiny msp430 preemptive multitasking system


Recommended Posts

Code is free for non-commercial use, for commerical use a negotiated donation amount.
?Though commercial use will be after I added more features.

?I was able to trim it down to 13words, though as SP is volatile it wastes two words moving SP to R15 twice that is never used (in high optimization) = 15 words
I have not tested this yet

  *taskrun = __get_SP_register();
  if (++taskrun == taskstackpnt+tasks) taskrun = taskstackpnt;
  __set_SP_register(*taskrun);


and in main.c change to
int* taskrun = taskstackpnt;
Link to post
Share on other sites
  • Replies 35
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular 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 ==========

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", yo

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

I ported the code over to msp430-gcc 4.6.3.  I did change some things around:

  • Most ints are changed to unsigned int, otherwise msp430-gcc will generate sign extension asm instructions each time you touch them.
  • I made the taskrun a volatile so it actually works with gcc
  • __task is replaced with the equivalent gcc __attribute__() directives
  • I moved some of the stack variables in main to ".bss" so you get the whole stack ... * otherwise you lose a couple of words
  • gcc generates better code for ints than char so those are replaced
  • I changed the WDT to tick at 1ms interval to make a systick easier
  • added a sys.c that contains "a sort of accurate" sys_delay(msec) 
  • added support for both msp430g2553 and msp430fr5969 both run at 8MHz
  • the init code doesn't bother with checking the size of the stack words for a default, you must supply a size for each task.
  • init code changed magic # to new magic # : ) to try and make it more obvious how the multi stack is being used
  • added a ".bss" count variable to task1 and task2 that can be checked
  • added a makefile, targets are all, clean, install, debug. (I assume you are running linux .. sorry windows people ) debug uses mspdebug and xterms to create 2 windows for debugging from the command line using msp430-gdb
  • probably other changes  ... 
  • note: msp430-elf- target doesn't really work yet.

You can always find the latest version here:  https://gist.github.com/RickKimball/3173f5ca73bc6dcdb7bc

 

-rick

 

Link to post
Share on other sites

...
1204               push.w  R4               ; last push

410F               mov.w   SP,R15           ; it does not remove it due to volatile
421E 0200          mov.w   &taskrun,R14
418E 0000          mov.w   SP,0x0(R14)
532E               incd.w  R14
903E 0208          cmp.w   #0x208,R14
2002               jne     0xC0CE
403E 0202          mov.w   #0x202,R14
4E82 0200          mov.w   R14,&taskrun
4E0F               mov.w   R14,R15         ; another wasted due to volatile
4E21               mov.w   @R14,SP

4134               pop.w   R4              ; first pop
... 

IAR is the King when it comes to high optimization, you can get twice the work done making it possible to use a mcu that cost half as much.

?So in other words it could pay for itself (though commercial use baseline EW430-BL cost $1299)

Link to post
Share on other sites
IAR is the King when it comes to high optimization, you can get twice the work done making it possible to use a mcu that cost half as much.

?So in other words it could pay for itself (though commercial use baseline EW430-BL cost $1299)

 

 

msp430-gcc can do ok too : ) . I used the -ffix-reg feature to generate some optimized switcher code:

//============= TASK SWITCHER ISR =============
__attribute__((interrupt(WDT_VECTOR), naked, used))
void taskswitcher(void);
void taskswitcher(void)
{
    4576:       04 41           mov     r1,     r4     
    4578:       34 52           add     #8,     r4      ;r2 As==11
#ifdef __MSP430_HAS_MSP430XV2_CPU__
   __asm__ volatile ("pushm #9,r15");
    457a:       8f 15           pushm   #9,     r15    
    "push R11\n push R10\n push  R9\n push  R8\n"
    "push  R7\n"
   );
#endif
  ++systick;
    457c:       92 53 18 1c     inc     &0x1c18
  *taskrun = (unsigned)__read_stack_pointer();
    4580:       0f 45           mov     r5,     r15    
    4582:       85 41 00 00     mov     r1,     0(r5)   ;0x0000(r5)
  if (++taskrun == lasttask ) taskrun = taskstackpnt;
    4586:       2f 53           incd    r15            
    4588:       05 4f           mov     r15,    r5     
    458a:       05 96           cmp     r6,     r5     
    458c:       02 20           jnz     $+6             ;abs 0x4592
    458e:       35 40 0e 1c     mov     #7182,  r5      ;#0x1c0e
  __write_stack_pointer((void *)*taskrun);
    4592:       21 45           mov     @r5,    r1     
#ifdef __MSP430_HAS_MSP430XV2_CPU__
  __asm__ volatile ("popm #9,r15");
    4594:       87 17           popm    #9,     r15
Link to post
Share on other sites

>cmp r6, r5

?I don't see where R6 is set?

I could reserve R4 as a __regvar and use it as the taskstackpnt,  compiler probably could then do the switching in 6 words plus one less push/pull?.

But if tasks run out of regs it will use the stack, so that is a trade off
but probably only happens with function-calls that pass a couple of longs.

Link to post
Share on other sites

I think this is the smallest I can get it if you always use 2,4 or 8 task.
?I did a second fibo, that task sure need a larger stack, it crashes if I don't give it 70 words each
 

__regvar __no_init unsigned int taskrun @ __R4;
?...
stackpnt[i] = (unsigned int) (multistack-12);  // PC+SR+11 dummy push? R4 is no longer pushed
?...
taskrun = 0;
...
asm (" mov SP,stackpnt(R4)");
asm (" incd R4");
asm (" bic #-8,R4");
asm (" mov stackpnt(R4),SP");

RESULT 7 WORDS
1205               push.w  R5 ; last push

4184 0200          mov.w   SP,0x200(R4)
5324               incd.w  R4
C034 FFF8          bic.w   #0xFFF8,R4
4411 0200          mov.w   0x200(R4),SP

4135               pop.w   R5 ; first pop
 

 taskrun = (int) stackpnt;
? ...
 asm (" mov SP,0x00(R4)");
 asm (" incd R4");
 asm (" cmp #stackpnt+3*2,R4");  // I can not get tasks define to work here
 asm (" jne $+6;");
 asm (" mov #stackpnt,R4");
 asm (" mov @R4,SP");
?RESULT 9 WORDS but work on any number tasks
Link to post
Share on other sites

I've been spending way too much time looking at this thing. So thanks for that. I did some timing testing with the msp430g2553 running at 8MHz, it seems like it takes about 8 to 10 microseconds to do the context switch. There is some overhead, however I was able to compensate for extra cycles by over clocking the DCO.

 

On the msp430g2553, I made the slight change to increase the DCO clock:

BCSCTL1 = CALBC1_8MHZ;                        // Set DCO to factory calibrated 8MHz
DCOCTL = CALDCO_8MHZ;
DCOCTL += 13;                                 // overclock 103% or 8.22MHz
Speeding it up this much seems to account for the context switch overhead. Any thoughts?

 

BTW: This was with 2 tasks. Splitting the 8MHz clock evenly between the 2 task I used 4MHz to calculate my delay cycle calls:

#include "common.h"

void task1(void) {
  volatile unsigned count=0; // 4 bytes on stack

  P1DIR |= BIT0;
  P1OUT &= ~BIT0;

  while(1){
    P1OUT ^= BIT0;
#if 1
    __delay_cycles(50*(4000000/1000));
#else
    sys_delay(50);
#endif
    P1OUT ^= BIT0;
#if 1
    __delay_cycles(450*(4000000/1000));
#else
    sys_delay(450); // 4+2 bytes of stack used
#endif
    ++count;
  }
-rick
Link to post
Share on other sites

... Unfortunately, I've not been successful getting this to work with msp430-elf-gcc. ...

I've got the msp430-elf-gcc version working properly now in the latest checkin https://gist.github.com/RickKimball/3173f5ca73bc6dcdb7bc

Turns out that I got lucky with msp430-gcc. I had forgot to add a "RETI" instruction in the WDT ISR. The only reason

it worked with msp430-gcc was the default exception handler had ended up in flash right after

the WDT ISR. The default exception handler just does a "RETI" so it just happened to work.

 

I also added a msp430fr5969 specific version that uses the FRAM for stack space. This frees you of the limits of RAM

and allows for larger stacks and more tasks. It should be easy to adapt this to any of the newer

chips that are only supported by msp430-elf-gcc. (RH/TI 4.9 gcc)

 

-rick

Link to post
Share on other sites
  • 2 years later...

@tonyp12 and other folks,  can anybody help me better understand the following lines? 

#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

and

funcpnt const taskpnt[]={ task1, task2, task3,  // <- PUT YOUR TASKS HERE
}; 

 

Have a good one..

Link to post
Share on other sites
  • 3 weeks later...

@MadMayonnaise

They are called Function Pointer Arrays.

I love them. I have been using them extensively to create my CLI - Command Line Interpreter. I can add a new command just by defining a new entry into an array.

They are an alternative to the Gigantic Switch Statement style of coding.

To get you started, here is an article from 1999 by Nigel Jones:
How to Create Jump Tables via Function Pointer Arrays in C and C++

I also found the book Programming Embedded Systems in C and C++ by Michael Barr to be exceptionally helpful. Chapter nine specifically.

 

 

Link to post
Share on other sites

Hi @zeke,

Thanks for the reply. I've already checked the links.

Can you also roughly explain the following code snippet? I know the concept of context switching and what needs to be done prior to it, but the following steps confused me a little bit. 

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

Thanks..

Link to post
Share on other sites

First thing, the snippet you posted seems to be incomplete. I cannot see the closing parenthesis.

Next, the code is initializing a data structure (called multistack) that will serve as The Stack.

When this code runs, the author does several things with The Stack:

  1. Saves the SP register value (which is the present location/address of The Stack Pointer)
  2. Checks to make sure that there are no more than "tasks" numbers of tasks.
  3. Initializes the index of The Stack so it knows where it is in The Stack
  4. Stores a task pointer into the stack (he calls it the PC - probably Program Counter)
  5. Stores the state of the GIE register (Global Interupt Enable register)
  6. Then makes space for 26 bytes of information in The Stack (he calls them 16 bit Words + 2 for wastage[see his post above])

This is the kind of operations that an operating system will do when it wants to stop doing one thing, switch context, and start doing another thing, complete that, then come back and continue doing what it was doing before the interruption.

Does that make sense?

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...