[ TUT ] [ C ] Multitasking - another approach

Go To Last Post
2 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As requested I'll introduce an approach to multitasking on the AVR, this solution is platform dependent in respect to how the stackpointer indexes etc but don't worry, we won't need to think about that unless you don't use an AVR! This has worked on the few AVRs I've come across, atmega169 and the atmega328p on the Arduino board (if you've got an Arduino UNO aswell you can test the .hex-file submitted very easily!).

 

Alright, so what am I suggesting? In C's standard library there is a header file called setjmp.h that provides, to quote wikipedia, "non-local jumps". This can be used in a nifty way to achieve multitasking, read the link for more.

For education purposes, a relatively small program called tinythreads has been written, which is what this tutorial is about. I am not the author of this program, I'm not sure who is.

http://www.sm.luth.se/csee/courses/d0003e/labs/tinythreads.h

http://www.sm.luth.se/csee/courses/d0003e/labs/tinythreads.c

This template is incomplete though, with code missing for the crucial yield-method, and code for using mutexes (not necessarily needed for multitasking). This is used for universities so I will not disclose the code here (if you're a good googler you can still find it though, or pm me to get the missing pieces).

 

There is a lot to read about this if you want to learn the intricate details of the implementation of tinythreads, here are the lecture slides that describe the functionality:

http://www.sm.luth.se/csee/courses/d0003e/lectures/lecture4.pdf

The font is comic sans which makes your eyes bleed but please scroll through it anyway.

 

So what does tinythreads do? It allocates stack space to methods you spawn, and switches between them. The switching can be implemented differently but the most basic way is just in a round-robin fashion. The switch can be triggered by the thread itself by a call to yield (cooperative multitasking) or via some interrupt (preemptive multitasking). What yield does in it's most simple implementation is (except disabling interrupts) is enqueuing the currently running thread and dispatching the first waiting task in the ready-queue.

 

How then do you use it? First, pm me about the implementation of "yield" if you can't figure it out yourself. Then, in your c-file, simply include tinythreads.h. Then to run your desired tasks, call on the method spawn, supplying your task and an argument to it (if your task doesn't take any arguments then just write a zero or whatever (and if you want your task to take more arguments you can easily modify the code in tinythreads - the struct "thread_block" and the definition of spawn)).

Your tasks can either spawn another task or instance of itself, or be endless. I will provide examples.

 

// in this example, yield must be called from an ISR (preemptive)

static void task1(int i) {
    while (1) ... ;
}

static void task2(int i) {
    while (1) ... ;
}

int main(void) {
    spawn(task1, 1);
    spawn(task2, 1);
    while (1) ... ;
}
// in this example, yield is called from within each task (cooperative)

static void task1(int i) {
    while (1) {
        ...
        yield();
    };
}

static void task2(int i) {
    while (1) {
        ...
        yield();
    }
}

int main(void) {
    spawn(task1, 1);
    spawn(task2, 1);
    while (1) {
        ...
        yield();
    }
}
// in this example, the tasks spawn new tasks, yield is called from the ISR

static void task1(int i) {
    ...
    spawn(task1, i);
}

static void task2(int i) {
    ...
    spawn(task2, i);
}

int main(void) {
    spawn(task1, 1);
    spawn(task2, 1);
    while (1) ... ;
}
// in this example, the tasks spawn new tasks, yield is called from main

static void task1(int i) {
    ...
    spawn(task1, i);
}

static void task2(int i) {
    ...
    spawn(task2, i);
}

int main(void) {
    spawn(task1, 1);
    spawn(task2, 1);
    while (1) yield();
}

There are some things you can play with in tinythreads, for instance what a "thread_block" struct may contain, how big the stack for each task is (it's denoted 80 in the standard implementation but that's quite a lot, you can get by with much less depending on your tasks), and how many threads you allow (4 by default).

The .hex-file I've submitted is a program that blinks three LEDs, connected to Arduino UNOs digital pins 2, 4 and 7 aswell as the on-board orange LED, they blink at an increasing rate then starts over, triggered by the watchdog timer. My code is modified though and not as the original tinythreads, I can of course give this aswell if wanted. I'm not sure how the .hex behaves on other atmegas than the 328p.

Attachment(s): 

sol i sinne - brun inne

Last Edited: Tue. Feb 3, 2015 - 09:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's a video of how the program I attached behaves, for you who can't test it IRL. Four LEDs blink with increasing speed (if you look at one LED at a time it's easier to distinguish). It's built upon the tinythreads kernel but I've modified some details in predefined routines and also added a task scheduler which decides which thread to run based on the desired "delay" (a parameter I added to the thread_block struct in tinythreads.c), and not just blindly round-robin switch as the "default" implementation would do.

 

While the LEDs are not being turned on or off or the kernel is context switching, the system is in Standby Mode, CPU otherwise @62500Hz

sol i sinne - brun inne

Last Edited: Tue. Feb 3, 2015 - 04:27 AM