Lesson 14: Real-Time OS FreeRTOS
About FreeRTOS Operation System
FreeRTOS is a real-time operating system kernel for embedded devices. It is a free RTOS, a portable, open source, and a mini Real Time kernel for microcontrollers. You can check http://www.FreeRTOS.org regularly for detailed information.
FreeRTOS has the following editions:
FreeRTOS
FreeRTOS has the following standard features:
- Preemptive or co-operative operation
- Very flexible task priority assignment
- Queues, binary semaphores, counting semaphores, mutexes, recursive mutexes
- Software timers, event groups
- Tick and Idle hook functions
- Stack overflow checking
OpenRTOS
OpenRTOS is a commercially licensed version of FreeRTOS provided under license from Real Time Engineers Ltd. by a third party.
SafeRTOS
SafeRTOS shares the same usage model as FreeRTOS but has been developed in accordance with the practices, procedures, and processes necessary to claim compliance with various internationally recognized safety-related standards.
Licensing
The FreeRTOS is based on an open-source license. It can be used in commercial applications and is freely available to everybody. FreeRTOS users retain ownership of their intellectual property.
See http://www.FreeRTOS.org/license for the latest open source license information.
Documents:
Data Types
Specific Data Types
FreeRTOS has two definitions for the port-specific data type: TickType_t and BaseType_t in the portmacro.h header file.
Prefix | Description | Configuration |
TickType_t | a data type used to hold the tick count value | configUSE_16_BIT_TICKS = 0 ➤ TickType_t is defined as an unsigned 32-bit type; configUSE_16_BIT_TICKS = 1 ➤ TickType_t is defined as an unsigned 16-bit type; |
BaseType_t | generally used for return types that can take only a very limited range of values; and for the pdTRUE/pdFALSE type Booleans | 8-bit architecture ➤ BaseType_t is defined as an 8-bit type; 16-bit architecture ➤ BaseType_t is defined as a 16-bit type; 32-bit architecture ➤ BaseType_t is defined as a 32-bit type; |
Prefixes for functions
Prefix | Description | Example | |
Function | Defined in | ||
x | return a non-standard type, such as BaseType_t, struct... | xQueueReceive() | queue.c |
v | return a void | vTaskPrioritySet() | task.c |
pv | return a pointer to void | pvTimerGetTimerID() | timers.c |
prv | private functions | prvCalculateSum() |
Prefixes for variables
Prefix | Description | Example |
c | int8_t (char) | int8_t cID; |
s | int16_t (short) | int16_t sVersion; |
l | int32_t (long) | int32_t lCount; |
x | BaseType_t and any other types(struct, task handles, queue handles...) | |
u | unsigned | uint32_t ulCount; |
p | pointer | int32_t *plCount; |
Create FreeRTOS Project
- Create a new Project in PSoC Creator.
- Download FreeRTOS_10.0.0_PSoC56.zip, and unzip it into the project folder.
- Add FreeRTOS paths into the Project
- Create Folders and add FreeRTOS files to your project
- Right-click on the Project in the Workspace Explorer window, then type FreeRTOS as the folder name.
- Select Add ➤ New Folder. Add the following folders under the FreeRTOS folder
- Right-click on the RTOS folder just created, and select Add ➤ Existing Item. Add the following files into the folders as shown as blows:
- Right-click on the Project in the Workspace Explorer window, then type FreeRTOS as the folder name.
Application Code
FreeRTOS Architecture
The following flowchart shows how to create a simple FreeRTOS-based application.
In the main() function:
- FreeRTOS_Init(): Initializes the interrupt vector table for FreeRTOS.
- MyPSoCSetup(): Initializes the PSoC components and global variables
- xTaskCreate(...): Creates a new instance of a task
- vTaskStartScheduler(): Starts the FreeRTOS scheduler running. If all is well, the scheduler will be running, and the next infinite loop will never be reached
- Infinite loop: If this code is executed, that means that there was insufficient FreeRTOS heap memory available for the idle and/or timer tasks to be created
Example FreeRTOS code
#include <project.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> // FreeRTOS Header Files #include "FreeRTOS_PSoC.h" #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "semphr.h" #include "timers.h" #include "event_groups.h" // Your Task Functions void vAppTask( void *pvParameters ); //------------------------------------------------------------------------------ void MyPSoCSetup( void ) { /* Start-up the peripherals. */ /* Enable and clear the LCD Display. */ LCD_Start(); LCD_ClearDisplay(); LCD_PrintString( "AirSupplyLab" ); } //------------------------------------------------------------------------------ int main( void ) { BaseType_t err; FreeRTOS_Init(); /* Place your initialization/startup code here (e.g. MyInst_Start()) */ MyPSoCSetup(); /* --- APPLICATION TASKS CAN BE CREATED HERE --- */ err = xTaskCreate( vAppTask, // Pointer to the function that implements the task "Task 1", // Text name for the task. This is to facilitate debugging only configMINIMAL_STACK_SIZE, // Stack depth - small microcontrollers will use much less stack than this NULL, // Task parameter 5, // Task priority NULL); // Task handle if (err != pdPASS){ LCD_Position(1,0); LCD_PrintString("Cannot create task!"); while(1){}; } /* Start the scheduler so the tasks start executing. */ vTaskStartScheduler(); /* Execution will only reach here if there was insufficient heap to start the scheduler. */ /* Should never reach here as the kernel will now be running. If vTaskStartScheduler() does return then it is very likely that there was insufficient (FreeRTOS) heap space available to create all the tasks, including the idle task that is created within vTaskStartScheduler() itself. */ for( ;; ){ vTaskDelay(1000); } } //------------------------------------------------------------------------------ void vAppTask(void *pvParameters) { for(;;){ LED_Write( !LED_Read()); vTaskDelay(pdMS_TO_TICKS(500)); // Delay for 500ms } }
Memory Management
FreeRTOS requires memory. The amount of memory required and how to manage that memory varies per application. To allow for this variation, FreeRTOS implements several different memory management schemes. These are implemented in source files named heap_1.c, heap_2.c, up through heap_5.c.
The memory management schemes included in FreeRTOS:
- heap_1 - the very simplest, does not permit memory to be freed.
- heap_2 - allows memory to be free but does not coalesce adjacent free blocks.
- heap_3 - simply wraps the standard malloc() and free() for thread safety.
- heap_4 - coalesces adjacent free blocks to avoid fragmentation. Includes absolute address placement option.
- heap_5 - as per heap_4, with the ability to span the heap across multiple non-adjacent memory areas.
heap_1.c
heap_1 is the simplest implementation of all. It does not permit memory to be freed once it has been allocated. Despite this, heap_1.c is appropriate for a large number of embedded applications. This is because many small and deeply embedded applications create all the tasks, queues, semaphores, etc. required when the system boots and then use all of these objects for the lifetime of the program (until the application is switched off again or is rebooted). Nothing ever gets deleted.
The implementation simply subdivides a single array into smaller blocks as RAM is requested. The total size of the array (the total size of the heap) is set by configTOTAL_HEAP_SIZE - which is defined in FreeRTOSConfig.h. The configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h configuration constant is provided to allow the heap to be placed at a specific address in memory.
The xPortGetFreeHeapSize() API function returns the amount of heap space that remains unallocated, allowing the configTOTAL_HEAP_SIZE setting to be optimized.
The heap_1 implementation:
- It can be used if your application never deletes a task, queue, semaphore, mutex, etc. (which covers the majority of applications in which FreeRTOS gets used).
- It is always deterministic (takes the same amount of time to execute) and cannot result in memory fragmentation.
- It is very simple and allocates memory from a statically allocated array, which is often suitable for use in applications that do not permit true dynamic memory allocation.
heap_2.c
heap_2 uses a best-fit algorithm and, unlike scheme 1, allows previously allocated blocks to be freed. It does not combine adjacent free blocks into a single large block. See heap_4.c for an implementation that does coalescence-free blocks.
The total amount of available heap space is set by configTOTAL_HEAP_SIZE - defined in FreeRTOSConfig.h. The configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h configuration constant is provided to allow the heap to be placed at a specific address in memory.
The xPortGetFreeHeapSize() API function returns the total amount of heap space that remains unallocated (allowing the configTOTAL_HEAP_SIZE setting to be optimized) but does not provide information on how the unallocated memory is fragmented into smaller blocks.
The pvPortCalloc() function has the same signature as the standard library malloc function. It allocates memory for an array of objects and initializes all bytes in the allocated storage to zero. If allocation succeeds, it returns a pointer to the lowest byte in the allocated memory block. On failure, it returns a null pointer.
This implementation:
- Can be used even when the application repeatedly deletes tasks, queues, semaphores, mutexes, etc., with the caveat below regarding memory fragmentation.
- Should not be used if the memory is allocated and freed is of a random size. For example:
- If an application dynamically creates and deletes tasks, and the size of the stack allocated to the tasks being created is always the same, then heap2.c can be used in most cases. However, if the stack size allocated to the tasks being created was not always the same, then the available free memory might become fragmented into many small blocks, eventually resulting in allocation failures. heap_4.c would be a better choice in this case.
- If an application dynamically creates and deletes queues. The queue storage area is the same in each case (the queue storage area is the queue item size multiplied by the length of the queue), then heap_2.c can be used in most cases. However, if the queue storage area were not the same in each case, the available free memory might become fragmented into many small blocks, eventually resulting in allocation failures. heap_4.c would be a better choice in this case.
- The application call pvPortMalloc() and vPortFree() directly, rather than just indirectly through other FreeRTOS API functions.
- Could possibly result in memory fragmentation problems if your application queues, tasks, semaphores, mutexes, etc. are in an unpredictable order. This would be unlikely for nearly all applications but should be kept in mind.
- Is not deterministic - but is much more efficient than most standard C library malloc implementations.
heap_2.c is suitable for many small real-time systems that have to create objects dynamically. See heap_4 for a similar implementation that combines free memory blocks into single larger blocks.
heap_3.c
This implements a simple wrapper for the standard C library malloc() and free() functions that will, in most cases, be supplied with your chosen compiler. The wrapper simply makes the malloc() and free() functions thread-safe.
This implementation:
- Requires the linker to set up a heap and the compiler library to provide malloc() and free() implementations.
- Is not deterministic.
- It will probably considerably increase the RTOS kernel code size.
- Note that the configTOTAL_HEAP_SIZE setting in FreeRTOSConfig.h has no effect when heap_3 is used.
heap_4.c
This scheme uses a first fit algorithm, and unlike scheme 2, it does combine adjacent free memory blocks into a single large block (it does include a coalescence algorithm).
The total amount of available heap space is set by configTOTAL_HEAP_SIZE - defined in FreeRTOSConfig.h. The configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h configuration constant is provided to allow the heap to be placed at a specific address in memory.
The xPortGetFreeHeapSize() API function returns the total amount of heap space that remains unallocated when the function is called, and the xPortGetMinimumEverFreeHeapSize() API function returns the lowest amount of free heap space that has existed system the FreeRTOS application booted. Neither function provides information on how the unallocated memory is fragmented into smaller blocks.
heap_4:
- Can be used even when the application repeatedly deletes tasks, queues, semaphores, mutexes, etc.
- Is much less likely than the heap_2 implementation to result in a heap space that is badly fragmented into multiple small blocks - even when the memory is allocated and freed is of random size.
- Is not deterministic - but is much more efficient than most standard C library malloc implementations.
- heap_4.c is particularly useful for applications that want to use the portable layer memory allocation schemes directly in the application code (rather than just indirectly by calling API functions that themselves call pvPortMalloc() and vPortFree()).
heap_5.c
This scheme uses the same first fit and memory coalescence algorithms as heap_4, and allows the heap to span multiple non-adjacent (non-contiguous) memory regions.
Heap_5 is initialised by calling vPortDefineHeapRegions(), and cannot be used until after vPortDefineHeapRegions() has executed. Creating an RTOS object (task, queue, semaphore, etc.) will implicitly call pvPortMalloc(), so it is essential that when using heap_5, vPortDefineHeapRegions() is called before the creation of any such object.