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; |
Prefixes for Macro
Prefix | Example | Location of the macro definition |
port | portMAX_DELAY | portale.h or portmacro.h |
task | taskENTER_CRITICAL() | task.h |
pd | pdTRUE | projdefs.h |
config | configUSE_PREEMPTION | FreeRTOSConfig.h |
err | errQUEUE_FULL | projdefs.h |
Common Macros
Macro | Value |
pdTRUE | 1 |
pdFALSE | 0 |
pdPASS | 1 |
pdFAIL | 0 |
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.
Lesson 15: Real-Time OS μC/OS-III
About μC/OS Real-Time Operating System
Micriμm's μC/OS (pronounced "Micro C O S") is a preemptive, highly portable, and scalable real-time kernel. μC/OS has three versions:
μC/OS
- Portable
- ROMable
- Very Scalable
- Preemptive Real-Time
- Multitasking Kernel
- Posted over 45 different CPU architecture
- 6 KB ~ 24 KB RTOS Kernel
μC/OS-II
- Can manage up to 250 application tasks
- Provides the following services:
- Semaphores
- Event Flags
- Mutual-exclusion semaphores
- Message mailboxes and queues
- Task management
- Time management
- Timer management
- Fixed-sized memory block management
μC/OS-III
- Included all features in μC/OS-II
- Additional features:
- Allows multiple tasks to run at the same priority level
- Round Robin Scheduling of tasks at equal priority
- Unlimited number of application tasks
- Unlimited number of kernel objects
- Unlimited number of priorities (32~256)
- The low interrupt disable time (near 0)
- Real-time configurable
Licensing
μC/OS-II is provided in source form, and μC/OS-III is a linkable library for a FREE evaluation, educational use, or peaceful research. If you plan on using μC/OS-II or μC/OS-III in a commercial product, you need to contact Micriμm to properly license its use in your product.
Create μC/OS-III Project
- Create a new Project in PSoC Creator.
- Download RTOS_uCOS-III_V3.03.01.zip, and unzip it into the project folder.
- Add RTOS path into the Project
- From the Project menu, select Build Setting...
- Expand the tree under ARM GCC XXXX, and click Compiler
- Add the following directories into the Additional Include Directories item:
.\RTOS; .\RTOS\uC-CPU; .\RTOS\uC-CPU\ARM-Cortex-M3\GNU; .\RTOS\uC-LIB; .\RTOS\uCOS-III\Source; .\RTOS\uCOS-III\Ports\ARM-Cortex-M3\Generic\GNU; .\RTOS\PSoC5\BSP; .\RTOS\PSoC5\BSP\OS\uCOS-III
- Add RTOS Library to the Project
- Expand the tree under ARM GCC XXXX, and click Linker
- Add the following libraries to the Additional Libraries item:
m; :uCOS-III-ARM-Cortex-M3.a
- Add the following directory to the Additional Libraries Directories item:
.\RTOS\PSoC5\LIB
- Create Folders and add RTOS files to your project
- Right-click on the Project in the Workspace Explorer window, then type RTOS as the folder name.
- Select Add ➤ New Folder. Add the following folders under the RTOS 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 RTOS as the folder name.
- Copy and paste the content from RTOS_main.c into your main.c
- Modify or create the AppTask_xxx based on the project design
- Modify the App_TaskCreate() to create all tasks
Application Code
main.c
The startup code for the compiler will bring the CPU to the main() function, which is the entry point for the application. In the main() function, you need to initialize the operating system, create the primary application task (App_TaskStart() function), begin multitasking.
Listing 1: main.c Code
int main (void) { OS_ERR os_err; BSP_PreInit(); // Perform BSP pre-initialization CPU_Init(); // Initialize the uC/CPU services OSInit(&os_err); // Init uC/OS-III OSTaskCreate((OS_TCB *)&App_TaskStartTCB, // Create the start task (CPU_CHAR *)"Start", (OS_TASK_PTR )App_TaskStart, (void *)0, (OS_PRIO )APP_CFG_TASK_START_PRIO, (CPU_STK *)&App_TaskStartStk[0], (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE_LIMIT, (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE, (OS_MSG_QTY )0u, (OS_TICK )0u, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&os_err); OSStart(&os_err); // Start multitasking (i.e. give control to uC/OS-III) }
- Line 5: BSP_PreInit()
It is a Board Support Package pre-initialization function. This function will perform all the hardware initialization required before the OS is initialized. Most of the time, this function will disable all interrupts to make sure the application does not get interrupted until it is fully initialized. - Line 7: CPU_Init()
Initializes μC/CPU. μC/CPU contains CPU-related code to enable/disable interrupts and enter/exit to/from critical sections, among other functions. - Line 9: OSInit()
Initialize the μC/OS-III. This function must be called before creating a task or any other kernel object. - Line 11: OSTaskCreate()
At least one task must be created. OSTaskCreate() will create the startup task. - Line 25: OSStart()
You need to call this function to let μC/OS-III start the multitasking and give control to μC/OS-III. At this point, there should be either 4 to 6 tasks created depending on configuration option: OS_IdleTask(), OS_TickTask(), OS_StatTask(), OS_TmrTask() (optional), OS_IntQTask() (optional) and now AppTaskStart().
You can create as many tasks as you want before calling OSStart(). However, it is recommended to only create one task, AppTaskStart(). Because having a single application task allows μC/OS-III to determine the relative speed of the CPU. This allows μC/OS-III to calculate the percentage of CPU usage at run-time.
If the application needs other kernel objects, such as semaphores and message queues, then it is recommended that these be created prior to calling OSStart().
Finally, notice that interrupts are not enabled.
AppTaskStart()
Single Task Application
- Line 1: We will be creating an application task, and it is necessary to allocate a task control block (OS_TCB) for this task
- Line 2: Each task created requires its own stack. A stack must be declared using the CPU_STK data type as shown. The stack can be allocated statically, as shown here, or dynamically from the heap using malloc().
Create Other Tasks
The listing below shows how to create other tasks once multitasking has started.
static void App_TaskCreate (void) { OS_ERR err; OSTaskCreate((OS_TCB *)&AppTask1_TCB, (CPU_CHAR *)"App Task 1", (OS_TASK_PTR )AppTask1, (void *)0, (OS_PRIO )5, (CPU_STK *)&AppTask1_Stk[0], (CPU_STK_SIZE )0, (CPU_STK_SIZE )128, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); OSTaskCreate((OS_TCB *)&AppTask2_TCB, (CPU_CHAR *)"App Task 2", (OS_TASK_PTR )AppTask2, (void *)0, (OS_PRIO )6, (CPU_STK *)&AppTask2_Stk[0], (CPU_STK_SIZE )0, (CPU_STK_SIZE )128, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); }
Interrupts in μC/OS-III
In the PSoC Creator, the interrupts are managed by the Interrupt component . To integrate the interrupt with μC/OS-III, the following code must be added to the Interrupt Service Routine (ISR) code.
Cy_ISR( MyISR ) { CPU_SR_ALLOC(); CPU_CRITICAL_ENTER(); OSIntEnter(); CPU_CRITICAL_EXIT(); // Your ISR Code Here OSIntExit(); }
- Line 3: CPU_SR_ALLOC()
This function allocates storage for a local variable to hold the value of the current interrupt disable status of the CPU. - Line 5: OSIntEnter()
Notify μC/OS-III that an ISR is being processed, which allows μC/OS-III to keep track of interrupt nesting. - Line 10: OSIntExit()
Notify μC/OS-III that the ISR is complete, which allows μC/OS-III to keep track of track of interrupt nesting. When the last nested interrupt completes OSIntExit() determines if a higher priority task is ready to run. If so, the interrupt return to the higher priority task instead of the interrupt task.
q
Your text...