freeRTOS Queu Management
Characteristics of a Queue
Data Storage
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.
There are two ways in which queue behavior could have been implemented:
1. Queue by copy
Queuing by copy means the data sent to the queue is copied byte for byte into the queue
2. Queue by reference
Queuing by reference means the queue only holds pointers to the data sent to the queue, not the data itself.
FreeRTOS uses the queue by copy method.
Queuing by copy is considered to be simultaneously more powerful and simpler to use than queueing by reference because:
- Stack variable can be sent directly to a queue, even though the variable will not exist after the function in which it is declared has exited.
- Data can be sent to a queue without first allocating a buffer to hold the data, and then copying the data into the allocated buffer.
- The sending task can immediately re-use the variable or buffer that was sent to the queue.
- The sending task and the receiving task are completely de-coupled—the application designer does not need to concern themselves with which task‘owns’ the data, or which task is responsible for releasing the data.
- Queuing by copy does not prevent the queue from also being used to queue by reference. For example, when the size of the data being queued makes it impractical to copy the data into the queue, then a pointer to the data can be copied into the queue instead.
- The RTOS takes complete responsibility for allocating the memory used to store data.
- In a memory protected system, the RAM that a task can access will be restricted. In that case queueing by reference could only be used if the sending and receiving task could both access the RAM in which the data was stored. Queuing by copy does not impose that restriction; the kernel always runs with full privileges, allowing a queue to be used to pass data across memory protection boundaries.
Queues are objects in their own right that can be accessed by any task or ISR that knows of their existence. Any number of tasks can write to the same queue, and any number of tasks can read from the same queue. In practice it is very common for a queue to have multiple writers, but much less common for a queue to have multiple readers.
Blocking on Queue Reads
When a task attempts to read from a queue, it can optionally specify a ‘block’ time. This is the time the task will be kept in the Blocked state to wait for data to be available from the queue, should the queue already be empty. A task that is in the Blocked state, waiting for data to become available from a queue, is automatically moved to the Ready state when another task or interrupt places data into the queue. The task will also be moved automatically from the Blocked state to the Ready state if the specified block time expires before data becomes available.
Queues can have multiple readers, so it is possible for a single queue to have morethan one task blocked on it waiting for data. When this is the case, only one task will be unblocked when data becomes available. The task that is unblocked will always be the highest priority task that is waiting for data. If the blocked tasks have equal priority, then the task that has been waiting for data the longest will be unblocked.
Blocking on Multiple Queues
Queues can be grouped into sets, allowing a task to enter the Blocked state to wait for data to become available on any of the queues in the set.
Queue function
xQueueCreate()
A queue must be explicitly created before it can be used.
Queues are referenced by handles, which are variables of type QueueHandle_t. The xQueueCreate() API function creates a queue and returns a QueueHandle_t that references the queue it created.
FreeRTOS V9.0.0 also includes the xQueueCreateStatic() function, which allocates the memory required to create a queue statically at compile time: FreeRTOS allocates RAM from the FreeRTOS heap when a queue is created. The RAM is used to hold both the queue data structures and the items that are contained in the queue. xQueueCreate() will return NULL if there is insufficient heap RAM available for the queue to be created.
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t
uxItemSize );
uxQueueLength:
The maximum number of items that the queue being created can hold at any one time.
uxItemSize:
The size in bytes of each data item that can be stored in the queue.
Return Value:
If 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.
A non-NULL value being returned indicates that the queue has been created successfully. The returned value should be stored as the handle to the created queue. After a queue has been created the xQueueReset() API function can be used to return the queue to its original empty state.
xQueueSendToBack()
send data to the back (tail) of a queue.
xQueueSendToFront()
send data to the front (head) of a queue.
Note: Never call xQueueSendToFront() or xQueueSendToBack() from an interrupt service routine. The interrupt-safe versions xQueueSendToFrontFromISR() and xQueueSendToBackFromISR() should be used in their place.
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
xQueue:
The 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.
pvItemToQueue:
A 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.
xTicksToWait:
The 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.
Returned value:
There are two possible return values:
1. pdPASS:
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 pdPASS will be 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
errQUEUE_FULL will be 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.
xQueueReceive()
receive (read) an item from a queue. The item that is received is removed from the queue.
Never call xQueueReceive() from an interrupt service routine. The interrupt-safe xQueueReceiveFromISR() API function.
BaseType_t xQueueReceive(
QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
xQueue:
The handle of the queue from which the data is being received (read). The queue handle will have been returned from the call to xQueueCreate() used to create the queue.
pvBuffer:
A 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.
xTicksToWait:
The 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 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.
Returned value:
There are two possible return values:
1. pdPASS
pdPASS will be 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
errQUEUE_EMPTY will be 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.
uxQueueMessagesWaiting()
used to query the number of items that are currently in a queue.
Never call uxQueueMessagesWaiting() from an interrupt service routine. The
interrupt-safe uxQueueMessagesWaitingFromISR() should be used in its place.
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
xQueue:
The handle of the queue being queried. The queue handle will have been returned
from the call to xQueueCreate() used to create the queue.
Returned value:
The number of items that the queue being queried is currently holding. If zero is
returned, then the queue is empty.
Example
Receiving Data From Multiple Sources
It is common in FreeRTOS designs for a task to receive data from more than one
source. The receiving task needs to know where the data came from to determine how
the data should be processed. An easy design solution is to use a single queue to
transfer structures with both the value of the data and the source of the data contained
in the structure’s fields.
- A queue is created that holds structures of type Data_t. The structure members
means to be sent to the queue in one message.
- A central Controller task is used to perform the primary system function. This
the queue.
- A CAN bus task is used to encapsulate the CAN bus interfacing functionality.
already decoded message to the Controller task in a Data_t structure. The
eDataID member of the transferred structure is used to let the Controller task
know what the data is—in the depicted case it is a motor speed value. The
lDataValue member of the transferred structure is used to let the Controller
task know the actual motor speed value.
- A Human Machine Interface (HMI) task is used to encapsulate all the HMI
values in a number of ways that have to be detected and interpreted within the
HMI task. When a new command is input, the HMI task sends the command
to the Controller task in a Data_t structure. The eDataID member of the
transferred structure is used to let the Controller task know what the data is—
in the depicted case it is a new set point value. The lDataValue member of the
transferred structure is used to let the Controller task know the actual set point
value.
Example2
留言
張貼留言