FreeRTOS Additions

Overview

ESP-IDF FreeRTOS is based on the Xtensa port of FreeRTOS v8.2.0 with significant modifications for SMP compatibility (see ESP-IDF FreeRTOS SMP Changes). However various features specific to ESP-IDF FreeRTOS have been added. The features are as follows:

Ring Buffers: Ring buffers were added to provide a form of buffer that could accept entries of arbitrary lengths.

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

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. You may apply for a piece of memory on the ring buffer to send an item, or just use the API to copy your data and send (according to the send API you call). For efficiency reasons, items are always retrieved from the ring buffer 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. Only this buffer type allows you getting the data item address and writting to the item by yourself.

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).

注解

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. This is useful especially when you need to send some data to the DMA.

注解

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 xRingbufferCreate() and xRingbufferSend() to create a ring buffer then send an item to it.

#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 the usage of xRingbufferSendAcquire() and xRingbufferSendComplete() instead of xRingbufferSend() to apply for the memory on the ring buffer (of type RINGBUF_TYPE_NOSPLIT) and then send an item to it. This way adds one more step, but allows getting the address of the memory to write to, and writing to the memory yourself.

#include "freertos/ringbuf.h"
#include "soc/lldesc.h"

typedef struct {
    lldesc_t dma_desc;
    uint8_t buf[1];
} dma_item_t;

#define DMA_ITEM_SIZE(N) (sizeof(lldesc_t)+(((N)+3)&(~3)))

...

    //Retrieve space for DMA descriptor and corresponding data buffer
    //This has to be done with SendAcquire, or the address may be different when copy
    dma_item_t item;
    UBaseType_t res =  xRingbufferSendAcquire(buf_handle,
                        &item, DMA_ITEM_SIZE(buffer_size), pdMS_TO_TICKS(1000));
    if (res != pdTRUE) {
        printf("Failed to acquire memory for item\n");
    }
    item->dma_desc = (lldesc_t) {
        .size = buffer_size,
        .length = buffer_size,
        .eof = 0,
        .owner = 1,
        .buf = &item->buf,
    };
    //Actually send to the ring buffer for consumer to use
    res = xRingbufferSendComplete(buf_handle, &item);
    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 xRingbufferReceive() and vRingbufferReturnItem()

...

    //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 xRingbufferReceiveSplit() and vRingbufferReturnItem()

...

    //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 xRingbufferReceiveUpTo() and vRingbufferReturnItem()

...

    //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 xRingbufferSendFromISR(), xRingbufferReceiveFromISR(), xRingbufferReceiveSplitFromISR(), xRingbufferReceiveUpToFromISR(), and 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.

Sending items to no-split/allow-split ring buffers

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.

Sending items to byte buffers

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.

Using SendAcquire and SendComplete

Items in no-split buffers are acquired (by SendAcquire) in strict FIFO order and must be sent to the buffer by SendComplete for the data to be accessible by the consumer. Multiple items can be sent or acquired without calling SendComplete, and the items do not necessarily need to be completed in the order they were acquired. However the receiving of data items must occur in FIFO order, therefore not calling SendComplete the earliest acquired item will prevent the subsequent items from being received.

The following diagrams illustrate what will happen when SendAcquire/SendComplete don’t happen in the same order. At the beginning, there is already an data item of 16 bytes sent to the ring buffer. Then SendAcquire is called to acquire space of 20, 8, 24 bytes on the ring buffer.

SendAcquire/SendComplete items in no-split ring buffers

After that, we fill (use) the buffers, and send them to the ring buffer by SendComplete in the order of 8, 24, 20. When 8 bytes and 24 bytes data are sent, the consumer still can only get the 16 bytes data item. Due to the usage if 20 bytes item is not complete, it’s not available, nor the following data items.

When the 20 bytes item is finally completed, all the 3 data items can be received now, in the order of 20, 8, 24 bytes, right after the 16 bytes item existing in the buffer at the beginning.

Allow-split/byte buffers do not allow using SendAcquire/SendComplete since acquired buffers are required to be complete (not wrapped).

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.

Wrap around in no-split buffers

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.

Wrap around in allow-split buffers

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.

注解

Allow-split buffers treats the both parts of the split item as two separate items, therefore call xRingbufferReceiveSplit() instead of xRingbufferReceive() to receive both parts of a split item in a thread safe manner.

Wrap around in byte buffers

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.

Retrieving/Returning items in no-split/allow-split ring buffers

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.

Retrieving/Returning data in byte buffers

Byte buffers do not allow multiple retrievals before returning (every retrieval must be followed by a return before another retrieval is permitted). When using xRingbufferReceive() or xRingbufferReceiveFromISR(), all continuous stored data will be retrieved. xRingbufferReceiveUpTo() or 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 xRingbufferReceive() or 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 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 xQueueSelectFromSet(). To check whether the selected queue set member is the ring buffer, call xRingbufferCanRead().

The following example demonstrates queue set usage with ring buffers.

#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 Buffers with Static Allocation

The xRingbufferCreateStatic() can be used to create ring buffers with specific memory requirements (such as a ring buffer being allocated in external RAM). All blocks of memory used by a ring buffer must be manually allocated beforehand then passed to the xRingbufferCreateStatic() to be initialized as a ring buffer. These blocks include the following:

  • The ring buffer’s data structure of type StaticRingbuffer_t
  • The ring buffer’s storage area of size xBufferSize. Note that xBufferSize must be 32-bit aligned for no-split/allow-split buffers.

The manner in which these blocks are allocated will depend on the users requirements (e.g. all blocks being statically declared, or dynamically allocated with specific capabilities such as external RAM).

注解

The CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION option must be enabled in menuconfig for statically allocated ring buffers to be available.

注解

When deleting a ring buffer created via xRingbufferCreateStatic(), the function vRingbufferDelete() will not free any of the memory blocks. This must be done manually by the user after vRingbufferDelete() is called.

The code snippet below demonstrates a ring buffer being allocated entirely in external RAM.

#include "freertos/ringbuf.h"
#include "freertos/semphr.h"
#include "esp_heap_caps.h"

#define BUFFER_SIZE     400      //32-bit aligned size
#define BUFFER_TYPE     RINGBUF_TYPE_NOSPLIT
...

//Allocate ring buffer data structure and storage area into external RAM
StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM);
uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*BUFFER_SIZE, MALLOC_CAP_SPIRAM);

//Create a ring buffer with manually allocated memory
RingbufHandle_t handle = xRingbufferCreateStatic(BUFFER_SIZE, BUFFER_TYPE, buffer_storage, buffer_struct);

...

//Delete the ring buffer after used
vRingbufferDelete(handle);

//Manually free all blocks of memory
free(buffer_struct);
free(buffer_storage);

Ring Buffer API Reference

注解

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.

Functions

RingbufHandle_t xRingbufferCreate(size_t xBufferSize, RingbufferType_t xBufferType)

Create a ring buffer.

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.
Parameters
  • xBufferSize: Size of the buffer in bytes. Note that items require space for overhead in no-split/allow-split buffers
  • xBufferType: Type of ring buffer, see documentation.

RingbufHandle_t xRingbufferCreateNoSplit(size_t xItemSize, size_t xItemNum)

Create a ring buffer of type RINGBUF_TYPE_NOSPLIT for a fixed item_size.

This API is similar to xRingbufferCreate(), but it will internally allocate additional space for the headers.

Return
A RingbufHandle_t handle to the created ring buffer, or NULL in case of error.
Parameters
  • xItemSize: Size of each item to be put into the ring buffer
  • xItemNum: Maximum number of items the buffer needs to hold simultaneously

RingbufHandle_t xRingbufferCreateStatic(size_t xBufferSize, RingbufferType_t xBufferType, uint8_t *pucRingbufferStorage, StaticRingbuffer_t *pxStaticRingbuffer)

Create a ring buffer but manually provide the required memory.

Note
The CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION option must be enabled for this to be available
Note
xBufferSize of no-split/allow-split buffers MUST be 32-bit aligned.
Return
A handle to the created ring buffer
Parameters
  • xBufferSize: Size of the buffer in bytes.
  • xBufferType: Type of ring buffer, see documentation
  • pucRingbufferStorage: Pointer to the ring buffer’s storage area. Storage area must of the same size as specified by xBufferSize
  • pxStaticRingbuffer: Pointed to a struct of type StaticRingbuffer_t which will be used to hold the ring buffer’s data structure

BaseType_t xRingbufferSend(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, TickType_t xTicksToWait)

Insert an item into 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 times out.

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 data is larger than the maximum permissible size of the buffer
Parameters
  • xRingbuffer: Ring buffer to insert the item into
  • pvItem: Pointer to data to insert. NULL is allowed if xItemSize is 0.
  • xItemSize: Size of data to insert.
  • xTicksToWait: Ticks to wait for room in the ring buffer.

BaseType_t xRingbufferSendFromISR(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, BaseType_t *pxHigherPriorityTaskWoken)

Insert an item into the ring buffer in an ISR.

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.

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 when the ring buffer does not have space.
Parameters
  • xRingbuffer: Ring buffer to insert the item into
  • pvItem: Pointer to data to insert. NULL is allowed if xItemSize is 0.
  • xItemSize: Size of data to insert.
  • pxHigherPriorityTaskWoken: Value pointed to will be set to pdTRUE if the function woke up a higher priority task.

BaseType_t xRingbufferSendAcquire(RingbufHandle_t xRingbuffer, void **ppvItem, size_t xItemSize, TickType_t xTicksToWait)

Acquire memory from the ring buffer to be written to by an external source and to be sent later.

Attempt to allocate buffer for an item to be sent into the ring buffer. This function will block until enough free space is available or until it timesout.

The item, as well as the following items SendAcquire or Send after it, will not be able to be read from the ring buffer until this item is actually sent into the ring buffer.

Note
Only applicable for no-split ring buffers now, 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 data is larger than the maximum permissible size of the buffer
Parameters
  • xRingbuffer: Ring buffer to allocate the memory
  • ppvItem: Double pointer to memory acquired (set to NULL if no memory were retrieved)
  • xItemSize: Size of item to acquire.
  • xTicksToWait: Ticks to wait for room in the ring buffer.

BaseType_t xRingbufferSendComplete(RingbufHandle_t xRingbuffer, void *pvItem)

Actually send an item into the ring buffer allocated before by xRingbufferSendAcquire.

Note
Only applicable for no-split ring buffers. Only call for items allocated by xRingbufferSendAcquire.
Return
  • pdTRUE if succeeded
  • pdFALSE if fail for some reason.
Parameters
  • xRingbuffer: Ring buffer to insert the item into
  • pvItem: Pointer to item in allocated memory to insert.

void *xRingbufferReceive(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait)

Retrieve an item from the ring buffer.

Attempt to retrieve an item from the ring buffer. This function will block until an item is available or until it times out.

Note
A call to vRingbufferReturnItem() is required after this to free the item retrieved.
Return
  • Pointer to the retrieved item on success; *pxItemSize filled with the length of the item.
  • NULL on timeout, *pxItemSize is untouched in that case.
Parameters
  • xRingbuffer: Ring buffer to retrieve the item from
  • pxItemSize: Pointer to a variable to which the size of the retrieved item will be written.
  • xTicksToWait: Ticks to wait for items in the ring buffer.

void *xRingbufferReceiveFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize)

Retrieve an item from the ring buffer in an ISR.

Attempt to retrieve an item from the ring buffer. This function returns immediately if there are no items available for retrieval

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; *pxItemSize filled with the length of the item.
  • NULL when the ring buffer is empty, *pxItemSize is untouched in that case.
Parameters
  • xRingbuffer: Ring buffer to retrieve the item from
  • pxItemSize: Pointer to a variable to which the size of the retrieved item will be written.

BaseType_t xRingbufferReceiveSplit(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize, TickType_t xTicksToWait)

Retrieve a split item from an allow-split ring buffer.

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 times out.

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
  • pdTRUE if an item (split or unsplit) was retrieved
  • pdFALSE when no item was retrieved
Parameters
  • xRingbuffer: Ring buffer to retrieve the item from
  • ppvHeadItem: Double pointer to first part (set to NULL if no items were retrieved)
  • ppvTailItem: Double pointer to second part (set to NULL if item is not split)
  • pxHeadItemSize: Pointer to size of first part (unmodified if no items were retrieved)
  • pxTailItemSize: Pointer to size of second part (unmodified if item is not split)
  • xTicksToWait: Ticks to wait for items in the ring buffer.

BaseType_t xRingbufferReceiveSplitFromISR(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize)

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

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
Parameters
  • xRingbuffer: Ring buffer to retrieve the item from
  • ppvHeadItem: Double pointer to first part (set to NULL if no items were retrieved)
  • ppvTailItem: Double pointer to second part (set to NULL if item is not split)
  • pxHeadItemSize: Pointer to size of first part (unmodified if no items were retrieved)
  • pxTailItemSize: Pointer to size of second part (unmodified if item is not split)

void *xRingbufferReceiveUpTo(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait, size_t xMaxSize)

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 times out.

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 on timeout, *pxItemSize is untouched in that case.
Parameters
  • xRingbuffer: Ring buffer to retrieve the item from
  • pxItemSize: Pointer to a variable to which the size of the retrieved item will be written.
  • xTicksToWait: Ticks to wait for items in the ring buffer.
  • xMaxSize: Maximum number of bytes to return.

void *xRingbufferReceiveUpToFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize, size_t xMaxSize)

Retrieve bytes from a byte buffer, specifying the maximum amount of bytes to retrieve. Call this from an ISR.

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.

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.
Parameters
  • xRingbuffer: Ring buffer to retrieve the item from
  • pxItemSize: Pointer to a variable to which the size of the retrieved item will be written.
  • xMaxSize: Maximum number of bytes to return.

void vRingbufferReturnItem(RingbufHandle_t xRingbuffer, void *pvItem)

Return a previously-retrieved item to the ring buffer.

Note
If a split item is retrieved, both parts should be returned by calling this function twice
Parameters
  • xRingbuffer: Ring buffer the item was retrieved from
  • pvItem: Item that was received earlier

void vRingbufferReturnItemFromISR(RingbufHandle_t xRingbuffer, void *pvItem, BaseType_t *pxHigherPriorityTaskWoken)

Return a previously-retrieved item to the ring buffer from an ISR.

Note
If a split item is retrieved, both parts should be returned by calling this function twice
Parameters
  • xRingbuffer: Ring buffer the item was retrieved from
  • pvItem: Item that was received earlier
  • pxHigherPriorityTaskWoken: Value pointed to will be set to pdTRUE if the function woke up a higher priority task.

void vRingbufferDelete(RingbufHandle_t xRingbuffer)

Delete a ring buffer.

Note
This function will not deallocate any memory if the ring buffer was created using xRingbufferCreateStatic(). Deallocation must be done manually be the user.
Parameters
  • xRingbuffer: Ring buffer to delete

size_t xRingbufferGetMaxItemSize(RingbufHandle_t xRingbuffer)

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.

Note
The max item size for a no-split buffer is limited to ((buffer_size/2)-header_size). This limit is imposed so that an item of max item size can always be sent to the an empty no-split buffer regardless of the internal positions of the buffer’s read/write/free pointers.
Return
Maximum size, in bytes, of an item that can be placed in a ring buffer.
Parameters
  • xRingbuffer: Ring buffer to query

size_t xRingbufferGetCurFreeSize(RingbufHandle_t xRingbuffer)

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
Note
An empty no-split buffer has a max current free size for an item that is limited to ((buffer_size/2)-header_size). See API reference for xRingbufferGetMaxItemSize().
Return
Current free size, in bytes, available for an entry
Parameters
  • xRingbuffer: Ring buffer to query

BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet)

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.

Return
  • pdTRUE on success, pdFALSE otherwise
Parameters
  • xRingbuffer: Ring buffer to add to the queue set
  • xQueueSet: Queue set to add the ring buffer’s read semaphore to

BaseType_t xRingbufferCanRead(RingbufHandle_t xRingbuffer, QueueSetMemberHandle_t xMember)

Check if the selected queue set member is the ring buffer’s read semaphore.

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 retrieved.

Return
  • pdTRUE when semaphore belongs to ring buffer
  • pdFALSE otherwise.
Parameters
  • xRingbuffer: Ring buffer which should be checked
  • xMember: Member returned from xQueueSelectFromSet

BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet)

Remove the ring buffer’s read semaphore from a queue set.

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

Return
  • pdTRUE on success
  • pdFALSE otherwise
Parameters
  • xRingbuffer: Ring buffer to remove from the queue set
  • xQueueSet: Queue set to remove the ring buffer’s read semaphore from

void vRingbufferGetInfo(RingbufHandle_t xRingbuffer, UBaseType_t *uxFree, UBaseType_t *uxRead, UBaseType_t *uxWrite, UBaseType_t *uxAcquire, UBaseType_t *uxItemsWaiting)

Get information about ring buffer status.

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.

Parameters
  • xRingbuffer: Ring buffer to remove from the queue set
  • uxFree: Pointer use to store free pointer position
  • uxRead: Pointer use to store read pointer position
  • uxWrite: Pointer use to store write pointer position
  • uxAcquire: Pointer use to store acquire pointer position
  • uxItemsWaiting: Pointer use to store number of items (bytes for byte buffer) waiting to be retrieved

void xRingbufferPrintInfo(RingbufHandle_t xRingbuffer)

Debugging function to print the internal pointers in the ring buffer.

Parameters
  • xRingbuffer: Ring buffer to show

Structures

struct xSTATIC_RINGBUFFER

Struct that is equivalent in size to the ring buffer’s data structure.

The contents of this struct are not meant to be used directly. This structure is meant to be used when creating a statically allocated ring buffer where this struct is of the exact size required to store a ring buffer’s control data structure.

Note
The CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION option must be enabled for this structure to be available.

Type Definitions

typedef void *RingbufHandle_t

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 struct xSTATIC_RINGBUFFER StaticRingbuffer_t

Struct that is equivalent in size to the ring buffer’s data structure.

The contents of this struct are not meant to be used directly. This structure is meant to be used when creating a statically allocated ring buffer where this struct is of the exact size required to store a ring buffer’s control data structure.

Note
The CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION option must be enabled for this structure to be available.

Enumerations

enum RingbufferType_t

Values:

RINGBUF_TYPE_NOSPLIT = 0

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_ALLOWSPLIT

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_BYTEBUF

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_MAX

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, CONFIG_FREERTOS_LEGACY_HOOKS should be enabled in project configuration menu.

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

Functions

esp_err_t esp_register_freertos_idle_hook_for_cpu(esp_freertos_idle_cb_t new_idle_cb, UBaseType_t cpuid)

Register a callback to be called from the specified core’s idle hook. The callback should return true if it should be called by the idle hook once per interrupt (or FreeRTOS tick), and return false if it should be called repeatedly as fast as possible by the idle hook.

Warning
Idle callbacks MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
Return
  • ESP_OK: Callback registered to the specified core’s idle hook
  • ESP_ERR_NO_MEM: No more space on the specified core’s idle hook to register callback
  • ESP_ERR_INVALID_ARG: cpuid is invalid
Parameters
  • new_idle_cb: Callback to be called
  • cpuid: id of the core

esp_err_t esp_register_freertos_idle_hook(esp_freertos_idle_cb_t new_idle_cb)

Register a callback to the idle hook of the core that calls this function. The callback should return true if it should be called by the idle hook once per interrupt (or FreeRTOS tick), and return false if it should be called repeatedly as fast as possible by the idle hook.

Warning
Idle callbacks MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
Return
  • ESP_OK: Callback registered to the calling core’s idle hook
  • ESP_ERR_NO_MEM: No more space on the calling core’s idle hook to register callback
Parameters
  • new_idle_cb: Callback to be called

esp_err_t esp_register_freertos_tick_hook_for_cpu(esp_freertos_tick_cb_t new_tick_cb, UBaseType_t cpuid)

Register a callback to be called from the specified core’s tick hook.

Return
  • ESP_OK: Callback registered to specified core’s tick hook
  • ESP_ERR_NO_MEM: No more space on the specified core’s tick hook to register the callback
  • ESP_ERR_INVALID_ARG: cpuid is invalid
Parameters
  • new_tick_cb: Callback to be called
  • cpuid: id of the core

esp_err_t esp_register_freertos_tick_hook(esp_freertos_tick_cb_t new_tick_cb)

Register a callback to be called from the calling core’s tick hook.

Return
  • ESP_OK: Callback registered to the calling core’s tick hook
  • ESP_ERR_NO_MEM: No more space on the calling core’s tick hook to register the callback
Parameters
  • new_tick_cb: Callback to be called

void esp_deregister_freertos_idle_hook_for_cpu(esp_freertos_idle_cb_t old_idle_cb, UBaseType_t cpuid)

Unregister an idle callback from the idle hook of the specified core.

Parameters
  • old_idle_cb: Callback to be unregistered
  • cpuid: id of the core

void esp_deregister_freertos_idle_hook(esp_freertos_idle_cb_t old_idle_cb)

Unregister an idle callback. If the idle callback is registered to the idle hooks of both cores, the idle hook will be unregistered from both cores.

Parameters
  • old_idle_cb: Callback to be unregistered

void esp_deregister_freertos_tick_hook_for_cpu(esp_freertos_tick_cb_t old_tick_cb, UBaseType_t cpuid)

Unregister a tick callback from the tick hook of the specified core.

Parameters
  • old_tick_cb: Callback to be unregistered
  • cpuid: id of the core

void esp_deregister_freertos_tick_hook(esp_freertos_tick_cb_t old_tick_cb)

Unregister a tick callback. If the tick callback is registered to the tick hooks of both cores, the tick hook will be unregistered from both cores.

Parameters
  • old_tick_cb: Callback to be unregistered

Type Definitions

typedef bool (*esp_freertos_idle_cb_t)(void)
typedef void (*esp_freertos_tick_cb_t)(void)