16
\$\begingroup\$

Multitasking is important these days. I wonder how we can achieve it in microcontrollers and embedded programming. I am designing a system which is based on a PIC microcontroller. I have designed its firmware in MplabX IDE using C and then designed an application for it in Visual Studio using C# .

Since I've gotten used to using threads in C# programming on the desktop to implement parallel tasks, is there a way to do the same in my microcontroller code? MplabX IDE provides pthreads.h but it is just a stub with no implementation. I know there is FreeRTOS support but using that makes your code more complex. Some forum says that interrupts can also be used as multi tasking but I don't think interrupts are equivalent to threads.

I am designing a system which sends some data to a UART and at the same time it need to send data to a website via (wired) ethernet. A user can control the output through the website but the output turns ON/OFF with a delay of 2-3 sec. So that is the problem I am facing. Is there any solution for multi tasking in microcontrollers?

\$\endgroup\$
12
  • 1
    \$\begingroup\$ Threads can be used only on processors which runs an OS, because threads are part of process, and processes are only used in OSes. \$\endgroup\$
    – Laki
    Commented Oct 7, 2015 at 9:43
  • \$\begingroup\$ @Zola yes you are right. But what in case of controllers? \$\endgroup\$
    – Aircraft
    Commented Oct 7, 2015 at 9:44
  • 2
    \$\begingroup\$ Possible duplicate of RTOS for Embedded Systems \$\endgroup\$ Commented Oct 7, 2015 at 9:46
  • 1
    \$\begingroup\$ Can you explain why you need true multitasking and cannot reasonably implement your software based on a round-robin task approach or a select() loop or similar? \$\endgroup\$ Commented Oct 7, 2015 at 16:39
  • 2
    \$\begingroup\$ Well, as I already said, I am sending & receiving data to uart and at the same time sending & receiving data to ethernet. Apart from this I also need to save data in SD card along with the time, so yes DS1307 RTC is involved and EEPROM is also involved. Till now I just have 1 UART but may be after few days I'll be sending & receiving data from 3 UART modules. Website will also receive data from 5 different systems installed at remote place. This all has to be parallel but right not its not parallel but with a delay of few secs. ! \$\endgroup\$
    – Aircraft
    Commented Oct 8, 2015 at 4:25

6 Answers 6

20
\$\begingroup\$

There are two main types of multitasking operating systems, preemptive and cooperative. Both allow multiple tasks to be defined in the system, the difference is how the task switching works. Of course with a single core-processor only one task is actually running at a time.

Both types of multitasking OS's require a separate stack for each task. So this implies two things: first, that the processor allows stacks to be placed anywhere in RAM and therefore has instructions to move the stack pointer (SP) around -- i.e. there is no special purpose hardware stack like there is on the low-end PIC's. This leaves out the PIC10, 12 and 16 series.

You can write an OS almost entirely in C, but the task switcher, where the SP gets move around has to be in assembly. At various times I've written task switchers for the PIC24, PIC32, 8051, and 80x86. The guts are all quite different depending on the architecture of the processor.

The second requirement is that there is enough RAM to provide for multiple stacks. Usually one would like at least a couple hundred bytes for a stack; but even at just 128 bytes per task, eight stacks is going to require 1K bytes of RAM -- you don't have to allocate the same size stack for each task though. Remember you need enough stack to handle the current task, and any calls to its nested subroutines, but also stack space for an interrupt call since you never know when one is going to occur.

There are fairly simple methods to determine how much stack you are using for each task; for example you can initialize all of the stacks to a particular value, say 0x55, and run the system for a while and then stop and examine memory.

You don't say what kind of PIC's you want to use. Most PIC24's and PIC32's will have plenty of room to run a multitasking OS; the PIC18 (the only 8-bit PIC to have stacks in RAM) has a maximum RAM size of 4K. So that's pretty iffy.

With cooperative multitasking (the simpler of the two), task switching is only done when the task "gives up" its control back to the OS. This happens whenever the task needs to call an OS routine to perform some function which it will wait for, such as an I/O request or timer call. This makes it easier for the OS to switch stacks, since it is not necessary to save all of the registers and state information, the SP can just be switched to another task (if there are no other tasks ready to run, an idle stack is given control). If the current task doesn't need to make an OS call but has been running for a while, it needs to give up control voluntarily to keep the system responsive.

The problem with cooperative multitasking is if the task never gives up control, it can hog the system. Only it and any interrupt routines that happen to be given control can run, so the OS will seem to lock up. This is the "cooperative" aspect of these systems. If a watchdog timer is implemented that is only reset when a task switch is performed, then it is possible to catch these errant tasks.

Windows 3.1 and earlier were cooperative operative systems, which is partly why their performance wasn't so great.

Preemptive multitasking is more difficult to implement. Here, tasks are not required to give up control manually, but instead each task can be given a maximum amount of time to run (say 10 ms), and then a task switch is performed to the next runable task if there is one. This requires arbitrarily stopping a task, saving all of the state information, and then switching the SP to another task and starting it. This makes the task switcher more complicated, requires more stack, and slows the system down a little bit.

For both cooperative and preemptive multitasking, interrupts can occur at any time which will temporarily preempt the running task.

As supercat points out in a comment, one advantage cooperative multitasking has is it is easier to share resources (e.g. hardware like a multi-channel ADC or software like modifying a linked list). Sometimes two tasks want access to the same resource at the same time. With preemptive scheduling, it would be possible for the OS to switch tasks in the middle of one task using a resource. So locks are necessary to prevent another task from coming in and accessing the same resource. With cooperative multitasking, this not necessary because the task controls when it will release it self back to the OS.

\$\endgroup\$
9
  • 3
    \$\begingroup\$ An advantage of cooperative multitasking is that it is in most cases not necessary to use locks to coordinate access to resources. It will be sufficient to ensure that tasks always leave resources in a shareable state whenever they relinquish control. Preemptive multitasking is much more complicated if a task may get switched out while it holds a lock on a resource needed by another task. In some cases, the second task may end up getting blocked for longer than it would have been under a cooperative system, since the task holding the lock would have been devoting the system's... \$\endgroup\$
    – supercat
    Commented Oct 7, 2015 at 17:44
  • 1
    \$\begingroup\$ ...full resources toward finishing the action that (on the pre-emptive system) would have required the lock, thus making the guarded object available to the second task. \$\endgroup\$
    – supercat
    Commented Oct 7, 2015 at 17:46
  • 1
    \$\begingroup\$ While cooperative multitaskers require discipline, ensuring that timing requirements will be met can sometimes be easier under a cooperative multitasker than under a preemptive one. Since very few locks will need to be held across a task switch, a five-task round-robin task-switch system where tasks are required not to go more than 10ms without yielding, combined with a little logic that says "If task X urgently needs to run, run it next", will ensure that task X never has to wait more than 10ms once it signals before it gets to run. By contrast, if a task requires a lock which task X... \$\endgroup\$
    – supercat
    Commented Oct 7, 2015 at 19:43
  • 1
    \$\begingroup\$ ...is going to need but gets switched out by a pre-emptive switcher before releasing it, X might not get to do anything useful until the CPU scheduler gets around to running the first task. Unless the scheduler includes logic to recognize and handle priority inversion, it might take awhile before it gets around to letting the first task finish its business and release the lock. Such problems are not unsolvable, but solving them requires a lot of complexity which could have been avoided in a cooperative system. Cooperative systems work great except for one gotcha: ... \$\endgroup\$
    – supercat
    Commented Oct 7, 2015 at 19:45
  • 3
    \$\begingroup\$ you don't need multiple stacks in cooperative if you code in continuations. In essence your code is divided up in functions void foo(void* context) the controller logic (kernel) pulls one pointer and function pointer pair of the queue and calls it one at a time. That function uses the context to store its variables and such and can then add submit a continuation to the queue. Those function must return quickly to let other tasks their moment in the CPU. This is an event based method only requiring a single stack. \$\endgroup\$ Commented Oct 8, 2015 at 11:35
16
\$\begingroup\$

Threading is provided by an operating system. In the embedded world we don't usually have an OS ("bare metal"). So this leaves the following options:

  • The classic main polling loop. Your main function has a while(1) which does task 1 then does task 2...
  • Main loop + ISR flags: You have an ISR which does the time-critical function and then alerts the main loop via a flag variable that the task needs service. Perhaps the ISR puts a new character in a circular buffer, and then tells the main loop to handle the data when it is ready to do so.
  • All ISR: Much of the logic here is executed from the ISR. On a modern controller like an ARM which has multiple priority levels. This can provide a powerful "thread-like" scheme, but can also be confusing to debug so it should be reserved only for critical timing constraints.
  • RTOS: An RTOS kernel (facilitated by a timer ISR) can allow for switching between multiple threads of execution. You mentioned FreeRTOS.

I would advise you use the simplest of the above schemes that will work for your application. From what you describe, I would have the main loop generating packets and placing them into circular buffers. Then have a UART ISR based driver that fires whenever the previous byte is done sending until the buffer is sent, then waits for more buffer content. Similar approach for the ethernet.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ This is a very useful answer because it addresses the root of the problem (how to multitask on a small embedded system, rather than threads as a solution). A paragraph about how it could apply to the original question would be superb, perhaps including the pros and cons of each for the scenario. \$\endgroup\$
    – David
    Commented Oct 7, 2015 at 17:24
8
\$\begingroup\$

As in any single-core processor doing real software multitasking is not possible. So you must take care to switch between multiple tasks one way. The different RTOS are taking care of that. They have a scheduler and based on a system tick they will switch between different tasks to give you a multitasking capability.

The concepts involved in doing so (context saving and restoring) are quite complicated, so doing this manually is probably going to be difficult and makes your code more complex and because you have never done that before, there will be errors in it. My advice here would be to use a tested RTOS just like FreeRTOS.

You mentioned that interrupts provide a level of multitasking. This is sort of true. The interrupt will interrupt your current program at any point and execute the code there, it's comparable to a two task system where you have 1 task with low priority and another with high priority which finishes inside one time-slice of the scheduler.

So you could write an interrupt handler for a recurring timer which will send a few packets over the UART, then let's the rest of your program execute for a few milliseconds and send the next few bytes. That way you sort of get a limited multitasking capability. But you will also have a rather long interrupt which might be a bad thing.

The only real way to get to do multiple tasks at the same time on a single-core MCU is to use the DMA and peripherals as they work independent of the core (DMA and MCU share the same bus, so they work a bit slower when both are active). So while the DMA is shuffling the bytes to the UART your core is free to send the stuff to the ethernet.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ thanks, DMA sounds interesting. I'll definitely search for it.! \$\endgroup\$
    – Aircraft
    Commented Oct 7, 2015 at 9:52
  • \$\begingroup\$ Not all series of PICs have DMA. \$\endgroup\$
    – Matt Young
    Commented Oct 7, 2015 at 15:43
  • 1
    \$\begingroup\$ I am using PIC32 ;) \$\endgroup\$
    – Aircraft
    Commented Oct 7, 2015 at 16:15
6
\$\begingroup\$

The other answers already described the most used options (main loop, ISR, RTOS). Here's another option as a compromise: Protothreads. It is basically a very lightweight lib for threads, that uses the main loop and some C macros, to "emulate" an RTOS. Of course it's no full OS, but for "simple" threads it can be useful.

\$\endgroup\$
9
  • \$\begingroup\$ from where can i download its source code for windows? I think its only available for linux.! \$\endgroup\$
    – Aircraft
    Commented Oct 7, 2015 at 10:11
  • \$\begingroup\$ @CZAbhinav It should be OS independent and you can get the latest download here. \$\endgroup\$
    – erebos
    Commented Oct 7, 2015 at 10:19
  • \$\begingroup\$ I am in windows right now and using MplabX, i don't think its useful here. Anyway thanks.! \$\endgroup\$
    – Aircraft
    Commented Oct 7, 2015 at 10:23
  • \$\begingroup\$ Haven't heard about protothreads, sounds like an interesting technique. \$\endgroup\$
    – Arsenal
    Commented Oct 7, 2015 at 13:32
  • \$\begingroup\$ @CZAbhinav What are you talking about? It's C code and has nothing to do with your operating system. \$\endgroup\$
    – Matt Young
    Commented Oct 7, 2015 at 15:14
3
\$\begingroup\$

My basic design for a minimal time-sliced RTOS haven't changed much over several micro families. It's basically a timer interrupt driving a state machine. The interrupt service routine is the OS kernel while the switch statement in the main loop is the user tasks. Device drivers are interrupt service routines for I/O interrupts.

The basic structure is as follows:

unsigned char tick;

void interrupt HANDLER(void) {
    device_driver_A();
    device_driver_B();
    if(T0IF)
    {
        TMR0 = TICK_1MS;
        T0IF = 0;   // reset timer interrupt
        tick ++;
    }
}

void main(void)
{
    init();

    while (1) {
        // periodic tasks:
        if (tick % 10 == 0) { // roughly every 10 ms
            task_A();
            task_B();    
        }
        if (tick % 55 == 0) { // roughly every 55 ms
            task_C();
            task_D();    
        }

        // tasks that need to run every loop:
        task_E();
        task_F();
    }
}

This is basically a cooperative multitasking system. Tasks are written to never enter an infinite loop but we don't care because the tasks run within an event loop so the infinite loop is implicit. This is a similar style of programming to event-oriented/nonblocking languages like javascript or go.

You can see an example of this style of architecture in my RC transmitter software (yes, I actually use it to fly RC airplanes so it's somewhat safety critical to prevent me crashing my planes and potentially killing people): https://github.com/slebetman/pic-txmod. It has basically 3 tasks - 2 real-time tasks implemented as stateful device drivers (see the ppmio stuff) and 1 background task implementing the mixing logic. So basically it's similar to your web server in that it has 2 I/O threads.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I wouldn't really call that 'cooperative multitasking', as that is really not substantially different than any other microcontroller program that has to do multiple things. \$\endgroup\$ Commented Oct 8, 2015 at 3:08
2
\$\begingroup\$

While I appreciate that the question specifically asks about the use of an embedded RTOS, it occurs to me that the broader question being asked is "how to achieve multitasking on an embedded platform".

I would strongly advise you to forget about using an embedded RTOS at least for the time being. I advise this because I think it is essential to first learn about how to achieve task 'concurrency' by means of extremely simple programming techniques consisting of simple task schedulers and state machines.

To extremely briefly explain the concept, each module of work that needs to be done (i.e. each 'task') has a particular function that must be called ('ticked') periodically for that module to do some stuff. The module retains its own current state. You then have a main infinite loop (the scheduler) that calls the module functions.

Crude illustration:

for(;;)
{
    main_lcd_ui_tick();
    networking_tick();
}


...

// In your LCD UI module:
void main_lcd_ui_tick(void)
{
    check_for_key_presses();
    update_lcd();
}

...

// In your networking module:
void networking_tick(void)
{
    //'Tick' the TCP/IP library. In this example, I'm periodically
    //calling the main function for Keil's TCP/IP library.
    main_TcpNet();
}

Single-threaded programming structure like this whereby you periodically call main state machine functions from a main scheduler loop is ubiquitous in embedded programming, and this is why I would strongly encourage the OP to be familiar and comfortable with it first, before diving straight into using RTOS tasks/threads.

I work on a type of embedded device that has a hardware LCD interface, internal web server, email client, DDNS client, VOIP, and many other features. Although we do use an RTOS (Keil RTX), the number of individual threads (tasks) used is very small and most of the 'multitasking' is achieved as described above.

To give a couple of examples of libraries that demonstrate this concept:

  1. The Keil networking library. The whole TCP/IP stack can be run single-threaded; you periodically call main_TcpNet(), which iterates the TCP/IP stack and any other networking option you have compiled in from the library (e.g. the web server). See http://www.keil.com/support/man/docs/rlarm/rlarm_main_tcpnet.htm. Admittedly, in some situations (possibly outside of the scope of this answer) you do reach a point where it starts to become beneficial or necessary to use threads (particularly if using blocking BSD sockets). (Futher note: The new V5 MDK-ARM actually spawns a dedicated Ethernet thread - but I'm just trying to provide an illustration.)

  2. The Linphone VOIP library. The linphone library itself is single-threaded. You call the iterate() function at a sufficient interval. See http://www.linphone.org/docs/liblinphone-javadoc/org/linphone/core/LinphoneCore.html#iterate(). (Bit of a poor example because I used this on an embedded Linux platform and linphone's dependency libraries undoubtedly spawn threads, but again it's to illustrate a point.)

Going back to the specific problem outlined by the OP, the problem seems to be the fact that UART communication must take place at the same time as some networking (transmitting packets via TCP/IP). I don't know what networking library you're actually using, but I'd assume it has a main function that needs to be called frequently. You would need to write your code that deals with UART data transmission/reception to be structured in a similar way, as a state machine that can be iterated by periodic calls to a main function.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ Thanks for this nice explanation, I am using TCP/IP library provided by microchip and it is very huge complex code. I somehow managed to break it into parts and make it usable according to my requirements. I'll definitely try one of your approach.! \$\endgroup\$
    – Aircraft
    Commented Oct 8, 2015 at 4:34
  • \$\begingroup\$ Have fun :) Using an RTOS definitely makes life easier in many situations. In my view, using a thread (task) makes programming effort a lot easier in one sense, since you can avoid having to break your task down into a state machine. Instead, you just write your task code just like you would in your C# programs, with your task code created as if it's the only thing that exists. It's essential to explore both approaches, and as you do more embedded programming, you start to get a feel for which approach is best in each situation. \$\endgroup\$ Commented Oct 8, 2015 at 9:21
  • \$\begingroup\$ I also prefer using threading option. :) \$\endgroup\$
    – Aircraft
    Commented Oct 8, 2015 at 9:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.