Merge branch 'doc/esp_ringbuf_acquire_complete_v4.0' into 'release/v4.0'
esp_ringbuf: add documents for SendAcquire and SendComplete (backport v4.0) See merge request espressif/esp-idf!7104
This commit is contained in:
commit
e5d25fb51e
2 changed files with 175 additions and 38 deletions
63
docs/_static/diagrams/ring-buffer/ring_buffer_send_acquire_complete.diag
vendored
Normal file
63
docs/_static/diagrams/ring-buffer/ring_buffer_send_acquire_complete.diag
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
#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_send_acquire_complete {
|
||||
node_width = 6
|
||||
node_height = 24
|
||||
default_fontsize = 12
|
||||
colwidth = 128
|
||||
|
||||
#Initial
|
||||
0-7: 8 [color = lightblue];
|
||||
8-23: 16 Available [color = lightyellow];
|
||||
24-127: Free
|
||||
|
||||
#Acquire item 1, 2, 3
|
||||
128-135: 8 [color = lightblue];
|
||||
136-151: 16 Available [color = lightyellow];
|
||||
152-179: 20 Acquired [color = lightgrey];
|
||||
180-195: 8 Acquired [color = lightgrey];
|
||||
196-227: 24 Acquired [color = lightgrey];
|
||||
228-255: 28 Free
|
||||
|
||||
#Complete item 2
|
||||
256-263: 8 [color = lightblue];
|
||||
264-279: 16 Available [color = lightyellow];
|
||||
280-307: 20 Acquired [color = lightgrey];
|
||||
308-315: 8 [color = pink];
|
||||
316-323: 8 Completed [color = pink];
|
||||
324-355: 24 Acquired [color = lightgrey];
|
||||
356-383: 28 Free
|
||||
|
||||
#Complete item 3
|
||||
384-391: 8 [color = lightblue];
|
||||
392-407: 16 Available [color = lightyellow];
|
||||
408-435: 20 Acquired [color = lightgrey];
|
||||
436-443: 8 [color = pink];
|
||||
444-451: 8 Completed [color = pink];
|
||||
452-459: 8 [color = pink];
|
||||
460-483: 24 Completed [color = pink];
|
||||
484-511: 28 Free
|
||||
|
||||
#Complete item 1
|
||||
512-519: 8 [color = lightblue];
|
||||
520-535: 16 Available [color = lightyellow];
|
||||
536-543: 8 [color = pink];
|
||||
544-563: 20 Completed [color = pink];
|
||||
564-571: 8 [color = pink];
|
||||
572-579: 8 Completed [color = pink];
|
||||
580-587: 8 [color = pink];
|
||||
588-611: 24 Completed [color = pink];
|
||||
612-639: 28 Free
|
||||
|
||||
#Return item 3
|
||||
640-647: 8 [color = lightblue];
|
||||
648-663: 16 Available [color = lightyellow];
|
||||
664-671: 8 [color = lightblue];
|
||||
672-691: 20 Available [color = lightyellow];
|
||||
692-699: 8 [color = lightblue];
|
||||
700-707: 8 Available [color = lightyellow];
|
||||
708-715: 8 [color = lightblue];
|
||||
716-739: 24 Available [color = lightyellow];
|
||||
740-767: 28 Free
|
||||
}
|
|
@ -23,14 +23,17 @@ 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:
|
||||
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
|
||||
**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.
|
||||
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
|
||||
|
@ -42,7 +45,8 @@ 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.
|
||||
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.
|
||||
|
||||
.. note::
|
||||
Each item stored in no-split/allow-split buffers will **require an additional 8 bytes for a header**.
|
||||
|
@ -76,6 +80,46 @@ and :cpp:func:`xRingbufferSend` to create a ring buffer then send an item to it.
|
|||
printf("Failed to send item\n");
|
||||
}
|
||||
|
||||
The following example demonstrates the usage of :cpp:func:`xRingbufferSendAcquire` and
|
||||
:cpp:func:`xRingbufferSendComplete` instead of :cpp:func:`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.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#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 :cpp:func:`xRingbufferReceive` and :cpp:func:`vRingbufferReturnItem`
|
||||
|
@ -83,7 +127,7 @@ 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));
|
||||
|
@ -162,23 +206,23 @@ using :cpp:func:`xRingbufferReceiveUpTo` and :cpp:func:`vRingbufferReturnItem`
|
|||
|
||||
|
||||
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`
|
||||
: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
|
||||
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
|
||||
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
|
||||
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**
|
||||
|
@ -188,12 +232,42 @@ respectively. An 8 byte header is then added in front of each item.
|
|||
:caption: Sending items to byte buffers
|
||||
:align: center
|
||||
|
||||
Byte buffers treat data as a sequence of bytes and does not incur any overhead
|
||||
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.
|
||||
|
||||
.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_acquire_complete.diag
|
||||
:caption: SendAcquire/SendComplete items in no-split ring buffers
|
||||
:align: center
|
||||
|
||||
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
|
||||
^^^^^^^^^^^
|
||||
|
||||
|
@ -207,15 +281,15 @@ with **56 bytes of free space that wraps around** and a sent item of **28 bytes*
|
|||
|
||||
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**.
|
||||
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
|
||||
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
|
||||
: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
|
||||
|
@ -232,17 +306,17 @@ as two parts to the buffer.
|
|||
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
|
||||
: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
|
||||
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
|
||||
data in two separate continuous parts, and each part continuous will be treated as a separate item by the
|
||||
byte buffer.
|
||||
|
||||
Retrieving/Returning
|
||||
|
@ -255,10 +329,10 @@ byte buffers in retrieving and returning data.
|
|||
: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
|
||||
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
|
||||
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
|
||||
|
@ -270,13 +344,13 @@ are not returned in they were retrieved (20, 8, 16). As such, the space is not f
|
|||
: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
|
||||
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`
|
||||
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
|
||||
|
@ -331,7 +405,7 @@ The :cpp:func:`xRingbufferCreateStatic` can be used to create ring buffers with
|
|||
- The ring buffer's data structure of type :cpp: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 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).
|
||||
|
||||
.. note::
|
||||
The :ref:`CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION` option must be enabled in `menuconfig` for statically allocated ring buffers to be available.
|
||||
|
@ -375,20 +449,20 @@ 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
@ -403,9 +477,9 @@ Ring Buffer API Reference
|
|||
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
|
||||
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.
|
||||
|
||||
|
@ -416,9 +490,9 @@ 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.
|
||||
Interrupt respectively.
|
||||
|
||||
Vanilla FreeRTOS hooks are referred to as **Legacy Hooks** in ESP-IDF FreeRTOS.
|
||||
Vanilla FreeRTOS hooks are referred to as **Legacy Hooks** in ESP-IDF FreeRTOS.
|
||||
To enable legacy hooks, :ref:`CONFIG_FREERTOS_LEGACY_HOOKS` should be enabled
|
||||
in :doc:`project configuration menu </api-reference/kconfig>`.
|
||||
|
||||
|
@ -433,9 +507,9 @@ 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.
|
||||
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
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue