FreeRTOS is known as Real-Time Operating System. It would probably be too dare to call it real-time-os, preferably a real-time scheduler where applications can be split into independent tasks that share complete processor resources by switching them rapidly. It. It looks like all functions are executed in parallel. This feature is called multitasking.
There are many debates on using RTOS on AVR microcontrollers as they are arguably too small for the running scheduler. The main limitation is a small amount of ram and increased power usage. If you use lots of tasks in the application, you will probably run out of RAM to save context when switching between tasks. Consider FreeRTOS only if you use larger scale AVRs like Atmega128 or Atmega256. Indeed you can find smaller schedulers that are specially designed for smaller microcontrollers, even tiny series. On the other hand, if you master FreeRTOS, it can be used with multiple microcontrollers like ARM Cortex, PIC, and various compilers, including IAR, GCC, and Keil Rowley, Attolic. And the main reason to keep an eye on it – it is free.
Probably it would take lots of time and space to go through RTOS theory. Some great information can be found on the FreeRTOS website itself. In this series of posts, we will focus on the practical side of using RTOS on an AVR microcontroller. We will go through several steps, from a single task application to more complex solutions.
Things needed to start with FreeRTOS
To begin using FreeRTOS, we need to download it from https://www.freertos.org/. While writing this post latest version was FreeRTOSV7.0.1. You’ll find FreeRTOS source code files, ports to specific microcontrollers, and many example programs for various microcontrollers and compilers in the downloaded package. We will use an old good Piconomic Atmega128L development board with an external memory expansion board that adds 8K of SRAM. You can choose any Atmega128 development board to blink LEDs, read buttons, and use USART. Programs will work fine on any of them. As a programming environment, we will use AVRStudio5, which is still entirely new and capricious. If you want to learn how to start working with AVRStudio5, check out this short tutorial.
Preparing AVRStudio5 project
First of all, we create a new project in AVRStudio5. For this, we select File->New Project and select the AVR GCC C Executable Project. Enter the proper location where your project will be stored.
Click OK. Then select Device “Atmega128” from device list:
Click OK, and you are set up with the first project with the main program file that contains some initial code. As the FreeRTOS package includes lots of files that we don’t need for our project, we will copy the necessary files to our folder. To make it easier to update FreeRTOS files with upcoming releases, we will maintain a folder structure close to the original. To do so in our project tree, we are going to create a Source folder. To do so, click the right mouse button on the project folder and select Add->New Folder and type in Source. Now import the following files to the Source folder from the downloaded FreeRTOS package Source folder:
Add all files that are in the Source directory (including readme.txt). Adding files is simple – click the right mouse button of the Source folder and select Add->Existing Item and in the file browser, select necessary files (multiple select is possible!).
We have added only kernel C files. Now we have to take care of headers. For this, we need to create an include folder inside the Source folder. Then add all files to it from the FreeRTOS package include the folder.
Now that we’ve taken care of the FreeRTOS kernel, we need port files. As you may know, port files are to support specific microcontroller hardware. Port files contain information on how to run the SysTick timer and how to save and restore context to and from the task. Porting is an essential part if we need to support one or another microcontroller. FreeRTOS package already has lots of port options, and most likely, you may find one that will support your selected MCU. In another case, you’ll have to write port on your own. As we are using the Atmega128L microcontroller, the included Atmega323 port works fine to use it for now. To include port files properly, let’s create a folder named portable inside the Source folder. And then, in the portable folder, we create a GCC folder. And in GCC, we create a folder named ATMega323. Then import porting files port.c and portmacro.h to this folder from the FreeRTOS package. Still, this isn’t finished with files. We also need the memory management file heap_1.c, which takes care of allocating and freeing memory for tasks and queues. To add this file to the project, create a MemMang folder in a portable folder, and add a file from the same folder in the downloaded package. And lastly, FreeRTOS needs FreeRTOSConfig.h configuration file that keeps all freeRTOS related settings. Just import it from FreeRTOS\Demo\AVR_ATMega323_WinAVR.
To make things neat, let us create another folder, Drivers, in the project root directory. This will store microcontroller peripheral drivers like USART, I2C, ADC, button, LED, and so on.
Before we are going to write some code, the project has to be configured. To start configuration, go to the menu Project->Properties. First, go to the Build tab.
This time we are going to configure the project as a release. Select Configuration as Release. So we need to generate .hex file (.map, .lss and .eep). These can be selected in the Build Artifact group.
In the Toolchain tab and Optimization, select -Os optimization. In directories, you will need to include all directories containing .h files in your project(I only managed to get working only with absolute paths). Add GCC_MEGA_AVR in Defined Symbols to tell core that we will use GCC for the AVR microcontroller.
FreeRTOS Configuration
FreeRTOS has several options that allow configuring applications for various needs. All predefined parameters are placed in FreeRTOSConfig.h file. Depending on what functions you are going to use, some of them may be omitted. AVR microcontroller is too small to use all features of RTOS because of limited RAM. For instance, using the trace facility probably would be killing. So for our basic example, we are using the following settings:
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( ( unsigned long ) 7372800 )
#define configTICK_RATE_HZ ( ( portTickType )1000 )
#define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 1 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 85 )
#define configTOTAL_HEAP_SIZE ( (size_t ) ( 3500 ) )
#define configMAX_TASK_NAME_LEN ( 8 )
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 1
#define configIDLE_SHOULD_YIELD 1
#define configQUEUE_REGISTRY_SIZE 0
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 0
#define INCLUDE_uxTaskPriorityGet 0
#define INCLUDE_vTaskDelete 0
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 0
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 0
We will use a preemptive kernel engine where kernel timer tick ISR determines which task should run next according to task priorities. Preemptive multitasking is similar to OS like Linux or Windows. The preemptive kernel uses more RAM but is more flexible. Also, we aren’t going to use an idle hook for now – so disable it too and make idle task yield immediately once it is run. We won’t be changing task priorities during program flow, so we disable this functionality too. We are going to use task delay utilities, so leave them ON.
Each functionality that isn’t used frees some amount of memory. For low memory devices is very important to select these carefully.
After all project properties are set, we can start coding our first application. Why not start with a single task – LED blink. To make code modular, we will begin by writing an LED library. So we need to write these routines and put them in separate .c and .h files.
LED freeRTOS driver
We created a Drivers folder in our project root directory. Create LED.c and LED.h files by pressing the right mouse button on the Drivers folder and selecting Add->New Item. AVRStudio5 will generate .c and .h templates where we can start writing our code.
We are going to control one LED connected to pin D6 in our test project. The writing driver is easy. First, we need to initialize the LED pin as output with a simple function:
void vLEDInit(void)
{
// Set LED_O as output pin
DDR_LED_O |= (1<<BIT_LED_O);
}
and then write the LED toggle function that will be called by the task.
void vLEDToggle(void)
{
//Toggle LED
PORT_LED_O ^= (1<<BIT_LED_O);
}
that’s it.
Creating a task
Later, we will add more tasks, so let’s put them all in one separate source file. I created mytasks.c file along with mytasks.h. Let’s create simple tasks that will toggle LED every second. We already have an LED toggling driver ready. The only thing left is managing proper delay so LED would toggle every second. FreeRTOS has a handy function that allows setting tasks delays when tasks should run. This is called vTaskDelayUntil(). It takes two parameters – previous wake time and ticks to wait. Now it’s a tricky part of all this. If you look back in FreeRTOSConfig.c file, you’ll find a line:
#define configTICK_RATE_HZ ( ( portTickType )1000 )
This means that the kernel timer ticks every 1ms. To get the 1s delay, we need to wait for 1000 ticks. All we need is to set this number as the second parameter.
void vLEDFlashTask( void *pvParameters )
{
vLEDInit();
portTickType xLastWakeTime;
const portTickType xFrequency = 1000;
xLastWakeTime=xTaskGetTickCount();
for( ;; )
{
vLEDToggle();
vTaskDelayUntil(&xLastWakeTime,xFrequency);
}
}
Function xTaskGetTickCount() returns current number of ticks calculated form program start. So we need to pass this number to our delay function as the first parameter. We are doing LED initialize before endless for(;;) loop. And task application goes inside an endless loop. Here goes our vLEDToggle() function and vTaskDelayUntil() function. vtaskDelayUntil function ensures that the task remains in the blocked state until the delay ends. So this means that the blocked task isn’t scheduled until the proper event unblocks it (in our case, delay exceeds). If we used the idle task hook function for setting MCU to power save mode, our application would require less energy as your task is run only every 1s.
Running freeRTOS task
Once the task is created, consider, the most significant job is done. Now what is left is to create this task in the main routine and start the scheduler. Before we start, we must decide what priority task should be assigned. As we are going to use one task (plus idle), we can use idle priority:
#define mainLED_TASK_PRIORITY ( tskIDLE_PRIORITY )
To create a task, we are going to use one handy function xTaskCreate(). It needs several parameters. The first one is a task function name that has to be run. The second is a task name that can be freely chosen. The third parameter is stack size. Our application is straightforward so that a minimal stack can be used. For more complex tasks, select this parameter carefully. Then follows the pointer to parameters that have to be passed to tasks. If there are no parameters to pass, use NULL. Following setting is task priority. And the last parameter is used to pass back a handle by reference so the task could be addressed. Pass NULL as we aren’t going to use it. Our created task should look as follows:
xTaskCreate( vLEDFlashTask, ( signed char * ) "LED", configMINIMAL_STACK_SIZE, NULL, mainLED_TASK_PRIORITY, NULL );
And lastly, we must start the scheduler, which is our RTOS kernel. This is done by calling vTaskStartScheduler().
The compiled project occupies about 6K of Flash memory and uses 3,6K(88.5%) of RAM. So RAM is a limiting factor on how many tasks you are going to run and what complexity they are if no preemption is required as alternative co-routines can be used as they can share a stack.
You can download the full project here: M128RTOS[~200KB]
Hi,
your instructions are nearly perfect for new commers like me.
I wanted to ask you for a little optimisation in the following points.
1) Ad some screenshots of the folder structure when you finished adding all the headerfiles from freeRTOS, just to be on the safe side.
2) In the last paragraph before talking about the freertosConfig.h file, you talked about “including all directories containing .h files”. From where? FreeRTOS? If so, which ones. This also could be solved if you would add a small screenshot of the files that you are talking about.
3) Add “GCC_MEGA_AVR” into “Defined Symbols”? A little elaboration with one or 2 screenshots would be great.
Again those are my own humble recommendations, and I would appreciate it if you can find some time
Thank you for suggestions. I’ll try to make these more clear when I find time. For now if you stuck at some point don’t hesitate to ask. And of course you can always download project archive to analyze it. It’s at the end of post.
Thank you for your answer.
I’am stuck at these 3 points that where mentioned in my frist post.
Where should the freertosCONFIG.h file located, if you consider that “./ ” represents the root if the project folder.
“GCC_MEGA_AVR” into “Defined Symbols” What is meant by that?
Thank you a lot for this lesson, actually i fellow all your steps, but an error has been appeared .
undefined reference to ‘vApplicationIdleHook’ ..???!!
What’s that? when i put comments mark before this function in tasks.c file, this error disappeared so i think something i missed in the initialization and declaration of RTOS files.
I’ll be grateful if u could help me to fix this error
at EreeRTOSconfig.h
change this line #define configUSE_IDLE_HOOK 1
to #define configUSE_IDLE_HOOK 0
(that’s a guess )
Good luck
Hello
When am I debugging the program and it comes to this line of code:
/* The wake time has not overflowed, so we can use the current block list. */
vListInsert( ( xList * ) pxDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
The simulation stopped
How do you debugging this project to AVR Debugger?
Hello,
I’m unable to import the FreeRTOSConfig.h file, the error says, cannot read such file, can some one pl help me.
Thanks in advance.
u gotta change the toochain directories to the one you downloaded your project into
i guess there are few things worth noting here for the newbies like me:
first, if you are downloading the project here then put it in D:ElektronikaProjectsM128RTOS , or you can change the path from toolchain directories otherwise you will get can’t find the directory for some files.
Second, you will need to change some outdated symbol
SIG_OUTPUT_COMPARE1A to TIMER1_COMPA_vect
and it will build successfully, then you can open proteus and just get atmega128 ,adjust the freq to external medium and in advanced properties remove default to 7372800Hz, and it will work perfectly!
also i would like to thank Scienceprog for this amazing tutorial, it’s way better than that on the official freertos site.
where can I find this file ? thanks
Error 1 FreeRTOS.h: No such file or directory C:\Users\antonius\Documents\Atmel Studio\6.1\M128RTOS\freertosm128\freertosm128.c 9 22 freertosm128