Taking and giving semaphore inside a task

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Taking and giving semaphore inside a task

Postby zazas321 » Thu Dec 02, 2021 10:41 am

Hello. In my program, I am parsing incoming BLE messages and based on those messages, I must activate a one shot RTOS task
some pseudo code:

Code: Select all

if(ble_command == 1){               
 xTaskCreate(task1,"task1",10000,&global_message,1,&task1_handle);
}
elseif(ble_command == 2){
 xTaskCreate(task2,"task2",10000,&global_message,1,&task2_handle);
}
else{
 xTaskCreate(task3,"task3",10000,&global_message,1,&task3_handle);
}
Whenever any of those 3 tasks are started, I must not allow any other tasks to start untill this one has finished. I may still be able to send BLE command and my parser will try to start a task but it must fail if there is a running task already.

I have decided to use semaphore for that.

Code: Select all

   spi_long_task_mutex = xSemaphoreCreateBinary();

Inside each task at the beggining, I check if this semaphore is free. If it is free, I do some stuff inside a task and it deletes itself. If I detect that the semaphore is already taken by someone else, I delete the task immediately.

Code: Select all

void task1(void* param){
        if(xSemaphoreTake(spi_long_task_mutex,100)==pdTRUE){
            for(;;)
            {   
                    printf("hello world \n");
                    xSemaphoreGive(spi_long_task_mutex);
                    vTaskDelete(0);
            }
         }
        else{
            printf("semaphore is taken by someone else \n");
            vTaskDelete(0);
        }
}
However, I notice that everytime I try to start any of the task, it just says that semaphore is taken by someone else even though it is definately not since I have just sent the first command after the device reset.
Is there something I am misunderstanding about the semaphores? According to how I understand it, this should just simply work

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: Taking and giving semaphore inside a task

Postby zazas321 » Thu Dec 02, 2021 10:54 am

Switching
spi_long_task_mutex = xSemaphoreCreateBinary();
to
spi_long_task_mutex = xSemaphoreCreateMutex();
Seems to fix my issue. However, I am not fully certain why in this particular case they behave like that. Could you help me understand the differences between binary and mutex and why binary did not work in my case?

ESP_igrr
Posts: 2071
Joined: Tue Dec 01, 2015 8:37 am

Re: Taking and giving semaphore inside a task

Postby ESP_igrr » Thu Dec 02, 2021 1:02 pm

FreeRTOS binary semaphores are initialized with the counter equal to 0. So before the task can "take" a semaphore, it has to be "given" by another task.

Mutexes are similar to binary semaphores, but have three distinctions:
- mutexes implement priority inheritance, and should be used when protecting a resource from concurrent access between tasks.
- mutexes are initialized with counter equal to 1, so they can be "taken" after creation
- if a mutex is "taken" by a certain task, it should be then "given" by the same task. Violating this rule in freertos leads to incorrect scheduler behavior, and in ESP-IDF there is an assertion to check this restriction. If in your use case the synchronization object can be taken in one task and given in another, use a semaphore, instead.

ESP_Sprite
Posts: 9749
Joined: Thu Nov 26, 2015 4:08 am

Re: Taking and giving semaphore inside a task

Postby ESP_Sprite » Fri Dec 03, 2021 2:02 am

Also, on a higher level, creating tasks dynamically generally is a bad idea as the task creation/deletion routines tend to be slow. It's better to have one task running continuously that normally sleeps on a semaphore or queue or something and wakes up when it needs to do work.

username
Posts: 535
Joined: Thu May 03, 2018 1:18 pm

Re: Taking and giving semaphore inside a task

Postby username » Fri Dec 03, 2021 6:28 am

Might want to check out xEventGroupWaitBits()
you can start your tasks and it will sit there using xEventGroupWaitBits() unit a certain bit is set somewhere else in the code.

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: Taking and giving semaphore inside a task

Postby zazas321 » Fri Dec 03, 2021 7:40 am

Thank you all for the answers. I will see if I can find a way to use a single task that would be waiting and then do the work when needed as you have mentioned as that makes sense.

In the meantime, could you confirm the following regarding the semaphore. You have suggested using the semaphore instead of mutex for this particular application but you said that by default it is initialized as 0 so in order for someone to take it, it must be given first. So is it correct to create it like that? :

Code: Select all

    spi_long_task_mutex = xSemaphoreCreateBinary(); // 
    xSemaphoreGive(spi_long_task_mutex); // give the semaphore immediately after creating so that my task can take it
    

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: Taking and giving semaphore inside a task

Postby zazas321 » Fri Dec 03, 2021 8:15 am

Also, I would like to explain why I decide to choose to create these one shot tasks.

This is all happening in the BLE callback code. From what I read in the esp-idf documentation, it is not recommended to have a code that takes a while to execute in the callback itself, therefore I have decided to create a one shot task to handle all the messages and then the task deletes itself.


For example, I have one BLE_queue_task that is running permamently and waiting on the queue. Once it receives the data on the queue, it will send packets to mobile application.



My ble parser task :

Code: Select all

static void BLE_parser_task(void *argument)
{
    ble_message_s data_received;
    for(;;)
    {   
        if (xQueueReceive(ble_parser_queue, &data_received, 0) == pdTRUE) {
            esp_ble_gatts_send_indicate(data_received.gatt_if, data_received.conn_id, heart_rate_handle_table[HRS_IDX_HR_CTNL_PT_VAL],data_received.length, data_received.payload, false);
            free(data_received.payload);
        }
        vTaskDelay(10/portTICK_PERIOD_MS);
    }
}

As you can see from above, I pass ble_message_s struct to the queue which contains all the information the task needs to send indicate message to the BLE subscriber.

And in my one shot task I will be sending a large number of BLE packets and once it is done, the task will delete itself.

Code: Select all

void task1(void* param){
        if(xSemaphoreTake(spi_long_task_mutex,100)==pdTRUE){ 
            for(int i = 0; i <100; i ++){
                        uint8_t data_buf[20] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
                        global_ble_message.conn_id = packet.conn_id;
                        global_ble_message.gatt_if = packet.gatt_if;
                        global_ble_message.payload = build_ble_response(20,data_buf);
                        global_ble_message.length = 20;
                        xQueueSend(ble_parser_queue,&global_ble_message, 10);
               }
            xSemaphoreGive(spi_long_task_mutex); // all 100 messages were sent, we can give back the semaphore now

         }
        else{
            printf("semaphore is taken by someone else \n");
            vTaskDelete(0);
        }
}
The reason why I chose to create a seperate tasks (task1, task2, task3), is becasuse the message structure for each will be different based on what BLE message has been received. I hope this makes more sense to you. I am quite new to FreeRTOS so I would like to know whether it is a good way to handle this

Who is online

Users browsing this forum: No registered users and 136 guests