Tag: FreeRTOS

FreeRTOS Mutual Exclusion – Mutex

Keywords: Embedded systems, ARM, FreeRTOS, Semaphore, Tasks Mutual Exclusion, Mutex Code Link: Tutorial Source Code Github – Keil As discussed in the introductory tutorial Mutexes are almost similar to Binary Semaphore with the exception of resource ownership. In Mutex Tasks owns a resource and can’t be taken even by […]

FreeRTOS Binary Semaphores – Process Synchronization

Keywords: Embedded systems, ARM, FreeRTOS, Semaphore, Tasks Synchronization, Mutex Code Link: Tutorial Source Code Github – Keil In the introductory tutorial we mentioned that Binary Semaphores are used for process Synchronization. In this tutorial we will explain what does process synchronization means and how freeRTOS Binary Semaphores can be used to […]

Introduction to Semaphores and Mutexes

Keywords: Embedded systems, ARM, FreeRTOS, Semaphore, Tasks, Mutex

“A Semaphores is a Synchronization technique used to control access to common resources in a multi-threaded Environment”.

“A Semaphores is a resource management technique used to manage access to shared resources in a multi-threaded environment”.

Most of the low power low end embedded systems follows legacy hardware architecture of shared memory where any part of volatile memory can be read/modified/written by any part of embedded software running on the hardware. This is not a big issue until the software gets complex and runs multiple units of independent software tasks accessing shared memory simultaneously e.g. System is performing multi-tasking.

In such systems on the major problem is memory protection and access to a common hardware/software resources (e.g. UART). Though some part of shared memory can be reserved (via some global flag variable) that can be set and released when accessing a resource; The problem in such systems arises when it comes to the memory operation Atomicity.

For Example, consider a UART being managed by a global flag and used by two tasks i.e. Task1 and Task-2 (video bellow). Each time a Task wants to access UART, it first checks whether the flag is set. If the flag is 0, it means its free if not it means UART is being used by another process. In this case, it is quite probable that one task say Task-1 just checks the flag and about to Set the flag (to indicate to other processes that UART is acquired) and a Context Switch occurs leaving the flag unset. The next task (Task-2) selected by Scheduler checks the same flag and finds the UART free ( as flag is not yet Set by the previous task), it sets it and start using UART for long time. Meanwhile again a Context Switch occurs bring back the first task (Task-1) to execute. The Task-1 has already checked the flag (before Context Switch) so it just simply Set the flag (flag is overwritten) and start using UART. Now the string sent by two Tasks mix up that can lead to severe bugs in critical systems.

The following video shows how two freeRTOS tasks trying to write string to same serial Port. As can be seen while the one task has not yet finished writing to serial port, the context switch preempt the first task and runs the second task resulting into mixing of two strings. The exact same things happens in situations of shared memory.

* Task1 Prints: “Message From Task-1: FreeRTOS Task-1 Running...
* Task2 Prints: “Message From Task-2: FreeRTOS Task-2 Running...

Another very common example is the use of a Shared Printer on network. Many computer on network may be accessing printer simultaneously but printer can only be assigned to one computer at a time.

In order to tackle such problem, Dijkestra proposed a significant technique called Semaphore for managing concurrent processes trying to gain access of some common resource.

Semaphores are not only limited to manage only single share resource being accessed by multiple processes, it can also be used to synchronized access to N-number of shared resources in a multi-threaded environment (see Semaphore types).

1. Semaphore Operations:

Semaphore implementation provides accesses to the follow operation.

1.1 Take(): Take() Operation is used to gain access to a resource. If the resource is not available, the Task is Blocked.

1.2 Give(): Give() Operation is used to release an already Taken resource.

2. Semaphore Internal:

Internally semaphore contains:

2.1 : A Variable – N : Indicates remaining resources available to be assigned. Initially this variable is set to the total number of resources available in system. For example if there are three UARTs available in system to be accessed by multiple process then this variable will be initially set to 3. In Semaphore, access to this variable is Thread-Safe and only available via standard Take() and Give() Operations.

2.2 A List: To keep track of of processes that are blocked on the semaphore and waiting for the access. Whenever there is an availability of of resource/resources, task/tasks in the list are unblocked to gain access of the assigned resources.

3. How Semaphore Works:

When ever a process is alloted access (via Take() Operation) to a resource by semaphore, the value of internal variable – N is decremented by 1. Similarly when a process release a resource (via Give() Operation), the internal variable – N is incremented by 1. Once the value of N becomes 0 any further process trying to gain access of a resource via the semaphore will be sent to blocked state and an entry is made to the internal list. Once a resource becomes available, the process in the block list is unblocked and given access.

4. Semaphore Types:

This following two types of Semaphores.

4.1 Binary Semaphore: Binary semaphore can take the value 0 & 1 only (the internal Variable – N value). In other words, binary semaphore can only be used to manage one single resource being accessed by many processes. Binary semaphore are mostly used for Task Synchronization rather than managing access to single resource. The following tutorial shows how binary semaphore can be used for synchronization purpose.

4.2 Counting Semaphore: Counting semaphore can take nonnegative integer as it internal variable – N value. While Binary semaphores are mostly used for task synchronization, Counting Semaphores are used to manage access to n-number of resource being accessed by multiple tasks.

5. Mutex:

Mutexes are similar to binary semaphores in the sense that both are used to protect Individual Resource except in Mutexes a process owns a resource. i.e. Once a process gain access of a resource, no can can make it release the resource until the process itself release it. In semaphore a process once gain access of a resource (Take()) can be released from anywhere while in case of mutexes, only the process that gained access can release the resource even if a high priority task is waiting for the resource. This can lead to the Priority Inversion problem (A higher priority task is waiting for the low priority task to complete) which is out of scope of this tutorial. In FreeRTOS Mutexes include priority inheritance mechanism to minimises the effect of Priority Inversion in some situation but it doesn’t completely solve Priority Inversion problem. Whereas binary semaphores are the better choice for implementing synchronisation (between tasks or between tasks and an interrupt), mutexes are the better choice for implementing simple mutual exclusion (hence ‘MUT’ual ‘EX’clusion).

5.1 Mutex Standard Operations:

Though the standard operation on Mutex are Lock() and UnLock() to gain and release ownership of a resource, FreeRTOS uses the same Take() and Give() APIs as a substitute to Mutex Lock() and UnLock() Operations.

6. FreeRTOS Semaphores, Mutexes APIs:

This following APIs are commonly used for freeRTOS Semaphore and Mutex Operations.

6.1 xSemaphoreCreateBinary():

SemaphoreHandle_t xSemaphoreCreateBinary( void );

This API creates a binary Semaphore and return a handle to it to be used for further references.

Parameter NameDescription
Return ValueIf NULL is returned, then the semaphore cannot be created because there is insufficient heap memory available for FreeRTOS to allocate the semaphore data structures.

If a non-NULL value is returned, the semaphore has been created successfully. The returned value should be stored as the handle to the created semaphore.

6.2 xSemaphoreCreateCounting():

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
                                            UBaseType_t uxInitialCount);

xSemaphoreCreateCounting() creates a Counting Semaphore with uxMaxCount be considered as the total number of resources available and uxInitialCount as the total number of resources CURRENTLY/INTIALLY avialble. The API resturns a Handle to the Counting Semaphore to be used for further references.

Parameter NameDescription
uxMaxCountThe maximum value to which the semaphore will count.
uxInitialCountThe initial count value of the semaphore after it has been created.
Return ValueIf NULL is returned, the semaphore cannot be created because there is insufficient heap memory available for FreeRTOS to allocate the semaphore data structures.

If a non-NULL value is returned, the semaphore has been created successfully. The returned value should be stored as the handle to the created semaphore.

6.3 xSemaphoreTake():

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

xSemaphoreTake() is used to perform the Take() operation on xSemaphore. If the semaphore is not available, the task is sent back to block state for xTicksToWait time in terms of Ticks.

Parameter Name/ Returned valueDescription
xSemaphoreThe semaphore being taken. A semaphore is referenced by a variable of type SemaphoreHandle_t. It must be explicitly created before it can be used.
xTicksToWaitThe maximum amount of time the task should remain in the Blocked state to wait for the semaphore if it is not already available.

If xTicksToWait is zero, then xSemaphoreTake() will return immediately if the semaphore is not available.

The block time is specified in tick periods, so the absolute time it represents depends on the tick frequency. The macro pdMS_TO_TICKS() can be used to convert a time specified in milliseconds to ticks.

Setting xTicksToWait to portMAX_DELAY will cause the task to wait indefinitely (without a timeout) if INCLUDE_vTaskSuspend is set to 1 in FreeRTOSConfig.h.
Return ValueThere are two possible return values:

1. pdPASS

pdPASS is returned only if the call to xSemaphoreTake() was successful in obtaining the semaphore.

If a block time was specified (xTicksToWait was not zero), then it is possible that the calling task was placed into the Blocked state to wait for the semaphore if it was not immediately available, but the semaphore became available before the block time expired.

2. pdFALSE

The semaphore is not available.

If a block time was specified (xTicksToWait was not zero), then the calling task will have been placed into the Blocked state to wait for the semaphore to become available, but the block time expired before this happened.

6.4 xSemaphoreGive():

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

xSemaphoreGive() releases a previously taken semaphore or Mutex.

Parameter NameDescription
xSemaphoreA handle to the semaphore being released. This is the handle returned when the semaphore was created.
Return ValuepdTRUE if the semaphore was released. pdFALSE if an error occurred.

6.5 xSemaphoreGiveFromISR():

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t
                                              *pxHigherPriorityTaskWoken );

From Interrupt Service Routine (ISR), Binary and counting semaphores can be given using the xSemaphoreGiveFromISR() function. xSemaphoreGiveFromISR() is the interrupt-safe version of xSemaphoreGive().

Parameter NameDescription
xSemaphoreA handle to the semaphore being released. This is the handle returned when the semaphore was created.
pxHigherPriorityTaskWokenxSemaphoreGiveFromISR() will set *pxHigherPriorityTaskWoken to pdTRUE if giving the semaphore caused a task to unblock, and the unblocked task has a priority higher than the currently running task. If xSemaphoreGiveFromISR() sets this value to pdTRUE then a context switch should be requested before the interrupt is exited.
Return ValuepdTRUE if the semaphore was released. If a semaphore is already available, it cannot be given, and xSemaphoreGiveFromISR() will return pdFAIL.

6.6 xSemaphoreCreateMutex():

SemaphoreHandle_t xSemaphoreCreateMutex( void );

Before a Mutex can be used, it must be created. This API is used to create a Mutex.

Parameter NameDescription
Return ValueIf the mutex type semaphore was created successfully then a handle to the created mutex is returned. If the mutex was not created because the memory required to hold the mutex could not be allocated then NULL is returned.

Note: The same same xSemaphoreTake() and xSemaphoreGive() APIs are used to perform Mutex Lock and UnLock operations.

References:

[1] – FreeRTOS Official Website

Exchange data between a Task and an ISR via Queue

Keywords: Embedded systems, ARM, FreeRTOS, STM32F4, Tasks, Queues, ISR, Code Link: Tutorial Source Code Github – Keil In the previous tutorial we demonstrated how to establish inter-task communication via queue. The link to the tutorial is given bellow. Inter Tasks Communication via Queues This tutorial […]

Inter Tasks Communication via Queues

Keywords: Embedded systems, ARM, FreeRTOS, STM32F4, Tasks, Queues Code Link: Tutorial Source Code Github – Keil In the previous tutorial we introduced Queues in general and freeRTOS queues in specific. We introduced freeRTOS APIs for Queues management. The link to the tutorial is given bellow. […]

Introduction to FreeRTOS Queues

Keywords: Embedded systems, ARM, FreeRTOS, STM32F4, Tasks, Queues

“A Queue is a Data Structure that holds a finite number of fixed-size data items and follows FIFO (First in First Out) access mechanism”.

Most of the low power low end embedded systems follows legacy hardware architecture of shared memory where any part of volatile memory can be read/modified/written by any part of embedded software running on the hardware. This is not a big issue until the software gets complex and runs multiple units of independent software tasks accessing shared memory simultaneously e.g. System is performing multi-tasking under an RTOS.

In such systems one of the major problem is memory protection and inter-tasks communication. Though some part of shared memory can be reserved (via some global variables/arrays) that can be written by one task and read by another task when required; The problem in such systems arises when it comes the memory operation Atomicity.

It is quite probable that one task is in the process of writing data to the shared memory and has partially completed the write operation (like writing to a large array) when a context switch occures and another task reads it assuming its the correct data. A more worse situation occurs when multiple tasks are trying to write data to the same shared memory simultaneously.

As an example, the following video shows two freeRTOS tasks trying to write string to same serial Port. As can be seen while the one task has not yet finished writing to serial port, the context switch preempt the first task and runs the second task resulting into mixing of two strings. The exact same things may happen in case of shared memory.

* Task1 Prints: “Message From Task-1: FreeRTOS Task-1 Running...
* Task2 Prints: “Message From Task-2: FreeRTOS Task-2 Running...

In order to tackle such problem, queues are used that ensures the data can’t be written to queue if its full (prevent replacing data) and can’t be read if its empty (prevents reading garbage values).

Queues are used to provide communication channels between various independent pieces of code like task-to-task, task-to-ISR and ISRs-to-task communication.

Generally in Queue, data is written to the back; called tail of the queue and removed from the front; called head of the queue – Figure-1 [1].

Figure-1: FreeRTOS Queue

1. FreeRTOS Queues:

FreeRTOS implements an enhanced version of generic Queue (FIFO). A queue can only be written to back (tail) and be read in revered order (from front to tail) i.e. FIFO (First in First out). This means if a message is written last, it will be read last as well. Thus Queue messaging mechanism has no message priority associated with them. FreeRTOS provides a partially way around it by providing APIs that can write both to the front and back of Queue. Thus the message written to the front will be read first (sort of high priority). This makes freeRTOS Queues follow both FIFO and LIFO mechanism. In this way FreeRTOS queues get somehow closer to POSIX messaging mechanism. More details are covered in the APIs discussion.

2. FreeRTOS Queues APIs:

This following APIs are used for freeRTOS Queue main Operations.

2.1 xQueueCreate():

Before a queue can be used, it must be created explicitly with predefined length and item size it will hold. Queues are referenced by handles, which are variables of type QueueHandle_t. The xQueueCreate() API function creates a queue and returns a QueueHandle_t as a references to the queue just created.

Parameter NameDescription
uxQueueLengthThe maximum number of items that the queue being created can hold at any one time.
uxItemSizeThe size, in bytes, of each data item that can be stored in the queue.
Return ValueIf NULL is returned, then the queue cannot be created because there is insufficient heap memory available for FreeRTOS to allocate the queue data structures and storage area.

If a non-NULL value is returned, the queue has been created successfully. The returned value should be stored as the handle to the created queue.

2.2 xQueueSendToBack() and xQueueSendToFront():

BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );

xQueueSendToBack() is used to send data to the back (tail) of a queue. xQueueSendToFront() is used to send data to the front (head) of a queue.

Parameter NameDescription
xQueueThe handle of the queue to which the data is being sent (written). The queue handle will have been returned from the call to xQueueCreate() used to create the queue.
pvItemToQueueA pointer to the data to be copied into the queue.

The size of each item that the queue can hold is set when the queue is created, so this many bytes will be copied from pvItemToQueue into the queue storage area.
xTicksToWaitThe maximum amount of time the task should remain in the Blocked state to wait for space to become available on the queue, should the queue already be full.

Both xQueueSendToFront() and xQueueSendToBack() will return immediately if xTicksToWait is zero and the queue is already full.

The block time is specified in tick periods, so the absolute time it represents is dependent on the tick frequency. The macro pdMS_TO_TICKS() can be used to convert a time specified in milliseconds into a time specified in ticks.

Setting xTicksToWait to portMAX_DELAY will cause the task to wait indefinitely (without timing out), provided INCLUDE_vTaskSuspend is set to 1 in FreeRTOSConfig.h.
Return ValueThere are two possible return values:

1. pdPASS

Returned only if data was successfully sent to the queue.

If a block time was specified (xTicksToWait was not zero), then it is possible the calling task was placed into the Blocked state, to wait for space to become available in the queue, before the function returned, but data was successfully written to the queue before the block time expired.

2. errQUEUE_FULL

Returned if data could not be written to the queue because the queue was already full.

If a block time was specified (xTicksToWait was not zero) then the calling task will have been placed into the Blocked state to wait for another task or interrupt to make space in the queue, but the specified block time expired before that happened.

Note: Do not call xQueueSendToFront() or xQueueSendToBack() from an interrupt service routine. Use the interrupt-safe versions, xQueueSendToFrontFromISR() and xQueueSendToBackFromISR(), instead.

2.3 xQueueReceive():

BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

xQueueReceive() is used to receive (read) a message from a queue. The message that is received is removed from the queue.

Parameter Name/ Returned valueDescription
xQueueThe handle of the queue to which the data is being sent (written). The queue handle will have been returned from the call to xQueueCreate() used to create the queue.
pvBufferA pointer to the memory into which the received data will be copied.

The size of each data item that the queue holds is set when the queue is created. The memory pointed to by pvBuffer must be at least large enough to hold that many bytes.
xTicksToWaitThe maximum amount of time the task should remain in the Blocked state to wait for data to become available on the queue, should the queue already be empty.

If xTicksToWait is zero, then xQueueReceive() will return immediately if the queue is already empty.

The block time is specified in tick periods, so the absolute time it represents depends on the tick frequency. The macro pdMS_TO_TICKS() can be used to convert a time specified in milliseconds into ticks.

Setting xTicksToWait to portMAX_DELAY will cause the task to wait indefinitely (without timing out), provided INCLUDE_vTaskSuspend is set to 1 in FreeRTOSConfig.h.
Return ValueThere are two possible return values:

1. pdPASS

Returned only if data was successfully read from the queue.

If a block time was specified (xTicksToWait was not zero), then it is possible the calling task was placed into the Blocked state, to wait for data to become available on the queue, but data was successfully read from the queue before the block time expired.

2. errQUEUE_EMPTY

Returned if data cannot be read from the queue because the queue is already empty. If a block time was specified (xTicksToWait was not zero,) then the calling task will have been placed into the Blocked state to wait for another task or interrupt to send data to the queue, but the block time expired before that happened.

Note: Do not call xQueueReceive() from an interrupt service routine. Use the interrupt-safe xQueueReceiveFromISR() API function instead.

2.4 uxQueueMessagesWaiting():

UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );

uxQueueMessagesWaiting() is used to query the number of items that are currently in a queue.

Parameter NameDescription
xQueueThe handle of the queue being queried. The queue handle will have been returned from the call to xQueueCreate() used to create the queue.
Return ValueThe number of items that the queue being queried is currently holding. If zero is returned, then the queue is empty.

Note: Do not call uxQueueMessagesWaiting() from an interrupt service routine. Use the interrupt-safe uxQueueMessagesWaitingFromISR() instead.

References:

[1] – FreeRTOS Official Website

FreeRTOS – Task Yielding

Keywords: Embedded systems, ARM, FreeRTOS, STM32F4, Tasks, Tasks Yielding Code Link: Tutorial Source Code Github – Keil As we know from introductory tutorial that scheduler divides time line into small time slots based on tick timer. In each time slot only one task is selected and allowed […]

FreeRTOS – Delaying Tasks

Keywords: Embedded systems, ARM, FreeRTOS, STM32F4, Tasks Delay, Tasks Code Link: Tutorial Source Code Github (vTaskDelay)- Keil Code Link: Tutorial Source Code Github (vTaskDelayUntil)- Keil By default any FreeRTOS task should run indefinitely under normal operation. This means a task will always be scheduled to […]

FreeRTOS – Deleting Tasks

Keywords: Embedded systems, ARM, FreeRTOS, STM32F4, Tasks, Task Deletion

Code Link: Tutorial Source Code Github – Keil

In the previous tutorials we showed how to create tasks, pass parameters to task, and how to change tasks priority on fly. It is highly recommended to go through those tutorials before proceeding further. In this tutorial we will show how to delete a task.

Sometime few functionalities are no more required in system at run time. For example, lets say an embedded controller connected to various sensors and runs separate Tasks to manage various sensors data. At some point a sensor gets damage. In such scenario, keeping the task running not only contributes to power consumption but also wasts CPU cycles to be used by other tasks for various operations. In this scenarios, to keep system isolated from damaged sensors, the respective controlling task is better to be deleted.

Note: When a task is deleted in freeRTOS (via respective API), in first step its entry is removed from all lists (ready, block, suspended etc.) but its Task Control Block (TCB) and the memory reserved for it at the time of Task creation is kept reserved. In 2nd step, when IDLE TASK is allowed to run, it free up the deleted tasks reserved memory.

A word about Idle Task:

The idle task is created automatically when the RTOS scheduler is started to ensure there is always at least one task that is able to run. It is created at the lowest possible priority to ensure it does not use any CPU time if there are higher priority application tasks in the ready state. The idle task is responsible for freeing memory allocated by RTOS to tasks that have been deleted [1]. The Idle task acts as a garbage collector that clean up deleted task memory. The idle task has no other active functions so can legitimately be starved of microcontroller time under all other conditions.

Delete Task API:

The following API is used to delete FreeRTOS Task.

void vTaskDelete( TaskHandle_t xTask );

/*
@Params:
	xTask: 	The handle of the task to be deleted. Passing NULL will cause the calling task to be deleted.
*/

Note: In freeRTOSConfig.h INCLUDE_vTaskDelete must be defined as 1 for this function to be available.

Note: Calling vTaskDelete(...) for a Task only performs the 1st step of deletion process (as mentioned earlier) i.e. removing Task entry from all Tasks lists and scheduling it for proper deletion via Idle Task. The task memory remains reserved until reclaimed by Idle Task, Figure-1.

Figure-1: FreeRTOS Task Deletion Process

Tutorial Scenario:

In this tutorial, we will create two freeRTOS Tasks (Task-1 and Task-2) with equal priorities. After 20 Task Switching iterations, Task-1 will be deleted leaving behind only Task-2 to run forever.

Let’s jump to programming. Following are the steps.

Steps:

1. First of all declare TaskHandle_t handler which will be used in vTaskDelete API for Task-1.

TaskHandle_t hTask1;

2. Next create two FreeRTOS Tasks of equal priorities. As we will delete Task-1 later on fly (see tutorial scenario) so we need to refer to Task-1 later in code. For this purpose we need a task handler. we have already declared a global TaskHandle_t handler in previous step. Here we will only pass its pointer xTaskCreate(...) API.

  const int task_1_Param = 1;
  const int task_2_Param = 2;

  xTaskCreate (vTask, "T1", 150, (void *)&task_1_Param, 1, &hTask1);

  xTaskCreate (vTask, "T2", 150, (void *)&task_2_Param, 1, NULL);

3. Define a counter variable to be used to count number of times tasks being switched.

int counter = 0;

4. Upon 20th iteration, delete Task-1 by passing it handle to xTaskDelete(...). At this point forward only Task-2 will run as Task-1 has already been deleted.

if (counter == 20) {      
  printf("Deleting Task-1....\n");
  flag = 1;
  vTaskDelete(hTask1);
}

Note: As we mentioned previously that xTaskDelete(...) only stages a task for deletion. The actual memory is reclaimed by Idle Task. So we have a check at the start of Task Function that checks whether Task-1 was deleted or not. If flag is set i.e. flag == 1, then the task will be delayed for 1-tick time so that idle task has time to run and reclaim Task-1 memory.

/*
   if flag is set, it means the Task-1 is deleted
   so give some time to idle task to reclaim memory
*/
if (flag == 1) {
   vTaskDelay(1);
   flag  = 0;
}

The complete code for Tasks Function (used by both Task-1 and Task-2) is given bellow.

void vTask(void * pvParams) {

  volatile unsigned int i = 0;
  const int * tParam = (const int *)pvParams;
 
  for (;;) {

    /*
      if flag is set, it means the Task-1 is deleted
      so give some time to idle task to reclaim memory
    */
    if (flag == 1) {
      vTaskDelay(1);
      flag  = 0;
    }
    
    printf("Task-%d Running.\n", *tParam);
 
    /*
      prevents useless counting and redeletion of deleted task on overflow.
    */
    
    if (counter <=20) {
      counter++;
    }
    
    if (counter == 20) {      
      printf("Deleting Task-1....\n");
      flag = 1;
      vTaskDelete(hTask1);
    }
    
    /*Dummy Delay - Lazy work */
    for (i =0; i < 500000; ++i);
  }
}

5. Start the Scheduler.

vTaskStartScheduler();

6. Finally Compile source code, connect STM32F4-Discovery board to PC and enter debug mode. Open printf serial window via View -> Serial Windows -> Debug (printf) Viewer. Run the Program, You will see Task execution messages as shown in bellow Video.

Note: We have routed printf messages to ST-Link debugger via ARM-ITM. There is a dedicated tutorial on how to redirect printf messages to debugger. Link to the tutorial is given bellow.

For complete source code refer to Github link given at the start of this tutorial.

Click the full screen button for more clear view.

References:

[1] – FreeRTOS Official Website

FreeRTOS – Changing Tasks Priorities

Keywords: Embedded systems, ARM, FreeRTOS, STM32F4, Tasks, Task Priorities Code Link: Tutorial Source Code Github – Keil In the previous tutorials we showed how to create FreeRTOS Tasks and pass parameters to it. In this tutorial we will demonstrate two things. Effect of Task Priority […]