Queues Allows to pass more information between the tasks. Suspend task if tries to “put” to full queue or “get” from empty one.
Semaphores are used to communication between the tasks without specifying the ID of the thread who can accept it. It allows counting multiple events and can be accepted by many threads.
Direct to task notifications are used to precise communication between the tasks. It is necessary to specify within signal thread id.
Mutexes are used to guard the shared resources. It must be taken and released always in that order by each task that uses the shared resource.
Event Groups are used to synchronize task with multiple events (OR-ed together). There could be 8 or 24 bit value used here (depends on configUSE_16_BIT_TICKS settings) – not implemented in CMSIS_OS API
Queue
A queue can hold a finite number of fixed size data items. The maximum number of items a queue can hold is called its ‘length’. Both the length and the size of each data item are set when the queue is created.
Queues are normally used as First In First Out (FIFO) buffers, where data is written to the end (tail) of the queue and removed from the front (head) of the queue. Below Figure demonstrates data being written to and read from a queue that is being used as a FIFO. It is also possible to write to the front of a queue, and to overwrite data that is already at the front of a queue
- All data send by queue must be of the same type, declared during queue creation phase. It can be simple variable or structure.
- Within CMSIS-RTOS API there are two types of queues:
- Message where one can send only integer type data or a pointer
- Mail where one can send memory blocks
- Length of queue is declared during creation phase and is defined as a number of items which will be send via queue.
- Operations within queues are performed in critical sections (blocking interrupts by programming BASEPRI register for the time of operation on queue.
- Tasks can block on queue sending or receiving data with a timeout or infinitely.
- If multiple tasks are blocked waiting for receiving/Sending data from/To a queue then only the task with the highest priority will be unblocked when a data/space is available. If both tasks have equal priority the task that has been waiting the longest will be unblocked.
Step by step procedure to create FreeRTOS queue using STM32CubeIDE
Step 1: Go to Pinout & Configurations Tab > FreeRTOS > Tasks & Queues Tab > Inside Queues > Add
Step 2:
step 3: for different task creation go to this page STM32-Cube IDE-FreeRTOS Tasks
Queue Related Function Documentation :
a) osMessageQueueId_t osMessageQueueNew
osMessageQueueId_t osMessageQueueNew ( uint32_t msg_count,uint32_t msg_size, const osMessageQueueAttr_t * attr )
Parameters
[in] | msg_count | maximum number of messages in queue. |
[in] | msg_size | maximum message size in bytes. |
[in] | attr | message queue attributes; NULL: default values. |
Returns
message queue ID for reference by other functions or NULL in case of error.
The function osMessageQueueNew creates and initializes a message queue object. The function returns a message queue object identifier or NULL in case of an error.
b) osStatus_t osMessageQueuePut
osStatus_t osMessageQueuePut ( osMessageQueueId_t mq_id, const void * msg_ptr, uint8_t msg_prio, uint32_t timeout )
Parameters
[in] | mq_id | message queue ID obtained by osMessageQueueNew. |
[in] | msg_ptr | pointer to buffer with message to put into a queue. |
[in] | msg_prio | message priority. |
[in] | timeout | Timeout Value or 0 in case of no time-out. |
Returns
status code that indicates the execution status of the function.
The blocking function osMessageQueuePut puts the message pointed to by msg_ptr into the the message queue specified by parameter mq_id. The parameter msg_prio is used to sort message according their priority (higher numbers indicate a higher priority) on insertion.
The parameter timeout specifies how long the system waits to put the message into the queue. While the system waits, the thread that is calling this function is put into the BLOCKED state. The parameter timeout can have the following values:
- when timeout is 0, the function returns instantly (i.e. try semantics).
- when timeout is set to osWaitForever the function will wait for an infinite time until the message is delivered (i.e. wait semantics).
- all other values specify a time in kernel ticks for a timeout (i.e. timed-wait semantics).
c) osStatus_t osMessageQueueGet
osStatus_t osMessageQueueGet ( osMessageQueueId_t mq_id, void * msg_ptr, uint8_t * msg_prio, uint32_t timeout )
Parameters
[in] | mq_id | message queue ID obtained by osMessageQueueNew. |
[out] | msg_ptr | pointer to buffer for message to get from a queue. |
[out] | msg_prio | pointer to buffer for message priority or NULL. |
[in] | timeout | Timeout Value or 0 in case of no time-out. |
Returns
status code that indicates the execution status of the function.
The function osMessageQueueGet retrieves a message from the message queue specified by the parameter mq_id and saves it to the buffer pointed to by the parameter msg_ptr. The message priority is stored to parameter msg_prio if not token{NULL}.
The parameter timeout specifies how long the system waits to retrieve the message from the queue. While the system waits, the thread that is calling this function is put into the BLOCKED state.
Below code will be generated by STM32CubeIDE
/* Definitions for myQueue01 */ osMessageQueueId_t myQueue01Handle; const osMessageQueueAttr_t myQueue01_attributes = { .name = "myQueue01" }; /* Create the queue(s) */ /* creation of myQueue01 */ myQueue01Handle = osMessageQueueNew (16, sizeof(uint16_t), &myQueue01_attributes); /* USER CODE BEGIN Header_SenderTask */ /** * @brief Function implementing the Sender thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_SenderTask */ void SenderTask(void *argument) { /* USER CODE BEGIN SenderTask */ /* Infinite loop */ for(;;) { osDelay(1000); osMessageQueuePut(myQueue01Handle, 0x01, 0, 100); } /* USER CODE END SenderTask */ } /* USER CODE BEGIN Header_ReceiverTask */ /** * @brief Function implementing the Receiver thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_ReceiverTask */ void ReceiverTask(void *argument) { /* USER CODE BEGIN ReceiverTask */ uint16_t localVa = 0; /* Infinite loop */ for(;;) { osDelay(1000); osMessageQueueGet(myQueue01Handle, localVa, 0, 100); } /* USER CODE END ReceiverTask */ }
Semaphore
Semaphores are used to manage and protect access to shared resources. Semaphores are very similar to Mutexes. Whereas a Mutex permits just one thread to access a shared resource at a time, a semaphore can be used to permit a fixed number of threads/ISRs to access a pool of shared resources. Using semaphores, access to a group of identical peripherals can be managed (for example multiple DMA channels).
A semaphore object should be initialized to the maximum number of available tokens. This number of available resources is specified as parameter of the osSemaphoreNew function. Each time a semaphore token is obtained with osSemaphoreAcquire (in available state), the semaphore count is decremented. When the semaphore count is 0 (i.e. depleted state), no more semaphore tokens can be obtained. The thread/ISR that tries to obtain the semaphore token needs to wait until the next token is free. Semaphores are released with osSemaphoreRelease incrementing the semaphore count
- Semaphores are used to synchronize tasks with other events in the system (especially IRQs)
- Waiting for semaphore is equal to wait() procedure, task is in blocked state not taking CPU time Semaphore should be created before usage
- In FreeRTOS implementation semaphores are based on queue mechanism
- In fact those are queues with length 1 and data size 0
- There are following types of semaphores in FreeRTOS:
- Binary – simple on/off mechanism
- Counting – counts multiple give and multiple take
- Mutex – Mutual Exclusion type semaphores (explained later on)
- Recursive (in CMSIS FreeRTOS used only for Mutexes)
- Turn on semaphore = give a semaphore can be done from other task or from interrupt subroutine (function osSemaphoreRelease() )
- Turn off semaphore = take a semaphore can be done from the task (function osSemaphoreWait() )
Semaphore Related Function Documentation :
a) osSemaphoreId_t osSemaphoreNew
osSemaphoreId_t osSemaphoreNew (uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t *attr)
Parameters
[in] | max_count | maximum number of available tokens. |
[in] | initial_count | initial number of available tokens. |
[in] | attr | semaphore attributes; NULL: default values. |
Returns
semaphore ID for reference by other functions or NULL in case of error.
The function osSemaphoreNew creates and initializes a semaphore object that is used to manage access to shared resources and returns the pointer to the semaphore object identifier or NULL in case of an error. It can be safely called before the RTOS is started (call to osKernelStart), but not before it is initialized (call to osKernelInitialize).
The parameter max_count specifies the maximum number of available tokens. A max_count value of 1 creates a binary semaphore.
The parameter initial_count sets the initial number of available tokens.
The parameter attr specifies additional semaphore attributes. Default attributes will be used if set to NULL.
b) osStatus_t osSemaphoreAcquire
osStatus_t osSemaphoreAcquire (osSemaphoreId_t semaphore_id, uint32_t timeout)
Parameters
[in] | semaphore_id | semaphore ID obtained by osSemaphoreNew. |
[in] | timeout | Timeout Value or 0 in case of no time-out. |
Returns
status code that indicates the execution status of the function.
The blocking function osSemaphoreAcquire waits until a token of the semaphore object specified by parameter semaphore_id becomes available. If a token is available, the function instantly returns and decrements the token count.
The parameter timeout specifies how long the system waits to acquire the token. While the system waits, the thread that is calling this function is put into the BLOCKED state.
c) osStatus_t osSemaphoreRelease
osStatus_t osSemaphoreRelease (osSemaphoreId_t semaphore_id)
Parameters
[in] | semaphore_id | semaphore ID obtained by osSemaphoreNew. |
Returns
status code that indicates the execution status of the function.
The function osSemaphoreRelease releases a token of the semaphore object specified by parameter semaphore_id. Tokens can only be released up to the maximum count specified at creation time, see osSemaphoreNew. Other threads that currently wait for a token of this semaphore object will be put into the READY state.
Step by step procedure to create FreeRTOS Semaphore using STM32CubeIDE
Step 1: Go to Pinout & Configurations Tab > FreeRTOS > Timers & Semaphores > Inside Binary Semaphores > Add
Step 2: Add Two Semaphores
step 3: for different task creation go to this page STM32-Cube IDE-FreeRTOS Tasks
Below code will be generated by STM32CubeIDE
/* Definitions for Empty */ osSemaphoreId_t EmptyHandle; const osSemaphoreAttr_t Empty_attributes = { .name = "Empty" }; /* Definitions for Filled */ osSemaphoreId_t FilledHandle; const osSemaphoreAttr_t Filled_attributes = { .name = "Filled" }; /* Create the semaphores(s) */ /* creation of Empty */ EmptyHandle = osSemaphoreNew(1, 1, &Empty_attributes); /* creation of Filled */ FilledHandle = osSemaphoreNew(1, 1, &Filled_attributes); /* USER CODE BEGIN Header_ConsumerTask */ /** * @brief Function implementing the Consumer thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_ConsumerTask */ void ConsumerTask(void *argument) { /* USER CODE BEGIN ConsumerTask */ /* Infinite loop */ for(;;) { osDelay(1000); osSemaphoreAcquire(FilledHandle, osWaitForever); // Consume data osSemaphoreRelease(EmptyHandle); } /* USER CODE END ConsumerTask */ } /* USER CODE BEGIN Header_ProducerTask */ /** * @brief Function implementing the Producer thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_ProducerTask */ void ProducerTask(void *argument) { /* USER CODE BEGIN ProducerTask */ /* Infinite loop */ for(;;) { osDelay(1000); osSemaphoreAcquire(EmptyHandle, osWaitForever); // produce data osSemaphoreRelease(FilledHandle); } /* USER CODE END ProducerTask */ }
Producer/Consumer Semaphore
The producer-consumer problem can be solved using two semaphores.
A first semaphore (empty) counts down the available (empty) buffers, i.e. the producer thread can wait for available buffer slots by acquiring from this one.
A second semaphore (filled) counts up the used (filled) buffers, i.e. the consumer thread can wait for available data by acquiring from this one.
Counting Semaphore:
Counting semaphores can be seen as a as queues of length greater than one. users of the semaphore (Tasks, IT) are not interested in the data that is stored in the queue, just whether the queue is empty or not
Counting semaphores are typically used for two purposes:
• Counting events : an event handler will ‘give’ a semaphore each time an event occurs (incrementing the semaphore count value), and a handler task will ‘take’ a semaphore each time it processes an event (decrementing the semaphore count value). The count value is the difference between the number of events that have occurred and the number that have been processed. In this case it is desirable for the count value to be zero when the semaphore is created.
• Resource management : the count value indicates the number of resources available. To obtain control of a resource a task must first obtain a semaphore decrementing the semaphore count value. When the count value reaches zero there are no free resources. When a task finishes with the resource it releases (gives) the semaphore back incrementing the semaphore count value. In this case it is desirable for the count value to be equal the maximum count value when the semaphore is created
Semaphores: binary vs counting