UART DMA (UHCI)
本文档描述了 ESP-IDF 中 UART DMA(UHCI)驱动的功能。目录如下:
概述
本文档将介绍如何将 UART 与 DMA 结合使用,以在高波特率下传输或接收大数据量。 ESP32-C6 的 2 个 UART 控制器通过主机控制接口(HCI)共享一组 DMA TX/RX 通道。在以下文档中,UHCI 是指控制 UART DMA 的实体。
备注
UART DMA 与 BT 共享 HCI 硬件,因此请勿同时使用 BT HCI 和 UART DMA,哪怕它们使用的是不同的 UART 端口。
快速入门
本节将快速指导您如何使用 UHCI 驱动。通过一个简单的传输和接收示例,展示了如何创建和启动 UHCI、启动传输和接收事务以及注册事件回调函数。一般使用流程如下:
创建并启用 UHCI 控制器
UHCI 控制器需要通过 uhci_controller_config_t
进行配置。
如果在 uhci_controller_config_t
完成了配置,用户可以调用 uhci_new_controller()
来分配并初始化一个 UHCI 控制器。此函数如果运行正常,将返回一个 UHCI 控制器句柄。此外,UHCI 必须与已初始化的 UART 驱动程序一起工作。以下代码可供参考。
#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通过中断被通知此事件。如果在某些事件发生时需要调用特定函数,可以通过调用 uhci_register_event_callbacks()
为 TX 和 RX 方向分别注册回调。 由于注册的回调函数在中断上下文中调用,用户应确保回调函数不会阻塞,例如仅调用带有 FromISR 后缀的 FreeRTOS API。回调函数具有布尔返回值,指示回调是否解除了更高优先级任务的阻塞状态。
UHCI 事件回调在 uhci_event_callbacks_t
中列出:
uhci_event_callbacks_t::on_tx_trans_done
为“传输完成”事件设置回调函数。函数原型声明为uhci_tx_done_callback_t
。uhci_event_callbacks_t::on_rx_trans_event
为“接收事件”设置回调函数。函数原型声明为uhci_rx_event_callback_t
。
备注
“rx-trans-event” 事件并不等同于“接收完成”。在一次接收事务中,该回调函数也可能在“部分接收”时被多次调用,此时可以通过 uhci_rx_event_data_t::flags::totally_received
标志区分“部分接收”和“接收完成”。
用户还可以通过 uhci_register_event_callbacks()
中的参数 user_data 保存自己的上下文。用户数据会直接传递给每个回调函数。
在回调函数中,用户可以获取由驱动填充的事件特定数据,该数据保存在 edata
中。注意, edata
指针仅在回调期间有效,请勿尝试保存该指针并在回调函数外部使用。
TX 事件数据在 uhci_tx_done_event_data_t
中定义:
uhci_tx_done_event_data_t::buffer
表示buffer
已经发送完成。
RX 事件数据在 uhci_rx_event_data_t
中定义:
uhci_rx_event_data_t::data
指向接收到的数据。数据保存在uhci_receive()
函数的buffer
参数中。用户在回调返回之前不应释放此接收缓冲区。uhci_rx_event_data_t::recv_size
表示接收到的数据大小。此值不会大于uhci_receive()
函数的buffer_size
参数。uhci_rx_event_data_t::flags::totally_received
指示当前接收缓冲区是否是事务中的最后一个。
启动 UHCI 传输
uhci_transmit()
是一个非阻塞函数,这意味着在调用后会立即返回。您可以通过 uhci_event_callbacks_t::on_tx_trans_done
相关回调指示事务完成。我们还提供了一个函数 uhci_wait_all_tx_transaction_done()
来阻塞线程,等待所有事务完成。
以下代码显示了如何通过 UHCI 接收数据:
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 接收
uhci_receive()
是一个非阻塞函数,这意味着该函数在调用后会立即返回。用户可以通过 uhci_rx_event_data_t::recv_size
获取相关的回调,以指示接收事件并判断事务是否完成。
以下代码展示了如何通过 UHCI 传输数据:
// 全局变量:队列的句柄
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 uhci_receive()
接口中,参数 read_buffer
是用户必须提供的缓冲区,参数 buffer_size
表示用户提供的缓冲区大小。在 UHCI 控制器的配置结构中,参数 uhci_controller_config_t::max_receive_internal_mem
指定了内部 DMA 工作空间的期望大小。软件将根据此工作空间大小分配一定数量的 DMA 节点,这些节点形成一个循环链表。
当一个节点被填满,但接收尚未完成时,将触发 uhci_event_callbacks_t::on_rx_trans_event
事件,且 uhci_rx_event_data_t::flags::totally_received
的值为 0。 当所有数据接收完成时,该事件将再次被触发,并且 uhci_rx_event_data_t::flags::totally_received
的值为 1。
此机制允许用户使用相对较小的缓冲区实现连续且快速的接收,而无需分配与接收总数据量相等大小的缓冲区。
备注
在接收完成之前,uhci_receive()
的参数 read_buffer
不可被释放。
卸载 UHCI 控制器
如果不再需要已安装的 UHCI 控制器,建议通过调用 uhci_del_controller()
回收资源,以释放底层硬件。
ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl));
高级功能
在理解了基本用法后,我们可以进一步探索 UHCI 驱动的高级功能。
关于低功耗
当启用电源管理时(即开启 CONFIG_PM_ENABLE),系统在进入睡眠前可能会调整或禁用时钟源。因此,UHCI 内部的 FIFO 可能无法正常工作。
通过创建电源管理锁,驱动程序可以避免上述问题. 驱动会根据不同的时钟源设置锁的类型. 驱动程序将在 uhci_receive()
或 uhci_transmit()
中获取锁,并在事务完成中断中释放锁。这意味着,这两个函数之间的任何 UHCI 事务都能保证正常稳定运行。
缓存安全
默认情况下,当由于写入或擦除主 Flash 导致缓存被禁用时,UHCI 所依赖的中断会被延迟. 因此,事务完成中断可能无法及时处理,这在实时应用中是不可接受的。更糟糕的是,当 UHCI 事务依赖 乒乓 中断来连续编码或复制 UHCI 缓冲区时,延迟的中断可能会导致不可预测的结果。
通过启用 Kconfig 选项 CONFIG_UHCI_ISR_CACHE_SAFE,可实现以下功能:
即使缓存被禁用,中断也能被服务。
将 ISR 使用的所有函数放入 IRAM [1]
将驱动对象放入 DRAM,防止其意外映射到 PSRAM。
此选项允许中断处理程序在缓存禁用时运行,但代价是增加了 IRAM 的消耗。
资源消耗
使用 IDF Size 工具检查 UHCI 驱动的代码和数据消耗。以下是基于 ESP32-C3 的测试结果(仅供参考,不同芯片型号可能会有所不同):
请注意以下数据仅供参考,不同芯片型号可能会有所不同.
启用 CONFIG_UHCI_ISR_CACHE_SAFE 时的资源消耗:
Component Layer |
Total Size |
DIRAM |
.bss |
.data |
.text |
Flash Code |
Flash Data |
.rodata |
---|---|---|---|---|---|---|---|---|
UHCI |
5733 |
680 |
8 |
34 |
638 |
4878 |
175 |
175 |
禁用 CONFIG_UHCI_ISR_CACHE_SAFE 时的资源消耗:
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 驱动提供了 CONFIG_UHCI_ISR_HANDLER_IN_IRAM 选项。启用该选项后,中断处理程序将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令时可能出现的缓存丢失带来的延迟。
备注
但是,中断处理程序调用的用户回调函数和用户上下文数据仍然可能位于 Flash 中,缓存缺失的问题还是会存在,这需要用户自己将回调函数和数据放入内部 RAM 中,比如使用 IRAM_ATTR
和 DRAM_ATTR
。
线程安全
驱动程序保证工厂函数 uhci_new_controller()
、uhci_register_event_callbacks()
和 uhci_del_controller()
的线程安全。这意味着用户可以从不同的 RTOS 任务中调用它们,而无需额外的锁保护。
其他 Kconfig 选项
CONFIG_UHCI_ENABLE_DEBUG_LOG 选项允许强制启用 UHCI 驱动的所有调试日志,无论全局日志级别设置如何。启用此选项可以帮助开发人员在调试过程中获取更详细的日志信息,从而更容易定位和解决问题,但会增加固件二进制文件的大小。
应用示例
peripherals/uart/uart_dma_ota 演示了如何使用 UART DMA 以 1Mbps 波特率快速 OTA 更新芯片固件。
API 参考
Header File
This header file can be included with:
#include "driver/uhci.h"
This header file is a part of the API provided by the
esp_driver_uart
component. To declare that your component depends onesp_driver_uart
, add the following to your CMakeLists.txt:REQUIRES esp_driver_uart
or
PRIV_REQUIRES esp_driver_uart
Functions
-
esp_err_t uhci_new_controller(const uhci_controller_config_t *config, uhci_controller_handle_t *ret_uhci_ctrl)
Create and initialize a new UHCI controller.
This function initializes a new UHCI controller instance based on the provided configuration. It allocates and configures resources required for the UHCI controller, such as DMA and communication settings. The created controller handle is returned through the output parameter.
- 参数:
config -- [in] Pointer to a
uhci_controller_config_t
structure containing the configuration parameters for the UHCI controller.ret_uhci_ctrl -- [out] Pointer to a variable where the handle to the newly created UHCI controller will be stored. This handle is used in subsequent operations involving the controller.
- 返回:
ESP_OK
: Controller successfully created and initialized.ESP_ERR_INVALID_ARG
: One or more arguments are invalid (e.g., null pointers or invalid config).ESP_ERR_NO_MEM
: Memory allocation for the controller failed.Other error codes: Indicate failure in the underlying hardware or driver initialization.
-
esp_err_t uhci_receive(uhci_controller_handle_t uhci_ctrl, uint8_t *read_buffer, size_t buffer_size)
Receive data from the UHCI controller.
This function retrieves data from the UHCI controller into the provided buffer. It is typically used for receiving data that was transmitted via UART and processed by the UHCI DMA controller.
备注
备注
The function is non-blocking, it just mounts the user buffer to the DMA. The return from the function doesn't mean a finished receive. You need to register corresponding callback function to get notification.
- 参数:
uhci_ctrl -- [in] Handle to the UHCI controller, which was previously created using
uhci_new_controller()
.read_buffer -- [out] Pointer to the buffer where the received data will be stored. The buffer must be pre-allocated by the caller.
buffer_size -- [in] The size of read buffer.
- 返回:
ESP_OK
: Data successfully received and written to the buffer.ESP_ERR_INVALID_ARG
: Invalid arguments (e.g., null buffer or invalid controller handle).
-
esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffer, size_t write_size)
Transmit data using the UHCI controller.
This function sends data from the provided buffer through the UHCI controller. It uses the DMA capabilities of UHCI to efficiently handle data transmission via UART.
备注
The function is an non-blocking api, which means this function will return immediately. You can get corresponding event from callbacks.
- 参数:
uhci_ctrl -- [in] Handle to the UHCI controller, which was previously created using
uhci_new_controller()
.write_buffer -- [in] Pointer to the buffer containing the data to be transmitted. The buffer must remain valid until the transmission is complete.
write_size -- [in] The number of bytes to transmit from the buffer.
- 返回:
ESP_OK
: Data successfully queued for transmission.ESP_ERR_INVALID_ARG
: Invalid arguments (e.g., null buffer, invalid handle, or zerowrite_size
).
-
esp_err_t uhci_del_controller(uhci_controller_handle_t uhci_ctrl)
Uninstall the UHCI (UART Host Controller Interface) driver and release resources.
This function deinitializes the UHCI controller and frees any resources allocated during its initialization. It ensures proper cleanup and prevents resource leaks when the UHCI controller is no longer needed.
- 参数:
uhci_ctrl -- [in] Handle to the UHCI controller, which was previously created using
uhci_new_controller()
. Passing an invalid or uninitialized handle may result in undefined behavior.- 返回:
ESP_OK
: The UHCI driver was successfully uninstalled, and resources were released.ESP_ERR_INVALID_ARG
: The provideduhci_ctrl
handle is invalid or null.
-
esp_err_t uhci_register_event_callbacks(uhci_controller_handle_t uhci_ctrl, const uhci_event_callbacks_t *cbs, void *user_data)
Register event callback functions for a UHCI controller.
This function allows the user to register callback functions to handle specific UHCI events, such as transmission or reception completion. The callbacks provide a mechanism to handle asynchronous events generated by the UHCI controller.
- 参数:
uhci_ctrl -- [in] Handle to the UHCI controller, which was previously created using
uhci_new_controller()
.cbs -- [in] Pointer to a
uhci_event_callbacks_t
structure that defines the callback functions to be registered. This structure includes pointers to the callback functions for handling UHCI events.user_data -- [in] Pointer to user-defined data that will be passed to the callback functions when they are invoked. This can be used to provide context or state information specific to the application.
- 返回:
ESP_OK
: Event callbacks were successfully registered.ESP_ERR_INVALID_ARG
: Invalid arguments (e.g., nulluhci_ctrl
handle orcbs
pointer).
-
esp_err_t uhci_wait_all_tx_transaction_done(uhci_controller_handle_t uhci_ctrl, int timeout_ms)
Wait for all pending TX transactions done.
- 参数:
uhci_ctrl -- [in] UHCI controller that created by
uhci_new_controller
timeout_ms -- [in] Timeout in milliseconds,
-1
means to wait forever
- 返回:
ESP_OK: All pending TX transactions is finished and recycled
ESP_ERR_INVALID_ARG: Wait for all pending TX transactions done failed because of invalid argument
ESP_ERR_TIMEOUT: Wait for all pending TX transactions done timeout
ESP_FAIL: Wait for all pending TX transactions done failed because of other error
Structures
-
struct uhci_controller_config_t
UHCI controller specific configurations.
Public Members
-
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
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
-
uint16_t rx_brk_eof
UHCI will end payload receive process when NULL frame is received by UART.
-
uint16_t idle_eof
UHCI will end payload receive process when UART has been in idle state.
-
uint16_t length_eof
UHCI will end payload receive process when the receiving byte count has reached the specific value.
-
struct uhci_controller_config_t rx_eof_flags
UHCI eof flags
-
uart_port_t uart_port
-
struct uhci_event_callbacks_t
Structure for defining callback functions for UHCI events.
Public Members
-
uhci_rx_event_callback_t on_rx_trans_event
Callback function for handling the completion of a reception.
-
uhci_tx_done_callback_t on_tx_trans_done
Callback function for handling the completion of a transmission.
-
uhci_rx_event_callback_t on_rx_trans_event
Header File
This header file can be included with:
#include "driver/uhci_types.h"
This header file is a part of the API provided by the
esp_driver_uart
component. To declare that your component depends onesp_driver_uart
, add the following to your CMakeLists.txt:REQUIRES esp_driver_uart
or
PRIV_REQUIRES esp_driver_uart
Structures
-
struct uhci_tx_done_event_data_t
UHCI TX Done Event Data.
-
struct uhci_rx_event_data_t
UHCI RX Done Event Data Structure.
Public Members
-
uint8_t *data
Pointer to the received data buffer
-
size_t recv_size
Number of bytes received
-
uint32_t totally_received
When callback is invoked, while this bit is not set, means the current event gives partial of whole data, the transaction has not been finished. If set, means the current event gives whole data, the transaction finished.
-
struct uhci_rx_event_data_t flags
I2C master config flags
-
uint8_t *data
Type Definitions
-
typedef struct uhci_controller_t *uhci_controller_handle_t
UHCI Controller Handle Type.
-
typedef bool (*uhci_tx_done_callback_t)(uhci_controller_handle_t uhci_ctrl, const uhci_tx_done_event_data_t *edata, void *user_ctx)
UHCI TX Done Callback Function Type.
- Param uhci_ctrl:
Handle to the UHCI controller that initiated the transmission.
- Param edata:
Pointer to a structure containing event data related to the completed transmission. This structure provides details such as the number of bytes transmitted and any status information relevant to the operation.
- Param user_ctx:
User-defined context passed during the callback registration. It can be used to maintain application-specific state or data.
- Return:
Whether a high priority task has been waken up by this callback function
-
typedef bool (*uhci_rx_event_callback_t)(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx)
UHCI RX Done Callback Function Type.
- Param uhci_ctrl:
Handle to the UHCI controller that initiated the transmission.
- Param edata:
Pointer to a structure containing event data related to receive event. This structure provides details such as the number of bytes received and any status information relevant to the operation.
- Param user_ctx:
User-defined context passed during the callback registration. It can be used to maintain application-specific state or data.
- Return:
Whether a high priority task has been waken up by this callback function
Header File
This header file can be included with:
#include "hal/uhci_types.h"
Structures
-
struct uhci_seper_chr_t
UHCI escape sequence.
-
struct uhci_swflow_ctrl_sub_chr_t
UHCI software flow control.
Public Members
-
uint8_t xon_chr
character for XON
-
uint8_t xon_sub1
sub-character 1 for XON
-
uint8_t xon_sub2
sub-character 2 for XON
-
uint8_t xoff_chr
character 2 for XOFF
-
uint8_t xoff_sub1
sub-character 1 for XOFF
-
uint8_t xoff_sub2
sub-character 2 for XOFF
-
uint8_t flow_en
enable use of software flow control
-
uint8_t xon_chr