freeRTOS/Re-factor ring buffers

This fixes multiple bugs with ring buffers and re-factors the code. The public
API has not changed, however the underlying implementation have various private
functions have been changed. The following behavioral changes have been made

-   Size of ring buffers for No-Split/Allow-Split buffers will not be rounded
    up to the nearest 32-bit aligned size. This was done to simplify the
    implementation

-   Item size for No-Split/Allow-Split buffers will also be rounded up to the
    nearest 32-bit aligned size.

The following bugs have been fixed

-   In copyItemToRingbufAllowSplit(), when copying an item where the aligned
    size is smaller than the remaining length, the function does not consider
    the case where the true size of the item is less than 4 bytes.

-   The copy functions will automatically wrap around the write pointers when
    the remaining length of the buffer is not large enough to fit a header, but
    does not consider if wrapping around will cause an overlap with the read
    pointer. This will make a full buffer be mistaken for an empty buffer

closes #1711
-   xRingbufferSend() can get stuck in a infinite loop when the size of the
    free memory is larger than the needed_size, but too small to fit in the ring
    buffer due to alignment and extra overhead of wrapping around.

closes #1846
-   Fixed documentation with ring buffer queue set API

-   Adding and removing from queue set does not consider the case where the
    read/write semaphores actually hold a value.

The following functions have been deprecated
    - xRingbufferIsNextItemWrapped() due to lack of thread safety
    - xRingbufferAddToQueueSetWrite() and xRingbufferRemoveFromQueueSetWrite()
    as adding the queue sets only work under receive operations.

The following functions have been added
    - xRingbufferReceiveSplit() and xRingbufferReceiveSplitFromISR() as a thread
    safe way to receive from allow-split buffers
    - vRingbufferGetInfo()

Documentation for ring buffers has also been added.
This commit is contained in:
Darian Leung 2018-04-19 01:20:34 +08:00 committed by bot
parent 32e838ddb6
commit 4bfa30967f
17 changed files with 2619 additions and 1186 deletions

View File

@ -1,3 +1,17 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FREERTOS_RINGBUF_H
#define FREERTOS_RINGBUF_H
@ -11,71 +25,48 @@ extern "C" {
#include <freertos/queue.h>
//An opaque handle for a ringbuff object.
/**
* Type by which ring buffers are referenced. For example, a call to xRingbufferCreate()
* returns a RingbufHandle_t variable that can then be used as a parameter to
* xRingbufferSend(), xRingbufferReceive(), etc.
*/
typedef void * RingbufHandle_t;
/**
* @brief The various types of buffer
*
* A ringbuffer instantiated by these functions essentially acts like a
* FreeRTOS queue, with the difference that it's strictly FIFO and with
* the main advantage that you can put in randomly-sized items. The capacity,
* accordingly, isn't measured in the amount of items, but the amount of
* memory that is used for storing the items. Dependent on the size of
* the items, more or less of them will fit in the ring buffer.
*
* This ringbuffer tries to be efficient with memory: when inserting an item,
* the item data will be copied to the ringbuffer memory. When retrieving
* an item, however, a reference to ringbuffer memory will be returned.
* The returned memory is guaranteed to be 32-bit aligned and contiguous.
* The application can use this memory, but as long as it does, ringbuffer
* writes that would write to this bit of memory will block.
*
* The requirement for items to be contiguous is slightly problematic when
* the only way to place the next item would involve a wraparound from the end
* to the beginning of the ringbuffer. This can be solved (or not) in a few ways,
* see descriptions of possible ringbuf_type_t types below.
*
* The maximum size of an item will be affected by ringbuffer type.
* When split items are allowed, it is acceptable to push items of
* (buffer_size)-16 bytes into the buffer.
* When it's not allowed, the maximum size is (buffer_size/2)-8 bytes.
* The bytebuf can fill the entire buffer with data, it has no overhead.
*/
typedef enum {
/** The insertion code will leave the room at the end of the ringbuffer
* unused and instead will put the entire item at the start of the ringbuffer,
* as soon as there is enough free space.
/**
* No-split buffers will only store an item in contiguous memory and will
* never split an item. Each item requires an 8 byte overhead for a header
* and will always internally occupy a 32-bit aligned size of space.
*/
RINGBUF_TYPE_NOSPLIT = 0,
/** The insertion code will split the item in two items; one which fits
* in the space left at the end of the ringbuffer, one that contains
* the remaining data which is placed in the beginning.
* Two xRingbufferReceive calls will be needed to retrieve the data.
/**
* Allow-split buffers will split an item into two parts if necessary in
* order to store it. Each item requires an 8 byte overhead for a header,
* splitting incurs an extra header. Each item will always internally occupy
* a 32-bit aligned size of space.
*/
RINGBUF_TYPE_ALLOWSPLIT,
/** This is your conventional byte-based ringbuffer. It does have no
* overhead, but it has no item contiguousness either: a read will just
* give you the entire written buffer space, or the space up to the end
* of the buffer, and writes can be broken up in any way possible.
* Note that this type cannot do a 2nd read before returning the memory
* of the 1st.
/**
* Byte buffers store data as a sequence of bytes and do not maintain separate
* items, therefore byte buffers have no overhead. All data is stored as a
* sequence of byte and any number of bytes can be sent or retrieved each
* time.
*/
RINGBUF_TYPE_BYTEBUF
} ringbuf_type_t;
/**
* @brief Create a ring buffer
* @brief Create a ring buffer
*
* @param buf_length Length of circular buffer, in bytes. Each entry will
* take up its own length, plus a header that at the moment
* is equal to sizeof(size_t).
* @param type Type of ring buffer, see ringbuf_type_t.
* @param[in] xBufferSize Size of the buffer in bytes. Note that items require
* space for overhead in no-split/allow-split buffers
* @param[in] xBufferType Type of ring buffer, see documentation.
*
* @return A RingbufHandle_t handle to the created ringbuffer, or NULL in case of error.
* @note xBufferSize of no-split/allow-split buffers will be rounded up to the nearest 32-bit aligned size.
*
* @return A handle to the created ring buffer, or NULL in case of error.
*/
RingbufHandle_t xRingbufferCreate(size_t buf_length, ringbuf_type_t type);
RingbufHandle_t xRingbufferCreate(size_t xBufferSize, ringbuf_type_t xBufferType);
/**
* @brief Create a ring buffer of type RINGBUF_TYPE_NOSPLIT for a fixed item_size
@ -83,282 +74,338 @@ RingbufHandle_t xRingbufferCreate(size_t buf_length, ringbuf_type_t type);
* This API is similar to xRingbufferCreate(), but it will internally allocate
* additional space for the headers.
*
* @param item_size Size of each item to be put into the ring buffer
* @param num_item Maximum number of items the buffer needs to hold simultaneously
* @param[in] xItemSize Size of each item to be put into the ring buffer
* @param[in] xItemNum Maximum number of items the buffer needs to hold simultaneously
*
* @return A RingbufHandle_t handle to the created ringbuffer, or NULL in case of error.
* @return A RingbufHandle_t handle to the created ring buffer, or NULL in case of error.
*/
RingbufHandle_t xRingbufferCreateNoSplit(size_t item_size, size_t num_item);
RingbufHandle_t xRingbufferCreateNoSplit(size_t xItemSize, size_t xItemNum);
/**
* @brief Delete a ring buffer
* @brief Insert an item into the ring buffer
*
* @param ringbuf Ring buffer to delete
*/
void vRingbufferDelete(RingbufHandle_t ringbuf);
/**
* @brief Get maximum size of an item that can be placed in the ring buffer
* Attempt to insert an item into the ring buffer. This function will block until
* enough free space is available or until it timesout.
*
* @param ringbuf Ring buffer to query
* @param[in] xRingbuffer Ring buffer to insert the item into
* @param[in] pvItem Pointer to data to insert. NULL is allowed if xItemSize is 0.
* @param[in] xItemSize Size of data to insert.
* @param[in] xTicksToWait Ticks to wait for room in the ring buffer.
*
* @return Maximum size, in bytes, of an item that can be placed in a ring buffer.
*/
size_t xRingbufferGetMaxItemSize(RingbufHandle_t ringbuf);
/**
* @brief Get current free size available in the buffer
*
* This gives the real time free space available in the ring buffer. So basically,
* this will be the maximum size of the entry that can be sent into the buffer.
*
* @note This API is not thread safe. So, if multiple threads are accessing the same
* ring buffer, it is the application's responsibility to ensure atomic access to this
* API and the subsequent Send
*
* @param ringbuf - Ring buffer to query
*
* @return Current free size, in bytes, available for an entry
*/
size_t xRingbufferGetCurFreeSize(RingbufHandle_t ringbuf);
/**
* @brief Check if the next item is wrapped
*
* This API tells if the next item that is available for a Receive is wrapped
* or not. This is valid only if the ring buffer type is RINGBUF_TYPE_ALLOWSPLIT
*
* @note This API is not thread safe. So, if multiple threads are accessing the same
* ring buffer, it is the application's responsibility to ensure atomic access to this
* API and the subsequent Receive
*
* @param ringbuf - Ring buffer to query
*
* @return true if the next item is wrapped around
* @return false if the next item is not wrapped
*/
bool xRingbufferIsNextItemWrapped(RingbufHandle_t ringbuf);
/**
* @brief Insert an item into the ring buffer
*
* @param ringbuf Ring buffer to insert the item into
* @param data Pointer to data to insert. NULL is allowed if data_size is 0.
* @param data_size Size of data to insert. A value of 0 is allowed.
* @param ticks_to_wait Ticks to wait for room in the ringbuffer.
* @note For no-split/allow-split ring buffers, the actual size of memory that
* the item will occupy will be rounded up to the nearest 32-bit aligned
* size. This is done to ensure all items are always stored in 32-bit
* aligned fashion.
*
* @return
* - pdTRUE if succeeded
* - pdFALSE on time-out or when the buffer is larger than indicated
* by xRingbufferGetMaxItemSize(ringbuf).
* - pdFALSE on time-out or when the data is larger than the maximum permissible size of the buffer
*/
BaseType_t xRingbufferSend(RingbufHandle_t ringbuf, void *data, size_t data_size, TickType_t ticks_to_wait);
BaseType_t xRingbufferSend(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, TickType_t xTicksToWait);
/**
* @brief Insert an item into the ring buffer from an ISR
* @brief Insert an item into the ring buffer in an ISR
*
* @param ringbuf Ring buffer to insert the item into
* @param data Pointer to data to insert. NULL is allowed if data_size is 0.
* @param data_size Size of data to insert. A value of 0 is allowed.
* @param[out] higher_prio_task_awoken Value pointed to will be set to pdTRUE
* if the push woke up a higher priority task.
* Attempt to insert an item into the ring buffer from an ISR. This function
* will return immediately if there is insufficient free space in the buffer.
*
* @return pdTRUE if succeeded, pdFALSE when the ring buffer does not have space.
*/
BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t data_size, BaseType_t *higher_prio_task_awoken);
/**
* @brief Retrieve an item from the ring buffer
* @param[in] xRingbuffer Ring buffer to insert the item into
* @param[in] pvItem Pointer to data to insert. NULL is allowed if xItemSize is 0.
* @param[in] xItemSize Size of data to insert.
* @param[out] pxHigherPriorityTaskWoken Value pointed to will be set to pdTRUE if the function woke up a higher priority task.
*
* @note A call to vRingbufferReturnItem() is required after this to free up
* the data received.
*
* @param ringbuf Ring buffer to retrieve the item from
* @param[out] item_size Pointer to a variable to which the size of the
* retrieved item will be written.
* @param ticks_to_wait Ticks to wait for items in the ringbuffer.
* @note For no-split/allow-split ring buffers, the actual size of memory that
* the item will occupy will be rounded up to the nearest 32-bit aligned
* size. This is done to ensure all items are always stored in 32-bit
* aligned fashion.
*
* @return
* - pointer to the retrieved item on success; *item_size filled with
* the length of the item.
* - NULL on timeout, *item_size is untouched in that case.
* - pdTRUE if succeeded
* - pdFALSE when the ring buffer does not have space.
*/
void *xRingbufferReceive(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait);
BaseType_t xRingbufferSendFromISR(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, BaseType_t *pxHigherPriorityTaskWoken);
/**
* @brief Retrieve an item from the ring buffer from an ISR
* @brief Retrieve an item from the ring buffer
*
* @note A call to vRingbufferReturnItemFromISR() is required after this to
* free up the data received
* Attempt to retrieve an item from the ring buffer. This function will block
* until an item is available or until it timesout.
*
* @param ringbuf Ring buffer to retrieve the item from
* @param[out] item_size Pointer to a variable to which the size of the
* retrieved item will be written.
* @param[in] xRingbuffer Ring buffer to retrieve the item from
* @param[out] pxItemSize Pointer to a variable to which the size of the retrieved item will be written.
* @param[in] xTicksToWait Ticks to wait for items in the ring buffer.
*
* @note A call to vRingbufferReturnItem() is required after this to free the item retrieved.
*
* @return
* - Pointer to the retrieved item on success; *item_size filled with
* the length of the item.
* - NULL when the ringbuffer is empty, *item_size is untouched in that case.
* - Pointer to the retrieved item on success; *pxItemSize filled with the length of the item.
* - NULL on timeout, *pxItemSize is untouched in that case.
*/
void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size);
void *xRingbufferReceive(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait);
/**
* @brief Retrieve bytes from a ByteBuf type of ring buffer,
* specifying the maximum amount of bytes to return
* @brief Retrieve an item from the ring buffer in an ISR
*
* @note A call to vRingbufferReturnItem() is required after this to free up
* the data received.
* Attempt to retrieve an item from the ring buffer. This function returns immediately
* if there are no items available for retrieval
*
* @param ringbuf Ring buffer to retrieve the item from
* @param[out] item_size Pointer to a variable to which the size
* of the retrieved item will be written.
* @param ticks_to_wait Ticks to wait for items in the ringbuffer.
* @param wanted_size Maximum number of bytes to return.
* @param[in] xRingbuffer Ring buffer to retrieve the item from
* @param[out] pxItemSize Pointer to a variable to which the size of the
* retrieved item will be written.
*
* @note A call to vRingbufferReturnItemFromISR() is required after this to free the item retrieved.
* @note Byte buffers do not allow multiple retrievals before returning an item
*
* @return
* - Pointer to the retrieved item on success; *item_size filled with
* the length of the item.
* - NULL on timeout, *item_size is untouched in that case.
* - Pointer to the retrieved item on success; *pxItemSize filled with the length of the item.
* - NULL when the ring buffer is empty, *pxItemSize is untouched in that case.
*/
void *xRingbufferReceiveUpTo(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait, size_t wanted_size);
void *xRingbufferReceiveFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize);
/**
* @brief Retrieve bytes from a ByteBuf type of ring buffer,
* specifying the maximum amount of bytes to return. Call this from an ISR.
* @brief Retrieve a split item from an allow-split ring buffer
*
* @note A call to vRingbufferReturnItemFromISR() is required after this
* to free up the data received.
* Attempt to retrieve a split item from an allow-split ring buffer. If the item
* is not split, only a single item is retried. If the item is split, both parts
* will be retrieved. This function will block until an item is available or
* until it timesout.
*
* @param ringbuf Ring buffer to retrieve the item from
* @param[out] item_size Pointer to a variable to which the size of the
* retrieved item will be written.
* @param wanted_size Maximum number of bytes to return.
* @param[in] xRingbuffer Ring buffer to retrieve the item from
* @param[out] ppvHeadItem Double pointer to first part (set to NULL if no items were retrieved)
* @param[out] ppvTailItem Double pointer to second part (set to NULL if item is not split)
* @param[out] pxHeadItemSize Pointer to size of first part (unmodified if no items were retrieved)
* @param[out] pxTailItemSize Pointer to size of second part (unmodified if item is not split)
* @param[in] xTicksToWait Ticks to wait for items in the ring buffer.
*
* @note Call(s) to vRingbufferReturnItem() is required after this to free up the item(s) retrieved.
* @note This function should only be called on allow-split buffers
*
* @return
* - Pointer to the retrieved item on success; *item_size filled with
* - pdTRUE if an item (split or unsplit) was retrieved
* - pdFALSE when no item was retrieved
*/
BaseType_t xRingbufferReceiveSplit(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize, TickType_t xTicksToWait);
/**
* @brief Retrieve a split item from an allow-split ring buffer in an ISR
*
* Attempt to retrieve a split item from an allow-split ring buffer. If the item
* is not split, only a single item is retried. If the item is split, both parts
* will be retrieved. This function returns immediately if there are no items
* available for retrieval
*
* @param[in] xRingbuffer Ring buffer to retrieve the item from
* @param[out] ppvHeadItem Double pointer to first part (set to NULL if no items were retrieved)
* @param[out] ppvTailItem Double pointer to second part (set to NULL if item is not split)
* @param[out] pxHeadItemSize Pointer to size of first part (unmodified if no items were retrieved)
* @param[out] pxTailItemSize Pointer to size of second part (unmodified if item is not split)
*
* @note Calls to vRingbufferReturnItemFromISR() is required after this to free up the item(s) retrieved.
* @note This function should only be called on allow-split buffers
*
* @return
* - pdTRUE if an item (split or unsplit) was retrieved
* - pdFALSE when no item was retrieved
*/
BaseType_t xRingbufferReceiveSplitFromISR(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize);
/**
* @brief Retrieve bytes from a byte buffer, specifying the maximum amount of bytes to retrieve
*
* Attempt to retrieve data from a byte buffer whilst specifying a maximum number
* of bytes to retrieve. This function will block until there is data available
* for retrieval or until it timesout.
*
* @param[in] xRingbuffer Ring buffer to retrieve the item from
* @param[out] pxItemSize Pointer to a variable to which the size of the retrieved item will be written.
* @param[in] xTicksToWait Ticks to wait for items in the ring buffer.
* @param[in] xMaxSize Maximum number of bytes to return.
*
* @note A call to vRingbufferReturnItem() is required after this to free up the data retrieved.
* @note This function should only be called on byte buffers
* @note Byte buffers do not allow multiple retrievals before returning an item
*
* @return
* - Pointer to the retrieved item on success; *pxItemSize filled with
* the length of the item.
* - NULL when the ringbuffer is empty, *item_size is untouched in that case.
* - NULL on timeout, *pxItemSize is untouched in that case.
*/
void *xRingbufferReceiveUpToFromISR(RingbufHandle_t ringbuf, size_t *item_size, size_t wanted_size);
void *xRingbufferReceiveUpTo(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait, size_t xMaxSize);
/**
* @brief Return a previously-retrieved item to the ringbuffer
* @brief Retrieve bytes from a byte buffer, specifying the maximum amount of
* bytes to retrieve. Call this from an ISR.
*
* @param ringbuf Ring buffer the item was retrieved from
* @param item Item that was received earlier
* Attempt to retrieve bytes from a byte buffer whilst specifying a maximum number
* of bytes to retrieve. This function will return immediately if there is no data
* available for retrieval.
*
* @param[in] xRingbuffer Ring buffer to retrieve the item from
* @param[out] pxItemSize Pointer to a variable to which the size of the retrieved item will be written.
* @param[in] xMaxSize Maximum number of bytes to return.
*
* @note A call to vRingbufferReturnItemFromISR() is required after this to free up the data received.
* @note This function should only be called on byte buffers
* @note Byte buffers do not allow multiple retrievals before returning an item
*
* @return
* - Pointer to the retrieved item on success; *pxItemSize filled with
* the length of the item.
* - NULL when the ring buffer is empty, *pxItemSize is untouched in that case.
*/
void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item);
void *xRingbufferReceiveUpToFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize, size_t xMaxSize);
/**
* @brief Return a previously-retrieved item to the ringbuffer from an ISR
* @brief Return a previously-retrieved item to the ring buffer
*
* @param ringbuf Ring buffer the item was retrieved from
* @param item Item that was received earlier
* @param[out] higher_prio_task_awoken Value pointed to will be set to pdTRUE
* if the push woke up a higher priority task.
* @param[in] xRingbuffer Ring buffer the item was retrieved from
* @param[in] pvItem Item that was received earlier
*
* @note If a split item is retrieved, both parts should be returned by calling this function twice
*/
void vRingbufferReturnItemFromISR(RingbufHandle_t ringbuf, void *item, BaseType_t *higher_prio_task_awoken);
void vRingbufferReturnItem(RingbufHandle_t xRingbuffer, void *pvItem);
/**
* @brief Add the ringbuffer to a queue set.
* @brief Return a previously-retrieved item to the ring buffer from an ISR
*
* This specifically adds the semaphore that indicates more space
* has become available in the ringbuffer.
* @param[in] xRingbuffer Ring buffer the item was retrieved from
* @param[in] pvItem Item that was received earlier
* @param[out] pxHigherPriorityTaskWoken Value pointed to will be set to pdTRUE
* if the function woke up a higher priority task.
*
* @param ringbuf Ring buffer to add to the queue set
* @param xQueueSet Queue set to add the ringbuffer to
* @note If a split item is retrieved, both parts should be returned by calling this function twice
*/
void vRingbufferReturnItemFromISR(RingbufHandle_t xRingbuffer, void *pvItem, BaseType_t *pxHigherPriorityTaskWoken);
/**
* @brief Delete a ring buffer
*
* @param[in] xRingbuffer Ring buffer to delete
*/
void vRingbufferDelete(RingbufHandle_t xRingbuffer);
/**
* @brief Get maximum size of an item that can be placed in the ring buffer
*
* This function returns the maximum size an item can have if it was placed in
* an empty ring buffer.
*
* @param[in] xRingbuffer Ring buffer to query
*
* @return Maximum size, in bytes, of an item that can be placed in a ring buffer.
*/
size_t xRingbufferGetMaxItemSize(RingbufHandle_t xRingbuffer);
/**
* @brief Get current free size available for an item/data in the buffer
*
* This gives the real time free space available for an item/data in the ring
* buffer. This represents the maximum size an item/data can have if it was
* currently sent to the ring buffer.
*
* @warning This API is not thread safe. So, if multiple threads are accessing
* the same ring buffer, it is the application's responsibility to
* ensure atomic access to this API and the subsequent Send
*
* @param[in] xRingbuffer Ring buffer to query
*
* @return Current free size, in bytes, available for an entry
*/
size_t xRingbufferGetCurFreeSize(RingbufHandle_t xRingbuffer);
/**
* @brief Add the ring buffer's read semaphore to a queue set.
*
* The ring buffer's read semaphore indicates that data has been written
* to the ring buffer. This function adds the ring buffer's read semaphore to
* a queue set.
*
* @param[in] xRingbuffer Ring buffer to add to the queue set
* @param[in] xQueueSet Queue set to add the ring buffer's read semaphore to
*
* @return
* - pdTRUE on success, pdFALSE otherwise
*/
BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet);
/**
* @brief Add the ringbuffer to a queue set.
* @brief Check if the selected queue set member is the ring buffer's read semaphore
*
* This specifically adds the semaphore that indicates something has been
* written into the ringbuffer.
*
* @param ringbuf Ring buffer to add to the queue set
* @param xQueueSet Queue set to add the ringbuffer to
*
* @return pdTRUE on success, pdFALSE otherwise
*/
BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
/**
* @brief Check if the selected queue set member is the ringbuffer's read semaphore
*
* This API checks if queue set member returned from xQueueSelectFromSet
* This API checks if queue set member returned from xQueueSelectFromSet()
* is the read semaphore of this ring buffer. If so, this indicates the ring buffer
* has items waiting to be read.
* has items waiting to be retrieved.
*
* @param ringbuf Ring buffer which should be checked
* @param member Member returned from xQueueSelectFromSet
* @param[in] xRingbuffer Ring buffer which should be checked
* @param[in] xMember Member returned from xQueueSelectFromSet
*
* @return pdTRUE when semaphore belongs to ringbuffer, pdFALSE otherwise.
* @return
* - pdTRUE when semaphore belongs to ring buffer
* - pdFALSE otherwise.
*/
BaseType_t xRingbufferCanRead(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member);
BaseType_t xRingbufferCanRead(RingbufHandle_t xRingbuffer, QueueSetMemberHandle_t xMember);
/**
* @brief Check if the selected queue set member is the ringbuffer's write semaphore
* @brief Remove the ring buffer's read semaphore from a queue set.
*
* This API checks if queue set member returned from xQueueSelectFromSet
* is the write semaphore of this ring buffer. If so, this indicates the ring buffer
* has items waiting for write.
* This specifically removes a ring buffer's read semaphore from a queue set. The
* read semaphore is used to indicate when data has been written to the ring buffer
*
* @param ringbuf Ring buffer which should be checked
* @param member Member returned from xQueueSelectFromSet
* @param[in] xRingbuffer Ring buffer to remove from the queue set
* @param[in] xQueueSet Queue set to remove the ring buffer's read semaphore from
*
* @return pdTRUE when semaphore belongs to ringbuffer, pdFALSE otherwise.
* @return
* - pdTRUE on success
* - pdFALSE otherwise
*/
BaseType_t xRingbufferCanWrite(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member);
BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet);
/**
* @brief Remove the ringbuffer from a queue set.
* @brief Get information about ring buffer status
*
* This specifically removes the semaphore that indicates more space
* has become available in the ringbuffer.
* Get information of the a ring buffer's current status such as
* free/read/write pointer positions, and number of items waiting to be retrieved.
* Arguments can be set to NULL if they are not required.
*
* @param ringbuf Ring buffer to remove from the queue set
* @param xQueueSet Queue set to remove the ringbuffer from
*
* @return pdTRUE on success, pdFALSE otherwise
* @param[in] xRingbuffer Ring buffer to remove from the queue set
* @param[out] uxFree Pointer use to store free pointer position
* @param[out] uxRead Pointer use to store read pointer position
* @param[out] uxWrite Pointer use to store write pointer position
* @param[out] uxItemsWaiting Pointer use to store number of items (bytes for byte buffer) waiting to be retrieved
*/
BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
void vRingbufferGetInfo(RingbufHandle_t xRingbuffer, UBaseType_t *uxFree, UBaseType_t *uxRead, UBaseType_t *uxWrite, UBaseType_t *uxItemsWaiting);
/**
* @brief Remove the ringbuffer from a queue set.
* @brief Debugging function to print the internal pointers in the ring buffer
*
* This specifically removes the semaphore that indicates something
* has been written to the ringbuffer.
*
* @param ringbuf Ring buffer to remove from the queue set
* @param xQueueSet Queue set to remove the ringbuffer from
*
* @return pdTRUE on success, pdFALSE otherwise
* @param xRingbuffer Ring buffer to show
*/
BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
void xRingbufferPrintInfo(RingbufHandle_t xRingbuffer);
/* -------------------------------- Deprecated Functions --------------------------- */
/**
* @brief Debugging function to print the internal pointers in the ring buffer
*
* @param ringbuf Ring buffer to show
/** @cond */ //Doxygen command to hide deprecated function from API Reference
/*
* Deprecated as function is not thread safe and does not check if an item is
* actually available for retrieval. Use xRingbufferReceiveSplit() instead for
* thread safe method of retrieve a split item.
*/
void xRingbufferPrintInfo(RingbufHandle_t ringbuf);
bool xRingbufferIsNextItemWrapped(RingbufHandle_t xRingbuffer) __attribute__((deprecated));
/*
* Deprecated as queue sets are not meant to be used for writing to buffers. Adding
* the ring buffer write semaphore to a queue set will break queue set usage rules,
* as every read of a semaphore must be preceded by a call to xQueueSelectFromSet().
* QueueSetWrite no longer supported.
*/
BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) __attribute__((deprecated));
/*
* Deprecated as queue sets are not meant to be used for writing to buffers.
* QueueSetWrite no longer supported.
*/
BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) __attribute__((deprecated));
/** @endcond */
#ifdef __cplusplus
}

File diff suppressed because it is too large Load Diff

View File

@ -1,227 +1,606 @@
/*
Test for multicore FreeRTOS ringbuffer.
*/
#include <string.h>
#include <stdio.h>
#include "rom/ets_sys.h"
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/ringbuf.h"
#include "freertos/xtensa_api.h"
#include "driver/timer.h"
#include "unity.h"
#include "soc/uart_reg.h"
#include "soc/dport_reg.h"
#include "soc/io_mux_reg.h"
#include "esp_intr_alloc.h"
//Definitions used in multiple test cases
#define TIMEOUT_TICKS 10
#define NO_OF_RB_TYPES 3
#define ITEM_HDR_SIZE 8
#define SMALL_ITEM_SIZE 8
#define LARGE_ITEM_SIZE (2 * SMALL_ITEM_SIZE)
#define BUFFER_SIZE 160 //4Byte aligned size
static RingbufHandle_t rb;
typedef enum {
TST_MOSTLYFILLED,
TST_MOSTLYEMPTY,
TST_INTTOTASK,
TST_TASKTOINT,
} testtype_t;
static const uint8_t small_item[SMALL_ITEM_SIZE] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
static const uint8_t large_item[LARGE_ITEM_SIZE] = { 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
static RingbufHandle_t buffer_handles[NO_OF_RB_TYPES];
static SemaphoreHandle_t done_sem;
static volatile testtype_t testtype;
intr_handle_t s_intr_handle;
static void task1(void *arg)
static void send_item_and_check(RingbufHandle_t handle, const uint8_t *item, size_t item_size, TickType_t ticks_to_wait, bool in_isr)
{
testtype_t oldtest;
char buf[100];
int i = 0;
int x, r;
while (1) {
oldtest = testtype;
if (testtype == TST_MOSTLYFILLED || testtype == TST_MOSTLYEMPTY) {
for (x = 0; x < 10; x++) {
sprintf(buf, "This is test %d item %d.", (int)testtype, i++);
ets_printf("TSK w");
xRingbufferPrintInfo(rb);
r = xRingbufferSend(rb, buf, strlen(buf) + 1, 2000 / portTICK_PERIOD_MS);
if (!r) {
printf("Test %d: Timeout on send!\n", (int)testtype);
}
if (testtype == TST_MOSTLYEMPTY) {
vTaskDelay(300 / portTICK_PERIOD_MS);
}
}
//Send NULL event to stop other side.
r = xRingbufferSend(rb, NULL, 0, 10000 / portTICK_PERIOD_MS);
}
while (oldtest == testtype) {
vTaskDelay(300 / portTICK_PERIOD_MS);
}
}
}
static void task2(void *arg)
{
testtype_t oldtest;
char *buf;
size_t len;
while (1) {
oldtest = testtype;
if (testtype == TST_MOSTLYFILLED || testtype == TST_MOSTLYEMPTY) {
while (1) {
ets_printf("TSK r");
xRingbufferPrintInfo(rb);
buf = xRingbufferReceive(rb, &len, 2000 / portTICK_PERIOD_MS);
if (buf == NULL) {
printf("Test %d: Timeout on recv!\n", (int)testtype);
} else if (len == 0) {
printf("End packet received.\n");
vRingbufferReturnItem(rb, buf);
break;
} else {
printf("Received: %s (%d bytes, %p)\n", buf, len, buf);
vRingbufferReturnItem(rb, buf);
}
if (testtype == TST_MOSTLYFILLED) {
vTaskDelay(300 / portTICK_PERIOD_MS);
}
}
}
while (oldtest == testtype) {
vTaskDelay(300 / portTICK_PERIOD_MS);
}
}
}
static void uartIsrHdl(void *arg)
{
char c;
char buf[50];
char *item;
int r;
size_t len;
BaseType_t xHigherPriorityTaskWoken;
SET_PERI_REG_MASK(UART_INT_CLR_REG(0), UART_RXFIFO_FULL_INT_CLR);
while (READ_PERI_REG(UART_STATUS_REG(0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) {
c = READ_PERI_REG(UART_FIFO_REG(0));
if (c == 'r') {
ets_printf("ISR r");
xRingbufferPrintInfo(rb);
item = xRingbufferReceiveFromISR(rb, &len);
if (item == NULL) {
ets_printf("ISR recv fail!\n");
} else if (len == 0) {
ets_printf("ISR recv NULL!\n");
vRingbufferReturnItemFromISR(rb, item, &xHigherPriorityTaskWoken);
} else {
ets_printf("ISR recv '%s' (%d bytes, %p)\n", buf, len, buf);
vRingbufferReturnItemFromISR(rb, item, &xHigherPriorityTaskWoken);
}
} else {
sprintf(buf, "UART: %c", c);
ets_printf("ISR w");
xRingbufferPrintInfo(rb);
r = xRingbufferSendFromISR(rb, buf, strlen(buf) + 1, &xHigherPriorityTaskWoken);
if (!r) {
ets_printf("ISR send fail\n");
}
}
}
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
static void uartRxInit()
{
WRITE_PERI_REG(UART_CONF1_REG(0), 1 << UART_RXFIFO_FULL_THRHD_S);
CLEAR_PERI_REG_MASK(UART_INT_ENA_REG(0), UART_TXFIFO_EMPTY_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
SET_PERI_REG_MASK(UART_INT_ENA_REG(0), UART_RXFIFO_FULL_INT_ENA);
ESP_ERROR_CHECK(esp_intr_alloc(ETS_UART0_INTR_SOURCE, 0, &uartIsrHdl, NULL, &s_intr_handle));
}
static void uartRxDeinit()
{
esp_intr_free(s_intr_handle);
}
static void testRingbuffer(int type, bool arbitrary)
{
TaskHandle_t th[2];
int i;
/* Arbitrary Length means buffer length which is not a multiple of 4 */
if (arbitrary) {
rb = xRingbufferCreate(31 * 3, type);
BaseType_t ret;
if (in_isr) {
ret = xRingbufferSendFromISR(handle, (void *)item, item_size, NULL);
} else {
rb = xRingbufferCreate(32 * 3, type);
ret = xRingbufferSend(handle, (void *)item, item_size, ticks_to_wait);
}
TEST_ASSERT_MESSAGE(ret == pdTRUE, "Failed to send item");
}
static void receive_check_and_return_item_no_split(RingbufHandle_t handle, const uint8_t *expected_data, size_t expected_size, TickType_t ticks_to_wait, bool in_isr)
{
//Receive item from no-split buffer
size_t item_size;
uint8_t *item;
if (in_isr) {
item = (uint8_t *)xRingbufferReceiveFromISR(handle, &item_size);
} else {
item = (uint8_t *)xRingbufferReceive(handle, &item_size, ticks_to_wait);
}
TEST_ASSERT_MESSAGE(item != NULL, "Failed to receive item");
TEST_ASSERT_MESSAGE(item_size == expected_size, "Item size is incorrect");
//Check data of received item
for (int i = 0; i < item_size; i++) {
TEST_ASSERT_MESSAGE(item[i] == expected_data[i], "Item data is invalid");
}
//Return item
if (in_isr) {
vRingbufferReturnItemFromISR(handle, (void *)item, NULL);
} else {
vRingbufferReturnItem(handle, (void *)item);
}
testtype = TST_MOSTLYFILLED;
}
xTaskCreatePinnedToCore(task1, "tskone", 2048, NULL, 3, &th[0], 0);
xTaskCreatePinnedToCore(task2, "tsktwo", 2048, NULL, 3, &th[1], 0);
uartRxInit();
printf("Press 'r' to read an event in isr, any other key to write one.\n");
printf("Test: mostlyfilled; putting 10 items in ringbuff ASAP, reading 1 a second\n");
vTaskDelay(5000 / portTICK_PERIOD_MS);
printf("Test: mostlyempty; putting 10 items in ringbuff @ 1/sec, reading as fast as possible\n");
testtype = TST_MOSTLYEMPTY;
vTaskDelay(5000 / portTICK_PERIOD_MS);
//Shut down all the tasks
for (i = 0; i < 2; i++) {
vTaskDelete(th[i]);
static void receive_check_and_return_item_allow_split(RingbufHandle_t handle, const uint8_t *expected_data, size_t expected_size, TickType_t ticks_to_wait, bool in_isr)
{
//Receive item
size_t item_size1, item_size2;
uint8_t *item1, *item2;
BaseType_t ret;
if (in_isr) {
ret = xRingbufferReceiveSplitFromISR(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2);
} else {
ret = xRingbufferReceiveSplit(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2, ticks_to_wait);
}
vRingbufferDelete(rb);
uartRxDeinit();
}
//= xRingbufferReceiveSplit(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2, ticks_to_wait);
TEST_ASSERT_MESSAGE(ret == pdTRUE, "Failed to receive item");
TEST_ASSERT_MESSAGE(item1 != NULL, "Failed to receive item");
// TODO: split this thing into separate orthogonal tests
TEST_CASE("FreeRTOS ringbuffer test, no splitting items", "[freertos]")
{
testRingbuffer(0, false);
}
TEST_CASE("FreeRTOS ringbuffer test, w/ splitting items", "[freertos]")
{
testRingbuffer(1, false);
}
TEST_CASE("FreeRTOS ringbuffer test, no splitting items, arbitrary length buffer", "[freertos]")
{
testRingbuffer(0, true);
}
TEST_CASE("FreeRTOS ringbuffer test, w/ splitting items, arbitrary length buffer", "[freertos]")
{
testRingbuffer(1, true);
}
TEST_CASE("FreeRTOS ringbuffer test, check if zero-length items are handled correctly", "[freertos]")
{
rb = xRingbufferCreate(32, 0);
int r;
void *v;
size_t sz;
for (int x=0; x<128; x++) {
if (x!=127) {
//Send an item
r = xRingbufferSend(rb, NULL, 0, 10000 / portTICK_PERIOD_MS);
assert(r==pdTRUE);
//Check data of received item(s) and return them
if (item2 == NULL) {
TEST_ASSERT_MESSAGE(item_size1 == expected_size, "Item size is incorrect");
for (int i = 0; i < item_size1; i++) {
TEST_ASSERT_MESSAGE(item1[i] == expected_data[i], "Item data is invalid");
}
if (x!=0) {
//Receive an item
v=xRingbufferReceive(rb, &sz, 10000 / portTICK_PERIOD_MS);
assert(sz==0);
vRingbufferReturnItem(rb, v); //actually not needed for NULL data...
//Return item
if (in_isr) {
vRingbufferReturnItemFromISR(handle, (void *)item1, NULL);
} else {
vRingbufferReturnItem(handle, (void *)item1);
}
} else {
//Item was split
TEST_ASSERT_MESSAGE(item_size1 + item_size2 == expected_size, "Total item size is incorrect");
for (int i = 0; i < item_size1; i++) {
TEST_ASSERT_MESSAGE(item1[i] == expected_data[i], "Head item data is invalid");
}
for (int i = 0; i < item_size2; i++) {
TEST_ASSERT_MESSAGE(item2[i] == expected_data[item_size1 + i], "Head item data is invalid");
}
//Return Items
if (in_isr) {
vRingbufferReturnItemFromISR(handle, (void *)item1, NULL);
vRingbufferReturnItemFromISR(handle, (void *)item2, NULL);
} else {
vRingbufferReturnItem(handle, (void *)item1);
vRingbufferReturnItem(handle, (void *)item2);
}
}
vRingbufferDelete(rb);
}
static void receive_check_and_return_item_byte_buffer(RingbufHandle_t handle, const uint8_t *expected_data, size_t expected_size, TickType_t ticks_to_wait, bool in_isr)
{
//Receive item
size_t item_size;
uint8_t *item;
if (in_isr) {
item = (uint8_t *)xRingbufferReceiveUpToFromISR(handle, &item_size, expected_size);
} else {
item = (uint8_t *)xRingbufferReceiveUpTo(handle, &item_size, ticks_to_wait, expected_size); //Limit amount of bytes returned to the size of one item
}
TEST_ASSERT_MESSAGE(item != NULL, "Failed to receive item");
//Check data of received item
for (int i = 0; i < item_size; i++) {
TEST_ASSERT_MESSAGE(item[i] == expected_data[i], "Item data is invalid");
}
//Return item
if (in_isr) {
vRingbufferReturnItemFromISR(handle, (void *)item, NULL);
} else {
vRingbufferReturnItem(handle, (void *)item);
}
//Check if item wrapped around
if (item_size < expected_size) {
//Item is wrapped, receive second portion
size_t item_size2;
uint8_t *item2;
if (in_isr) {
item2 = (uint8_t *)xRingbufferReceiveUpToFromISR(handle, &item_size2, expected_size - item_size);
} else {
item2 = (uint8_t *)xRingbufferReceiveUpTo(handle, &item_size2, ticks_to_wait, expected_size - item_size);
}
//= (uint8_t *)xRingbufferReceiveUpTo(handle, &item_size2, ticks_to_wait, expected_size - item_size);
TEST_ASSERT_MESSAGE(item2 != NULL, "Failed to receive item");
TEST_ASSERT_MESSAGE(item_size + item_size2 == expected_size, "Total item size is incorrect");
for (int i = 0; i < item_size2; i++) {
TEST_ASSERT_MESSAGE(item2[i] == expected_data[item_size + i], "Item data is invalid");
}
if (in_isr) {
vRingbufferReturnItemFromISR(handle, (void *)item2, NULL);
} else {
vRingbufferReturnItem(handle, (void *)item2);
}
} else {
TEST_ASSERT_MESSAGE(item_size == expected_size, "Item size is incorrect");
}
}
/* ----------------- Basic ring buffer behavior tests cases --------------------
* The following test cases will test basic send, receive, and wrap around
* behavior of each type of ring buffer. Each test case will do the following
* 1) Send multiple items (nearly fill the buffer)
* 2) Receive and check the sent items (also prepares the buffer for a wrap around
* 3) Send a final item that causes a wrap around
* 4) Receive and check the wrapped item
*/
TEST_CASE("Test ring buffer No-Split", "[freertos]")
{
//Create buffer
RingbufHandle_t buffer_handle = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_NOSPLIT);
TEST_ASSERT_MESSAGE(buffer_handle != NULL, "Failed to create ring buffer");
//Calculate number of items to send. Aim to almost fill buffer to setup for wrap around
int no_of_items = (BUFFER_SIZE - (ITEM_HDR_SIZE + SMALL_ITEM_SIZE)) / (ITEM_HDR_SIZE + SMALL_ITEM_SIZE);
//Test sending items
for (int i = 0; i < no_of_items; i++) {
send_item_and_check(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false);
}
//Test receiving items
for (int i = 0; i < no_of_items; i++) {
receive_check_and_return_item_no_split(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false);
}
//Write pointer should be near the end, test wrap around
uint32_t write_pos_before, write_pos_after;
vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_before, NULL);
//Send large item that causes wrap around
send_item_and_check(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false);
//Receive wrapped item
receive_check_and_return_item_no_split(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false);
vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_after, NULL);
TEST_ASSERT_MESSAGE(write_pos_after < write_pos_before, "Failed to wrap around");
//Cleanup
vRingbufferDelete(buffer_handle);
}
TEST_CASE("Test ring buffer Allow-Split", "[freertos]")
{
//Create buffer
RingbufHandle_t buffer_handle = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_ALLOWSPLIT);
TEST_ASSERT_MESSAGE(buffer_handle != NULL, "Failed to create ring buffer");
//Calculate number of items to send. Aim to almost fill buffer to setup for wrap around
int no_of_items = (BUFFER_SIZE - (ITEM_HDR_SIZE + SMALL_ITEM_SIZE)) / (ITEM_HDR_SIZE + SMALL_ITEM_SIZE);
//Test sending items
for (int i = 0; i < no_of_items; i++) {
send_item_and_check(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false);
}
//Test receiving items
for (int i = 0; i < no_of_items; i++) {
receive_check_and_return_item_allow_split(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false);
}
//Write pointer should be near the end, test wrap around
uint32_t write_pos_before, write_pos_after;
vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_before, NULL);
//Send large item that causes wrap around
send_item_and_check(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false);
//Receive wrapped item
receive_check_and_return_item_allow_split(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false);
vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_after, NULL);
TEST_ASSERT_MESSAGE(write_pos_after < write_pos_before, "Failed to wrap around");
//Cleanup
vRingbufferDelete(buffer_handle);
}
TEST_CASE("Test ring buffer Byte Buffer", "[freertos]")
{
//Create buffer
RingbufHandle_t buffer_handle = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_BYTEBUF);
TEST_ASSERT_MESSAGE(buffer_handle != NULL, "Failed to create ring buffer");
//Calculate number of items to send. Aim to almost fill buffer to setup for wrap around
int no_of_items = (BUFFER_SIZE - SMALL_ITEM_SIZE) / SMALL_ITEM_SIZE;
//Test sending items
for (int i = 0; i < no_of_items; i++) {
send_item_and_check(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false);
}
//Test receiving items
for (int i = 0; i < no_of_items; i++) {
receive_check_and_return_item_byte_buffer(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false);
}
//Write pointer should be near the end, test wrap around
uint32_t write_pos_before, write_pos_after;
vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_before, NULL);
//Send large item that causes wrap around
send_item_and_check(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false);
//Receive wrapped item
receive_check_and_return_item_byte_buffer(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false);
vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_after, NULL);
TEST_ASSERT_MESSAGE(write_pos_after < write_pos_before, "Failed to wrap around");
//Cleanup
vRingbufferDelete(buffer_handle);
}
/* ----------------------- Ring buffer queue sets test ------------------------
* The following test case will test receiving from ring buffers that have been
* added to a queue set. The test case will do the following...
* 1) Ring buffer of each type is created and added to the queue set
* 2) A receiving task is created to select from the queue set and read from the appropriate ring buffer
*/
static void queue_set_receiving_task(void *queue_set_handle)
{
QueueSetHandle_t queue_set = (QueueSetHandle_t)queue_set_handle;
//Receive multiple items via queue set
BaseType_t done = pdFALSE;
int no_of_items = BUFFER_SIZE / SMALL_ITEM_SIZE;
int items_rec_count[NO_OF_RB_TYPES] = {0};
while (done != pdTRUE) {
xQueueSetMemberHandle member = xQueueSelectFromSet(queue_set, TIMEOUT_TICKS);
//Read from selected ring buffer
if (xRingbufferCanRead(buffer_handles[0], member) == pdTRUE) {
//No-split buffer
receive_check_and_return_item_no_split(buffer_handles[0], small_item, SMALL_ITEM_SIZE, 0, false);
items_rec_count[0] ++;
} else if (xRingbufferCanRead(buffer_handles[1], member) == pdTRUE) {
//Allow-split buffer
receive_check_and_return_item_allow_split(buffer_handles[1], small_item, SMALL_ITEM_SIZE, 0, false);
items_rec_count[1] ++;
} else if (xRingbufferCanRead(buffer_handles[2], member) == pdTRUE){
//Byte buffer
receive_check_and_return_item_byte_buffer(buffer_handles[2], small_item, SMALL_ITEM_SIZE, 0, false);
items_rec_count[2] ++;
} else {
TEST_ASSERT_MESSAGE( false, "Error with queue set member");
}
//Check for completion
if (items_rec_count[0] == no_of_items &&
items_rec_count[1] == no_of_items &&
items_rec_count[2] == no_of_items) {
done = pdTRUE;
}
}
xSemaphoreGive(done_sem);
vTaskDelete(NULL);
}
TEST_CASE("Test ring buffer with queue sets", "[freertos]")
{
QueueSetHandle_t queue_set = xQueueCreateSet(NO_OF_RB_TYPES);
done_sem = xSemaphoreCreateBinary();
//Create ring buffer of each type, then add them to a queue set
for (int i = 0; i < NO_OF_RB_TYPES; i++) {
buffer_handles[i] = xRingbufferCreate(BUFFER_SIZE, i);
TEST_ASSERT_MESSAGE(buffer_handles[i] != NULL, "Failed to create ring buffer");
TEST_ASSERT_MESSAGE(xRingbufferAddToQueueSetRead(buffer_handles[i], queue_set) == pdPASS, "Failed to add to read queue set");
}
//Create a task to send items to each ring buffer
int no_of_items = BUFFER_SIZE / SMALL_ITEM_SIZE;
xTaskCreatePinnedToCore(queue_set_receiving_task, "rec tsk", 2048, (void *)queue_set, UNITY_FREERTOS_PRIORITY + 1 , NULL, 0);
//Send multiple items to each type of ring buffer
for (int i = 0; i < no_of_items; i++) {
for (int j = 0; j < NO_OF_RB_TYPES; j++) {
send_item_and_check(buffer_handles[j], small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false);
}
}
xSemaphoreTake(done_sem, portMAX_DELAY);
vSemaphoreDelete(done_sem);
//Remove and delete ring buffers from queue sets
for (int i = 0; i < NO_OF_RB_TYPES; i++) {
TEST_ASSERT_MESSAGE(xRingbufferRemoveFromQueueSetRead(buffer_handles[i], queue_set) == pdTRUE, "Failed to remove from read queue set");
vRingbufferDelete(buffer_handles[i]);
}
vQueueDelete(queue_set);
}
/* -------------------------- Test ring buffer ISR -----------------------------
* The following test case tests ring buffer ISR API. A timer is used to trigger
* the ISR. The test case will do the following
* 1) ISR will be triggered periodically by timer
* 2) The ISR will iterate through all ring buffer types where each iteration
* will send then receive an item to a ring buffer.
*/
#define TIMER_GROUP 0
#define TIMER_NUMBER 0
#define ISR_ITERATIONS ((BUFFER_SIZE / SMALL_ITEM_SIZE) * 2)
intr_handle_t ringbuffer_isr_handle;
static int buf_type;
static int iterations;
static void ringbuffer_isr(void *arg)
{
//Clear timer interrupt
TIMERG0.int_clr_timers.t0 = 1;
TIMERG0.hw_timer[xPortGetCoreID()].config.alarm_en = 1;
//Test sending to buffer from ISR from ISR
if (buf_type < NO_OF_RB_TYPES) {
send_item_and_check(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true);
}
//Receive item from ISR
if (buf_type == RINGBUF_TYPE_NOSPLIT) {
//Test receive from ISR for no-split buffer
receive_check_and_return_item_no_split(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true);
buf_type++;
} else if (buf_type == RINGBUF_TYPE_ALLOWSPLIT) {
//Test send from ISR to allow-split buffer
receive_check_and_return_item_allow_split(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true);
buf_type++;
} else if (buf_type == RINGBUF_TYPE_BYTEBUF) {
//Test receive from ISR for byte buffer
receive_check_and_return_item_byte_buffer(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true);
buf_type++;
} else if (buf_type == NO_OF_RB_TYPES) {
//Check if all iterations complete
if (iterations < ISR_ITERATIONS) {
iterations++;
buf_type = 0; //Reset and iterate through each buffer type again
return;
} else {
//Signal complete
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(done_sem, &task_woken);
if (task_woken == pdTRUE) {
buf_type++;
portYIELD_FROM_ISR();
}
}
}
}
static void setup_timer()
{
//Setup timer for ISR
int timer_group = TIMER_GROUP;
int timer_idx = TIMER_NUMBER;
timer_config_t config;
config.alarm_en = 1;
config.auto_reload = 1;
config.counter_dir = TIMER_COUNT_UP;
config.divider = 10000;
config.intr_type = TIMER_INTR_LEVEL;
config.counter_en = TIMER_PAUSE;
timer_init(timer_group, timer_idx, &config); //Configure timer
timer_pause(timer_group, timer_idx); //Stop timer counter
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL); //Load counter value
timer_set_alarm_value(timer_group, timer_idx, 20); //Set alarm value
timer_enable_intr(timer_group, timer_idx); //Enable timer interrupt
timer_set_auto_reload(timer_group, timer_idx, 1); //Auto Reload
timer_isr_register(timer_group, timer_idx, ringbuffer_isr, NULL, 0, &ringbuffer_isr_handle); //Set ISR handler
}
static void cleanup_timer()
{
timer_disable_intr(TIMER_GROUP, TIMER_NUMBER);
esp_intr_free(ringbuffer_isr_handle);
}
TEST_CASE("Test ring buffer ISR", "[freertos]")
{
for (int i = 0; i < NO_OF_RB_TYPES; i++) {
buffer_handles[i] = xRingbufferCreate(BUFFER_SIZE, i);
}
done_sem = xSemaphoreCreateBinary();
buf_type = 0;
iterations = 0;
setup_timer();
//Start timer to trigger ISR
timer_start(TIMER_GROUP, TIMER_NUMBER);
//Wait for ISR to complete multiple iterations
xSemaphoreTake(done_sem, portMAX_DELAY);
//Cleanup
cleanup_timer();
vSemaphoreDelete(done_sem);
for (int i = 0; i < NO_OF_RB_TYPES; i++) {
vRingbufferDelete(buffer_handles[i]);
}
}
/* ---------------------------- Test ring buffer SMP ---------------------------
* The following test case tests each type of ring buffer in an SMP fashion. A
* sending task and a receiving task is created. The sending task will split
* a continuous piece of data into items of random length and send it to a ring
* buffer. The receiving task will receive and check those items.
* Every permutation of core pinning of the sending and receiving task will be
* tested.
*/
#define SRAND_SEED 3 //Arbitrarily chosen srand() seed
#define SMP_TEST_ITERATIONS 4
static const char continuous_data[] = {"A_very_long_string_that_will_be_split_into_"
"items_of_random_lengths_and_sent_to_the_ring_"
"buffer._The_maximum_random_length_will_also_"
"be_increased_over_multiple_iterations_in_this"
"_test"};
#define CONT_DATA_LEN sizeof(continuous_data)
#define CONT_DATA_TEST_BUFF_LEN (CONT_DATA_LEN/2) //This will guarantee that the buffer must do a wrap around at some point
typedef struct {
RingbufHandle_t buffer;
ringbuf_type_t type;
} task_args_t;
static SemaphoreHandle_t tasks_done;
static SemaphoreHandle_t tx_done;
static SemaphoreHandle_t rx_done;
static void send_to_buffer(RingbufHandle_t buffer, size_t max_item_size)
{
for (int iter = 0; iter < SMP_TEST_ITERATIONS; iter++) {
size_t bytes_sent = 0; //Number of data bytes sent in this iteration
size_t next_item_size; //Size of next item to send
while (bytes_sent < CONT_DATA_LEN) {
//Get size of next item
next_item_size = rand() % (max_item_size + 1);
if (next_item_size + bytes_sent > CONT_DATA_LEN) {
next_item_size = CONT_DATA_LEN - bytes_sent;
}
//Send item
TEST_ASSERT_MESSAGE(xRingbufferSend(buffer, (void *)&(continuous_data[bytes_sent]), next_item_size, TIMEOUT_TICKS) == pdTRUE, "Failed to send an item");
bytes_sent += next_item_size;
}
xSemaphoreGive(tx_done);
xSemaphoreTake(rx_done, portMAX_DELAY);
}
}
static void read_from_buffer(RingbufHandle_t buffer, ringbuf_type_t buf_type, size_t max_rec_size)
{
for (int iter = 0; iter < SMP_TEST_ITERATIONS; iter++) {
size_t bytes_rec = 0; //Number of data bytes received in this iteration
while (bytes_rec < CONT_DATA_LEN) {
size_t item_size, item_size2; //Possible for allow split buffers to receive two items
char *item_data, *item_data2;
//Select appropriate receive function for type of ring buffer
if (buf_type == RINGBUF_TYPE_NOSPLIT) {
item_data = (char *)xRingbufferReceive(buffer, &item_size, TIMEOUT_TICKS);
} else if (buf_type == RINGBUF_TYPE_ALLOWSPLIT) {
BaseType_t ret = xRingbufferReceiveSplit(buffer, (void **)&item_data, (void **)&item_data2, &item_size, &item_size2, TIMEOUT_TICKS);
TEST_ASSERT_MESSAGE(ret == pdTRUE, "Failed to receive any item");
} else {
item_data = (char *)xRingbufferReceiveUpTo(buffer, &item_size, TIMEOUT_TICKS, max_rec_size);
}
//Check received item and return it
TEST_ASSERT_MESSAGE(item_data != NULL, "Failed to receive an item");
if (buf_type == RINGBUF_TYPE_BYTEBUF) {
TEST_ASSERT_MESSAGE(item_size <= max_rec_size, "Received data exceeds max size");
}
for (int i = 0; i < item_size; i++) {
//Check item_data is valid
TEST_ASSERT_MESSAGE(item_data[i] == continuous_data[bytes_rec + i], "Received data is corrupted");
}
bytes_rec += item_size;
vRingbufferReturnItem(buffer, item_data);
if (buf_type == RINGBUF_TYPE_ALLOWSPLIT && item_data2 != NULL) {
//Check item_data2 is valid
for (int i = 0; i < item_size2; i++) {
TEST_ASSERT_MESSAGE(item_data2[i] == continuous_data[bytes_rec + i], "Received split data is corrupted");
}
bytes_rec += item_size2;
vRingbufferReturnItem(buffer, item_data2);
}
}
TEST_ASSERT_MESSAGE(bytes_rec == CONT_DATA_LEN, "Total length of received data is incorrect");
xSemaphoreGive(rx_done);
xSemaphoreTake(tx_done, portMAX_DELAY);
}
}
static void send_task(void *args)
{
RingbufHandle_t buffer = ((task_args_t *)args)->buffer;
size_t max_item_len = xRingbufferGetMaxItemSize(buffer);
//Test sending short length items
send_to_buffer(buffer, 1);
//Test sending mid length items
send_to_buffer(buffer, max_item_len/2);
//Test sending long length items
send_to_buffer(buffer, max_item_len);
vTaskDelete(NULL);
}
static void rec_task(void *args)
{
RingbufHandle_t buffer = ((task_args_t *)args)->buffer;
size_t max_rec_len = xRingbufferGetMaxItemSize(buffer);
//Test receiving short length items
read_from_buffer(buffer, ((task_args_t *)args)->type, 1);
//Test receiving mid length items
read_from_buffer(buffer, ((task_args_t *)args)->type, max_rec_len/2);
//Test receiving long length items
read_from_buffer(buffer, ((task_args_t *)args)->type, max_rec_len);
xSemaphoreGive(tasks_done);
vTaskDelete(NULL);
}
TEST_CASE("Test ring buffer SMP", "[freertos]")
{
ets_printf("size of buf %d\n", CONT_DATA_LEN);
tx_done = xSemaphoreCreateBinary(); //Semaphore to indicate send is done for a particular iteration
rx_done = xSemaphoreCreateBinary(); //Semaphore to indicate receive is done for a particular iteration
tasks_done = xSemaphoreCreateBinary(); //Semaphore used to to indicate send and receive tasks completed running
srand(SRAND_SEED); //Seed RNG
//Iterate through buffer types (No split, split, then byte buff)
for (ringbuf_type_t buf_type = 0; buf_type <= RINGBUF_TYPE_BYTEBUF; buf_type++) {
//Create buffer
task_args_t task_args;
task_args.buffer = xRingbufferCreate(CONT_DATA_TEST_BUFF_LEN, buf_type); //Create buffer of selected type
task_args.type = buf_type;
for (int prior_mod = -1; prior_mod < 2; prior_mod++) { //Test different relative priorities
//Test every permutation of core affinity
for (int send_core = 0; send_core < portNUM_PROCESSORS; send_core++) {
for (int rec_core = 0; rec_core < portNUM_PROCESSORS; rec_core ++) {
ets_printf("Type: %d, PM: %d, SC: %d, RC: %d\n", buf_type, prior_mod, send_core, rec_core);
xTaskCreatePinnedToCore(send_task, "send tsk", 2048, (void *)&task_args, 10 + prior_mod, NULL, send_core);
xTaskCreatePinnedToCore(rec_task, "rec tsk", 2048, (void *)&task_args, 10, NULL, rec_core);
xSemaphoreTake(tasks_done, portMAX_DELAY);
vTaskDelay(5); //Allow idle to clean up
}
}
}
//Delete ring buffer
vRingbufferDelete(task_args.buffer);
vTaskDelay(10);
}
//Cleanup
vSemaphoreDelete(tx_done);
vSemaphoreDelete(rx_done);
vSemaphoreDelete(tasks_done);
}

View File

@ -0,0 +1,30 @@
#Diagram demonstrating reading and returning an item in a byte buffer
#Buffer of 128 bytes, with 68 bytes occupied but wrapped. All data is read
packetdiag ring_buffer_read_ret_byte_buf {
node_width = 6
node_height = 24
default_fontsize = 12
colwidth = 128
#Initial
0-29: 30 [color = lightyellow];
30-89: 60 Free
90-127: 38 [color = lightyellow];
#Read all continuous data
128-157: 30 [color = lightyellow];
158-217: 60 Free
218-255: 38 [color = pink];
#Return data
256-285: 30 [color = lightyellow];
286-383: 98 Free
#Read remaining data
384-413: 30 [color = pink];
414-511: 98 Free
#Return data
512-639: 128 Free
}

View File

@ -0,0 +1,86 @@
#Diagram demonstrating reading and returning an item in a No-Split/Allow-Split ring buffer
#Buffer of 128 bytes, with 4 items of 16, 20, 8 and 24 bytes. First 3 items are read and returned
packetdiag ring_buffer_read_ret_non_byte_buf {
node_width = 6
node_height = 24
default_fontsize = 12
colwidth = 128
#Initial
0-7: 8 [color = lightblue];
8-23: 16 [color = lightyellow];
24-31: 8 [color = lightblue];
32-51: 20 [color = lightyellow];
52-59: 8 [color = lightblue];
60-67: 8 [color = lightyellow];
68-75: 8 [color = lightblue];
76-99: 24 [color = lightyellow];
100-127: 28 Free
#Read item 1
128-135: 8 [color = pink];
136-151: 16 [color = pink];
152-159: 8 [color = lightblue];
160-179: 20 [color = lightyellow];
180-187: 8 [color = lightblue];
188-195: 8 [color = lightyellow];
196-203: 8 [color = lightblue];
204-227: 24 [color = lightyellow];
228-255: 28 Free
#Read item 2
256-263: 8 [color = pink];
264-279: 16 [color = pink];
280-287: 8 [color = pink];
288-307: 20 [color = pink];
308-315: 8 [color = lightblue];
316-323: 8 [color = lightyellow];
324-331: 8 [color = lightblue];
332-355: 24 [color = lightyellow];
356-383: 28 Free
#Read item 3
384-391: 8 [color = pink];
392-407: 16 [color = pink];
408-415: 8 [color = pink];
416-435: 20 [color = pink];
436-443: 8 [color = pink];
444-451: 8 [color = pink];
452-459: 8 [color = lightblue];
460-483: 24 [color = lightyellow];
484-511: 28 Free
#Return item 2
512-519: 8 [color = pink];
520-535: 16 [color = pink];
536-563: Ret [color = lightgrey];
564-571: 8 [color = pink];
572-579: 8 [color = pink];
580-587: 8 [color = lightblue];
588-611: 24 [color = lightyellow];
612-639: 28 Free
#Return item 3
640-647: 8 [color = pink];
648-663: 16 [color = pink];
664-691: Ret [color = lightgrey];
692-707: Ret [color = lightgrey];
708-715: 8 [color = lightblue];
716-739: 24 [color = lightyellow];
740-767: 28 Free
#Return item 1
768-791: Ret [color = lightgrey];
792-819: Ret [color = lightgrey];
820-835: Ret [color = lightgrey];
836-843: 8 [color = lightblue];
844-867: 24 [color = lightyellow];
868-895: 28 Free
#End state
896-963: 68 Free
964-971: 8 [color = lightblue];
972-995: 24 [color = lightyellow];
996-1023: 28 Free
}

View File

@ -0,0 +1,21 @@
#Diagram demonstrating sending in byte buffer
#Buffer of 128 bytes, and 3 items of size 18, 3, and 27 bytes sent
packetdiag ring_buffer_send_byte_buf {
node_width = 6
node_height = 24
default_fontsize = 12
colwidth = 128
#Add 18 byte item
0-17: 18 [color = lightyellow];
18-127: 110 Free
#Add 3 byte item
128-148: 21 [color = lightyellow];
149-255: 107 Free
#Add 27 byte item
256-303: 48 [color = lightyellow];
304-383: 80 Free
}

View File

@ -0,0 +1,30 @@
#Diagram demonstrating sending in a No-Split/Allow-Split ring buffer
#Buffer of 128 bytes, and 3 items of size 18, 3, and 27 bytes sent
packetdiag ring_buffer_send_non_byte_buf {
node_width = 6
node_height = 24
default_fontsize = 12
colwidth = 128
#Add 18 byte item
0-7: 8 [color = lightblue];
8-27: 20 [color = lightyellow];
28-127: 100 Free
#Add 3 byte item
128-135: 8 [color = lightblue];
136-155: 20 [color = lightyellow];
156-163: 8 [color = lightblue];
164-167: 4 [color = lightyellow];
168-255: 88 Free
#Add 27 byte item
256-263: 8 [color = lightblue];
264-283: 20 [color = lightyellow];
284-291: 8 [color = lightblue];
292-295: 4 [color = lightyellow];
296-303: 8 [color = lightblue];
304-331: 28 [color = lightyellow];
332-383: 52 Free
}

View File

@ -0,0 +1,37 @@
#Diagram demonstrating wrap around in a Allow-Split ring buffer
#Buffer of 128 bytes, with 56 bytes free, and 28 bytes sent
packetdiag ring_buffer_wrap_allow_split {
node_width = 6
node_height = 24
default_fontsize = 12
colwidth = 128
#Initial state
0-39: 40 Free
40-47: 8 [color = lightblue];
48-63: 16 [color = lightyellow];
64-71: 8 [color = lightblue];
72-111: 40 [color = lightyellow];
112-127: 16 Free
#Send first part
128-167: 40 Free
168-175: 8 [color = lightblue];
176-191: 16 [color = lightyellow];
192-199: 8 [color = lightblue];
200-239: 40 [color = lightyellow];
240-247: 8 [color = lightblue];
248-255: 8 [color = lightyellow];
#Send second part
256-263: 8 [color = lightblue];
264-283: 20 [color = lightyellow];
284-295: 12 Free
296-303: 8 [color = lightblue];
304-319: 16 [color = lightyellow];
320-327: 8 [color = lightblue];
328-367: 40 [color = lightyellow];
368-375: 8 [color = lightblue];
376-383: 8 [color = lightyellow];
}

View File

@ -0,0 +1,23 @@
#Diagram demonstrating wrap around in byte buffer
#Buffer of 128 bytes, with 56 bytes free, and 28 bytes sent
packetdiag ring_buffer_wrap_byte_buf {
node_width = 6
node_height = 24
default_fontsize = 12
colwidth = 128
#Initial state
0-39: 40 Free
40-111: 72 [color = lightyellow];
112-127: 16 Free
#Fill up free space at the end of the buffer
128-167: 40 Free
168-255: 88 [color = lightyellow];
#Wrap around remaining data
256-267: 12 [color = lightyellow];
268-295: 28 Free
296-383: 88 [color = lightyellow];
}

View File

@ -0,0 +1,35 @@
#Diagram demonstrating wrap around in a No-Split ring buffer
#Buffer of 128 bytes, with 56 bytes free, and 28 bytes sent
packetdiag ring_buffer_wrap_no_split {
node_width = 6
node_height = 24
default_fontsize = 12
colwidth = 128
#Initial state
0-39: 40 Free
40-47: 8 [color = lightblue];
48-63: 16 [color = lightyellow];
64-71: 8 [color = lightblue];
72-111: 40 [color = lightyellow];
112-127: 16 Free
#Set dummy data
128-167: 40 Free
168-175: 8 [color = lightblue];
176-191: 16 [color = lightyellow];
192-199: 8 [color = lightblue];
200-239: 40 [color = lightyellow];
240-255: Dummy [color = lightgrey];
#Send wrap around item
256-263: 8 [color = lightblue];
264-291: 28 [color = lightyellow];
292-295: 4 Free
296-303: 8 [color = lightblue];
304-319: 16 [color = lightyellow];
320-327: 8 [color = lightblue];
328-367: 40 [color = lightyellow];
368-383: Dummy [color = lightgrey];
}

View File

@ -16,6 +16,9 @@ of FreeRTOS v8.2.0. This guide outlines the major differences between vanilla
FreeRTOS and ESP-IDF FreeRTOS. The API reference for vanilla FreeRTOS can be
found via http://www.freertos.org/a00106.html
For information regarding features that are exclusive to ESP-IDF FreeRTOS,
see :doc:`ESP-IDF FreeRTOS Additions<../api-reference/system/freertos_additions>`.
:ref:`backported-features`: Although ESP-IDF FreeRTOS is based on the Xtensa
port of FreeRTOS v8.2.0, a number of FreeRTOS v9.0.0 features have been backported
to ESP-IDF.
@ -70,10 +73,6 @@ used to free memory pointed to by TLSP. Call
:cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback()` to set TLSP and Deletion
Callbacks.
:ref:`FreeRTOS Hooks<hooks_api_reference>`: Vanilla FreeRTOS Hooks were not designed for SMP.
ESP-IDF provides its own Idle and Tick Hooks in addition to the Vanilla FreeRTOS
hooks. For full details, see the ESP-IDF Hooks API Reference.
:ref:`esp-idf-freertos-configuration`: Several aspects of ESP-IDF FreeRTOS can be
configured using ``make meunconfig`` such as running ESP-IDF in Unicore Mode,
or configuring the number of Thread Local Storage Pointers each task will have.

View File

@ -6,7 +6,8 @@ Overview
This section contains documentation of FreeRTOS types, functions, and macros. It is automatically generated from FreeRTOS header files.
For more information about FreeRTOS features specific to ESP-IDF, see :doc:`ESP-IDF FreeRTOS SMP Changes<../../api-guides/freertos-smp>`.
For more information about FreeRTOS features specific to ESP-IDF, see :doc:`ESP-IDF FreeRTOS SMP Changes<../../api-guides/freertos-smp>`
and :doc:`ESP-IDF FreeRTOS Additions<freertos_additions>`.
Task API
@ -35,8 +36,4 @@ Event Group API
.. include:: /_build/inc/event_groups.inc
Ringbuffer API
--------------
.. include:: /_build/inc/ringbuf.inc

View File

@ -0,0 +1,403 @@
FreeRTOS Additions
==================
Overview
--------
ESP-IDF FreeRTOS is based on the Xtensa port of FreeRTOS v8.2.0 with significant modifications
for SMP compatibility (see :doc:`ESP-IDF FreeRTOS SMP Changes<../../api-guides/freertos-smp>`).
However various features specific to ESP-IDF FreeRTOS have been added. The features are as follows:
:ref:`ring-buffers`: Ring buffers were added to provide a form of buffer that could accept
entries of arbitrary lengths.
:ref:`hooks`: ESP-IDF FreeRTOS hooks provides support for registering extra Idle and
Tick hooks at run time. Moreover, the hooks can be asymmetric amongst both CPUs.
.. _ring-buffers:
Ring Buffers
------------
The ESP-IDF FreeRTOS ring buffer is a strictly FIFO buffer that supports arbitrarily sized items.
Ring buffers are a more memory efficient alternative to FreeRTOS queues in situations where the
size of items is variable. The capacity of a ring buffer is not measured by the number of items
it can store, but rather by the amount of memory used for storing items. Items are sent to
ring buffers by copy, however for efficiency reasons **items are retrieved by reference**. As a
result, all retrieved items **must also be returned** in order for them to be removed from
the ring buffer completely. The ring buffers are split into the three following types:
**No-Split** buffers will guarantee that an item is stored in contiguous memory and will not
attempt to split an item under any circumstances. Use no-split buffers when items must occupy
contiguous memory.
**Allow-Split** buffers will allow an item to be split when wrapping around if doing so will allow
the item to be stored. Allow-split buffers are more memory efficient than no-split buffers but
can return an item in two parts when retrieving.
**Byte buffers** do not store data as separate items. All data is stored as a sequence of bytes,
and any number of bytes and be sent or retrieved each time. Use byte buffers when separate items
do not need to be maintained (e.g. a byte stream).
.. note::
No-split/allow-split buffers will always store items at 32-bit aligned addresses. Therefore when
retrieving an item, the item pointer is guaranteed to be 32-bit aligned.
.. note::
Each item stored in no-split/allow-split buffers will **require an additional 8 bytes for a header**.
Item sizes will also be rounded up to a 32-bit aligned size (multiple of 4 bytes), however the true
item size is recorded within the header. The sizes of no-split/allow-split buffers will also
be rounded up when created.
Usage
^^^^^
The following example demonstrates the usage of :cpp:func:`xRingbufferCreate`
and :cpp:func:`xRingbufferSend` to create a ring buffer then send an item to it.
.. code-block:: c
#include "freertos/ringbuf.h"
static char tx_item[] = "test_item";
...
//Create ring buffer
RingbufHandle_t buf_handle;
buf_handle = xRingbufferCreate(1028, RINGBUF_TYPE_NOSPLIT);
if (buf_handle == NULL) {
printf("Failed to create ring buffer\n");
}
//Send an item
UBaseType_t res = xRingbufferSend(buf_handle, tx_item, sizeof(tx_item), pdMS_TO_TICKS(1000));
if (res != pdTRUE) {
printf("Failed to send item\n");
}
The following example demonstrates retrieving and returning an item from a **no-split ring buffer**
using :cpp:func:`xRingbufferReceive` and :cpp:func:`vRingbufferReturnItem`
.. code-block:: c
...
//Receive an item from no-split ring buffer
size_t item_size;
char *item = (char *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(1000));
//Check received item
if (item != NULL) {
//Print item
for (int i = 0; i < item_size; i++) {
printf("%c", item[i]);
}
printf("\n");
//Return Item
vRingbufferReturnItem(buf_handle, (void *)item);
} else {
//Failed to receive item
printf("Failed to receive item\n");
}
The following example demonstrates retrieving and returning an item from an **allow-split ring buffer**
using :cpp:func:`xRingbufferReceiveSplit` and :cpp:func:`vRingbufferReturnItem`
.. code-block:: c
...
//Receive an item from allow-split ring buffer
size_t item_size1, item_size2;
char *item1, *item2;
BaseType_t ret = xRingbufferReceiveSplit(buf_handle, (void **)&item1, (void **)&item2, &item_size1, &item_size2, pdMS_TO_TICKS(1000));
//Check received item
if (ret == pdTRUE && item1 != NULL) {
for (int i = 0; i < item_size1; i++) {
printf("%c", item1[i]);
}
vRingbufferReturnItem(buf_handle, (void *)item1);
//Check if item was split
if (item2 != NULL) {
for (int i = 0; i < item_size2; i++) {
printf("%c", item2[i]);
}
vRingbufferReturnItem(buf_handle, (void *)item2);
}
printf("\n");
} else {
//Failed to receive item
printf("Failed to receive item\n");
}
The following example demonstrates retrieving and returning an item from a **byte buffer**
using :cpp:func:`xRingbufferReceiveUpTo` and :cpp:func:`vRingbufferReturnItem`
.. code-block:: c
...
//Receive data from byte buffer
size_t item_size;
char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(1000), sizeof(tx_item));
//Check received data
if (item != NULL) {
//Print item
for (int i = 0; i < item_size; i++) {
printf("%c", item[i]);
}
printf("\n");
//Return Item
vRingbufferReturnItem(buf_handle, (void *)item);
} else {
//Failed to receive item
printf("Failed to receive item\n");
}
For ISR safe versions of the functions used above, call :cpp:func:`xRingbufferSendFromISR`, :cpp:func:`xRingbufferReceiveFromISR`,
:cpp:func:`xRingbufferReceiveSplitFromISR`, :cpp:func:`xRingbufferReceiveUpToFromISR`, and :cpp:func:`vRingbufferReturnItemFromISR`
Sending to Ring Buffer
^^^^^^^^^^^^^^^^^^^^^^
The following diagrams illustrate the differences between no-split/allow-split buffers
and byte buffers with regards to sending items/data. The diagrams assume that three
items of sizes **18, 3, and 27 bytes** are sent respectively to a **buffer of 128 bytes**.
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_non_byte_buf.diag
:caption: Sending items to no-split/allow-split ring buffers
:align: center
For no-split/allow-split buffers, a header of 8 bytes precedes every data item. Furthermore, the space
occupied by each item is **rounded up to the nearest 32-bit aligned size** in order to maintain overall
32-bit alignment. However the true size of the item is recorded inside the header which will be
returned when the item is retrieved.
Referring to the diagram above, the 18, 3, and 27 byte items are **rounded up to 20, 4, and 28 bytes**
respectively. An 8 byte header is then added in front of each item.
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_byte_buf.diag
:caption: Sending items to byte buffers
:align: center
Byte buffers treat data as a sequence of bytes and does not incur any overhead
(no headers). As a result, all data sent to a byte buffer is merged into a single item.
Referring to the diagram above, the 18, 3, and 27 byte items are sequentially written to the
byte buffer and **merged into a single item of 48 bytes**.
Wrap around
^^^^^^^^^^^
The following diagrams illustrate the differences between no-split, allow-split, and byte
buffers when a sent item requires a wrap around. The diagrams assumes a buffer of **128 bytes**
with **56 bytes of free space that wraps around** and a sent item of **28 bytes**.
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_no_split.diag
:caption: Wrap around in no-split buffers
:align: center
No-split buffers will **only store an item in continuous free space and will not split
an item under any circumstances**. When the free space at the tail of the buffer is insufficient
to completely store the item and its header, the free space at the tail will be **marked as dummy data**.
The buffer will then wrap around and store the item in the free space at the head of the buffer.
Referring to the diagram above, the 16 bytes of free space at the tail of the buffer is
insufficient to store the 28 byte item. Therefore the 16 bytes is marked as dummy data and
the item is written to the free space at the head of the buffer instead.
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_allow_split.diag
:caption: Wrap around in allow-split buffers
:align: center
Allow-split buffers will attempt to **split the item into two parts** when the free space at the tail
of the buffer is insufficient to store the item data and its header. Both parts of the
split item will have their own headers (therefore incurring an extra 8 bytes of overhead).
Referring to the diagram above, the 16 bytes of free space at the tail of the buffer is insufficient
to store the 28 byte item. Therefore the item is split into two parts (8 and 20 bytes) and written
as two parts to the buffer.
.. note::
Allow-split buffers treats the both parts of the split item as two separate items, therefore call
:cpp:func:`xRingbufferReceiveSplit` instead of :cpp:func:`xRingbufferReceive` to receive both
parts of a split item in a thread safe manner.
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_byte_buf.diag
:caption: Wrap around in byte buffers
:align: center
Byte buffers will **store as much data as possible into the free space at the tail of buffer**. The remaining
data will then be stored in the free space at the head of the buffer. No overhead is incurred when wrapping
around in byte buffers.
Referring to the diagram above, the 16 bytes of free space at the tail of the buffer is insufficient to
completely store the 28 bytes of data. Therefore the 16 bytes of free space is filled with data, and the
remaining 12 bytes are written to the free space at the head of the buffer. The buffer now contains
data in two separate continuous parts, and each part continuous will be treated as a separate item by the
byte buffer.
Retrieving/Returning
^^^^^^^^^^^^^^^^^^^^
The following diagrams illustrates the differences between no-split/allow-split and
byte buffers in retrieving and returning data.
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_read_ret_non_byte_buf.diag
:caption: Retrieving/Returning items in no-split/allow-split ring buffers
:align: center
Items in no-split/allow-split buffers are **retrieved in strict FIFO order** and **must be returned**
for the occupied space to be freed. Multiple items can be retrieved before returning, and the items
do not necessarily need to be returned in the order they were retrieved. However the freeing of space
must occur in FIFO order, therefore not returning the earliest retrieved item will prevent the space
of subsequent items from being freed.
Referring to the diagram above, the **16, 20, and 8 byte items are retrieved in FIFO order**. However the items
are not returned in they were retrieved (20, 8, 16). As such, the space is not freed until the first item
(16 byte) is returned.
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_read_ret_byte_buf.diag
:caption: Retrieving/Returning data in byte buffers
:align: center
Byte buffers **do not allow multiple retrievals before returning** (every retrieval must be followed by a return
before another retrieval is permitted). When using :cpp:func:`xRingbufferReceive` or
:cpp:func:`xRingbufferReceiveFromISR`, all continuous stored data will be retrieved. :cpp:func:`xRingbufferReceiveUpTo`
or :cpp:func:`xRingbufferReceiveUpToFromISR` can be used to restrict the maximum number of bytes retrieved. Since
every retrieval must be followed by a return, the space will be freed as soon as the data is returned.
Referring to the diagram above, the 38 bytes of continuous stored data at the tail of the buffer is retrieved,
returned, and freed. The next call to :cpp:func:`xRingbufferReceive` or :cpp:func:`xRingbufferReceiveFromISR`
then wraps around and does the same to the 30 bytes of continuous stored data at the head of the buffer.
Ring Buffers with Queue Sets
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ring buffers can be added to FreeRTOS queue sets using :cpp:func:`xRingbufferAddToQueueSetRead` such that every
time a ring buffer receives an item or data, the queue set is notified. Once added to a queue set, every
attempt to retrieve an item from a ring buffer should be preceded by a call to :cpp:func:`xQueueSelectFromSet`.
To check whether the selected queue set member is the ring buffer, call :cpp:func:`xRingbufferCanRead`.
The following example demonstrates queue set usage with ring buffers.
.. code-block:: c
#include "freertos/queue.h"
#include "freertos/ringbuf.h"
...
//Create ring buffer and queue set
RingbufHandle_t buf_handle = xRingbufferCreate(1028, RINGBUF_TYPE_NOSPLIT);
QueueSetHandle_t queue_set = xQueueCreateSet(3);
//Add ring buffer to queue set
if (xRingbufferAddToQueueSetRead(buf_handle, queue_set) != pdTRUE) {
printf("Failed to add to queue set\n");
}
...
//Block on queue set
xQueueSetMemberHandle member = xQueueSelectFromSet(queue_set, pdMS_TO_TICKS(1000));
//Check if member is ring buffer
if (member != NULL && xRingbufferCanRead(buf_handle, member) == pdTRUE) {
//Member is ring buffer, receive item from ring buffer
size_t item_size;
char *item = (char *)xRingbufferReceive(buf_handle, &item_size, 0);
//Handle item
...
} else {
...
}
Ring Buffer API Reference
-------------------------
.. note::
Ideally, ring buffers can be used with multiple tasks in an SMP fashion where the **highest
priority task will always be serviced first.** However due to the usage of binary semaphores
in the ring buffer's underlying implementation, priority inversion may occur under very
specific circumstances.
The ring buffer governs sending by a binary semaphore which is given whenever space is
freed on the ring buffer. The highest priority task waiting to send will repeatedly take
the semaphore until sufficient free space becomes available or until it times out. Ideally
this should prevent any lower priority tasks from being serviced as the semaphore should
always be given to the highest priority task.
However in between iterations of acquiring the semaphore, there is a **gap in the critical
section** which may permit another task (on the other core or with an even higher priority) to
free some space on the ring buffer and as a result give the semaphore. Therefore the semaphore
will be given before the highest priority task can re-acquire the semaphore. This will result
in the **semaphore being acquired by the second highest priority task** waiting to send, hence
causing priority inversion.
This side effect will not affect ring buffer performance drastically given if the number
of tasks using the ring buffer simultaneously is low, and the ring buffer is not operating
near maximum capacity.
.. include:: /_build/inc/ringbuf.inc
.. _hooks:
Hooks
-----
FreeRTOS consists of Idle Hooks and Tick Hooks which allow for application
specific functionality to be added to the Idle Task and Tick Interrupt.
ESP-IDF provides its own Idle and Tick Hook API in addition to the hooks
provided by Vanilla FreeRTOS. ESP-IDF hooks have the added benefit of
being run time configurable and asymmetrical.
Vanilla FreeRTOS Hooks
^^^^^^^^^^^^^^^^^^^^^^
Idle and Tick Hooks in vanilla FreeRTOS are implemented by the user
defining the functions ``vApplicationIdleHook()`` and ``vApplicationTickHook()``
respectively somewhere in the application. Vanilla FreeRTOS will run the user
defined Idle Hook and Tick Hook on every iteration of the Idle Task and Tick
Interrupt respectively.
Vanilla FreeRTOS hooks are referred to as **Legacy Hooks** in ESP-IDF FreeRTOS.
To enable legacy hooks, :ref:`CONFIG_FREERTOS_LEGACY_HOOKS`,
:ref:`CONFIG_FREERTOS_LEGACY_IDLE_HOOK`, and :ref:`CONFIG_FREERTOS_LEGACY_TICK_HOOK`
should all be enabled in ``make menuconfig``.
Due to vanilla FreeRTOS being designed for single core, ``vApplicationIdleHook()``
and ``vApplicationTickHook()`` can only be defined once. However, the ESP32 is dual core
in nature, therefore same Idle Hook and Tick Hook are used for both cores (in other words,
the hooks are symmetrical for both cores).
ESP-IDF Idle and Tick Hooks
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Due to the the dual core nature of the ESP32, it may be necessary for some
applications to have separate hooks for each core. Furthermore, it may
be necessary for the Idle Tasks or Tick Interrupts to execute multiple hooks
that are configurable at run time. Therefore the ESP-IDF provides it's own hooks
API in addition to the legacy hooks provided by Vanilla FreeRTOS.
The ESP-IDF tick/idle hooks are registered at run time, and each tick/idle hook
must be registered to a specific CPU. When the idle task runs/tick Interrupt
occurs on a particular CPU, the CPU will run each of its registered idle/tick hooks
in turn.
Hooks API Reference
-------------------
.. include:: /_build/inc/esp_freertos_hooks.inc

View File

@ -1,51 +0,0 @@
.. _hooks_api_reference:
FreeRTOS Hooks
==============
Overview
--------
FreeRTOS consists of Idle Hooks and Tick Hooks which allow for application
specific funtiionality to be added to the Idle Task and Tick Interrupt. The
ESP32 is dual core in nature, hence the ESP-IDF provides its own Idle and Tick
Hooks that are dual core compatible in addition to the hooks provided by Vanilla
FreeRTOS.
Vanilla FreeRTOS Hooks
----------------------
Idle and Tick Hooks in vanilla FreeRTOS are implemented by defining
implementations for the functions ``vApplicationIdleHook`` and
``vApplicationTickHook`` respectively somewhere in the application. Vanilla
FreeRTOS will run the user defined Idle Hook every iteration of the Idle Task,
whereas the user defined Tick Hook will run once per tick interrupt (given that
there are no pended ticks).
Due to vanilla FreeRTOS being designed for single core, ``vApplicationIdleHook``
and ``vApplicationTickHook`` will be run in both cores on the ESP32. In
other words, the same Idle Hook and Tick Hook are used for both cores.
To enable the vanilla FreeRTOS hooks in ESP-IDF, :ref:`CONFIG_FREERTOS_LEGACY_HOOKS`
must be enabled in ``make menuconfig``. :ref:`CONFIG_FREERTOS_LEGACY_IDLE_HOOK`
and :ref:`CONFIG_FREERTOS_LEGACY_TICK_HOOK` should also be enabled.
ESP-IDF Idle and Tick Hooks
---------------------------
Due to the dual core nature of the ESP32, it may be necessary for some
applications to have seperate Idle Hooks for each core. Furthermore, it may
be necessary for Idle and Tick Hooks to have execute multiple functionalities
that are configurable at run time. Therefore the ESP-IDF provides it's own Idle
and Tick Hooks in addition to the hooks provided by Vanilla FreeRTOS.
The ESP-IDF Hooks do not operate in the same way as Vanilla FreeRTOS Hooks
where users provide a definition for each of the hooks. Instead, the ESP-IDF
Hooks are predefined to call a list of user registered callbacks specific to
each core. Users can register and deregister callbacks which are run on the
Idle or Tick Hook of a specific core.
API Reference
-------------
.. include:: /_build/inc/esp_freertos_hooks.inc

View File

@ -5,7 +5,7 @@ System API
:maxdepth: 1
FreeRTOS <freertos>
FreeRTOS Hooks <hooks>
FreeRTOS Additions <freertos_additions>
Heap Memory Allocation <mem_alloc>
Heap Memory Debugging <heap_debug>
Interrupt Allocation <intr_alloc>

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/system/freertos_additions.rst

View File

@ -1 +0,0 @@
.. include:: ../../../en/api-reference/system/hooks.rst