The main_full follows the standard FreeRTOS demo project [1] and as such, implements a task commonly called "Check" (prvCheckTask()), whose function is to check, from time to time (every 2.5 seconds in this case) whether all the other tasks are working correctly (I think I'll go further on this on the next post). It's this task which is posting the OK messages on console. The standard tasks created are (check on main_full() function; if you don't know it yet, Ctrl + Click on the name of the function will lead you to its definition):
- Notify Task (vStartTaskNotifyTask(), implementation on the file FreeRTOS\Demo\Common\Minimal\TaskNotify.c or Standard_Demo_Tasks\TaskNotify.c under project explorer in Eclipse). This task is created with the Idle Task priority and its objective is to test the behavior of direct task notifications. It's important to say that the TaskNotify.c doesn't implement the direct task notification itself, but an API for that. The direct task notification implementation can be found on task.c (directory FreeRTOS\Source or under "FreeRTOS Source") and task.h (directory FreeRTOS\Source\include or under "FreeRTOS Source\include") [2]. The notifications are given at random moments by a software timer (prvNotifyingTimer(), http://www.freertos.org/RTOS-software-timer.html), created by the task, and by an ISR (xNotifyTaskFromISR()) and taken 4 times by the task itself (it tests several options, like clearing or not the notifications, bocking or not, etc).
- Blocking Queue Tasks (vStartBlockingQueueTasks(), implementation on FreeRTOS\Demo\Common\Minimal\BlockQ.c or Standard_Demo_Tasks\BlockQ.c). This actually creates 3 sets of 2 tasks, each set working on its queue in a Consumer/Producer fashion [3]:
- Set 1: The queue has only one position filled by the Producer (vBlockingQueueProducer) with an incrementing value and no blocking time. The Consumer (vBlockingQueueConsumer) will receive the value and compare it with the expected value, making use of the blocking time defined on the Queue function (remember from the "Understanding Blinky part 1"?). As the Consumer has a higher priority than the Producer (sent as parameter on main_full's call to vStartBlockingQueueTasks), it will always leave the blocking state as soon as the queue is filled, so there will always be room for the value when the Producer tries to send it.
- Set 2: This is similar to Set 1, but this time the Producer can be blocked while waiting to fill the one position queue. It also has a higher priority, meaning the Consumer will always be blocked as soon as it reads the value from the queue.
- Set 3: This time, both the tasks have the same priority, but the queue is longer and they both can block if there is no position to be written or value to be read. This means the queue can be read or written either when the queue is empty (the consumer blocks) or full (the producer blocks) or when a context switch happens.
- An observation about this sets of tasks: they all use the same functions to create the task for the Producer and Consumer (vBlockingQueueProducer and vBlockingQueueConsumer) and the only difference are the parameters used for each. When the task is created, each will have its own stack with its own variables, but the code size will always be the same for 3 sets or 20. Also, the blocking time, when used, is always 1000 ms.
- Semaphore Tasks (vStartSemaphoreTasks(), implementation on FreeRTOS\Demo\Common\Minimal\semtest.c or Standard_Demo_Tasks\semtest.c). Two sets of two tasks each are created in order to show how binary semaphores are used. Each set has a common variable whose access is protected by a semaphore. One set uses polling on the semaphore to check whether it's available (the task Yields if the semaphore is already taken - to Yield means the task goes to Ready state, allowing other tasks to run) and the other uses a blocking time (as on the Blocking Queue Tasks, it uses the blocking time defined on the Semaphore functions). When the tasks can use the common variable, they clear it and count it back up to a predefined value, in order to make use of some processing time and be sure that their processing time will end. This ensures the semaphore blockage is working correctly.
- A few observations: all the tasks use the same function prvSemaphoreTest(), which receives as parameters, among others, the blocking time, if there is one, and a pointer to the common variable; also, the blocking time, when used, is 100 ms. The tasks priority is set on the main_full call to vStartSemaphoreTasks().
- Polled Queue Tasks (vStartPolledQueueTasks(), implementation on FreeRTOS\Demo\Common\Minimal\PollQ.c or Standard_Demo_Tasks\PollQ.c). The aim of this is to show how to use the queue without blocking any task, but using the polling method. Two tasks (one Consumer and one Producer) and a queue with 10 positions are created. The producer will fill the queue with 3 consecutive values and sleep (block) for 200 ms. The consumer will check if there are any values stored in the queue by using the function uxQueueMessagesWaiting() and, if there are, it will read all the available values, emptying the queue, so that whenever the Producer runs, there is always free position to be filled. In the end, it will also sleep for 200 ms.
- Integer Math Tasks (vStartIntegerMathTasks(), implementation on FreeRTOS\Demo\Common\Minimal\integer.c or Standard_Demo_Tasks\integer.c). This is a good test to the context switching of the RTOS. It creates a single task that performs a calculation between 'long' variables with a known result. If the result calculated is as predicted, then the context switching is working fine, otherwise something is wrong (registers may not be saved correctly, maybe something with the stack, etc).
- Generic Queue Tasks (vStartGenericQueueTasks(), implementation on FreeRTOS\Demo\Common\Minimal\GenQTest.c or Standard_Demo_Tasks\GenQTest.c). Performs a few other tests regarding the usage of queues. Four tasks, one queue and one mutex are created:
- prvSendFrontAndBackTest(): tests the functions xQueueSendToFront() and xQueueSendToBack() by filling the queue with this functions, then reading and comparing whether all was added correctly.
- prvLowPriorityMutexTask(), prvMediumPriorityMutexTask() and prvHighPriorityMutexTask(): they test the usage of the mutual exclusion (MutEx) mechanism [4]. The mutex is basically a binary semaphore with priority inheritance, which means that when a higher priority task asks for a mutex that is held by a lower priority task, the priority of the second is raised to the first's in order to ensure the higher priority task will remain in blocked state for a minimal amount of time.
- Queue Peek Tasks (vStartQueuePeekTasks(), implementation on FreeRTOS\Demo\Common\Minimal\QPeek.c or Standard_Demo_Tasks\QPeek.c). Performs the operation Peek on a Queue. Peeking means the value will be read from the queue, but not deleted, so that other tasks can read it too. A total of 4 tasks are created, each with a different priority, in order to show how tasks wake while peeking and reading the queue.
- Math Tasks (vStartMathTasks(), implementation on FreeRTOS\Demo\Common\Minimal\flop.c or Standard_Demo_Tasks\flop.c). Very similar to Integer Math Tasks, but using floating point calculations. Four different tasks are created all with the same priority as the Idle Function, which makes them a good test to the scheduler (all should run once in a while) and the context switching mechanism (the results are known prior to execution).
- Recursive Mutex Tasks (vStartRecursiveMutexTasks(), implementation on FreeRTOS\Demo\WIN32-MingW\recmutex.c or DemosModifiedForLowTickRate/recmutex.c). Shows how the recursive mutex can be used. A recursive mutex is a type of mutex that can be taken several times by the same task, but also has to be given (returned) the same amount of times before another task takes it. Also, the priority inheritance explained earlier also apply. Three tasks are created: controlling, blocking and polling, with descending priorities. The first will take the mutex twice, with a delay in between, making the blocking task block and the polling one to keep polling, then it will give back twice, allowing the blocking task to wake up and take the mutex, and then suspends. The blocking task, after taking the mutex, will give it back and suspend, allowing the polling task to take it. When the polling task takes the mutex, it resumes the other tasks, which will try to take the mutex (held by the polling task) and block, making the polling task priority to be risen until it gives the mutex back, which it does and all the cycle repeats.
- Counting Semaphore Tasks (vStartCountingSemaphoreTasks(), implementation on FreeRTOS\Demo\Common\Minimal\countsem.c or Standard_Demo_Tasks\countsem.c). Shows a simple usage of counting semaphores. Those are semaphores that can be taken and given several times (taking decrements and giving increments, from 0 to a maximum value defined when the semaphore is created). This can be used to signalize the occurrence of an event several times (several keystrokes, for instance), where the interrupt service of the event will give the semaphore and the handler will take it. Two tasks and semaphores are created: one whose semaphore starts with the max value and one that start with zero. Both give and take the semaphore endlessly, making it run from 0 to max and back to 0.
- Dynamic Priority Tasks (vStartDynamicPriorityTasks(), implementation on FreeRTOS\Demo\Common\Minimal\dynamic.c or Standard_Demo_Tasks\dynamic.c). Creates two sets of tasks, one with three tasks and one with two tasks. The first set tests the access to a shared variable with the manipulation of its own priorities (vTaskPrioritySet()) and suspending the scheduler (vTaskSuspendAll() and xTaskResumeAll()). The other set tests the access to a queue also suspending the scheduler.
- Queue Set Tasks (vStartQueueSetTasks(), implementation on FreeRTOS\Demo\Common\Minimal\QueueSet.c or Standard_Demo_Tasks\QueueSet.c). Tests the usage of Queue Sets [5]. Imagine a task receives messages of different types from several other tasks (one sends only a signal - a semaphore -, other sends one byte of data, one sends a stream, etc) and each must be processed differently. Usually, a struct is created as the data type of the queue, where the type and data can be indicated. The Queue Set is used for that, as you can add to the set several different queues and/or semaphores and the Receiver Task can block waiting for a message in any of those queues and semaphores with only one function call. In this case, two tasks are created (a receiver and a sender) and used along with a call for a function on the Tick Hook (that's the function that runs at every tick, thus running from an interrupt), also a queue set of size 9 is created (prvSetupTest()), meaning a max of 9 events can be stored, so 3 queues of 3 positions are added to the set. The Receiver Task (prvQueueSetReceivingTask()) always blocks waiting for a message from any of the elements on the set, then receives and tests it and start over. The Sender Task (prvQueueSetSendingTask()) only writes messages to a random queue on the queue set. The function called from the ISR (Interrupt Service Routine) first tries to read from the queue set (if there is a message waiting), then writes to one of the queue, like the sender (as it runs every tick, there is a counter to ensure that the queue set code only runs once every 100 ticks).
- Queue Overwrite Tasks (vStartQueueOverwriteTask(), implementation on FreeRTOS\Demo\Common\Minimal\QueueOverwrite.c or Standard_Demo_Tasks\QueueOverwrite.c). Tests the usage of Queue Overwrite both from a task (prvQueueOverwriteTask()) and from an ISR (vQueueOverwritePeriodicISRDemo(), from the Tick Hook), each with its own queue. The overwrite function only works on queues with size 1 and is used to overwrite the value stored on queue (if there is any, otherwise, the new value is just added to the queue).
- Queue Space Task (prvDemoQueueSpaceFunctions(), implementation on main_full.c itself). Tests a few other queue related functions, such as uxQueueSpacesAvailable(), which return the number of positions still available on the queue and xQueueReset(), which clears the queue.
- Event Group Tasks (vStartEventGroupTasks(), implementation on FreeRTOS\Demo\Common\Minimal\EventsGroupDemo.c or Standard_Demo_Tasks\EventsGroupDemo.c). Event group is a set of event bits (or flags) that can be set, get, cleared or waited to be set (the whole group of flags or just part of it). Event groups can also be used as a synchronization (also called rendezvous) device. Four tasks are created (Master, Slave and two Synchronization Tasks) and a call from an ISR is made (vPeriodicEventGroupsProcessing()). The master first tests the blockage of tasks (the Sync Tasks) by the event group (each will wait for one of the flags on the event group in blocked mode and, when unblocked, will suspend), by using prvSelectiveBitsTestMasterFunction() (from the master) and prvSelectiveBitsTestSlaveFunction() (from the sync tasks). The second part starts by testing the blockage of a single task (the slave) from a single flag or a group of flags (the flags can be cleared by the blocked task after it unblocks) (see prvBitCombinationTestMasterFunction() and the slave task itself - prvTestSlaveTask()). Finally, it performs a few sync tests by itself and by using the other 3 tasks (the slave and the two sync tasks) (prvPerformTaskSyncTests()).
- Interrupt Semaphores Tasks (vStartInterruptSemaphoreTasks(), implementation on FreeRTOS\Demo\Common\Minimal\IntSemTest.c or Standard_Demo_Tasks\IntSemTest.c). This tests the usage of semaphore, mutex and counting semaphore with interrupts, including the priority inheritance mechanism. Three tasks are created: a master (vInterruptMutexMasterTask(), which takes the mutex given from the ISR) and slave (vInterruptMutexSlaveTask(), which tests the inheritance mechanism) and a counting semaphore task (vInterruptCountingSemaphoreTask(), it waits until the ISR function has incremented the semaphore, then takes all the semaphores). The ISR function runs from the Tick Hook (vInterruptSemaphorePeriodicTest()).
- Timer Demo Tasks (vStartTimerDemoTask(), implementation on FreeRTOS\Demo\Common\Minimal\TimerDemo.c or Standard_Demo_Tasks\TimerDemo.c). Creates and starts the maximum number of software timers the API can handle before they're started by the OS (20, defined in FreeRTOSConfig.h as configTIMER_QUEUE_LENGTH) with timing multiple of 2.5 seconds, which only increments each its own variable by the end of the specified time (prvAutoReloadTimerCallback()). Then creates the timers used on the interrupt routines(prvISRAutoReloadTimerCallback() and prvISROneShotTimerCallback()), which also just increment its variables. A task for managing the timers is also created (prvTimerTestTask()), it tests several functions from the Software Timer API. There are also a few tests made from a function called from an interrupt (vTimerPeriodicISRTests()).
- Suicidal Tasks (vCreateSuicidalTasks(), implementation on FreeRTOS\Demo\Common\Minimal\death.c or Standard_Demo_Tasks\death.c). This creates one task (the Creator) that creates 2 other tasks every 1 second. One of the created tasks will kill the other (vTaskDelete(xTaskToKill)) and then kill itself (vTaskDelete( NULL )).
Phew. That was a lot (and took me a lot to gather all this info). My suggestion: read this a few times, read the code of each of these tasks and functions and understand what is happening. Play around with the code, write a few printf's and this will get you more familiar with the OS. See you soon!
[1] http://www.freertos.org/a00102.html
[2] http://www.freertos.org/RTOS-task-notifications.html
[3] http://www.freertos.org/a00013.html
[4] http://www.freertos.org/Real-time-embedded-RTOS-mutexes.html
[5] http://www.freertos.org/Pend-on-multiple-rtos-objects.html
[1] http://www.freertos.org/a00102.html
[2] http://www.freertos.org/RTOS-task-notifications.html
[3] http://www.freertos.org/a00013.html
[4] http://www.freertos.org/Real-time-embedded-RTOS-mutexes.html
[5] http://www.freertos.org/Pend-on-multiple-rtos-objects.html
No comments:
Post a Comment