Tuesday, February 23, 2016

Full demo - part 2

Well, a few things left unsaid. 


The Check Task

This is there to ensure everything is going as expected. It will run every 2.5 seconds asking each and every set of tasks described in the last post whether they're working fine and will print a "OK" message if so (that's the OK message you saw the first time you run the main_full program).
Each set of tasks has a "xAre*********TasksStillRunning()", which is implemented close to the tasks code and will return a value informing whether or not the functionality tested is ok. You'll notice the order of the tasks is different here from the part 1, but that's how it was implemented.
  • Timer Demo Tasks: xAreTimerDemoTasksStillRunning(), as each successful test of the Software Timer increments a counter, this function tests whether this value has changed, thus all is running fine.
  • Notify Task: xAreTaskNotificationTasksStillRunning(), as the functions and tasks increment counters whenever a notification is taken and given, this test will check whether the counting is changing and if every notification given is taken.
  • Interrupt Semaphores Tasks: xAreInterruptSemaphoreTasksStillRunning(), checks whether the counting semaphores are still working and the master is still running.
  • Event Group Tasks: xAreEventGroupTasksStillRunning(), checks whether the master and slave are still running and the ISR calls are ok.
  • Integer Math Tasks: xAreIntegerMathsTaskStillRunning(), checks whether the answer from the calculations keep being correct all the time.
  • Generic Queue Tasks: xAreGenericQueueTasksStillRunning(), check if the queues and mutexes keep on going well.
  • Queue Peek Tasks: xAreQueuePeekTasksStillRunning(), checks if the queue keeps on being peeked.
  • Blocking Queue Tasks: xAreBlockingQueuesStillRunning(), checks whether all the consumers and producers keep on consuming and producing.
  • Semaphore Tasks: xAreSemaphoreTasksStillRunning(), check if the sempahores are being taken and given.
  • Polled Queue TasksxArePollingQueuesStillRunning(), checks if the producer keeps producing and the consumer keeps consuming to and from the queue.
  • Math Tasks: xAreMathsTaskStillRunning(), same as integer math tasks, but this time there are more tasks testing the floating point operations.
  • Recursive Mutex Tasks: xAreRecursiveMutexTasksStillRunning(), checks if the controlling, blocking and polling tasks keep on running.
  • Counting Semaphore Tasks: xAreCountingSemaphoreTasksStillRunning(), checks if the semaphores keep on being incremented and decremented.
  • Suicidal Tasks: xIsCreateTaskStillRunning(), checks if the creator is still alive and creating and if no more than 4 extra tasks (besides all the other tasks that were created in this project) are created.
  • Dynamic Priority Tasks: xAreDynamicPriorityTasksStillRunning(), checks if everything is still running.
  • Queue Set Tasks: xAreQueueSetTasksStillRunning(), checks if all tasks are still running, if all queues are being used and if the ISR function is still sending values to the queues.
  • Queue Overwrite Tasks: xIsQueueOverwriteTaskStillRunning(), checks if the task and the ISR function are still working.
  • Queue Space Task: (not tested).

Idle Task/Hook

The Idle Hook is a function that runs every time the Idle Task runs, if configUSE_IDLE_HOOK is set to 1. The function, in this case, is defined in main.c (vApplicationIdleHook()) and only has a call to vFullDemoIdleFunction() (defined in main_full.c). It is used to demonstrate how one can use the Idle Hook to do something. 
The first thing it does is to sleep (15ms) just to allow any task that could have been terminated by the idle task to actually terminate. 
Then there are a few demonstrations (prvDemonstrateTaskStateAndHandleGetFunctions()), such as get the idle task handle (should be equal to the idle hook's handle) and the timer daemon handle. It also creates a test task (prvTestTask(), it doesn't actually do anything) to show what could be done with its handle (get its state, suspend, delete). This only happens once while the application is running.
Then there is a demonstration of pending a function call (prvDemonstratePendingFunctionCall()), that is, having the RTOS daemon task (timer service task) call some function (xTimerPendFunctionCall()).
Then, a mutex, created in main_full() function just to this purpose, is deleted to demonstrate the usage of  vSemaphoreDelete().
In the end, a test to heap_5.c is performed, by malloc'ing a random size void variable and freeing it.


Tick Task/Hook

Similar to the Idle Hook, the Tick Hook is a piece of code called whenever a Tick happens (defined in configTICK_RATE_HZ, in FreeRTOSConfig.h), if configUSE_IDLE_HOOK is set to 1. The function is defined in main.c (vApplicationTickHook()) and only performs the call to vFullDemoTickHookFunction(), defined in main_full.c. This is where most (maybe all?) the ISR functions described in part 1 are called, such as vTimerPeriodicISRTests(), from Timer Demo Tasks, xNotifyTaskFromISR() and vQueueSetAccessQueueSetFromISR().

Other Hooks

There are two other hooks defined: vApplicationMallocFailedHook(), which runs if configUSE_MALLOC_FAILED_HOOK is set to 1 and when a malloc (pvPortMalloc()) fails, and vApplicationStackOverflowHook(), which runs if configCHECK_FOR_STACK_OVERFLOW is set to 1 or 2 (which is not) and when (can you guess?) a stack overflow is detected.

On the next post, I'll talk about the FreeRTOSConfig.h, which I mentioned a few times here and there, but never gone further. See you soon!

Friday, February 19, 2016

Moving on: Full demo - part 1

Now that we understand the simple Blinky project, let's check a more complete demo project: the full main. Back in Eclipse, search for the string mainCREATE_SIMPLE_BLINKY_DEMO_ONLY in main.c (line 112) and change it back to 0. Compile (Ctrl + B) and Run (F11). A new string will appear in console every 2.5 seconds. First of all we must know that the main_full is a more comprehensive test and demo application. It has no particular function, but to show how everything works and relates, and also to be a starting point when creating your own project.



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

Thursday, January 28, 2016

FreeRTOS - Coding Standard and Styling

This post is heavily based on the official webpage [1] and I guess it couldn't be very different. This time we'll understand a few things on how the code is written, for instance, what's up with the 'x' in front of xTaskCreate() or the 'ul' in ulValueToSend. First of all, we should have in mind that the FreeRTOS is coded in C language according to MISRA guidelines [2], which I don't know at all, but I'm sure we can get what they mean.

Naming Convention

Variables:

  • uint32_t: prefix 'ul', as in 'unsigned long'
  • uint16_t: prefix 'us', as in 'unsigned short'
  • uint8_t: prefix 'uc', as in 'unsigned char'
  • Non standard types: prefix 'x' or 'ux', when unsigned. Examples:
    • BaseType_t (used on xTaskCreate)
    • QueueHandle_t (used on xQueue)
    • TickType_t (used on xNextWakeTime)
  • Enumerated: prefix 'e'
  • Pointers have an additional prefix 'p'
Functions:
  • Private functions (file scope static only): prefix 'prv'. Examples:
    • prvQueueReceiveTask: the Receiver
    • prvQueueSendTask: the Sender
  • FreeRTOS API functions: prefixed according to its return type (see Variables) or 'v', as in 'void'; after this prefix, the name of the .c file where it is defined. Examples:
    • xTaskCreate: non standard type, defined on task.c
    • xQueueCreate: non standard type, defined on queue.c
    • vTaskStartScheduler: void type, defined on task.c
Macros:
  • Prefixed with the file where it is defined, in lowercase
  • Its name must be written in uppercase
  • Examples:
    • configMINIMAL_STACK_SIZE, configASSERT: defined in FreeRTOSConfig.h
    • mainQUEUE_RECEIVE_TASK_PRIORITY: defined in main.h (or .c, or in the case of Blinky, main_blinky.c)
    • pdMS_TO_TICKS: define in projdefs.h
    • portMAX_DELAY: defined in portmacro.h

Data Types

There are a few (4) special data types largely used:
  • TickType_t: if configUSE_16_BIT_TICKS != 0, then it's an unsigned short (16 bits), otherwise, it's an unsigned long (32 bits)
  • BaseType_t: the most efficient data type for the architecture (for 32-bit architectures, it's a signed 32 bit type; for a 16-bit, it's a signed 16 bits, etc)
  • UBaseType_t: just an unsigned BaseType_t
  • StackType_t: used internally by the FreeRTOS, has the width of the memory where the stack is located (usually 16 bits for 16-bit architecture and 32 bits for 32-bit)

Style Guide

This is something in which I believe a lot. Having a style, preferably a simple one makes the code much easier to read, which facilitates maintenance. FreeRTOS has its own rules:
  • Indentation with TABs (4 spaces)
  • Comments with '//' and don't pass column 80, unless after a parameter describing it
  • The layout must be easy to read (check FreeRTOS webpage for details [3])


Wednesday, January 27, 2016

Understanding Blinky - part 2


This post will discuss the FreeRTOS itself. One of its main capabilities is to simulate a multitasking. We know each processor core can only be processing one task at a time, but when you change the task being processed quickly enough, you can pretend that all are being processed at the same time. FreeRTOS does that with the use of the scheduler, which is basically the piece of code that will choose which task will run. Every once in a while, the scheduler will stop the current task execution and handle the processor's control, along with the task's stack, to another task, that eventually will be stopped (or will hold its own execution, i.e. through a delay) and another will be assigned and so on.

http://www.freertos.org/implementation/a00004.html

Stack

Each task usually needs some memory to store some variables. This memory, on bare metal projects, is usually the RAM itself, with a well defined address within it. When talking about an RTOS, the RAM is divided into pieces, called stacks [1], and each piece will hold the task's memory (the size of the piece is defined when the task is created, on xCreateTask()'s parameters). This piece will also hold a few more information relevant to the task, such as the point where the code was stopped the last time, the processor's registers values, etc, thus it needs a minimal size, which is defined, in Blinky project, as configMINIMAL_STACK_SIZE. The developer should always think well how much memory the task needs in order not to have problems. The stack is usually bigger than it actually needs, as you better spare memory and don't have a stack overflow problem. Other points to be reviewed before determining the size are [2]:

  • The function call nesting depth
  • The number and size of function scope variable declarations
  • The number of function parameters

Priorities

Imagine you have three tasks running the whole time doing what they're were meant to do. When one task execution is stopped, how does the scheduler choose which task will run next? If the tasks have different priorities (defined on xCreateTask()'s parameters), then the highest priority will run [3]. Care must be taken in this situation, otherwise, one task may never run (imagine two tasks that never stop executing by they're own have higher priority than another: when one task is running, the other is always waiting in front of the line, so that the lowest priority is always in the back and never runs). The lowest priority is defined by tskIDLE_PRIORITY and the higher the number, the higher the priority.

Task states

I've talked about a few of the states, like when the task is blocked (for example, when the Sender stops for a delay), running or waiting to be executed, so let's check those states thoroughly [4]. The tasks can be in 4 states: Running (it is using the processor), Ready (it is waiting to be processed), Blocked (it is waiting for some event, temporal - such as a delay - or external - such as a queue or semaphore event; in this case, it is not running nor waiting to be processed) or Suspended (it explicitly entered this state through the function vTaskSuspend() and can only exit by another explicit call of the function xTaskResume(); in this state, it is also not running nor waiting to be processed).
The task will enter the Ready state when it is created, when the scheduler stops its execution, when the event that was blocking it occurs or when it exits the Suspended state. It will only enter the Running state when the scheduler says so.

http://www.freertos.org/RTOS-task-states.html

Idle Task

If there are no tasks on Ready state (waiting to run), there is always one simple task, with lowest priority level possible that will use the processor: the Idle Task [5]. Basically, what it does is to free the memory from deleted tasks (if there are any and if your application allow that). When at least one task has the priority as the Idle, it is possible to tell the Idle task to yield the execution as soon as possible when these other tasks are in a Ready state [6]. It is possible to run some code whenever the Idle Task is called by the use of the Idle Task Hook, although it is imperative that the function doesn't block the task (for instance, using a vTaskDelay() or waiting for a semaphore), if that is the case, you should write a task with the Idle Task priority (this will use more RAM, but will permit more flexibility).

configASSERT()

This function is quite important during the development phase. It is often used to check if the task execution is OK and can continue. For instance, when the Sender and Receiver tasks check whether the parameters are right, it does so with the use of the configASSERT() [7]. This function will call another function when there is a problem: vAssertCalled(), which, in this project, will stop the execution and place the application on a loop. This function should be written by the developer in order to facilitate the debug of the code. When the project is mature enough, it can be suppressed to avoid code overhead.


[1] http://www.freertos.org/Stacks-and-stack-overflow-checking.html
[2] http://www.freertos.org/FAQMem.html#StackSize
[3] http://www.freertos.org/RTOS-task-priority.html
[4] http://www.freertos.org/RTOS-task-states.html
[5] http://www.freertos.org/RTOS-idle-task.html
[6] http://www.freertos.org/a00110.html#configIDLE_SHOULD_YIELD
[7] http://www.freertos.org/a00110.html#configASSERT

Monday, January 25, 2016

Understanding Blinky - part 1

Ok, all compiled, all working, but nothing was truly understood. Let's dig through Blinky's code and study a bit about how does it work. It probably won't make much difference, but I'll be using the Eclipse + MinGW version to explain. Just for me to remember, this project creates two tasks and a queue. One task is the Sender (it posts a message on the queue) and the other is the Receiver (it receives the message from the queue and prints another message on the screen).

main_blinky()

We'll start checking main_blinky() function (main_blinky.c, line 162), since I believe it's a little more practical than going through FreeRTOS' main() function. The first thing it does is to create a queue (xQueueCreate() [1]), with mainQUEUE_LENGTH positions (1 position only), each of them with sizeof( unsigned long ) bytes (in my case, 4 bytes). A queue is a simple way to send messages from one task to the other, usually used as a FIFO. In this case, the queue identifier (xQueue) is a global variable that can be used by both tasks.

Moving on, the tasks are created with xTaskCreate() and the parameters are [2]:

  • A pointer to the function that implements the task (prvQueueReceiveTask for the Receiver and prvQueueSendTask for the Sender)
  • The name of the task, only used for easier debug ("RX" and "TX")
  • The size of the stack, in Words (configMINIMAL_STACK_SIZE for both, which is 50 Words, or 200 bytes, since each Word is 4 bytes wide)
  • A pointer to the parameter(s) sent to the task (mainQUEUE_RECEIVE_PARAMETER and mainQUEUE_SEND_PARAMETER, used just to show how the functionality works; it is important to notice that the content must exist during the entire time the task is running, thus declaring it static is a good idea, otherwise, the task could access some address that no longer exists)
  • The priority of the task (mainQUEUE_RECEIVE_TASK_PRIORITY for the receiver and mainQUEUE_SEND_TASK_PRIORITY for the sender; in this case, the receiver has a higher priority and both the task have priorities higher than the Idle Task, which has the lowest possible priority)
  • A handler to the task, which can be used to check if the task was created, to delete the task, usw. (this is not used on Blinky).
Finally, the scheduler is started and the main_blinky ends on an endless loop.

prvQueueSendTask

The Sender uses a few local variables: xNextWakeTime, which holds the moment, in ticks, when the task will wake up; ulValueToSend, which holds the value to be stored on the queue; and xBlockTime, which stored the amount of time, in ticks, that the task will be sleeping (blocked state - 200 ms). The task starts by checking whether the parameters are right using the configASSERT() function, and if they're not, it will stop the execution. The xNextWakeTime is initialized with the amount of ticks since the scheduler started (xTaskGetTickCount()). The endless loop contains a delay function (vTaskDelayUntil()), which receives as parameters the previous wake time (currently stored on xNextWakeTime) and the amount of ticks the task will remain in blocked stated (xBlockTime), and the post-to-queue funtion (xQueueSend(), which is equivalent to xQueueSendToBack(), thus using the queue as a FIFO [3]), which receives as parameters the handler/identifier of the queue (xQueue, the global variable), the value to be sent (ulValueToSend - the value will be copied to the queue) and the amount of time, in ticks, that the function should wait if the queue is full (for Blinky, the queue should always be empty by the time this function is called, so the wait period is zero).

prvQueueReceiveTask

The Receiver only uses one local variable: ulReceivedValue, which will be filled with the valued stored on the queue. Similar to the Sender, the first action is to check whether the parameters received by the task are right and stop the execution in case they're not. The endless loop will receive the value from the queue and, if the value is right, will print a message. The function used to receive is xQueueReceive() [4], which receives as parameters the handler/identifier of the queue (xQueue), a place to store the value read (ulReceivedValue) and the amount of time, in ticks, it will wait in blocked mode until there is something on the queue (portMAX_DELAY, which is the maximum amount of time possible; and, as INCLUDE_vTaskSuspend is set to 1 on FreeRTOSConfig.h, the task may wait forever, or actually until the Sender posts a messsage). The rest of the task is pretty straight forward.


Idle Task? Scheduler? Blocked mode? Scenes for our next episode...