Friday, 7 March 2014

RTOS in PIC Microcontrollers: The Devil Inside!

A modern digital circuit with microcontrollers at its heart. (courtesy

 Microcontrollers are workhorses for most digital electronic devices. A lot of people like me love the power of processing and control delivered by these tiny computers. If you are reading this blog, I can safely assume you are passionate about microcontrollers and want to push the tiny beasts to their full potential and design real world electronic systems.

I assume that you have prior understanding of what microcontrollers are and have been working with them for some time. If you are new to this field it would be great searching and learning about Microcontrollers and their use for various simpler problems.

Focus areas: RTOS based parallel thread application design using PIC microcontrollers with OSA RTOS in mikroC pro for PIC series compilers.

Why do I need RTOS?

Most microcontroller programmers write sequential code for their applications. The microcontroller would read through each function called and do what it is asked to. This is okay for smaller applications where microcontroller is supposed to do only one particular thing. Lets say you want to use your microcontroller to blink an LED and nothing else, you would be okay doing it without feeling the need for running an operating system inside your microcontroller. But for any other applications where more than one thing is supposed to be done simultaneously, you are stuck.

Did you ever feel that your computer works beautifully while you write documents, surf the web and listen to music, all simultaneously. This is made possible by operating systems that share your processor's time amongst tasks. All modern embedded electronic devices you have been using use an operating system of some kind or other. 

Think about a very simple problem, suppose now instead of one LED blinking from your microcontroller, you need 5 leds blinking, all with different frequencies. Sounds like a stupid problem with no real use and any actual device would be more complicated than this but you still can't do it with sequential code you have been writing!. 

Practical problems generally require a larger number of tasks running in parallel. A couple of thread might only be functioning to allow for user interaction while the device's core activity keeps active in the background. 

A RTOS(Real Time Operating System) allows you to run a number or tasks periodically or otherwise providing services such as scheduling to share processor's time amongst different tasks. All processors run only one line of code at a moment.

This tutorial will take you through developing a multi thread application on a PIC18 microcontroller. The compiler used will be MikroC Pro for PIC, just because I love it, no other reason. The operating system used is OSA. We will design an application that will provide you with a basic template to use OSA in pretty much most applications you would come across.

About OSA

I have worked with different RTOSes until now, and never really found a great ultra-low footprint and efficient RTOS to be ported onto devices like microcontrollers with extremely limited amount of memory. A PIC18f452 microcontroller would generally have about 1.5KB of RAM. You would never want to use most of its memory for the scheduler itself.

OSA is a non-preemptive cooperative RTOS for low memory devices and has been ported onto all popular microcontrollers and is generally supported by almost all famous compilers including MPLAB, CCS PICC, AVR studio and MikroC Pro. A very large library of functions are provided by the OS kernel. The program structure ends up being very similar to what you have been working with. While consuming only about 60 byte of memory in RAM, OSA is an ideal choice for microcontroller based applications. You will fell in its love, I suspect :) .

Some terms that I will be using around:
Context switching: Pausing the task where it is and attending to another task, the operating will come back to this task later.

Task: A piece of code that the operating system will execute. Task is a function that the OS will call repeatedly.

Thread: This could be used interchangeably with Task, but in this tutorial's context it is a function that never ends(has a while(1) loop inside) and runs in parallel with other threads.

Being non-preemptive is a disadvantage, it means if your program gets stuck in a long loop inside a low priority task, the scheduler would not automatically preempt or forcefully switch context. Switching of context is upto user in all cooperative RTOSes. This is however not a major setback and you can cope with these issues with some intelligent programming. This is what makes this microcontroller based RTOS different from complex operating systems like Windows, so its good enough with a tiny computer like PIC. 

Making your first multi-thread program

The Plan is to solve the same simple problem that I talked about, We will make a program that blinks 4 LEDs with very different frequencies, all simultaneously. We will launch 4 threads. You are used to having a main function in your microcontroller program where you would have an endless loop to keep the program going. We will write a code that will give you 4 such functions that 'apparently' operate in parallel and give you a feel as if you had 4 main functions in your program. When you are good with blinking 4 LEDs in parallel, you could just fill in the code and do pretty much whatever you like.

To get going with writing our first application,  things that you need to download are:
It would be advisable to browse through the OSA website and study its documentation. It is very well written, something you won't expect for most open source softwares. I Loved it!.

Step one:

Create a new folder and extract the OSA zip file there.Launch MikroC pro and Create a project. Select the same folder where you extracted OSA. OSA works for most microcontrollers but I would prefer going with 18F452, it has a reasonable memory for our work. Use a 8 to 12Mhz crystal.

Why am I selecting such a low frequency crystal?, afterall I am not planning any mercy on this little creature. You are right, there is no mercy today, we will use a 12Mhz crystal and enable PLL to use our microcontroller at 48Mhz!!!, thats the other name of torture but It gives us extremely small context switching overhead and supreme performance.

Now we need to add the operating system's files to our project. Click Add and select osa.h and osa.c files

Click next and include all libraries, then select open configuration for project and select oscillator as HS oscillator with PLL enabled.

Now, we turn back to writing code. Save the project. Open the OSAcfg_Tool.exe file and select the following options. Set Tasks to 4 because we will be having 4 threads in our application, select settings as shownm when done click Save and save the configuration file in the folder with your current project.

The saved file OSAcfg.h requires adding into the project. In MikroC, with your project open, goto Project Menu and add OSAcfg.h file into the project (failure to do so will cause error).

Configuring the timer:

I will not go very technical and briefly discuss why an RTOS program requires configuring a timer. All RTOS need to keep a sense of physical time. So at every moment the Kernel would know what time it is and based on this information generate precise delays, and schedule function calls. In simple words, you can't follow your daily routine without having any sense of time. You know that there are timers inside PIC that allows you to count time. But our Operating system's clock runs much slower than the timer. So we would increment the Operating system's clock by one TICK every time the timer overflows.

 I am planning to have a tick coming every 1ms. This means something, my application will not be able to measure any time less than that 1ms, you won't be able to call delays smaller than 1ms, this is generally okay if none of the thread requires a time delay less than 1ms. Selecting a longer period for tick has a benefit that your controller will spend less time servicing the timer overflow interrupt service routine and more time doing what it is supposed to do.

Yeah, you forgot the prescalers, configuration registers and that stuff you studied in class, I hated them because there are programs made to remember those things. Launch the timer calculator. It knows all that you forgot :).

The timer utility generates precise settings for your timer. It generates an InitTimer0 function that you would call at the start of the program to configure the timer. Copy both the functions to your project.

In your main function, insert a call to InitTimer0. At the start of the project, inculde the OS headers by using the #include <osa.h>  directive. To increment clock every overflow of timer, insert a call to OS_Timer() in the interrupt service routine. So your code should look something like this by now:

Now we need to add the Threads and run scheduler to complete the program. To Define a task that would be called by the scheduler it is defined in the following way:

You should be able to understand the importance of OS_Yeild and OS_Delay() functions. In your RTOS program, OS_Delay is just a very efficient counterpart of Delay_ms you have been using before. Delay_ms is a very inefficient way to waste your processor's time by just tying it up in a useless loop. When you call OS_Delay, rather than wasting time on some dummy instructions, you program will attend to other threads. This should help you see the benefit of employing an RTOS in your application. But remember, an RTOS program thread that never Yields or calls delay will never allow other threads any time on the processor. So you must always call OS_Yeild often in your threads so justice is done to all threads. The scheduler will decide based on the priority of each thread, who gets what percentage of total time.

The RTOS developers generally encourage use of global variables. Particularly because during context switching, the local variables must be conserved in memory. so when all threads are running in parallel, all local variables exist in the memory at the same time. So having local variables hardly has a benefit. But just in case if it allows some memory optimization in your code, remember to use 'static' identifier with local variable declarations.

Just like one thread defined above, you can define any number of threads provided you have informed the OS about them while creating the configuration file.

Last step is create the threads and call the OS scheduler from the main function.

Thats it!!, just fill in the respective tasks with code that you like and you have OSA running with 4 parallel threads in your program. OSA is powerful and a well developed operating system, the routines that I have discussed are the crucial ones and would usually be enough for standard programs. However, you must explore all of its services in its documentation.

The entire example discussed above is available for download at the following link, including a proteus simulation of how it works on your microcontroller. The zipped file also contains the timer utility, OSA configuration file generation tool and OSA documentation.

Credits to Dr Jahangir Ikram and my fellow team members at LUMS for the priceless debates and discussions on technical issues.