mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-29 18:27:20 +02:00
Merge branch 'docs/uhci_programming_guide' into 'master'
docs(uhci): Added implementation for uart-dma (uhci) programming guide See merge request espressif/esp-idf!38663
This commit is contained in:
@ -22,8 +22,8 @@ typedef struct {
|
||||
uart_port_t uart_port; /*!< UART port that connect to UHCI controller */
|
||||
size_t tx_trans_queue_depth; /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background */
|
||||
size_t max_transmit_size; /*!< Maximum transfer size in one transaction, in bytes. This decides the number of DMA nodes will be used for each transaction */
|
||||
size_t max_receive_internal_mem; /*!< Maximum transfer size in one transaction, in bytes. Each DMA node can point to a maximum of 4096 bytes. This value determines the number of DMA nodes used for each transaction. When your transfer size is large enough, it is recommended to set this value greater than 4096 to facilitate efficient ping-pong operations, such as 10 * 1024. */
|
||||
size_t dma_burst_size; /*!< DMA burst size, in bytes */
|
||||
size_t max_receive_internal_mem; /*!< Internal DMA usage memory. Each DMA node can point to a maximum of x bytes (depends on chip). This value determines the number of DMA nodes used for each transaction. When your transfer size is large enough, it is recommended to set this value greater than x to facilitate efficient ping-pong operations, such as 2 * x. */
|
||||
size_t dma_burst_size; /*!< DMA burst size, in bytes. Set to 0 to disable data burst. Otherwise, use a power of 2. */
|
||||
size_t max_packet_receive; /*!< Max receive size, auto stop receiving after reach this value, only valid when `length_eof` set true */
|
||||
|
||||
struct {
|
||||
|
@ -203,7 +203,6 @@ static esp_err_t uhci_gdma_initialize(uhci_controller_handle_t uhci_ctrl, const
|
||||
// Initialize DMA RX channel
|
||||
gdma_channel_alloc_config_t rx_alloc_config = {
|
||||
.direction = GDMA_CHANNEL_DIRECTION_RX,
|
||||
.sibling_chan = uhci_ctrl->tx_dir.dma_chan,
|
||||
#if CONFIG_UHCI_ISR_CACHE_SAFE
|
||||
.flags.isr_cache_safe = true,
|
||||
#endif
|
||||
|
@ -109,7 +109,7 @@ BITSCRAMBLER_DOCS = ['api-reference/peripherals/bitscrambler.rst']
|
||||
|
||||
CLK_TREE_DOCS = ['api-reference/peripherals/clk_tree.rst']
|
||||
|
||||
UART_DOCS = ['api-reference/peripherals/uart.rst']
|
||||
UART_DOCS = ['api-reference/peripherals/uart.rst', 'api-reference/peripherals/uhci.rst']
|
||||
|
||||
SDMMC_DOCS = ['api-reference/peripherals/sdmmc_host.rst']
|
||||
|
||||
|
@ -149,6 +149,8 @@ INPUT = \
|
||||
$(PROJECT_PATH)/components/esp_driver_tsens/include/driver/temperature_sensor_etm.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uart.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uart_vfs.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uhci.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uhci_types.h \
|
||||
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_com.h \
|
||||
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_driver.h \
|
||||
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_mac.h \
|
||||
@ -270,6 +272,7 @@ INPUT = \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/efuse_hal.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/eth_types.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/lp_core_types.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/uhci_types.h \
|
||||
$(PROJECT_PATH)/components/heap/include/esp_heap_caps_init.h \
|
||||
$(PROJECT_PATH)/components/heap/include/esp_heap_caps.h \
|
||||
$(PROJECT_PATH)/components/heap/include/esp_heap_task_info.h \
|
||||
|
@ -18,6 +18,15 @@ Each UART controller is independently configurable with parameters such as baud
|
||||
|
||||
Additionally, the {IDF_TARGET_NAME} chip has one low-power (LP) UART controller. It is the cut-down version of regular UART. Usually, the LP UART controller only support basic UART functionality with a much smaller RAM size, and does not support IrDA or RS485 protocols. For a full list of difference between UART and LP UART, please refer to the **{IDF_TARGET_NAME} Technical Reference Manual** > **UART Controller (UART)** > **Features** [`PDF <{IDF_TARGET_TRM_EN_URL}#uart>`__]).
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
uhci
|
||||
|
||||
.. only:: SOC_UHCI_SUPPORTED
|
||||
|
||||
The {IDF_TARGET_NAME} chip also supports using DMA with UART. For details, see to :doc:`uhci`.
|
||||
|
||||
Functional Overview
|
||||
-------------------
|
||||
|
||||
|
299
docs/en/api-reference/peripherals/uhci.rst
Normal file
299
docs/en/api-reference/peripherals/uhci.rst
Normal file
@ -0,0 +1,299 @@
|
||||
UART DMA (UHCI)
|
||||
===============
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
This document describes the functionality of the UART DMA(UHCI) driver in ESP-IDF. The table of contents is as follows:
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This document shows how to use UART and DMA together for transmitting or receiving large data volumes using high baud rates. {IDF_TARGET_SOC_UART_HP_NUM} HP UART controllers on {IDF_TARGET_NAME} share one group of DMA TX/RX channels via host controller interface (HCI). This document assumes that UART DMA is controlled by UHCI entity.
|
||||
|
||||
.. note::
|
||||
|
||||
The UART DMA shares the HCI hardware with Bluetooth, so please don't use BT HCI together with UART DMA, even if they use different UART ports.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
This section will quickly guide you on how to use the UHCI driver. Through a simple example including transmitting and receiving, it demonstrates how to create and start a UHCI, initiate a transmit and receive transactions, and register event callback functions. The general usage process is as follows:
|
||||
|
||||
Creating and Enabling the UHCI controller
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
UHCI controller requires the configuration specified by :cpp:type:`uhci_controller_config_t`.
|
||||
|
||||
If the configurations in :cpp:type:`uhci_controller_config_t` is specified, users can call :cpp:func:`uhci_new_controller` to allocate and initialize a uhci controller. This function will return a uhci controller handle if it runs correctly. Besides, UHCI must work with the installed UART driver. As a reference, see the code below.
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define EX_UART_NUM 1 // Define UART port number
|
||||
|
||||
// For uart port configuration, please refer to UART programming guide.
|
||||
// Please double-check as the baud rate might be limited by serial port chips.
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 1 * 1000 * 1000,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
//UART parameter config
|
||||
ESP_ERROR_CHECK(uart_param_config(EX_UART_NUM, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_RX_IO, -1, -1));
|
||||
|
||||
uhci_controller_config_t uhci_cfg = {
|
||||
.uart_port = EX_UART_NUM, // Connect uart port to UHCI hardware.
|
||||
.tx_trans_queue_depth = 30, // Queue depth of transaction queue.
|
||||
.max_receive_internal_mem = 10 * 1024, // internal memory usage, for more information, please refer to API reference.
|
||||
.max_transmit_size = 10 * 1024, // Maximum transfer size in one transaction, in bytes.
|
||||
.dma_burst_size = 32, // Burst size.
|
||||
.rx_eof_flags.idle_eof = 1, // When to trigger a end of frame event, you can choose `idle_eof`, `rx_brk_eof`, `length_eof`, for more information, please refer to API reference.
|
||||
};
|
||||
|
||||
uhci_controller_handle_t uhci_ctrl;
|
||||
|
||||
ESP_ERROR_CHECK(uhci_new_controller(&uhci_cfg, &uhci_ctrl));
|
||||
|
||||
Register Event Callbacks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When an event occurs on the UHCI controller (e.g., transmission or receiving is completed), the CPU is notified of this event via an interrupt. If there is a function that needs to be called when a particular events occur, you can register a callback for that event with the ISR for UHCI (Interrupt Service Routine) by calling :cpp:func:`uhci_register_event_callbacks` for both TX and RX respectively. Since the registered callback functions are called in the interrupt context, the user should ensure that the callback function is non-blocking, e.g., by making sure that only FreeRTOS APIs with the ``FromISR`` suffix are called from within the function. The callback function has a boolean return value used to indicate whether a higher priority task has been unblocked by the callback.
|
||||
|
||||
The UHCI event callbacks are listed in the :cpp:type:`uhci_event_callbacks_t`:
|
||||
|
||||
- :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` sets a callback function for the "trans-done" event. The function prototype is declared in :cpp:type:`uhci_tx_done_callback_t`.
|
||||
|
||||
- :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` sets a callback function for "receive" event. The function prototype is declared in :cpp:type:`uhci_rx_event_callback_t`.
|
||||
|
||||
.. note::
|
||||
|
||||
The "rx-trans-event" is not equivalent to "receive-finished". This callback can also be called at a "partial-received" time, for many times during one receive transaction, which can be notified by :cpp:member:`uhci_rx_event_data_t::flags::totally_received`.
|
||||
|
||||
Users can save their own context in :cpp:func:`uhci_register_event_callbacks` as well, via the parameter ``user_data``. The user data is directly passed to each callback function.
|
||||
|
||||
In the callback function, users can fetch the event-specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is **only** valid during the callback, please do not try to save this pointer and use that outside of the callback function.
|
||||
|
||||
The TX event data is defined in :cpp:type:`uhci_tx_done_event_data_t`:
|
||||
|
||||
- :cpp:member:`uhci_tx_done_event_data_t::buffer` indicates the buffer has been sent out.
|
||||
|
||||
The RX event data is defined in :cpp:type:`uhci_rx_event_data_t`:
|
||||
|
||||
- :cpp:member:`uhci_rx_event_data_t::data` points to the received data. The data is saved in the ``buffer`` parameter of the :cpp:func:`uhci_receive` function. Users should not free this receive buffer before the callback returns.
|
||||
- :cpp:member:`uhci_rx_event_data_t::recv_size` indicates the number of received data. This value is not larger than the ``buffer_size`` parameter of :cpp:func:`uhci_receive` function.
|
||||
- :cpp:member:`uhci_rx_event_data_t::flags::totally_received` indicates whether the current received buffer is the last one in the transaction.
|
||||
|
||||
Initiating UHCI Transmission
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:cpp:func:`uhci_transmit` is a non-blocking function, which means this function will immediately return after you call it. The related callback can be obtained via :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` to indicate that the transaction is done. The function :cpp:func:`uhci_wait_all_tx_transaction_done` can be used to indicate that all transactions are finished.
|
||||
|
||||
Data can be transmitted via UHCI as follows:
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_wr[DATA_LENGTH];
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
data_wr[i] = i;
|
||||
}
|
||||
ESP_ERROR_CHECK(uhci_transmit(uhci_ctrl, data_wr, DATA_LENGTH));
|
||||
// Wait all transaction finishes
|
||||
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
|
||||
|
||||
Initiating UHCI Reception
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:cpp:func:`uhci_receive` is a non-blocking function, which means this function will immediately return after it is called. The related callback can be obtained via :cpp:member:`uhci_rx_event_data_t::recv_size` to indicate the receive event. It can be useful to determine if a transaction has been finished.
|
||||
|
||||
Data can be transmitted via UHCI as follows:
|
||||
|
||||
.. code:: c
|
||||
|
||||
// global variable: handle of queue.
|
||||
QueueHandle_t uhci_queue;
|
||||
|
||||
IRAM_ATTR static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
// parameter `user_ctx` is parsed by the third parameter of function `uhci_register_event_callbacks`
|
||||
uhci_context_t *ctx = (uhci_context_t *)user_ctx;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
uhci_event_t evt = 0;
|
||||
if (edata->flags.totally_received) {
|
||||
evt = UHCI_EVT_EOF;
|
||||
ctx->receive_size += edata->recv_size;
|
||||
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
|
||||
} else {
|
||||
evt = UHCI_EVT_PARTIAL_DATA;
|
||||
ctx->receive_size += edata->recv_size;
|
||||
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
|
||||
ctx->p_receive_data += edata->recv_size;
|
||||
}
|
||||
|
||||
xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
// In task
|
||||
uhci_event_callbacks_t uhci_cbs = {
|
||||
.on_rx_trans_event = s_uhci_rx_event_cbs,
|
||||
};
|
||||
|
||||
// Register callback and start reception.
|
||||
ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx));
|
||||
ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, 100));
|
||||
|
||||
uhci_event_t evt;
|
||||
while (1) {
|
||||
// A queue in task for receiving event triggered by UHCI.
|
||||
if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
|
||||
if (evt == UHCI_EVT_EOF) {
|
||||
printf("Received size: %d\n", ctx->receive_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
In the API :cpp:func:`uhci_receive` interface, the parameter `read_buffer` is a buffer that must be provided by the user, and parameter `buffer_size` represents the size of the buffer supplied by the user. In the configuration structure of the UHCI controller, the parameter :cpp:member:`uhci_controller_config_t::max_receive_internal_mem` specifies the desired size of the internal DMA working space. The software allocates a certain number of DMA nodes based on this working space size. These nodes form a circular linked list.
|
||||
|
||||
When a node is filled, but the reception has not yet completed, the event :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` will be triggered, accompanied by :cpp:member:`uhci_rx_event_data_t::flags::totally_received` set to 0. When all the data has been fully received, the :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` event will be triggered again with :cpp:member:`uhci_rx_event_data_t::flags::totally_received` set to 1.
|
||||
|
||||
This mechanism allows the user to achieve continuous and fast reception using a relatively small buffer, without needing to allocate a buffer the same size as the total data being received.
|
||||
|
||||
.. note::
|
||||
|
||||
The parameter `read_buffer` of :cpp:func:`uhci_receive` cannot be freed until receive finishes.
|
||||
|
||||
Uninstall UHCI controller
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If a previously installed UHCI controller is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`uhci_del_controller`, so that the underlying hardware is released.
|
||||
|
||||
.. code:: c
|
||||
|
||||
ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl));
|
||||
|
||||
Advanced Features
|
||||
-----------------
|
||||
|
||||
As the basic usage has been covered, it's time to explore more advanced features of the UHCI driver.
|
||||
|
||||
Power Management
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system may adjust or disable the clock source before going to sleep. As a result, the FIFO inside the UHCI can't work as expected.
|
||||
|
||||
The driver can prevent the above issue by creating a power management lock. The lock type is set based on different clock sources. The driver will acquire the lock in :cpp:func:`uhci_receive` or :cpp:func:`uhci_transmit`, and release it in the transaction-done interrupt. That means, any UHCI transactions between these two functions are guaranteed to work correctly and stably.
|
||||
|
||||
Cache Safe
|
||||
^^^^^^^^^^
|
||||
|
||||
By default, the interrupt on which UHCI relies is deferred when the Cache is disabled for reasons such as writing or erasing the main flash. Thus, the transaction-done interrupt fails to be handled in time, which is unacceptable in a real-time application. What is worse, when the UHCI transaction relies on **ping-pong** interrupt to successively encode or copy the UHCI buffer, a delayed interrupt can lead to an unpredictable result.
|
||||
|
||||
There is a Kconfig option :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` that has the following features:
|
||||
|
||||
1. Enable the interrupt being serviced even when the cache is disabled
|
||||
2. Place all functions used by the ISR into IRAM [1]_
|
||||
3. Place the driver object into DRAM in case it is mapped to PSRAM by accident
|
||||
|
||||
This Kconfig option allows the interrupt handler to run while the cache is disabled but comes at the cost of increased IRAM consumption.
|
||||
|
||||
Resource Consumption
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use the :doc:`/api-guides/tools/idf-size` tool to check the code and data consumption of the UHCI driver. The following are the test results under 2 different conditions (using ESP32-C3 as an example):
|
||||
|
||||
**Note that the following data are not exact values and are for reference only; they may differ on different chip models.**
|
||||
|
||||
Resource consumption when :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` is enabled:
|
||||
|
||||
.. list-table:: Resource Consumption
|
||||
:widths: 10 10 10 10 10 10 10 10 10
|
||||
:header-rows: 1
|
||||
|
||||
* - Component Layer
|
||||
- Total Size
|
||||
- DIRAM
|
||||
- .bss
|
||||
- .data
|
||||
- .text
|
||||
- Flash Code
|
||||
- Flash Data
|
||||
- .rodata
|
||||
* - UHCI
|
||||
- 5733
|
||||
- 680
|
||||
- 8
|
||||
- 34
|
||||
- 638
|
||||
- 4878
|
||||
- 175
|
||||
- 175
|
||||
|
||||
Resource consumption when :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` is disabled:
|
||||
|
||||
.. list-table:: Resource Consumption
|
||||
:widths: 10 10 10 10 10 10 10 10 10 10
|
||||
:header-rows: 1
|
||||
|
||||
* - Component Layer
|
||||
- Total Size
|
||||
- DIRAM
|
||||
- .bss
|
||||
- .data
|
||||
- .text
|
||||
- Flash Code
|
||||
- .text
|
||||
- Flash Data
|
||||
- .rodata
|
||||
* - UHCI
|
||||
- 5479
|
||||
- 42
|
||||
- 8
|
||||
- 34
|
||||
- 0
|
||||
- 5262
|
||||
- 5262
|
||||
- 175
|
||||
- 175
|
||||
|
||||
Performance
|
||||
^^^^^^^^^^^
|
||||
|
||||
To improve the real-time response capability of interrupt handling, the UHCI driver provides the :ref:`CONFIG_UHCI_ISR_HANDLER_IN_IRAM` option. Enabling this option will place the interrupt handler in internal RAM, reducing the latency caused by cache misses when loading instructions from Flash.
|
||||
|
||||
.. note::
|
||||
|
||||
However, user callback functions and context data called by the interrupt handler may still be located in Flash, and cache miss issues will still exist. Users need to place callback functions and data in internal RAM, for example, using :c:macro:`IRAM_ATTR` and :c:macro:`DRAM_ATTR`.
|
||||
|
||||
Thread Safety
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The factory function :cpp:func:`uhci_new_controller`, :cpp:func:`uhci_register_event_callbacks` and :cpp:func:`uhci_del_controller` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
|
||||
|
||||
Other Kconfig Options
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_UHCI_ENABLE_DEBUG_LOG` is allowed for the forced enabling of all debug logs for the UHCI driver, regardless of the global log level setting. Enabling this option can help developers obtain more detailed log information during the debugging process, making it easier to locate and resolve issues, but it will increase the size of the firmware binary.
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
|
||||
- :example:`peripherals/uart/uart_dma_ota` demonstrates how to use the uart dma for fast OTA the chip firmware with 1M baud rate speed.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/uhci.inc
|
||||
.. include-build-file:: inc/components/esp_driver_uart/include/driver/uhci_types.inc
|
||||
.. include-build-file:: inc/components/hal/include/hal/uhci_types.inc
|
||||
|
||||
.. [1]
|
||||
The callback function, e.g., :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done`, :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` and the functions invoked by itself should also reside in IRAM, users need to take care of this by themselves.
|
@ -18,6 +18,15 @@
|
||||
|
||||
此外,{IDF_TARGET_NAME} 芯片还有一个满足低功耗需求的 LP UART 控制器。LP UART 是原 UART 的功能剪裁版本。它只支持基础 UART 功能,不支持 IrDA 或 RS485 协议,并且只有一块较小的 RAM 存储空间。想要全面了解的 UART 及 LP UART 功能区别,请参考 **{IDF_TARGET_NAME} 技术参考手册** > UART 控制器 (UART) > 主要特性 [`PDF <{IDF_TARGET_TRM_EN_URL}#uart>`__]。
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
uhci
|
||||
|
||||
.. only:: SOC_UHCI_SUPPORTED
|
||||
|
||||
{IDF_TARGET_NAME} 芯片也支持 UART DMA 模式, 请参考 :doc:`uhci` 以获得更多信息.
|
||||
|
||||
功能概述
|
||||
-------------------
|
||||
|
||||
|
299
docs/zh_CN/api-reference/peripherals/uhci.rst
Normal file
299
docs/zh_CN/api-reference/peripherals/uhci.rst
Normal file
@ -0,0 +1,299 @@
|
||||
UART DMA (UHCI)
|
||||
===============
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
本文档描述了 ESP-IDF 中 UART DMA(UHCI)驱动的功能。目录如下:
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
概述
|
||||
------------
|
||||
|
||||
本文档将介绍如何将 UART 与 DMA 结合使用,以在高波特率下传输或接收大数据量。 {IDF_TARGET_NAME} 的 {IDF_TARGET_SOC_UART_HP_NUM} 个 UART 控制器通过主机控制接口(HCI)共享一组 DMA TX/RX 通道。在以下文档中,UHCI 是指控制 UART DMA 的实体。
|
||||
|
||||
.. note::
|
||||
|
||||
UART DMA 与 BT 共享 HCI 硬件,因此请勿同时使用 BT HCI 和 UART DMA,哪怕它们使用的是不同的 UART 端口。
|
||||
|
||||
快速入门
|
||||
-----------
|
||||
|
||||
本节将快速指导您如何使用 UHCI 驱动。通过一个简单的传输和接收示例,展示了如何创建和启动 UHCI、启动传输和接收事务以及注册事件回调函数。一般使用流程如下:
|
||||
|
||||
创建并启用 UHCI 控制器
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
UHCI 控制器需要通过 :cpp:type:`uhci_controller_config_t` 进行配置。
|
||||
|
||||
如果在 :cpp:type:`uhci_controller_config_t` 完成了配置,用户可以调用 :cpp:func:`uhci_new_controller` 来分配并初始化一个 UHCI 控制器。此函数如果运行正常,将返回一个 UHCI 控制器句柄。此外,UHCI 必须与已初始化的 UART 驱动程序一起工作。以下代码可供参考。
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define EX_UART_NUM 1 // 定义 UART 端口
|
||||
|
||||
// 关于 UART 端口配置项,请参考 UART 编程指南
|
||||
// 请注意波特率有可能受限于串口芯片
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 1 * 1000 * 1000,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
// UART 参数配置
|
||||
ESP_ERROR_CHECK(uart_param_config(EX_UART_NUM, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_RX_IO, -1, -1));
|
||||
|
||||
uhci_controller_config_t uhci_cfg = {
|
||||
.uart_port = EX_UART_NUM, // 将指定 UART 端口连接到 UHCI 硬件
|
||||
.tx_trans_queue_depth = 30, // 发送队列的队列深度
|
||||
.max_receive_internal_mem = 10 * 1024, // 内部接收内存大小,更多信息请参考 API 注释。
|
||||
.max_transmit_size = 10 * 1024, // 单次传输的最大传输量,单位是字节
|
||||
.dma_burst_size = 32, // 突发传输大小
|
||||
.rx_eof_flags.idle_eof = 1, // 结束帧的条件,用户可以选择 `idle_eof`, `rx_brk_eof` 和 `length_eof`, 关于更多信息请参考 API 注释.
|
||||
};
|
||||
|
||||
uhci_controller_handle_t uhci_ctrl;
|
||||
|
||||
ESP_ERROR_CHECK(uhci_new_controller(&uhci_cfg, &uhci_ctrl));
|
||||
|
||||
注册事件回调
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
当 UHCI 控制器上发生事件(例如传输或接收完成)时,CPU通过中断被通知此事件。如果在某些事件发生时需要调用特定函数,可以通过调用 :cpp:func:`uhci_register_event_callbacks` 为 TX 和 RX 方向分别注册回调。 由于注册的回调函数在中断上下文中调用,用户应确保回调函数不会阻塞,例如仅调用带有 `FromISR` 后缀的 FreeRTOS API。回调函数具有布尔返回值,指示回调是否解除了更高优先级任务的阻塞状态。
|
||||
|
||||
UHCI 事件回调在 :cpp:type:`uhci_event_callbacks_t` 中列出:
|
||||
|
||||
- :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` 为“传输完成”事件设置回调函数。函数原型声明为 :cpp:type:`uhci_tx_done_callback_t`。
|
||||
|
||||
- :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` 为“接收事件”设置回调函数。函数原型声明为 :cpp:type:`uhci_rx_event_callback_t`。
|
||||
|
||||
.. note::
|
||||
|
||||
“rx-trans-event” 事件并不等同于“接收完成”。在一次接收事务中,该回调函数也可能在“部分接收”时被多次调用,此时可以通过 :cpp:member:`uhci_rx_event_data_t::flags::totally_received` 标志区分“部分接收”和“接收完成”。
|
||||
|
||||
用户还可以通过 :cpp:func:`uhci_register_event_callbacks` 中的参数 `user_data` 保存自己的上下文。用户数据会直接传递给每个回调函数。
|
||||
|
||||
在回调函数中,用户可以获取由驱动填充的事件特定数据,该数据保存在 ``edata`` 中。注意, ``edata`` 指针仅在回调期间有效,请勿尝试保存该指针并在回调函数外部使用。
|
||||
|
||||
TX 事件数据在 :cpp:type:`uhci_tx_done_event_data_t` 中定义:
|
||||
|
||||
- :cpp:member:`uhci_tx_done_event_data_t::buffer` 表示 ``buffer`` 已经发送完成。
|
||||
|
||||
RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义:
|
||||
|
||||
- :cpp:member:`uhci_rx_event_data_t::data` 指向接收到的数据。数据保存在 :cpp:func:`uhci_receive` 函数的 ``buffer`` 参数中。用户在回调返回之前不应释放此接收缓冲区。
|
||||
- :cpp:member:`uhci_rx_event_data_t::recv_size` 表示接收到的数据大小。此值不会大于 :cpp:func:`uhci_receive` 函数的 ``buffer_size`` 参数。
|
||||
- :cpp:member:`uhci_rx_event_data_t::flags::totally_received` 指示当前接收缓冲区是否是事务中的最后一个。
|
||||
|
||||
启动 UHCI 传输
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:cpp:func:`uhci_transmit` 是一个非阻塞函数,这意味着在调用后会立即返回。您可以通过 :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` 相关回调指示事务完成。我们还提供了一个函数 :cpp:func:`uhci_wait_all_tx_transaction_done` 来阻塞线程,等待所有事务完成。
|
||||
|
||||
以下代码显示了如何通过 UHCI 接收数据:
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_wr[DATA_LENGTH];
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
data_wr[i] = i;
|
||||
}
|
||||
ESP_ERROR_CHECK(uhci_transmit(uhci_ctrl, data_wr, DATA_LENGTH));
|
||||
// 等待所有传输完成
|
||||
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
|
||||
|
||||
启动 UHCI 接收
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:cpp:func:`uhci_receive` 是一个非阻塞函数,这意味着该函数在调用后会立即返回。用户可以通过 :cpp:member:`uhci_rx_event_data_t::recv_size` 获取相关的回调,以指示接收事件并判断事务是否完成。
|
||||
|
||||
以下代码展示了如何通过 UHCI 传输数据:
|
||||
|
||||
.. code:: c
|
||||
|
||||
// 全局变量:队列的句柄
|
||||
QueueHandle_t uhci_queue;
|
||||
|
||||
IRAM_ATTR static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
// 参数 `user_ctx` 是由函数 `uhci_register_event_callbacks` 的第三个参数传递的。
|
||||
uhci_context_t *ctx = (uhci_context_t *)user_ctx;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
uhci_event_t evt = 0;
|
||||
if (edata->flags.totally_received) {
|
||||
evt = UHCI_EVT_EOF;
|
||||
ctx->receive_size += edata->recv_size;
|
||||
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
|
||||
} else {
|
||||
evt = UHCI_EVT_PARTIAL_DATA;
|
||||
ctx->receive_size += edata->recv_size;
|
||||
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
|
||||
ctx->p_receive_data += edata->recv_size;
|
||||
}
|
||||
|
||||
xQueueSendFromISR(uhci_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
// 在任务中
|
||||
uhci_event_callbacks_t uhci_cbs = {
|
||||
.on_rx_trans_event = s_uhci_rx_event_cbs,
|
||||
};
|
||||
|
||||
// 注册回调,并开始启动回收
|
||||
ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx));
|
||||
ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, 100));
|
||||
|
||||
uhci_event_t evt;
|
||||
while (1) {
|
||||
// 一个在任务中的队列用来接收 UHCI 抛出的事件
|
||||
if (xQueueReceive(uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
|
||||
if (evt == UHCI_EVT_EOF) {
|
||||
printf("Received size: %d\n", ctx->receive_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
在 API :cpp:func:`uhci_receive` 接口中,参数 ``read_buffer`` 是用户必须提供的缓冲区,参数 ``buffer_size`` 表示用户提供的缓冲区大小。在 UHCI 控制器的配置结构中,参数 :cpp:member:`uhci_controller_config_t::max_receive_internal_mem` 指定了内部 DMA 工作空间的期望大小。软件将根据此工作空间大小分配一定数量的 DMA 节点,这些节点形成一个循环链表。
|
||||
|
||||
当一个节点被填满,但接收尚未完成时,将触发 :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` 事件,且 :cpp:member:`uhci_rx_event_data_t::flags::totally_received` 的值为 0。 当所有数据接收完成时,该事件将再次被触发,并且 :cpp:member:`uhci_rx_event_data_t::flags::totally_received` 的值为 1。
|
||||
|
||||
此机制允许用户使用相对较小的缓冲区实现连续且快速的接收,而无需分配与接收总数据量相等大小的缓冲区。
|
||||
|
||||
.. note::
|
||||
|
||||
在接收完成之前,:cpp:func:`uhci_receive` 的参数 ``read_buffer`` 不可被释放。
|
||||
|
||||
卸载 UHCI 控制器
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
如果不再需要已安装的 UHCI 控制器,建议通过调用 :cpp:func:`uhci_del_controller` 回收资源,以释放底层硬件。
|
||||
|
||||
.. code:: c
|
||||
|
||||
ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl));
|
||||
|
||||
高级功能
|
||||
-----------------
|
||||
|
||||
在理解了基本用法后,我们可以进一步探索 UHCI 驱动的高级功能。
|
||||
|
||||
关于低功耗
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
当启用电源管理时(即开启 :ref:`CONFIG_PM_ENABLE`),系统在进入睡眠前可能会调整或禁用时钟源。因此,UHCI 内部的 FIFO 可能无法正常工作。
|
||||
|
||||
通过创建电源管理锁,驱动程序可以避免上述问题. 驱动会根据不同的时钟源设置锁的类型. 驱动程序将在 :cpp:func:`uhci_receive` 或 :cpp:func:`uhci_transmit` 中获取锁,并在事务完成中断中释放锁。这意味着,这两个函数之间的任何 UHCI 事务都能保证正常稳定运行。
|
||||
|
||||
缓存安全
|
||||
^^^^^^^^^^
|
||||
|
||||
默认情况下,当由于写入或擦除主 Flash 导致缓存被禁用时,UHCI 所依赖的中断会被延迟. 因此,事务完成中断可能无法及时处理,这在实时应用中是不可接受的。更糟糕的是,当 UHCI 事务依赖 **乒乓** 中断来连续编码或复制 UHCI 缓冲区时,延迟的中断可能会导致不可预测的结果。
|
||||
|
||||
通过启用 Kconfig 选项 :ref:`CONFIG_UHCI_ISR_CACHE_SAFE`,可实现以下功能:
|
||||
|
||||
1. 即使缓存被禁用,中断也能被服务。
|
||||
2. 将 ISR 使用的所有函数放入 IRAM [1]_
|
||||
3. 将驱动对象放入 DRAM,防止其意外映射到 PSRAM。
|
||||
|
||||
此选项允许中断处理程序在缓存禁用时运行,但代价是增加了 IRAM 的消耗。
|
||||
|
||||
资源消耗
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
使用 :doc:`/api-guides/tools/idf-size` 工具检查 UHCI 驱动的代码和数据消耗。以下是基于 ESP32-C3 的测试结果(仅供参考,不同芯片型号可能会有所不同):
|
||||
|
||||
**请注意以下数据仅供参考,不同芯片型号可能会有所不同.**
|
||||
|
||||
启用 :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` 时的资源消耗:
|
||||
|
||||
.. list-table:: 资源消耗
|
||||
:widths: 10 10 10 10 10 10 10 10 10
|
||||
:header-rows: 1
|
||||
|
||||
* - Component Layer
|
||||
- Total Size
|
||||
- DIRAM
|
||||
- .bss
|
||||
- .data
|
||||
- .text
|
||||
- Flash Code
|
||||
- Flash Data
|
||||
- .rodata
|
||||
* - UHCI
|
||||
- 5733
|
||||
- 680
|
||||
- 8
|
||||
- 34
|
||||
- 638
|
||||
- 4878
|
||||
- 175
|
||||
- 175
|
||||
|
||||
禁用 :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` 时的资源消耗:
|
||||
|
||||
.. list-table:: 资源消耗
|
||||
:widths: 10 10 10 10 10 10 10 10 10 10
|
||||
:header-rows: 1
|
||||
|
||||
* - Component Layer
|
||||
- Total Size
|
||||
- DIRAM
|
||||
- .bss
|
||||
- .data
|
||||
- .text
|
||||
- Flash Code
|
||||
- .text
|
||||
- Flash Data
|
||||
- .rodata
|
||||
* - UHCI
|
||||
- 5479
|
||||
- 42
|
||||
- 8
|
||||
- 34
|
||||
- 0
|
||||
- 5262
|
||||
- 5262
|
||||
- 175
|
||||
- 175
|
||||
|
||||
关于性能
|
||||
^^^^^^^^
|
||||
|
||||
为了提升中断处理的实时响应能力, UHCI 驱动提供了 :ref:`CONFIG_UHCI_ISR_HANDLER_IN_IRAM` 选项。启用该选项后,中断处理程序将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令时可能出现的缓存丢失带来的延迟。
|
||||
|
||||
.. note::
|
||||
|
||||
但是,中断处理程序调用的用户回调函数和用户上下文数据仍然可能位于 Flash 中,缓存缺失的问题还是会存在,这需要用户自己将回调函数和数据放入内部 RAM 中,比如使用 :c:macro:`IRAM_ATTR` 和 :c:macro:`DRAM_ATTR`。
|
||||
|
||||
线程安全
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
驱动程序保证工厂函数 :cpp:func:`uhci_new_controller`、:cpp:func:`uhci_register_event_callbacks` 和 :cpp:func:`uhci_del_controller` 的线程安全。这意味着用户可以从不同的 RTOS 任务中调用它们,而无需额外的锁保护。
|
||||
|
||||
其他 Kconfig 选项
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_UHCI_ENABLE_DEBUG_LOG` 选项允许强制启用 UHCI 驱动的所有调试日志,无论全局日志级别设置如何。启用此选项可以帮助开发人员在调试过程中获取更详细的日志信息,从而更容易定位和解决问题,但会增加固件二进制文件的大小。
|
||||
|
||||
应用示例
|
||||
--------------------
|
||||
|
||||
- :example:`peripherals/uart/uart_dma_ota` 演示了如何使用 UART DMA 以 1Mbps 波特率快速 OTA 更新芯片固件。
|
||||
|
||||
API 参考
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/uhci.inc
|
||||
.. include-build-file:: inc/components/esp_driver_uart/include/driver/uhci_types.inc
|
||||
.. include-build-file:: inc/components/hal/include/hal/uhci_types.inc
|
||||
|
||||
.. [1]
|
||||
回调函数(例如 :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` 、:cpp:member:`uhci_event_callbacks_t::on_rx_trans_event`)及其调用的函数也应驻留在 IRAM 中,用户需要自行注意这一点。
|
Reference in New Issue
Block a user