diff --git a/docs/en/api-reference/system/freertos_additions.rst b/docs/en/api-reference/system/freertos_additions.rst index d47497095d..832baf43b3 100644 --- a/docs/en/api-reference/system/freertos_additions.rst +++ b/docs/en/api-reference/system/freertos_additions.rst @@ -1,6 +1,8 @@ FreeRTOS (Supplemental Features) ================================ +:link_to_translation:`zh_CN:[中文]` + ESP-IDF provides multiple features to supplement the features offered by FreeRTOS. These supplemental features are available on all FreeRTOS implementations supported by ESP-IDF (i.e., ESP-IDF FreeRTOS and Amazon SMP FreeRTOS). This document describes these supplemental features and is split into the following sections: .. contents:: Contents @@ -19,7 +21,6 @@ ESP-IDF adds various new features to supplement the capabilities of FreeRTOS as - **IDF Additional API**: ESP-IDF specific functions added to augment the features of FreeRTOS. - **Component Specific Properties**: Currently added only one component specific property ``ORIG_INCLUDE_PATH``. - .. -------------------------------------------------- Ring Buffers ----------------------------------------------------- Ring Buffers @@ -31,13 +32,19 @@ FreeRTOS provides stream buffers and message buffers as the primary mechanisms t - Data is passed by copy - Unable to reserve buffer space for a deferred send (i.e., send acquire) -Therefore, ESP-IDF provides a separate ring buffer implementation to address the issues above. ESP-IDF ring buffers are strictly FIFO buffers 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. The ring buffer provides APIs to send an item, or to allocate space for an item in the ring buffer to be filled manually by the user. For efficiency reasons, **items are always retrieved from the ring buffer by reference**. As a result, all retrieved items **must also be returned** to the ring buffer by using :cpp:func:`vRingbufferReturnItem` or :cpp:func:`vRingbufferReturnItemFromISR`, in order for them to be removed from the ring buffer completely. The ring buffers are split into the three following types: +Therefore, ESP-IDF provides a separate ring buffer implementation to address the issues above. -**No-Split buffers** guarantee that an item is stored in contiguous memory and does 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 to get the data item address and write to the item by yourself.** Refer to the documentation of the functions :cpp:func:`xRingbufferSendAcquire` and :cpp:func:`xRingbufferSendComplete` for more details. +ESP-IDF ring buffers are strictly FIFO buffers 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. + +The ring buffer provides APIs to send an item, or to allocate space for an item in the ring buffer to be filled manually by the user. For efficiency reasons, **items are always retrieved from the ring buffer by reference**. As a result, all retrieved items **must also be returned** to the ring buffer by using :cpp:func:`vRingbufferReturnItem` or :cpp:func:`vRingbufferReturnItemFromISR`, 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** guarantee that an item is stored in contiguous memory and does not attempt to split an item under any circumstances. Use No-Split buffers when items must occupy contiguous memory. **Only this buffer type allows reserving buffer space for deferred sending.** Refer to the documentation of the functions :cpp:func:`xRingbufferSendAcquire` and :cpp:func:`xRingbufferSendComplete` for more details. **Allow-Split buffers** allow an item to be split in two parts when wrapping around the end of the buffer if there is enough space at the tail and the head of the buffer combined to store the item. 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 can be sent or retrieved each time. Use byte buffers when separate items do not need to be maintained (e.g., a byte stream). +**Byte buffers** do not store data as separate items. All data is stored as a sequence of bytes, and any number of bytes can be sent or retrieved each time. Use byte buffers when separate items do not need to be maintained, e.g., a byte stream. .. note:: @@ -45,12 +52,12 @@ Therefore, ESP-IDF provides a separate ring buffer implementation to address the .. note:: - Each item stored in No-Split or Allow-Split buffers **requires an additional 8 bytes for a header**. Item sizes are also 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 and Allow-Split buffers will also be rounded up when created. + Each item stored in No-Split or Allow-Split buffers **requires an additional 8 bytes for a header**. Item sizes are also rounded up to a 32-bit aligned size, i.e., multiple of 4 bytes. However the true item size is recorded within the header. The sizes of No-Split and 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 and then send an item to it. +The following example demonstrates the usage of :cpp:func:`xRingbufferCreate` and :cpp:func:`xRingbufferSend` to create a ring buffer and then send an item to it: .. code-block:: c @@ -72,7 +79,7 @@ The following example demonstrates the usage of :cpp:func:`xRingbufferCreate` an 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 acquire memory on the ring buffer (of type `RINGBUF_TYPE_NOSPLIT`) and then send an item to it. This adds one more step, but allows getting the address of the memory to write to, and writing to the memory yourself. +The following example demonstrates the usage of :cpp:func:`xRingbufferSendAcquire` and :cpp:func:`xRingbufferSendComplete` instead of :cpp:func:`xRingbufferSend` to acquire memory on the ring buffer (of type :cpp:enumerator:`RINGBUF_TYPE_NOSPLIT`) and then send an item to it. This adds one more step, but allows getting the address of the memory to write to, and writing to the memory yourself. .. code-block:: c @@ -190,16 +197,16 @@ The following example demonstrates retrieving and returning an item from a **byt } -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` +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`. .. note:: - Two calls to RingbufferReceive[UpTo][FromISR]() are required if the bytes wraps around the end of the ring buffer. + Two calls to ``RingbufferReceive[UpTo][FromISR]()`` are required if the bytes wraps around the end of the ring buffer. Sending to Ring Buffer ^^^^^^^^^^^^^^^^^^^^^^ -The following diagrams illustrate the differences between No-Split and Allow-Split buffers as compared to byte buffers with regard 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**. +The following diagrams illustrate the differences between No-Split and Allow-Split buffers as compared to byte buffers with regard to sending items or 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 or Allow-Split ring buffers @@ -252,7 +259,7 @@ Referring to the diagram above, the 16 bytes of free space at the tail of the bu :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). +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. @@ -271,7 +278,7 @@ Referring to the diagram above, the 16 bytes of free space at the tail of the bu Retrieving/Returning ^^^^^^^^^^^^^^^^^^^^ -The following diagrams illustrate the differences between No-Split and Allow-Split buffers as compared to byte buffers in retrieving and returning data. +The following diagrams illustrate the differences between No-Split and Allow-Split buffers as compared to 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 and Allow-Split ring buffers @@ -294,7 +301,7 @@ 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. +The following example demonstrates queue set usage with ring buffers: .. code-block:: c @@ -333,17 +340,16 @@ The following example demonstrates queue set usage with ring buffers. Ring Buffers with Static Allocation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The :cpp:func:`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 :cpp:func:`xRingbufferCreateStatic` to be initialized as a ring buffer. These blocks include the following: +The :cpp:func:`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 :cpp:func:`xRingbufferCreateStatic` to be initialized as a ring buffer. These blocks include the following: -- The ring buffer's data structure of type :cpp:type:`StaticRingbuffer_t` +- 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 and Allow-Split buffers. The manner in which these blocks are allocated depends on the users requirements (e.g., all blocks being statically declared, or dynamically allocated with specific capabilities such as external RAM). .. note:: - When deleting a ring buffer created via :cpp:func:`xRingbufferCreateStatic`, - the function :cpp:func:`vRingbufferDelete` will not free any of the memory blocks. This must be done manually by the user after :cpp:func:`vRingbufferDelete` is called. + When deleting a ring buffer created via :cpp:func:`xRingbufferCreateStatic`, the function :cpp:func:`vRingbufferDelete` will not free any of the memory blocks. This must be done manually by the user after :cpp:func:`vRingbufferDelete` is called. The code snippet below demonstrates a ring buffer being allocated entirely in external RAM. @@ -388,13 +394,13 @@ However, the FreeRTOS tick hook and idle hook have the following draw backs: - The FreeRTOS hooks are registered at compile time - Only one of each hook can be registered -- On multi-core targets, the FreeRTOS hooks are symmetric, meaning each CPU's tick interrupt and idle tasks ends up calling the same hook. +- On multi-core targets, the FreeRTOS hooks are symmetric, meaning each core's tick interrupt and idle tasks ends up calling the same hook Therefore, ESP-IDF tick and idle hooks are provided to supplement the features of FreeRTOS tick and idle hooks. The ESP-IDF hooks have the following features: - The hooks can be registered and deregistered at run-time -- Multiple hooks can be registered (with a maximum of 8 hooks of each type per CPU) -- On multi-core targets, the hooks can be asymmetric, meaning different hooks can be registered to each CPU +- Multiple hooks can be registered (with a maximum of 8 hooks of each type per core) +- On multi-core targets, the hooks can be asymmetric, meaning different hooks can be registered to each core ESP-IDF hooks can be registered and deregistered using the following APIs: @@ -417,13 +423,13 @@ ESP-IDF hooks can be registered and deregistered using the following APIs: TLSP Deletion Callbacks ----------------------- -Vanilla FreeRTOS provides a Thread Local Storage Pointers (TLSP) feature. These are pointers stored directly in the Task Control Block (TCB) of a particular task. TLSPs allow each task to have its own unique set of pointers to data structures. Vanilla FreeRTOS expects users to... +Vanilla FreeRTOS provides a Thread Local Storage Pointers (TLSP) feature. These are pointers stored directly in the Task Control Block (TCB) of a particular task. TLSPs allow each task to have its own unique set of pointers to data structures. Vanilla FreeRTOS expects users to: - set a task's TLSPs by calling :cpp:func:`vTaskSetThreadLocalStoragePointer` after the task has been created. - get a task's TLSPs by calling :cpp:func:`pvTaskGetThreadLocalStoragePointer` during the task's lifetime. - free the memory pointed to by the TLSPs before the task is deleted. -However, there can be instances where users may want the freeing of TLSP memory to be automatic. Therefore, ESP-IDF provides the additional feature of TLSP deletion callbacks. These user provided deletion callbacks are called automatically when a task is deleted, thus allowing the TLSP memory to be cleaned up without needing to add the cleanup logic explicitly to the code of every task. +However, there can be instances where users may want the freeing of TLSP memory to be automatic. Therefore, ESP-IDF provides the additional feature of TLSP deletion callbacks. These user-provided deletion callbacks are called automatically when a task is deleted, thus allowing the TLSP memory to be cleaned up without needing to add the cleanup logic explicitly to the code of every task. The TLSP deletion callbacks are set in a similar fashion to the TLSPs themselves. @@ -432,7 +438,7 @@ The TLSP deletion callbacks are set in a similar fashion to the TLSPs themselves When implementing TLSP callbacks, users should note the following: -- The callback **must never attempt to block or yield** and critical sections should be kept as short as possible +- The callback **must never attempt to block or yield** and critical sections should be kept as short as possible. - The callback is called shortly before a deleted task's memory is freed. Thus, the callback can either be called from :cpp:func:`vTaskDelete` itself, or from the idle task. .. --------------------------------------------- ESP-IDF Additional API ------------------------------------------------ @@ -442,7 +448,7 @@ When implementing TLSP callbacks, users should note the following: IDF Additional API ------------------ -The :component_file:`freertos/esp_additions/include/freertos/idf_additions.h` header contains FreeRTOS related helper functions added by ESP-IDF. Users can include this header via ``#include "freertos/idf_additions.h"``. +The :component_file:`freertos/esp_additions/include/freertos/idf_additions.h` header contains FreeRTOS-related helper functions added by ESP-IDF. Users can include this header via ``#include "freertos/idf_additions.h"``. .. ------------------------------------------ Component Specific Properties -------------------------------------------- diff --git a/docs/zh_CN/api-reference/system/freertos.rst b/docs/zh_CN/api-reference/system/freertos.rst index 7fbe7b967c..659223eb0c 100644 --- a/docs/zh_CN/api-reference/system/freertos.rst +++ b/docs/zh_CN/api-reference/system/freertos.rst @@ -126,7 +126,7 @@ ESP-IDF FreeRTOS FreeRTOS 附加功能 ------------------ -ESP-IDF 还为 FreeRTOS 提供了一些补充功能,如环形缓冲区、ESP-IDF 风格的 Tick 和 Idle 钩子、以及 TLSP 删除回调。要了解更多信息,请参见 :doc:`freertos_additions`。 +ESP-IDF 还为 FreeRTOS 提供了一些补充功能,如环形 buffer、ESP-IDF 风格的 tick 钩子和 idle 钩子、以及 TLSP 删除回调。要了解更多信息,请参见 :doc:`freertos_additions`。 .. _freertos-heap: diff --git a/docs/zh_CN/api-reference/system/freertos_additions.rst b/docs/zh_CN/api-reference/system/freertos_additions.rst index 36e4800fb4..0f8db58e71 100644 --- a/docs/zh_CN/api-reference/system/freertos_additions.rst +++ b/docs/zh_CN/api-reference/system/freertos_additions.rst @@ -1 +1,481 @@ -.. include:: ../../../en/api-reference/system/freertos_additions.rst \ No newline at end of file +FreeRTOS(附加功能) +================================ + +:link_to_translation:`en:[English]` + +ESP-IDF 为 FreeRTOS 提供了多种附加功能。这些附加功能适用于 ESP-IDF 支持的所有 FreeRTOS 实现,即 ESP-IDF FreeRTOS 和 Amazon SMP FreeRTOS。本文档介绍了这些附加功能,内容包括以下几个部分: + +.. contents:: 目录 + :depth: 2 + +.. ---------------------------------------------------- Overview ------------------------------------------------------- + +概述 +-------- + +ESP-IDF 为 FreeRTOS 提供了以下附加功能: + +- **环形 buffer**:FIFO 缓冲区,支持任意长度的数据项。 +- **ESP-IDF tick 钩子和 idle 钩子**:ESP-IDF 提供了多个自定义的 tick 钩子和 idle 钩子,相较于 FreeRTOS,支持的钩子数量更多且更灵活。 +- **线程本地存储指针 (TLSP) 删除回调**:当一个任务被删除时,TLSP 删除回调会自动运行,从而自动清理 TLSP。 +- **IDF 附加 API**:专用于 ESP-IDF 的附加函数,用于增强 FreeRTOS 的功能。 +- **组件专用功能**:目前只添加了一个专用于组件的功能,即 ``ORIG_INCLUDE_PATH``。 + +.. -------------------------------------------------- Ring buffers ----------------------------------------------------- + +环形 buffer +------------ + +FreeRTOS 提供了流 buffer 和消息 buffer,作为在任务和 ISR 之间发送任意大小数据的主要机制。然而,FreeRTOS 流 buffer 和消息 buffer 具有以下限制: + +- 仅支持单一的发送者和单一的接收者 +- 数据通过复制的方式进行传递 +- 无法为延迟发送(即发送获取)预留 buffer 空间 + +为此,ESP-IDF 提供了一个单独的环形 buffer 来解决上述问题。 + +ESP-IDF 环形 buffer 是一个典型的 FIFO buffer,支持任意大小的数据项。在数据项大小可变的情况下,环形 buffer 比 FreeRTOS 队列更节约内存,可以替代 FreeRTOS 队列使用。环形 buffer 的容量不是由可以存储的数据项数量衡量的,而是由用于存储数据项的内存量来衡量的。 + +环形 buffer 提供了 API 来发送数据项,或为环形 buffer 中的数据项分配空间,以便进行手动填充。为提高效率, **数据项都通过引用的方式从环形 buffer 中检索出来**。因此,所有检索出的数据项也 **必须** 通过 :cpp:func:`vRingbufferReturnItem` 或 :cpp:func:`vRingbufferReturnItemFromISR` 返回到环形 buffer,以便将其从环形 buffer 中完全移除。 + +环形 buffer 分为以下三种类型: + +**不可分割 buffer**:确保将一个数据项存储在连续的内存中,并且在任何情况下都不会尝试分割数据项。当数据项必须占用连续的内存时,请使用不可分割 buffer。 **仅不可分割 buffer 允许为延迟发送保留缓冲空间。** 更多信息请参考函数 :cpp:func:`xRingbufferSendAcquire` 和 :cpp:func:`xRingbufferSendComplete` 的文档。 + +**可分割 buffer**:当数据项在 buffer 末尾绕回时,如果 buffer 头部和尾部的总空间足够,则支持将一个数据项分成两部分进行存储。可分割 buffer 比不可分割 buffer 更节省内存,但在检索时可能会返回数据项的两个部分。 + +**字节 buffer**:不将数据存储为单独的数据项。所有数据都存储为字节序列,每次可以发送或检索任意大小的字节。当不需要单独维护数据项时,推荐使用字节 buffer,例如字节流。 + +.. note:: + + 不可分割 buffer 和可分割 buffer 在 32 位对齐地址上存储数据项。因此,在检索一个数据项时,数据项指针一定也是 32 位对齐的。这在向 DMA 发送数据时非常有用。 + +.. note:: + + 存储在不可分割或可分割 buffer 中的每个数据项 **需要额外的 8 字节用于标头**。数据项大小会向上取整为 32 位对齐大小,即 4 字节的倍数,实际的数据项大小则记录在标头中。不可分割和可分割 buffer 的大小在创建时也会向上取整。 + +使用方法 +^^^^^^^^^^ + +以下示例演示了如何使用 :cpp:func:`xRingbufferCreate` 和 :cpp:func:`xRingbufferSend` 来创建环形 buffer,并向其发送数据项: + +.. code-block:: c + + #include "freertos/ringbuf.h" + static char tx_item[] = "test_item"; + + ... + + //创建环形 buffer + RingbufHandle_t buf_handle; + buf_handle = xRingbufferCreate(1028, RINGBUF_TYPE_NOSPLIT); + if (buf_handle == NULL) { + printf("Failed to create ring buffer\n"); + } + + //发送一个数据项 + UBaseType_t res = xRingbufferSend(buf_handle, tx_item, sizeof(tx_item), pdMS_TO_TICKS(1000)); + if (res != pdTRUE) { + printf("Failed to send item\n"); + } + +以下示例演示了如何使用 :cpp:func:`xRingbufferSendAcquire` 和 :cpp:func:`xRingbufferSendComplete` 代替 :cpp:func:`xRingbufferSend` 来获取环形 buffer(:cpp:enumerator:`RINGBUF_TYPE_NOSPLIT` 类型)上的内存,然后向其发送一个数据项。虽然增加了一个步骤,但可以实现获取要写入内存的地址,并自行写入内存。 + +.. 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))) + + ... + + //为 DMA 描述符和相应的数据 buffer 检索空间 + //此步骤必须通过 SendAcquire 完成,否则,复制时地址可能会不同 + 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, + }; + //实际发送到环形 buffer 以供使用 + res = xRingbufferSendComplete(buf_handle, &item); + if (res != pdTRUE) { + printf("Failed to send item\n"); + } + +以下示例演示了使用 :cpp:func:`xRingbufferReceive` 和 :cpp:func:`vRingbufferReturnItem` 从 **不可分割环形 buffer** 中检索和返回数据项: + +.. code-block:: c + + ... + + //从不可分割环形 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"); + //返回数据项 + vRingbufferReturnItem(buf_handle, (void *)item); + } else { + //数据项检索失败 + printf("Failed to receive item\n"); + } + + +以下示例演示了使用 :cpp:func:`xRingbufferReceiveSplit` 和 :cpp:func:`vRingbufferReturnItem` 从 **可分割环形 buffer** 中检索和返回数据项: + +.. code-block:: c + + ... + + //从可分割环形 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)); + + //检查收到的数据项 + 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 { + //接收数据项失败 + printf("Failed to receive item\n"); + } + + +以下示例演示了使用 :cpp:func:`xRingbufferReceiveUpTo` 和 :cpp:func:`vRingbufferReturnItem` 从 **字节 buffer** 中检索和返回数据项: + +.. code-block:: c + + ... + + //从字节 buffer 中接收数据 + size_t item_size; + char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(1000), sizeof(tx_item)); + + //检查接收到的数据 + if (item != NULL) { + //Print item + for (int i = 0; i < item_size; i++) { + printf("%c", item[i]); + } + printf("\n"); + //返回数据项 + vRingbufferReturnItem(buf_handle, (void *)item); + } else { + //接收数据项失败 + printf("Failed to receive item\n"); + } + + +对于以上函数的 ISR 安全版本,请调用 :cpp:func:`xRingbufferSendFromISR`、 :cpp:func:`xRingbufferReceiveFromISR`、 :cpp:func:`xRingbufferReceiveSplitFromISR`、 :cpp:func:`xRingbufferReceiveUpToFromISR` 和 :cpp:func:`vRingbufferReturnItemFromISR`。 + +.. note:: + + 当字节在环形 buffer 的末端绕回时,需调用 ``RingbufferReceive[UpTo][FromISR]()`` 两次。 + +发送到环形 buffer +^^^^^^^^^^^^^^^^^^^^^^ + +以下图表将不可分割和可分割 buffer 与字节 buffer 进行对比,说明了三者在发送数据或数据项方面的差异。图表中,假设分别向 **128 字节的 buffer** 发送大小为 **18、3 和 27 字节** 的三个数据项: + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_non_byte_buf.diag + :caption: 向不可分割或可分割的环形 buffer 发送数据项 + :align: center + +对于不可分割和可分割 buffer,每个数据项前都有 8 字节标头信息。此外,为了保持整体的 32 位对齐,每个数据项占用的空间都会 **向上取整到最接近的 32 位对齐大小**。数据项的实际大小会记录在标头中,并在检索数据项时返回。 + +参考上图,18、3 和 27 字节的数据项分别 **向上取整为 20、4 和 28 字节**,然后在每个数据项前面添加一个 8 字节的标头。 + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_byte_buf.diag + :caption: 向字节 buffer 发送数据项 + :align: center + +字节 buffer 将数据视为一个字节序列,不会产引入任何额外开销,不添加标头信息。因此,发送到字节 buffer 的所有数据都会合并成一个数据项。 + +参考上图,18、3 和 27 字节的数据项被顺序写入字节 buffer,并 **合并成一个 48 字节的数据项**。 + +使用 SendAcquire 和 SendComplete +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +不可分割 buffer 中的数据项严格按照 FIFO 顺序通过 ``SendAcquire`` 获取,并且必须通过 ``SendComplete`` 发送到 buffer 以便访问。也可以发送或获取多个数据项,且无需严格遵照获取顺序,但接收数据项却必须遵循 FIFO。所以,如果不为最早获取的数据项调用 ``SendComplete``,就无法接收后续数据项。 + +以下图表说明了当 ``SendAcquire`` 和 ``SendComplete`` 顺序不同时的情形。一开始,已经有一个 16 字节的数据项发送到环形 buffer。然后调用 ``SendAcquire`` 在环形 buffer 上获取 20、8、24 字节的空间。 + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_acquire_complete.diag + :caption: 在不可分割环形 buffer 中 SendAcquire/SendComplete 数据项 + :align: center + +然后填充 buffer,按照 8、24、20 字节的顺序通过 ``SendComplete`` 将数据项发送到环形 buffer。当 8 字节和 24 字节的数据发送后,仍只能获取 16 字节的数据项。因此,如果不为 20 字节数据项调用 ``SendComplete``,就无法获取该数据项,也无法获取 20 字节后的数据项。 + +当 20 字节数据项最终发送完成后,就可以在 buffer 中最初的 16 字节数据项之后,按照 20、8、24 字节的顺序接收所有的三个数据项。 + +由于 ``SendAcquire`` 及 ``SendComplete`` 要求所获取的 buffer 必须是完整的(未包装的),故可分割 buffer 和字节 buffer 不支持上述调用操作。 + + +绕回 +^^^^^^^^^^^ + +以下图表说明了发送数据项需要绕回时,不可分割、可分割和字节 buffer 之间的差异。图表假设有一个 **128 字节的 buffer**,其中有 **56 字节的空闲空间可以绕回使用**,并发送了一个 **28 字节** 的数据项。 + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_no_split.diag + :caption: 在不可分割 buffer 中绕回 + :align: center + +不可分割 buffer **只在连续的空闲空间中存储数据项,在任何情况下都不分割数据项**。当 buffer 尾部的空闲空间不足以完全存储数据项及其标头时,尾部的空闲空间将被 **标记为虚拟数据**。然后,数据项将绕回并存储在 buffer 头部的空闲空间中。 + +参考上图, buffer 尾部的 16 字节空闲空间不足以存储 28 字节的数据项,因此,这 16 字节被标记为虚拟数据,然后将数据项写入了 buffer 头部的空闲空间中。 + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_allow_split.diag + :caption: 在可分割 buffer 中绕回 + :align: center + +当 buffer 尾部的空闲空间不足以存储数据项及其标头时,可分割 buffer 会尝试 **将数据项分割成两部分**。分割的的两部分数据项都将有自己的标头,因此会产生额外的 8 字节开销。 + +参考上图, buffer 尾部的 16 字节空闲空间不足以存储 28 字节的数据项。因此将数据项分割成两部分(8 字节和 20 字节),并将两部分写入 buffer。 + +.. note:: + + 可分割 buffer 将其分割好的两部分数据视为两个独立的数据项,因此不应调用 :cpp:func:`xRingbufferReceive`。需调用 :cpp:func:`xRingbufferReceiveSplit` 以线程安全的方式接收分割的两部分数据项。 + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_byte_buf.diag + :caption: 在字节 buffer 中绕回 + :align: center + +字节 buffer **将尽可能多的数据存储到 buffer 尾部的空闲空间** 中。剩余的数据会存储在 buffer 头部的空闲空间。在字节 buffer 中绕回不会产生任何额外开销。 + +参考上图,buffer 尾部的 16 字节空闲空间不足以完全存储 28 字节的数据,因此,将数据填入这 16 字节空闲空间后,剩余的 12 字节会被写入 buffer 头部的空闲空间。此时,buffer 包含两个独立的连续数据,并且每个连续数据都被字节 buffer 视为一个独立数据项。 + +检索/返回 +^^^^^^^^^^^^^^^^^^^^ + +以下图表说明了在检索和返回数据时,不可分割、可分割 buffer 和字节 buffer 之间的差异: + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_read_ret_non_byte_buf.diag + :caption: 在不可分割和可分割环形 buffer 中检索/返回数据项 + :align: center + +不可分割 buffer 和可分割 buffer 中的数据项 **按严格的 FIFO 顺序检索** 并 **必须返回**,以释放占用的空间。在返回之前可以检索多个数据项,且不必按照检索的顺序返回数据项。但是,释放空间必须按 FIFO 顺序进行,因此如果不返回最早检索的数据项,就无法释放后续数据项占用的空间。 + +参考上图, **16、20 和 8 字节的数据项按 FIFO 顺序被检索出来**。但是,这些数据项并不是按照被检索的顺序返回的。最先返回的是 20 字节的数据项,然后分别返回 8 字节和 16 字节的数据项。直到第一个数据项(即 16 字节的数据项)返回后,空间才会被释放。 + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_read_ret_byte_buf.diag + :caption: 在字节 buffer 中检索/返回数据 + :align: center + +字节 buffer **不允许在返回之前进行多次检索** (每次检索必须在下一次检索之前返回结果)。使用 :cpp:func:`xRingbufferReceive` 或 :cpp:func:`xRingbufferReceiveFromISR` 时,会检索所有连续存储的数据。使用 :cpp:func:`xRingbufferReceiveUpTo` 或 :cpp:func:`xRingbufferReceiveUpToFromISR` 可限制检索的最大字节数。由于每次检索后都必须返回,因此数据一返回就会释放空间。 + +参考上图, buffer 尾部 38 字节连续存储的数据被检索、返回和释放。然后,下一次调用 :cpp:func:`xRingbufferReceive` 或 :cpp:func:`xRingbufferReceiveFromISR` 时,buffer 将绕回并对头部的 30 字节连续存储数据进行同样的处理。 + +使用队列集的环形 buffer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +使用 :cpp:func:`xRingbufferAddToQueueSetRead` 可以将环形 buffer 添加到 FreeRTOS 队列集中,这样每次环形 buffer 接收一个数据项或数据时,队列集都会收到通知。添加到队列集后,每次从环形 buffer 检索数据项时都应该先调用 :cpp:func:`xQueueSelectFromSet`。要检查选定的队列集成员是否为环形 buffer,调用 :cpp:func:`xRingbufferCanRead`。 + +以下示例演示了如何使用包含环形 buffer 的队列集: + +.. code-block:: c + + #include "freertos/queue.h" + #include "freertos/ringbuf.h" + + ... + + //创建环形 buffer 和队列集 + RingbufHandle_t buf_handle = xRingbufferCreate(1028, RINGBUF_TYPE_NOSPLIT); + QueueSetHandle_t queue_set = xQueueCreateSet(3); + + //向队列集中添加环形 buffer + if (xRingbufferAddToQueueSetRead(buf_handle, queue_set) != pdTRUE) { + printf("Failed to add to queue set\n"); + } + + ... + + //阻塞队列集 + QueueSetMemberHandle_t member = xQueueSelectFromSet(queue_set, pdMS_TO_TICKS(1000)); + + //检查成员是否为环形 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); + + //处理数据项 + ... + + } else { + ... + } + +使用静态分配的环形 buffer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:cpp:func:`xRingbufferCreateStatic` 可用于创建具有特定内存需求的环形 buffer(如在外部 RAM 中分配的环形 buffer)。环形 buffer 使用的所有内存块都必须在创建之前手动分配,然后传递给 :cpp:func:`xRingbufferCreateStatic` 以初始化为环形 buffer。这些内存块中包括: + +- 环形 buffer 的数据结构类型 :cpp:type:`StaticRingbuffer_t`。 +- 环形 buffer 的存储区域,大小为 ``xBufferSize``。注意,对于不可分割和可分割 buffer,``xBufferSize`` 必须为 32 位对齐大小。 + +这些块的分配方式取决于具体的需求。例如,静态声明所有块,或动态分配为具有特定功能的块,如外部 RAM。 + +.. note:: + + 当删除通过 :cpp:func:`xRingbufferCreateStatic` 创建的环形 buffer 时,:cpp:func:`vRingbufferDelete` 函数不会释放任何内存块。释放内存必须在调用 :cpp:func:`vRingbufferDelete` 后手动完成。 + +下面的代码片段演示了一个完全在外部 RAM 中分配的环形 buffer: + +.. code-block:: c + + #include "freertos/ringbuf.h" + #include "freertos/semphr.h" + #include "esp_heap_caps.h" + + #define BUFFER_SIZE 400 //32 位对齐大小 + #define BUFFER_TYPE RINGBUF_TYPE_NOSPLIT + ... + + //将 环形 buffer 数据结构体和存储区分配到外部 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); + + //使用手动分配的内存创建环形 buffer + RingbufHandle_t handle = xRingbufferCreateStatic(BUFFER_SIZE, BUFFER_TYPE, buffer_storage, buffer_struct); + + ... + + //使用后删除环形 buffer + vRingbufferDelete(handle); + + //手动释放所有内存块 + free(buffer_struct); + free(buffer_storage); + + +.. ------------------------------------------- ESP-IDF Tick and Idle Hooks --------------------------------------------- + +ESP-IDF tick 钩子 和 idle 钩子 +---------------------------------- + +FreeRTOS 允许应用程序在编译时提供一个 tick 钩子和一个 idle 钩子: + +- FreeRTOS tick 钩子可以通过 :ref:`CONFIG_FREERTOS_USE_TICK_HOOK` 选项启用。应用程序必须提供 ``void vApplicationTickHook( void )`` 回调。 +- FreeRTOS idle 钩子可以通过 :ref:`CONFIG_FREERTOS_USE_IDLE_HOOK` 选项启用。应用程序必须提供 ``void vApplicationIdleHook( void )`` 回调。 + +然而,FreeRTOS tick 钩子和 idle 钩子有以下不足: + +- FreeRTOS 钩子是在编译时注册的 +- 每种钩子只能注册一个 +- 在多核目标芯片上,FreeRTOS 钩子是对称的,即每个内核的 tick 中断和 idle 任务最终都会调用同一个钩子 + +因此,ESP-IDF 提供了 tick 钩子和 idle 钩子来补充 FreeRTOS tick 和 idle 钩子的功能。ESP-IDF 钩子具有以下功能: + +- 钩子可以在运行时注册和注销 +- 可以注册多个钩子。每个内核中,同一类型的钩子最多可以注册 8 个 +- 在多核目标芯片上,钩子可以是不对称的,即可以为每个内核注册不同的钩子 + +使用以下 API 注册和注销 ESP-IDF 钩子: + +- 对于 tick 钩子: + + - 用 :cpp:func:`esp_register_freertos_tick_hook` 或 :cpp:func:`esp_register_freertos_tick_hook_for_cpu` 注册 + - 用 :cpp:func:`esp_deregister_freertos_tick_hook` 或 :cpp:func:`esp_deregister_freertos_tick_hook_for_cpu` 注销 + +- 对于 idle 钩子: + + - 使用 :cpp:func:`esp_register_freertos_idle_hook` 或 :cpp:func:`esp_register_freertos_idle_hook_for_cpu` 注册 + - 使用 :cpp:func:`esp_deregister_freertos_idle_hook` 或 :cpp:func:`esp_deregister_freertos_idle_hook_for_cpu` 注销 + +.. note:: + + 在 cache 被禁用时,tick 中断仍保持活动,因此任何 tick 钩子(FreeRTOS 或 ESP-IDF)函数都必须放在内部 RAM 中。请参考 :ref:`SPI flash API documentation ` 了解详情。 + +.. -------------------------------------------------- TLSP Callback ---------------------------------------------------- + +TLSP 删除回调 +----------------------- + +原生 FreeRTOS 提供了线程本地存储指针 (TLSP) 功能,这些指针直接存储在特定任务的任务控制块 (TCB) 中。TLSP 允许每个任务拥有自己的数据结构指针集合。在原生 FreeRTOS 中: + +- 在任务创建后,需调用 :cpp:func:`vTaskSetThreadLocalStoragePointer` 设置任务的 TLSP。 +- 在任务的生命周期中,需调用 :cpp:func:`pvTaskGetThreadLocalStoragePointer` 获取任务的 TLSP。 +- 在删除任务前,需释放 TLSP 指向的内存。 + +然而,为了能够欧自动释放 TLSP 内存,ESP-IDF 额外提供了 TLSP 删除回调功能。当删除任务时,这些删除回调函数会被自动调用,从而清除 TLSP 内存,无需在每个任务的代码中显式添加内存清除逻辑。 + +设置 TLSP 删除回调的方式与设置 TLSP 类似。 + +- :cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback` 设置了特定的 TLSP 及其关联的回调。 +- 调用原生 FreeRTOS 函数 :cpp:func:`vTaskSetThreadLocalStoragePointer` 只会将 TLSP 的关联删除回调设置为 `NULL`,也就是说,在任务删除期间不会调用该 TLSP 的回调。 + +在实现 TLSP 回调时,应注意以下几点: + +- 回调 **绝对不能尝试阻塞或让出**,并且应尽可能缩短临界区的时间。 +- 回调是在删除任务的内存即将被释放前调用的。因此,回调可以通过 :cpp:func:`vTaskDelete` 本身调用,也可以从空闲任务中调用。 + +.. --------------------------------------------- ESP-IDF Additional API ------------------------------------------------ + +.. _freertos-idf-additional-api: + +IDF 附加 API +------------------ + +:component_file:`freertos/esp_additions/include/freertos/idf_additions.h` 头文件包含了 ESP-IDF 添加的与 FreeRTOS 相关的辅助函数。通过 ``#include "freertos/idf_additions.h"`` 可添加此头文件。 + +.. ------------------------------------------ Component Specific Properties -------------------------------------------- + +组件专用功能 +----------------------------- + +除了基本 CMake 构建属性中提供的标准组件变量外,FreeRTOS 组件还提供了参数(目前只有一个参数)以简化与其他模块的集成: + +- `ORIG_INCLUDE_PATH` - 包含指向 freeRTOS 根包含文件夹的绝对路径。因此可以直接用 `#include "FreeRTOS.h"` 引用头文件,而无需使用 `#include "freertos/FreeRTOS.h"`。 + + +.. -------------------------------------------------- API Reference ---------------------------------------------------- + +API 参考 +------------- + +环形 buffer API +^^^^^^^^^^^^^^^ + +.. include-build-file:: inc/ringbuf.inc + +钩子 API +^^^^^^^^^ + +.. include-build-file:: inc/esp_freertos_hooks.inc + +附加 API +^^^^^^^^^^^^^^ + +.. include-build-file:: inc/idf_additions.inc diff --git a/docs/zh_CN/contribute/copyright-guide.rst b/docs/zh_CN/contribute/copyright-guide.rst index 1bb17c927a..0d3409485a 100644 --- a/docs/zh_CN/contribute/copyright-guide.rst +++ b/docs/zh_CN/contribute/copyright-guide.rst @@ -3,7 +3,7 @@ :link_to_translation:`en:[English]` -ESP-IDF 基于 :project_file:`the Apache License 2.0 `,并包含一些不同许可证下的第三方版权代码。要了解更多信息,请参考 :doc:`the list of copyrights and licenses <../../../COPYRIGHT>`。 +ESP-IDF 基于 :project_file:`the Apache License 2.0 `,并包含一些不同许可证下的第三方版权代码。要了解更多信息,请参考 :doc:`../../../COPYRIGHT`。 本页面介绍了如何在源代码中正确标注版权标头。ESP-IDF 使用 `Software Package Data Exchange (SPDX) `_ 格式,简短易读,能够方便自动化工具处理及进行版权检查。 @@ -46,7 +46,7 @@ ESP-IDF 的某些部分特意采用了限制性较小的许可证,方便在商 第三方许可证 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -受到不同许可证许可,经 Espressif Systems 修改,并包含在 ESP-IDF 中的代码不能使用 Apache License 2.0 进行许可,即便检查器可能提出此类建议。建议保留原有版权标头,并在前面添加 SPDX 标识。 +受到不同许可证许可,经乐鑫修改,并包含在 ESP-IDF 中的代码不能使用 Apache License 2.0 进行许可,即便检查器可能提出此类建议。建议保留原有版权标头,并在前面添加 SPDX 标识。 例如,对于一个由 "GNU General Public License v2.0 及以上" 许可证许可、John Doe 持有、且由 Espressif Systems 做出额外修改的代码文件,请按照如下示例提供标头:: diff --git a/docs/zh_CN/contribute/index.rst b/docs/zh_CN/contribute/index.rst index 0fd1b8ad33..d1c9b8eae9 100644 --- a/docs/zh_CN/contribute/index.rst +++ b/docs/zh_CN/contribute/index.rst @@ -19,7 +19,7 @@ * 要提交的代码是否符合 ESP-IDF :doc:`style-guide`? -* 是否安装了 ESP-IDF :doc:`pre-commit hook `? +* 是否安装了 ESP-IDF :doc:`pre-commit 钩子 `? * 代码文档是否符合 :doc:`documenting-code` 的要求?