780 likes | 1.27k Views
Introduction to FreeRTOS. A real-time operating system. Real-Time Operating Systems (RTOS). An operating system (OS) is software that manages the hardware and software resources and provides common services to applications.
E N D
Introduction to FreeRTOS A real-time operating system
Real-Time Operating Systems (RTOS) • An operating system (OS) is software that manages the hardware and software resources and provides common services to applications. • Real-time programs must guarantee a response within specified time constraints. • A real-time operating system (RTOS) is an OS designed for real-time applications. • FreeRTOS is an RTOS for embedded applications.
Some Benefits … • Modularity: Software can be developed in the form of a number of tasks, each task having a well-defined function. • Modularity helps team development, code reuse, and testing. • Software can be entirely event driven, without wasting time and energy by polling for events that have not occurred. • Highly responsive applications: ISRs can be made very short, by deferring processing to a task. • Application development can be shorter and simpler when using the functions provided by the RTOS.
Some Drawbacks … • A bare metal system has no OS. A bare metal approach may result in more efficient applications, but these may take more time develop. • Some resources are reserved by the RTOS and are no longer available. • The SysTick, SVCall, and PendSV interrupts are normally reserved. • In principle, these could still be used with FreeRTOS by modifying the handler definitions of port.c.
TASKS • The user writes the program in the form of a number of tasks. • A task is implemented by a function that never returns. • FreeRTOS provides a scheduler that ensures the user defined tasks run concurrently. • CPU time is divided into slices. • During each time slice, exactly one task uses the CPU. • In this way, the tasks that are ready to run can share the CPU. • Scheduling details will be covered in more detail later.
CREATING TASKS #include"FreeRTOS.h" #include"task.h“ BaseType_txTaskCreate(TaskFunction_t pxTaskCode, constchar* const pcName, \ const configSTACK_DEPTH_TYPE usStackDepth, void* const pvParameters, \ UBaseType_t uxPriority, TaskHandle_t* const pxCreatedTask ); • The stack depth needs special attention: insufficient stack space may cause abnormal program execution and debugger malfunction. • Due to limited RAM space (KL43 has 32KB), the stack cannot be too big. • Stack depth has to be specified in words, not bytes. • The stack is private; each task has its own stack.
CREATING TASKS—EXAMPLE void vTask1(void *pv) { volatileint z, v; for(;;) { z = id; v = SysTick->VAL; z = z + 1 + (int)pv; } } void vTask3(void* pv) { volatileuint32_t z, u; u = (int)pv; for(z = 0;;) { id = u + 1; stck = SysTick->VAL; z = z + 2 + u; } } • int main(void) { • … • xTaskCreate(vTask1, "TASK A", configMINIMAL_STACK_SIZE + 10, 1, 0, NULL); • xTaskCreate(vTask1, "TASK B", configMINIMAL_STACK_SIZE + 10, 2, 0, NULL); • xTaskCreate(vTask3, "TASK P", configMINIMAL_STACK_SIZE + 100, 3, 0, NULL); • vTaskStartScheduler(); // returns only if the scheduler cannot start • for(;;); // the main function must not return • return 0; // a return value has to be specified • }
TASK STATES A task is • Running when using the CPU. • Ready when waiting for the CPU. • Blocked when waiting for an event. • Suspended when unavailable to the scheduler.
TASK PRIORITIES • The user is free to define the priority of his tasks. • The priority of a task is a number between 0 and configMAX_PRIORITIES – 1. • A higher number indicates a higher priority. • configMAX_PRIORITIES can be adjusted in FreeRTOSConfig.h. The default is: #define configMAX_PRIORITIES 5 • In addition to the user defined tasks, the RTOS will also start the idle task and the timer service task. • The idle task has 0 (minimum) priority. It frees the memory used by deleted tasks. • The timer service task is used (among others) to manage user defined timers. By default, it has maximum priority: #define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)
TASK SCHEDULING • The scheduling algorithm determines how the CPU is shared by the tasks. • It can be selected in FreeRTOSConfig.h. • The most common is Prioritized Preemptive Scheduling with Time Slicing. • It is selected with: #define configUSE_PREEMPTION 1 #define configUSE_TIME_SLICING 1 • The algorithm will not change task priority (fixed priority). • A running task will be preempted when a higher priority task becomes ready. • At any time, the running task will have a priority higher or equal than any ready task. • Time slicing is used to share processing time between ready tasks of equal priority. • A “take it in turn” policy (round robin scheduling) is used for tasks of equal priority.
TASK SCHEDULING—TIME SLICES • Any time a task becomes ready, if it has a higher priority than the running task, it will preempt it and start immediately to run. • At the end of each time slice, if there are ready tasks of equal priority to the running task, a ready task will be selected and will run next. • A time slice equals the interval between two SysTick interrupts. By default: #define configTICK_RATE_HZ ((TickType_t)200)
TASK SCHEDULING—STARVATION • If higher priority tasks are always ready or running, the low priority tasks will never be executed. • A task that cannot enter the running state is said to be starved. • If higher priority tasks never block to wait for events, lower priority tasks will be starved. • When no user task can run, the idle task is run. • The idle task (a zero priority always-ready task) must not be starved. • If there are other ready tasks of zero priority, the idle task can be configured to yield before finishing its time slice. The default is #define configIDLE_SHOULD_YIELD 1
TASK SCHEDULING—SAVING ENERGY • Normally, the idle task runs when nothing else can run. • The idle task includes an infinite loop. • A callback function (the idle hook function) can be called from each iteration. • This function can set the CPU in a low power mode. • To enable the idle hook function: #define configUSE_IDLE_HOOK 1 • The prototype of the idle hook function is void vApplicationIdleHook( void ); • Example: void vApplicationIdleHook(void) { __asmvolatile ("wfe"); // wait for interrupt; CPU waits in low power mode }
TASK SCHEDULING—USEFUL HOOKS • Other callback functions that may be helpful (must be enabled before use): void vApplicationTickHook( void ); // called after a SysTick interrupt void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName ); // called when the stack of xTask overflows void vApplicationMallocFailedHook( void ); // called if out of memory • Enabled with #define configUSE_TICK_HOOK 1 #define configCHECK_FOR_STACK_OVERFLOW 1 #define configUSE_MALLOC_FAILED_HOOK 1
QUEUES • A queue is a first in first out (FIFO) data structure allowing one or more sender processes to send data to one or more receiver processes. • Some useful functions (available via #include"queue.h"): QueueHandle_txQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize ); BaseType_txQueueSend( QueueHandle_t xQueue, constvoid *pvItemToQueue, TickType_t xTicksToWait ); BaseType_txQueueReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait );
QUEUES • A process that sends an item to a full queue will be blocked until the queue is no longer full or the timeout expires. • A process that attempts to take one item from an empty queue will be blocked until the queue is no longer empty or the timeout expires. • The timeout is specified by xTicksToWait. • If INCLUDE_vTaskSuspendis enabled (this is the default) and xTicksToWait == portMAX_DELAY, then the task will wait indefinitely (no timeout). • Otherwise, the timeout will have xTicksToWait tick periods. • (The definition of configTICK_RATE_HZspecifies the tick frequency in Hz.) • ISRs may only call the FromISR version of functions, such as xQueueSendFromISR and xQueueReceiveFromISR.
IMPORTANT! • FreeRTOS has two versions of functions: • One for code executed from ISRs. • One for code that is not executed from ISRs. • The functions that may be called from ISRs have the FromISR suffix. • The functions that may be called from other contexts do not have this suffix.
INTERRUPTS • To ensure system responsiveness, an ISR should be short. • The ISR should • Process the operations that need immediate attention (e.g. clear flags). • Defer lengthy operations to a task. • There are several ways to defer data processing to a task. • The ISR writes data to a queue and a task reads it and processes it. • With a pendable function executed by the timer service task. • The ISR signals a task that data is ready by means of a semaphore. • …
PENDABLE FUNCTIONS • An ISR can ask the timer service task to run a certain function. • The timer service task will process the request when it will run. BaseType_txTimerPendFunctionCallFromISR(PendedFunction_t vFunction, void *pvParam1, uint32_t ulParam2, BaseType_t *pxHigherPriorityTaskWoken); • Format of pended function: voidvFunction( void *pvParam1, uint32_t ulParam2 ); Example: void TPM1_IRQHandler(void) { BaseType_t woken = pdFALSE; TPM1->SC |= TPM_SC_TOF_MASK; // clear the flag bit xTimerPendFunctionCallFromISR(vTimerPendFn3, 0, 0, &woken); portYIELD_FROM_ISR(woken); }
PENDABLE FUNCTIONS • By using portYIELD_FROM_ISR(woken), the example ensures that if the task interrupted by the interrupt has a lower priority than the timer service task, the system will switch to the latter and execute it right-away. • By default, the timer service task has the highest task priority: #define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) • When the ISR asks the timer service task to execute the function, the request is placed into a queue. • The timer service task reads commands from the queue and executes them. The default queue length is: #define configTIMER_QUEUE_LENGTH 10
SEMAPHORES • ISRs can defer a function call to the timer service task. • Processing can also be deferred to other tasks by means of semaphores. • A binary semaphore is similar to a queue of length 1. • The queue is full when it has an item and empty otherwise. • A binary semaphore can be used for deferred processing as follows: • A task waits in the blocked state until the semaphore is given. • When the ISR occurs, it gives the semaphore, unblocking in this way the task. • The task takes the semaphore and performs the deferred processing.
SOME REMARKABLE FUNCTIONS #include“semphr.h” SemaphoreHandle_txSemaphoreCreateBinary( void ); BaseType_txSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,\ BaseType_t *pxHigherPriorityTaskWoken ); BaseType_txSemaphoreTake( SemaphoreHandle_t xSemaphore, \ TickType_t xTicksToWait ); voidvSemaphoreDelete( SemaphoreHandle_t xSemaphore );
IMPORTANT! • Some FreeRTOS functions (see xSemaphoreGiveFromISR) may not be called before the task scheduler has started. • The following function can be used to check the scheduler state: #include"task.h" BaseType_txTaskGetSchedulerState( void );
EXAMPLE void vTaskPB(void *pv) { // The task waiting for the semaphore while(1) { xSemaphoreTake(semA, portMAX_DELAY); printf("\nThe semaphore was taken."); } } SemaphoreHandle_t semA; int main(void) { … semA = xSemaphoreCreateBinary(); … vTaskStartScheduler(); … } void PORTA_IRQHandler(void) { // The ISR giving the semaphore BaseType_t woken = pdFALSE; PORTA->PCR[4] |= PORT_PCR_ISF_MASK; // clear the interrupt flag if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { xSemaphoreGiveFromISR(semA, &woken); portYIELD_FROM_ISR(woken); } }
MUTUAL EXCLUSION • Tasks and ISRs may share common resources, such as variables. • Inconsistent and wrong results may occur when more than one task or ISR attempt to use a shared resource at the same time. • It is necessary to ensure that the critical sections of code that access a shared resource have exclusive access to the resource. • Mutual exclusion can be ensured with semaphores. • FreeRTOS provides mutex semaphores for mutual exclusion.
MUTUAL EXCLUSION Mutex semaphores and binary semaphores differ in several respects: • Different initial state: a mutex semaphore can be taken as soon as it is created. • A binary semaphore has to be given before being taken. • The semaphore must be returned: A task or ISR taking a mutex semaphore, has to give it back. • Otherwise, no other task or ISR can use the shared resource. • Priority inheritance: A task holding a mutex semaphore inherits the highest priority among the tasks waiting for the semaphore. • This is done in order to avoid priority inversion.
EXAMPLE void vTaskA(void *pv) { while(1) { xSemaphoreTake(mtx, portMAX_DELAY); // The critical section begins … // The critical section ends xSemaphoreGive(mtx); } } Implement mutual exclusion between the critical sections of tasks A and B. Note the function used to create mutex semaphores. SemaphoreHandle_t mtx; int main(void) { … mtx = xSemaphoreCreateMutex(); … vTaskStartScheduler(); … } void vTaskB(void *pv) { while(1) { xSemaphoreTake(mtx, portMAX_DELAY); // The critical section begins … // The critical section ends xSemaphoreGive(mtx); } }
INTERRUPTS—CONFIGURATION • Mutual exclusion is enforced by disabling interrupts. • In this way the critical section code cannot be interrupted. • Cortex-M3 cores and higher, allow disabling all interrupts that have a priority below a prespecified level. • FreeRTOS uses this feature in order to implement mutual exclusion. • Given the maximum priority of the interrupts that use RTOS functions, mutual exclusion is implemented by disabling all interrupts of lower or equal priority.
INTERRUPTS—CONFIGURATION • FreeRTOS needs to know • The lowest interrupt priority (the tick interrupt uses it). • The highest priority of the interrupts that call RTOS functions. • A critical section is created by writing configMAX_SYSCALL_INTERRUPT_PRIORITY into the Cortex-M BASEPRI register. This disables interrupts of lower or equal priority. • As priority 0 interrupts (the highest priority possible) cannot be masked with BASEPRI, configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0. • Example: Specify the lowest priority of K22 and set the highest priority to 1. #defineconfigKERNEL_INTERRUPT_PRIORITY 0b11100000 //see section 3.2.2.1 #defineconfigMAX_SYSCALL_INTERRUPT_PRIORITY 0b00100000 // Lowest priority on KL43 (see section 3.2.1) is 3 (0b11000000).
PRIORITIES • Both tasks and interrupts have priorities. • The priority of tasks determines the order in which the RTOS schedules them. • The priority of interrupts determines the order in which the NVIC of the ARM core responds to concurrent requests. • Task and interrupt priorities are completely unrelated. • Any enabled interrupt can interrupt any task (of any priority). • The lowest priority interrupt can interrupt the highest priority task. • The tick interrupt, which is used for scheduling tasks, has normally the lowest interrupt priority.
DELAYS #include"FreeRTOS.h" #include"task.h“ voidvTaskDelay( TickType_t xTicksToDelay ); This function blocks the task for xTicksToDelay ticks. The definition of configTICK_RATE_HZspecifies the tick frequency in Hz. Examples: vTaskDelay(100); // blocks the task for 100 ticks vTaskDelay(pdMS_TO_TICKS(100)); //blocks for 100 milliseconds