Skip to the content.

FreeRTOS-esp-idf

Learning FreeRTOS in ESP-IDF

Table Of Contents

What Is An RTOS

Why Use An RTOS

There are well-established techniques for writing good embedded software without the use of an RTOS. In some cases, these techniques may provide the most appropriate solution; however as the solution becomes more complex, the benefits of an RTOS become more apparent. These include:

What should be considered when choosing an RTOS

Alternatively, a full featured OS like Linux or WinCE could be used. These provide a feature rich operating system environment, normally supplied with drivers, GUI’s and middleware components. Full featured OS’s are generally less responsive, require more memory and more processing power than micro kernels, and are mainly used on powerful embedded processors where system resources are plentiful.

Trust, quality of product, and quality of support is everything.

How does the RTOS affect the development of the design

The choice of RTOS can greatly affect the development of the design. By selecting an appropriate RTOS the developer gains:

Peripheral support, memory usage and real-time capability are key features that govern the suitability of the RTOS. Using the wrong RTOS, particularly one that does not provide sufficient real time capability, will severely compromise the design and viability of the final product.

The RTOS needs to be of high quality and easy to use. Developing embedded projects is difficult and time consuming – the developer does not want to be struggling with RTOS related problems as well. The RTOS must be a trusted component that the developer can rely on, supported by in-depth training and good, responsive support.

Scheduling Algorithm

Preemptive Scheduling

Task Name Arrival Time(μs) Execute Time(μs)
Task 1 10 50
Task 2 40 50
Task 3 60 40

Non Preemptive Scheduling

Difference between RTOS and GPOS

Characteristics Real Time Operating System (RTOS) General Purpose Operating System (GPOS)
Time Critical An RTOS differs in that it typically provides a hard real time response, providing a fast, highly deterministic reaction to external events OS’s typically provide a non-deterministic, soft real time response, where there are no guarantees as to when each task will complete, but they will try to stay responsive to the user.
Task Scheduling RTOS uses pre-emptive task scheduling method which is based on priority levels. Here a high priority process gets executed over the low priority ones. In the case of a GPOS – task scheduling is not based on “priority” always! GPOS is programmed to handle scheduling in such a way that it manages to achieve high throughput
Hardware and Economical Factors An RTOS is usually designed for a low end, stand alone device like an ATM, Vending machines, Kiosks etc. RTOS is light weight and small in size compared to a GPOS A GPOS is made for high end, general purpose systems like a personal computer, a work station, a server system etc
Latency Issues An RTOS has no such issues because all the process and threads in it has got bounded latencies – which means – a process/thread will get executed within a specified time limit GPOS is unbounded dispatch latency, which most GPOS falls into. The more number of threads to schedule, latencies will get added up
Preemptible Kernel The kernel of an RTOS is preemptible A GPOS kernel is not preemptible
Examples FreeRTOS,Embox etc.. MacOS, Ubuntu etc..

Introduction to FreeRTOS

The structure of the FreeRTOS/Source directory is shown below.

FreeRTOS
    |
    +-Source        The core FreeRTOS kernel files
        |
        +-include   The core FreeRTOS kernel header files
        |
        +-Portable  Processor specific code.
            |
            +-Compiler x    All the ports supported for compiler x
            +-Compiler y    All the ports supported for compiler y
            +-MemMang       The sample heap implementations

Features Overview :

General Overview

Tasks

Each executing program is a task (or thread) under control of the operating system and hence if it can execute multiple tasks then it is said to be multi-tasking. However it is not concurrent.

Scheduling

Scheduling

FreeRTOS Prioritized Preemptive Scheduling with Time Slicing

FreeRTOS kernel supports two types of scheduling policy:

FreeRTOS uses the combination of above two Scheduling Policy , so the scheduling policy is FreeRTOS Prioritized Preemptive Scheduling with Time Slicing

This picture shows the timing diagram about the execution sequence of high, medium and low priority tasks.

Note

Img

PremptiveContext

A FreeRTOS application will start up and execute just like a non-RTOS application until vTaskStartScheduler() is called. vTaskStartScheduler() is normally called from the application’s main() function. The RTOS only controls the execution sequencing after vTaskStartScheduler() has been called.

What Is Stack Memory

void hello_world_task(void* p)
{
    char mem[128];
    char *amool = char* malloc(32);
    while(1) {
      foo();
    }
}
//The task above uses 128 + 4 (used to hold amool pointer , rest is heap) + <bytes used by foo> bytes of stack.

Tech Stack Size

Controlling Stacks

Simple Task

void hello_world_task(void* p)
{
    while(1) {
        puts("Hello World!");
        vTaskDelay(1000);
    }
}

int main()
{
    xTaskCreate(hello_world_task, (signed char*)"task_name", STACK_BYTES(2048), 0, 1, 0);
    vTaskStartScheduler();

    return -1;
}

Controlling Task

In FreeRTOS, you have precise control of when tasks will use the CPU. The rules are simple:

Here are some of the ways you can give up the CPU:

Basics of Queue

void terminal_task(void* p)
{
     // Assume you got a user-command to play an mp3:
     xQueueSend(song_name_queue, "song_name.mp3", 0);
  
     ...
}

void mp3_play_task(void* p)
{
    char song_name[32];
    while(1) {
        if(xQueueReceive(song_name_queue, &song_name[0], portMAX_DELAY)) {
            // Start to play the song.
        }
    }
}

Basics of Semaphores

Basics of Mutex

  // In main(), initialize your Mutex:
  SemaphoreHandle_t spi_bus_lock = xSemaphoreCreateMutex();

  void task_one()
  {
      while(1) {
          if(xSemaphoreTake(spi_bus_lock, 1000)) {
              // Use Guarded Resource
  
              // Give Semaphore back:
              xSemaphoreGive(spi_bus_lock);
          }
      }
  }
  void task_two()
  {
      while(1) {
          if(xSemaphoreTake(spi_bus_lock, 1000)) {
              // Use Guarded Resource
  
              // Give Semaphore back:
              xSemaphoreGive(spi_bus_lock);
          }
      }
  }

Some More

Mutex Vs Semaphore

Quoting from here

The Toilet Example  (c) Copyright 2005, Niclas Winquist ;)

Mutex:

Is a key to a toilet. One person can have the key - occupy the toilet - at the time. When finished, the person gives (frees) the key to the next person in the queue.

Officially: "Mutexes are typically used to serialise access to a section of  re-entrant code that cannot be executed concurrently by more than one thread. A mutex object only allows one thread into a controlled section, forcing other threads which attempt to gain access to that section to wait until the first thread has exited from that section."
Ref: Symbian Developer Library

(A mutex is really a semaphore with value 1.) (*


Semaphore:

Is the number of free identical toilet keys. Example, say we have four toilets with identical locks and keys. The semaphore count - the count of keys - is set to 4 at beginning (all four toilets are free), then the count value is decremented as people are coming in. If all toilets are full, ie. there are no free keys left, the semaphore count is 0. Now, when eq. one person leaves the toilet, semaphore is increased to 1 (one free key), and given to the next person in the queue.

Officially: "A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number. Threads can request access to the resource (decrementing the semaphore), and can signal that they have finished using the resource (incrementing the semaphore)."
Ref: Symbian Developer Library


(* - Please note, that some web posts indicate, that this statement is not quite accurate 

Benchmarks

According to this ring benchmark the esp32 arduino core is 35% slower than esp-idf’s FreeRTOS.

FreeRTOS for ESP32

Creating And Deleting Tasks

xTaskCreate

Parameters Description
pvTaskCode Pointer to the task entry function (just the name of the function that implements the task)
pcName A descriptive name for the task. This is mainly used to facilitate debugging, but can also be used to obtain a task handle.
usStackDepth The number of words (not bytes!) to allocate for use as the task’s stack. For example, if the stack is 16-bits wide and usStackDepth is 100, then 200 bytes will be allocated for use as the task’s stack.
pvParameters A value that will passed into the created task as the task’s parameter.It can be set to be NULL if there is no parameters.
uxPriority The priority at which the created task will execute.This should be between 1-10 for general use.
pxCreatedTask Used to pass a handle to the created task out of the xTaskCreate() function. pxCreatedTask is optional and can be set to NULL.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

void task1(void * params)
{
  while (true)
  {
    printf("reading temperature from %s\n", (char *) params);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void task2(void * params) 
{
  while (true)
  {
    printf("reading humidity from %s\n", (char *) params);
    vTaskDelay(2000 / portTICK_PERIOD_MS);
  }
}

void app_main(void)
{
   const char* task1_paramter = "task 1";
   const char* task2_paramter = "task 2"; 
   xTaskCreate(&task1, "temperature reading", 2048, (void*) task1_paramter, 2, NULL);
   xTaskCreate(&task2, "humidity reading", 2048, (void*)task2_paramter, 2, NULL);
}

vTaskDelete

NOTE: The idle task is responsible for freeing the RTOS kernel allocated memory from tasks that have been deleted. It is therefore important that the idle task is not starved of microcontroller processing time if your application makes any calls to vTaskDelete (). Memory allocated by the task code is not automatically freed, and should be freed before the task is deleted.

Parameters Description
xTask The handle of the task to be deleted. Passing NULL will cause the calling task to be deleted.

TaskHandle_t task2_handler=NULL;

void task1(void *params) {

for (int i = 0; i < 5; i++)
{
    printf("reading temperature from %s\n", (char *)params);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL); }

void task2(void *params) { for (int i = 0; i < 5; i++) { printf(“reading humidity from %s\n”, (char *)params); vTaskDelay(2000 / portTICK_PERIOD_MS); } if(task2_handler!=NULL){ vTaskDelete(task2_handler); }

}

void app_main(void) { const char *task1_paramter = “task 1”; const char *task2_paramter = “task 2”; xTaskCreate(&task1, “temperature reading”, 2048, (void *)task1_paramter, 2, NULL); xTaskCreate(&task2, “humidity reading”, 2048, (void *)task2_paramter, 2, &task2_handler); }

## Delays
* Delay is used to suspend execution of a task for a particular time. 
* Whenever one task enters delay state, the other tasks runs and vice-versa.

Why not use Hardware Timers for Delays?
* Although using Hardware Timers for delays is much more efficient than software based delays, ultimately the availability of such Timers depends on your Hardware.
* Moreover in a complex or large implementation you might not have the luxury of using Hardware Timers(either due to cost, space or internal hardware constraints) and hence would have to resort to using Software based delays.

### pdMS_TO_TICKS
```c
TickType_t pdMS_TO_TICKS(uint32_t millis)

Parameters Description
millis The time in milliseconds that is to be converted into freeRTOS ticks

vTaskDelay

void vTaskDelay(TickType_t xTicksToDelay)

Parameters Description
xTicksToDelay No. of ticks to be delayed

vTaskDelayUntil


void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)

Parameters Description
pxPreviousWakeTime Pointer to a variable that holds the time at which the task was last unblocked. The variable must be initialised with the current time prior to its first use.Following this the variable is automatically updated within vTaskDelayUntil().
xTimeIncrement The cycle time period. The task will be unblocked at time (pxPreviousWakeTime + xTimeIncrement). Calling vTaskDelayUntil with the same xTimeIncrement parameter value will cause the task to execute with a fixed interval period.

Difference between vTaskDelay and vTaskDelayUntil

vTaskDelay vTaskDelayUntil
In vTaskDelay you say how long after calling vTaskDelay you want to be woken In vTaskDelayUntil you say the time at which you want to be woken
The parameter in vTaskDelay is the delay period in number of ticks from now The parameter in vTaskDelayUntil is the absolute time in ticks at which you want to be woken calculated as an increment from the time you were last woken.
vTaskDelay is relative to the function itself vTaskDelayUntil is absolute in terms of the ticks set by scheduler and FreeRTOS Kernel.

Example 1:-


#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t taskHandle1=NULL;
TaskHandle_t taskHandle2=NULL;

void myTask1(void *p)
{
    while(1)
    {
        printf("hello world\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void myTask2(void *p)
{
    TickType_t mylastunblock=xTaskGetTickCount();
    while(1)
    {
        vTaskDelay(pdMS_TO_TICKS(1000));
        printf("SRA is Great\n");
        vTaskDelayUntil(&mylastunblock,pdMS_TO_TICKS(5000));
    }
}

void app_main()
{
    xTaskCreate(myTask1,"Task 1",2048,NULL,5,taskHandle1);
    xTaskCreate(myTask2,"Task 2",2048,NULL,5,taskHandle2);
}

Example 2 :-

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t taskHandle1=NULL;
TaskHandle_t taskHandle2=NULL;

void myTask1(void *p)
{
    while(1)
    {
        printf("hello world\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void myTask2(void *p)
{
    TickType_t mylastunblock=xTaskGetTickCount();
    while(1)
    {
        vTaskDelay(pdMS_TO_TICKS(1000));
        printf("SRA is Great\n");
        
        // instead of using vTaskDelayUntil we used vTaskDelay
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

void app_main()
{
    xTaskCreate(myTask1,"Task 1",2048,NULL,5,taskHandle1);
    xTaskCreate(myTask2,"Task 2",2048,NULL,5,taskHandle2);
}

Suspending And Resuming Task

vTaskSuspend

void vTaskSuspend( TaskHandle_t xTaskToSuspend )
Parameters Description
xTaskToSuspend Handle to the task being suspended. Passing a NULL handle will cause the calling task to be suspended.

vTaskResume

void vTaskResume( TaskHandle_t xTaskToResume )

Parameters Description
xTicksToResume Handle to the task being readied

Example :- In this example task2 is suspending task1 after a 5 sec delay ,after the delay of another 5 sec task1 is resumed.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t taskhandle1=NULL;
TaskHandle_t taskhandle2=NULL;

void myTask1(void *p){
    int count =0;
    while(1){
        printf("Hello World: %d\n",count++);
        vTaskDelay(1000/portTICK_PERIOD_MS);
    }

}

void myTask2(void *p){
    while(1){
        vTaskDelay(5000/portTICK_PERIOD_MS);
        vTaskSuspend(taskhandle1);
        vTaskDelay(5000/portTICK_PERIOD_MS);
        vTaskResume(taskhandle1);
    }
}

void app_main(){
    xTaskCreate(myTask1,"Task 1",2048,NULL,5,&taskhandle1);
    xTaskCreate(myTask2,"Task 2",2048,NULL,5,&taskhandle2);
}

FreeRTOS InterTask Communication

Queue Communication

xQueueCreate

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize );
Parameters Description
uxQueueLength The maximum number of items the queue can hold at any one time.
uxItemSize The size, in bytes, required to hold each item in the queue.Items are queued by copy, not by reference, so this is the number of bytes that will be copied for each queued item. Each item in the queue must be the same size.

xQueueSend

BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait)
Parameters Description
xQueue The handle to the queue on which the item is to be posted.
pvItemToQueue A pointer to the item that is to be placed on the queue. The size of the items the queue will hold was defined when the queue was created, so this many bytes will be copied from pvItemToQueue into the queue storage area.
xTicksToWait The maximum amount of time the task should block waiting for space to become available on the queue, should it already be full. The call will return immediately if the queue is full and xTicksToWait is set to 0. The time is defined in tick periods so the constant portTICK_PERIOD_MS should be used to convert to real time if this is required.

xQueueReceive

BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait)
Parameters Description
xQueue The handle to the queue on which the item is to be received.
pvBuffer Pointer to the buffer into which the received item will be copied.
xTicksToWait The maximum amount of time the task should block waiting for an item to receive should the queue be empty at the time of the call. Setting xTicksToWait to 0 will cause the function to return immediately if the queue is empty. The time is defined in tick periods so the constant portTICK_PERIOD_MS should be used to convert to real time if this is required.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

QueueHandle_t queue;

void sendingTheCount(void *p){
    int count=0 ;
    
    while(1){
        count++;
        printf("Received message \n");
        long ok=xQueueSend(queue,&count,1000/portTICK_PERIOD_MS);
        if(ok){
            printf("added message to queue\n");
        }
        else{
            printf("failed to add message to the queue\n");
        }
        vTaskDelay(5000/portTICK_PERIOD_MS);
    }
}

void Task1(void *params){
    while(true){
        int rxInt;
        if(xQueueReceive(queue,&rxInt,5000/portTICK_PERIOD_MS)){
            printf("doing something with count %d\n\n",rxInt);
        }
        
    }
}

void app_main(void){
    queue=xQueueCreate(3,sizeof(int));
    xTaskCreate(&sendingTheCount,"get HTTP",2048,NULL,2,NULL);
    xTaskCreate(&Task1,"Task 1",2048,NULL,1,NULL);
}

Binary Semaphore

xSemaphoreCreateBinary

SemaphoreHandle_t xSemaphoreCreateBinary( void );

xSemaphoreGive

xSemaphoreGive( SemaphoreHandle_t xSemaphore );
Parameters Description
xSemaphore A handle to the semaphore being released. This is the handle returned when the semaphore was created.

xSemaphoreTake

xSemaphoreTake( SemaphoreHandle_t xSemaphore,
                 TickType_t xTicksToWait );
Parameters Description
xSemaphore A handle to the semaphore being taken - obtained when the semaphore was created.
xTicksToWait The time in ticks to wait for the semaphore to become available. The macro portTICK_PERIOD_MS can be used to convert this to a real time. A block time of zero can be used to poll the semaphore.

EXAMPLE

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"

SemaphoreHandle_t bi_sema = NULL;
static int shared_int = 0;

void led_blink_1()
{
    const char task[] = "led blink 1";
    while (1)
    {

        if (bi_sema != NULL)
        {
            // See if we can obtain the semaphore.  If the semaphore is not
            // available wait 10 ticks to see if it becomes free.
            if (xSemaphoreTake(bi_sema, 10/portTICK_PERIOD_MS) == pdTRUE)
            {
                // We were able to obtain the semaphore and can now access the
                // shared resource.
                shared_int += 1;
                // ...
                ESP_LOGI(task,"Semaphore Taken Succesfully | Shared Res - %d", shared_int);
                // We have finished accessing the shared resource.  Release the
                // semaphore.
                xSemaphoreGive(bi_sema);
            }
            else
            {
                // We could not obtain the semaphore and can therefore not
                // access the shared resource safely.
                ESP_LOGW(task, "Failed in taking Semaphore");
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void led_blink_2()
{
    const char task[] = "led blink 2";
    while (1)
    {
        if (bi_sema != NULL)
        {
            // See if we can obtain the semaphore.  If the semaphore is not
            // available wait 10 ticks to see if it becomes free.
            if (xSemaphoreTake(bi_sema, 10/portTICK_PERIOD_MS) == pdTRUE)
            {
                // We were able to obtain the semaphore and can now access the
                // shared resource.
                shared_int -= 1;
                // ...
                ESP_LOGI(task,"Semaphore Taken Succesfully | Shared Res - %d", shared_int);
                // We have finished accessing the shared resource.  Release the
                // semaphore.
                xSemaphoreGive(bi_sema);
            }
            else
            {
                // We could not obtain the semaphore and can therefore not
                // access the shared resource safely.
                ESP_LOGW(task, "Failed in taking Semaphore");
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main()
{
    bi_sema = xSemaphoreCreateBinary();
    // xSemaphoreCreateBinary() Creates a new binary semaphore instance, and
    // returns a handle by which the new semaphore can be referenced.
    
    xSemaphoreGive(bi_sema);
    // The semaphore must be given before it can be taken if calls are made
    // using xSemaphoreCreateBinary()

    xTaskCreate(&led_blink_1, "Led Blink 1", 4096, NULL, 0, NULL);
    xTaskCreate(&led_blink_2, "Led Blink 2", 4096, NULL, 1, NULL);
}

Mutex

xSemaphoreCreateMutex

SemaphoreHandle_t xSemaphoreCreateMutex( void )

Example

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"

SemaphoreHandle_t mutex_1  = NULL;

static int shared_int = 0;

void led_blink_1(void *paramter)
{
    const char task[] = "led blink 1";
    while (1)
    {

        if (mutex_1 != NULL)
        {
            // See if we can obtain the semaphore.  If the semaphore is not
            // available wait 10 ticks to see if it becomes free.
            if (xSemaphoreTake(mutex_1, 1000/portTICK_PERIOD_MS) == pdTRUE)
            {
                // We were able to obtain the semaphore and can now access the
                // shared resource.
                shared_int += 1;
                // ...
                ESP_LOGI(task,"Semaphore Taken Succesfully | Shared Res - %d", shared_int);

                vTaskDelay(1500/portTICK_PERIOD_MS);
                // We have finished accessing the shared resource.  Release the
                // semaphore.
                xSemaphoreGive(mutex_1);
            }
            else
            {
                // We could not obtain the semaphore and can therefore not
                // access the shared resource safely.
                ESP_LOGW(task, "Failed in taking Semaphore");
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}


void led_blink_2(void *paramter)
{
    const char task[] = "led blink 2";
    while (1)
    {
        if (mutex_1 != NULL)
        {
            // See if we can obtain the semaphore.  If the semaphore is not
            // available wait 10 ticks to see if it becomes free.
            if (xSemaphoreTake(mutex_1, 1000/portTICK_PERIOD_MS) == pdTRUE)
            {
                // We were able to obtain the semaphore and can now access the
                // shared resource.
                shared_int -= 1;
                // ...
                ESP_LOGI(task,"Semaphore Taken Succesfully | Shared Res - %d", shared_int);
                // We have finished accessing the shared resource.  Release the
                // semaphore.
                xSemaphoreGive(mutex_1);
            }
            else
            {
                // We could not obtain the semaphore and can therefore not
                // access the shared resource safely.
                ESP_LOGW(task, "Failed in taking Semaphore");
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main()
{
    mutex_1 = xSemaphoreCreateMutex();

    // Semaphore cannot be used before a call to xSemaphoreCreateMutex().
    // This is a macro so pass the variable in directly.

    xTaskCreate(&led_blink_1, "Led Blink 1", 4096, NULL, 0, NULL);
    xTaskCreate(&led_blink_2, "Led Blink 2", 4096, NULL, 1, NULL);
}

Resources

Assignment

1) Implement semaphore like functionality using queues.

* If task A is blocked then task B can only process if task A is freed. vice versa. (Explore [FreeRTOS Queue API](https://www.freertos.org/a00018.html))

* When boot button is pressed all tasks should get suspended if they were running and resumed if they were suspended. if suspended the tasks then you must also delete the queue. 

2) See If the above task can be implemented using Task Notifications.

3) Deadlock Example

Task

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"

SemaphoreHandle_t bi_sema_1 = NULL;
SemaphoreHandle_t bi_sema_2 = NULL;
static int shared_int = 0;

void led_blink_1()
{
    const char task[] = "led blink 1";
    while (1)
    {

        if (bi_sema_1 != NULL)
        {
            // See if we can obtain the semaphore.  If the semaphore is not
            // available wait 10 ticks to see if it becomes free.
            if (xSemaphoreTake(bi_sema_1, portMAX_DELAY) == pdTRUE)
            {
                // We were able to obtain the semaphore and can now access the
                // shared resource.
                shared_int += 1;
                // ...
                ESP_LOGI(task, "Semaphore 1 Taken Succesfully | Shared Res - %d", shared_int);
                // We have finished accessing the shared resource.  Release the
                // semaphore.
                if (xSemaphoreTake(bi_sema_2, portMAX_DELAY) == pdTRUE)
                {
                    // We were able to obtain the semaphore and can now access the
                    // shared resource.
                    shared_int -= 1;
                    // ...
                    ESP_LOGI(task, "Semaphore 2 Taken Succesfully | Shared Res - %d", shared_int);
                }
                else
                {
                    // We could not obtain the semaphore and can therefore not
                    // access the shared resource safely.
                    ESP_LOGW(task, "Failed in taking Semaphore 2");
                }
                xSemaphoreGive(bi_sema_1);
                xSemaphoreGive(bi_sema_2);
            }
            else
            {
                // We could not obtain the semaphore and can therefore not
                // access the shared resource safely.
                ESP_LOGW(task, "Failed in taking Semaphore 1");
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void led_blink_2()
{
    const char task[] = "led blink 2";
    while (1)
    {
        if (bi_sema_2 != NULL)
        {
            // See if we can obtain the semaphore.  If the semaphore is not
            // available wait 10 ticks to see if it becomes free.
            if (xSemaphoreTake(bi_sema_2, portMAX_DELAY) == pdTRUE)
            {
                // We were able to obtain the semaphore and can now access the
                // shared resource.
                shared_int -= 1;
                // ...
                ESP_LOGI(task, "Semaphore 2 Taken Succesfully | Shared Res - %d", shared_int);
                // We have finished accessing the shared resource.  Release the
                // semaphore.
                if (xSemaphoreTake(bi_sema_1, portMAX_DELAY) == pdTRUE)
                {
                    // We were able to obtain the semaphore and can now access the
                    // shared resource.
                    shared_int -= 1;
                    // ...
                    ESP_LOGI(task, "Semaphore 1 Taken Succesfully | Shared Res - %d", shared_int);
                }
                else
                {
                    // We could not obtain the semaphore and can therefore not
                    // access the shared resource safely.
                    ESP_LOGW(task, "Failed in taking Semaphore 1");
                }
                xSemaphoreGive(bi_sema_2);
                xSemaphoreGive(bi_sema_1);
            }
            else
            {
                // We could not obtain the semaphore and can therefore not
                // access the shared resource safely.
                ESP_LOGW(task, "Failed in taking Semaphore 2");
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main()
{
    bi_sema_1 = xSemaphoreCreateBinary();
    bi_sema_2 = xSemaphoreCreateBinary();
    // xSemaphoreCreateBinary() Creates a new binary semaphore instance, and
    // returns a handle by which the new semaphore can be referenced.

    xSemaphoreGive(bi_sema_1);
    xSemaphoreGive(bi_sema_2);
    // The semaphore must be given before it can be taken if calls are made
    // using xSemaphoreCreateBinary()

    xTaskCreate(&led_blink_1, "Led Blink 1", 4096, NULL, 0, NULL);
    xTaskCreate(&led_blink_2, "Led Blink 2", 4096, NULL, 1, NULL);
}