Merge branch 'feat/uhci_send_multi_buffer' into 'master'

feat(uhci): support transmit multi buffer

Closes IDF-15871

See merge request espressif/esp-idf!50446
This commit is contained in:
morris
2026-07-15 16:41:11 +08:00
7 changed files with 294 additions and 40 deletions

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -19,7 +19,8 @@ extern "C" {
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_transmit_size; /*!< Maximum transfer size in one transaction, in bytes. Note that this is the total size of all buffers combined */
size_t max_transmit_buffer_count; /*!< Maximum number of buffers that can be transmitted together in one transaction, via `uhci_multi_buffer_transmit()`. Set to 0 or 1 if only single-buffer transmit (`uhci_transmit()`) is needed. */
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 */
@@ -39,6 +40,14 @@ typedef struct {
uhci_tx_done_callback_t on_tx_trans_done; /*!< Callback function for handling the completion of a transmission. */
} uhci_event_callbacks_t;
/**
* @brief One buffer segment used by `uhci_multi_buffer_transmit()`
*/
typedef struct {
const uint8_t *write_buffer; /*!< Pointer to this buffer segment. Must remain valid until the transmission is complete. */
size_t buffer_size; /*!< Size of this buffer segment, in bytes */
} uhci_transmit_buffer_info_t;
/**
* @brief Create and initialize a new UHCI controller.
*
@@ -93,16 +102,43 @@ esp_err_t uhci_receive(uhci_controller_handle_t uhci_ctrl, uint8_t *read_buffer,
* @param[in] write_buffer Pointer to the buffer containing the data to be transmitted.
* The buffer must remain valid until the transmission is complete.
* @param[in] write_size The number of bytes to transmit from the buffer.
* Must not exceed `uhci_controller_config_t.max_transmit_size`.
*
* @note The function is an non-blocking api, which means this function will return immediately. You can
* get corresponding event from callbacks.
*
* @return
* - `ESP_OK`: Data successfully queued for transmission.
* - `ESP_ERR_INVALID_ARG`: Invalid arguments (e.g., null buffer, invalid handle, or zero `write_size`).
* - `ESP_ERR_INVALID_ARG`: Invalid arguments (e.g., null buffer, invalid handle, zero `write_size`, or
* `write_size` exceeds `max_transmit_size`).
* - `ESP_ERR_INVALID_STATE`: No free transaction descriptor available.
*/
esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffer, size_t write_size);
/**
* @brief Transmit several discontinuous buffers as a single UHCI transaction
*
* Unlike `uhci_transmit()`, this accepts an array of buffer segments instead of a single
* contiguous buffer. For `array_size > 1`, the buffers are assembled into one DMA link list in
* the given order (only the last segment is marked EOF).
*
* @note All buffer segments must remain valid until the transmission is complete (same contract as
* `uhci_transmit()`).
*
* @param[in] uhci_ctrl Handle to the UHCI controller, which was previously created using
* `uhci_new_controller()`.
* @param[in] buffer_info_array Array of buffer segments to transmit, in order. The combined size of
* all buffers must not exceed `uhci_controller_config_t.max_transmit_size`.
* @param[in] array_size Number of entries in `buffer_info_array`. Must not exceed `max_transmit_buffer_count`.
*
* @return
* - `ESP_OK`: Data successfully queued for transmission.
* - `ESP_ERR_INVALID_ARG`: Invalid arguments, `array_size` exceeds `max_transmit_buffer_count`, or the
* combined size of all buffers exceeds `max_transmit_size`.
* - `ESP_ERR_INVALID_STATE`: No free transaction descriptor available.
*/
esp_err_t uhci_multi_buffer_transmit(uhci_controller_handle_t uhci_ctrl, const uhci_transmit_buffer_info_t *buffer_info_array, size_t array_size);
/**
* @brief Uninstall the UHCI (UART Host Controller Interface) driver and release resources.
*

View File

@@ -27,7 +27,11 @@ typedef struct uhci_controller_t *uhci_controller_handle_t;
* @brief UHCI TX Done Event Data
*/
typedef struct {
uint8_t *buffer; /**< Pointer to the which data buffer has been finished the transaction */
uint8_t *buffer; /**< Pointer to the which data buffer has been finished the transaction.
When the transaction was submitted with more than one discontinuous buffer segment
(via `uhci_multi_buffer_transmit()`), this only points to the first segment and
should be treated as an identifying handle for the transaction, not as
the start of a `sent_size`-byte contiguous region. */
size_t sent_size; /**< Size has been sent out */
} uhci_tx_done_event_data_t;

View File

@@ -59,11 +59,14 @@ static bool uhci_gdma_tx_callback_eof(gdma_channel_handle_t dma_chan, gdma_event
BaseType_t do_yield = pdFALSE;
uhci_controller_handle_t uhci_ctrl = (uhci_controller_handle_t) user_data;
uhci_transaction_desc_t *trans_desc = NULL;
uhci_tx_done_event_data_t evt_data = {0};
bool need_yield = false;
uhci_tx_fsm_t expected_fsm = UHCI_TX_FSM_RUN;
if (atomic_compare_exchange_strong(&uhci_ctrl->tx_dir.tx_fsm, &expected_fsm, UHCI_TX_FSM_ENABLE_WAIT)) {
trans_desc = uhci_ctrl->tx_dir.cur_trans;
evt_data.buffer = (uint8_t *)trans_desc->buf_info[0].write_buffer;
evt_data.sent_size = trans_desc->total_size;
xQueueSendFromISR(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_COMPLETE], &trans_desc, &do_yield);
if (do_yield) {
need_yield = true;
@@ -78,10 +81,6 @@ static bool uhci_gdma_tx_callback_eof(gdma_channel_handle_t dma_chan, gdma_event
#endif
if (uhci_ctrl->tx_dir.on_tx_trans_done) {
uhci_tx_done_event_data_t evt_data = {
.buffer = uhci_ctrl->tx_dir.cur_trans->buffer,
.sent_size = uhci_ctrl->tx_dir.cur_trans->buffer_size,
};
if (uhci_ctrl->tx_dir.on_tx_trans_done(uhci_ctrl, &evt_data, uhci_ctrl->user_data)) {
need_yield |= true;
}
@@ -214,7 +213,11 @@ static esp_err_t uhci_gdma_initialize(uhci_controller_handle_t uhci_ctrl, const
// create DMA link list
gdma_get_alignment_constraints(uhci_ctrl->tx_dir.dma_chan, &uhci_ctrl->tx_dir.int_mem_align, &uhci_ctrl->tx_dir.ext_mem_align);
size_t buffer_alignment = UHCI_MAX(uhci_ctrl->tx_dir.int_mem_align, uhci_ctrl->tx_dir.ext_mem_align);
size_t num_dma_nodes = esp_dma_calculate_node_count(config->max_transmit_size, buffer_alignment, DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
// Given that the combined size of all buffers does not exceed `max_transmit_size` and
// the number of buffers does not exceed `max_transmit_buffer_count`, a single transfer
// requires at most `esp_dma_calculate_node_count(max_transmit_size) + max_transmit_buffer_count - 1` DMA descriptors.
size_t extra_multi_buf_nodes = (config->max_transmit_buffer_count > 1) ? (config->max_transmit_buffer_count - 1) : 0;
size_t num_dma_nodes = esp_dma_calculate_node_count(config->max_transmit_size, buffer_alignment, DMA_DESCRIPTOR_BUFFER_MAX_SIZE) + extra_multi_buf_nodes;
gdma_link_list_config_t dma_link_config = {
.item_alignment = 4,
.num_items = num_dma_nodes,
@@ -282,16 +285,24 @@ static esp_err_t uhci_gdma_deinitialize(uhci_controller_handle_t uhci_ctrl)
static void uhci_do_transmit(uhci_controller_handle_t uhci_ctrl, uhci_transaction_desc_t *trans)
{
uhci_ctrl->tx_dir.cur_trans = trans;
size_t buffer_alignment = esp_ptr_internal(trans->buffer) ? uhci_ctrl->tx_dir.int_mem_align : uhci_ctrl->tx_dir.ext_mem_align;
gdma_buffer_mount_config_t mount_config = {
.buffer = trans->buffer,
.buffer_alignment = buffer_alignment,
.length = trans->buffer_size,
.flags = {
.mark_eof = true,
.mark_final = GDMA_FINAL_LINK_TO_NULL,
}
};
size_t buf_count = trans->buf_info_count;
gdma_buffer_mount_config_t *mount_configs = uhci_ctrl->tx_dir.mount_configs;
for (size_t i = 0; i < buf_count; i++) {
bool is_last = (i == buf_count - 1);
size_t buffer_alignment = esp_ptr_internal(trans->buf_info[i].write_buffer) ? uhci_ctrl->tx_dir.int_mem_align : uhci_ctrl->tx_dir.ext_mem_align;
mount_configs[i] = (gdma_buffer_mount_config_t) {
.buffer = (void *)trans->buf_info[i].write_buffer,
.buffer_alignment = buffer_alignment,
.length = trans->buf_info[i].buffer_size,
.flags = {
// Only the last buffer segment is marked EOF/final, so the whole set of segments is
// seen by UHCI as a single transaction.
.mark_eof = is_last,
.mark_final = is_last ? GDMA_FINAL_LINK_TO_NULL : GDMA_FINAL_LINK_TO_DEFAULT,
}
};
}
#if CONFIG_PM_ENABLE
// acquire power manager lock
@@ -300,7 +311,7 @@ static void uhci_do_transmit(uhci_controller_handle_t uhci_ctrl, uhci_transactio
}
#endif
gdma_link_mount_buffers(uhci_ctrl->tx_dir.dma_link, 0, &mount_config, 1, NULL);
gdma_link_mount_buffers(uhci_ctrl->tx_dir.dma_link, 0, mount_configs, buf_count, NULL);
gdma_start(uhci_ctrl->tx_dir.dma_chan, gdma_link_get_head_addr(uhci_ctrl->tx_dir.dma_link));
}
@@ -386,23 +397,43 @@ esp_err_t uhci_receive(uhci_controller_handle_t uhci_ctrl, uint8_t *read_buffer,
return ESP_OK;
}
esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffer, size_t write_size)
esp_err_t uhci_multi_buffer_transmit(uhci_controller_handle_t uhci_ctrl, const uhci_transmit_buffer_info_t *buffer_info_array, size_t array_size)
{
ESP_RETURN_ON_FALSE(uhci_ctrl, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE((write_buffer != NULL), ESP_ERR_INVALID_ARG, TAG, "write buffer null");
ESP_RETURN_ON_FALSE(buffer_info_array && array_size > 0, ESP_ERR_INVALID_ARG, TAG, "invalid buffer info array");
ESP_RETURN_ON_FALSE(array_size <= uhci_ctrl->tx_dir.max_buf_count, ESP_ERR_INVALID_ARG, TAG,
"array_size %zu exceeds max_transmit_buffer_count", array_size);
size_t alignment = 0;
size_t cache_line_size = 0;
esp_ptr_external_ram(write_buffer) ? (alignment = uhci_ctrl->tx_dir.ext_mem_align, cache_line_size = uhci_ctrl->ext_mem_cache_line_size) : (alignment = uhci_ctrl->tx_dir.int_mem_align, cache_line_size = uhci_ctrl->int_mem_cache_line_size);
size_t total_size = 0;
for (size_t i = 0; i < array_size; i++) {
const uint8_t *write_buffer = buffer_info_array[i].write_buffer;
size_t write_size = buffer_info_array[i].buffer_size;
ESP_RETURN_ON_FALSE(write_buffer != NULL && write_size > 0, ESP_ERR_INVALID_ARG, TAG, "buffer segment %zu is invalid", i);
ESP_RETURN_ON_FALSE(((((uintptr_t)write_buffer) & (alignment - 1)) == 0) && (((write_size) & (alignment - 1)) == 0), ESP_ERR_INVALID_ARG,
TAG, "buffer address or size are not %d bytes aligned", alignment);
total_size += write_size;
if (cache_line_size > 0) {
// Write back to cache to synchronize the cache before DMA start
ESP_RETURN_ON_ERROR(esp_cache_msync((void *)write_buffer, write_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), TAG, "cache sync failed");
size_t alignment = 0;
size_t cache_line_size = 0;
if (esp_ptr_external_ram(write_buffer)) {
alignment = uhci_ctrl->tx_dir.ext_mem_align;
cache_line_size = uhci_ctrl->ext_mem_cache_line_size;
} else {
alignment = uhci_ctrl->tx_dir.int_mem_align;
cache_line_size = uhci_ctrl->int_mem_cache_line_size;
}
ESP_RETURN_ON_FALSE(((((uintptr_t)write_buffer) & (alignment - 1)) == 0) && (((write_size) & (alignment - 1)) == 0), ESP_ERR_INVALID_ARG,
TAG, "buffer segment %zu address or size are not %zu bytes aligned", i, alignment);
if (cache_line_size > 0) {
// Write back to cache to synchronize the cache before DMA start
ESP_RETURN_ON_ERROR(esp_cache_msync((void *)write_buffer, write_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), TAG, "cache sync failed");
}
}
ESP_RETURN_ON_FALSE(total_size <= uhci_ctrl->tx_dir.max_transmit_size, ESP_ERR_INVALID_ARG, TAG,
"total transmit size %zu exceeds max_transmit_size %zu", total_size, uhci_ctrl->tx_dir.max_transmit_size);
uhci_transaction_desc_t *t = NULL;
if (xQueueReceive(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_READY], &t, 0) != pdTRUE) {
@@ -412,9 +443,11 @@ esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffe
}
ESP_RETURN_ON_FALSE(t, ESP_ERR_INVALID_STATE, TAG, "no free transaction descriptor, please consider increasing trans_queue_depth");
memset(t, 0, sizeof(uhci_transaction_desc_t));
t->buffer = write_buffer;
t->buffer_size = write_size;
for (size_t i = 0; i < array_size; i++) {
t->buf_info[i] = buffer_info_array[i];
}
t->buf_info_count = array_size;
t->total_size = total_size;
ESP_RETURN_ON_FALSE(xQueueSend(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_PROGRESS], &t, 0) == pdTRUE, ESP_ERR_NO_MEM, TAG, "uhci tx transaction queue full");
atomic_fetch_add(&uhci_ctrl->tx_dir.num_trans_inflight, 1);
@@ -432,6 +465,15 @@ esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffe
return ESP_OK;
}
esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffer, size_t write_size)
{
uhci_transmit_buffer_info_t buf_info = {
.write_buffer = write_buffer,
.buffer_size = write_size,
};
return uhci_multi_buffer_transmit(uhci_ctrl, &buf_info, 1);
}
esp_err_t uhci_del_controller(uhci_controller_handle_t uhci_ctrl)
{
ESP_RETURN_ON_FALSE(uhci_ctrl, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
@@ -460,6 +502,10 @@ esp_err_t uhci_del_controller(uhci_controller_handle_t uhci_ctrl)
heap_caps_free(uhci_ctrl->tx_dir.trans_desc_pool);
}
if (uhci_ctrl->tx_dir.mount_configs) {
heap_caps_free(uhci_ctrl->tx_dir.mount_configs);
}
if (uhci_ctrl->rx_dir.buffer_size_per_desc_node) {
free(uhci_ctrl->rx_dir.buffer_size_per_desc_node);
}
@@ -518,12 +564,19 @@ esp_err_t uhci_new_controller(const uhci_controller_config_t *config, uhci_contr
ESP_GOTO_ON_FALSE(uhci_ctrl->tx_dir.trans_queues[i], ESP_ERR_NO_MEM, err, TAG, "no mem for transaction queue");
}
uhci_ctrl->tx_dir.trans_desc_pool = heap_caps_calloc(config->tx_trans_queue_depth, sizeof(uhci_transaction_desc_t), UHCI_MEM_ALLOC_CAPS);
uhci_ctrl->tx_dir.max_buf_count = config->max_transmit_buffer_count > 0 ? config->max_transmit_buffer_count : 1;
uhci_ctrl->tx_dir.max_transmit_size = config->max_transmit_size;
uhci_ctrl->tx_dir.mount_configs = heap_caps_calloc(uhci_ctrl->tx_dir.max_buf_count, sizeof(gdma_buffer_mount_config_t), UHCI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(uhci_ctrl->tx_dir.mount_configs, ESP_ERR_NO_MEM, err, TAG, "no mem for buffer mount config array");
size_t desc_elem_size = sizeof(uhci_transaction_desc_t) + uhci_ctrl->tx_dir.max_buf_count * sizeof(uhci_transmit_buffer_info_t);
uhci_ctrl->tx_dir.trans_desc_pool = heap_caps_calloc(config->tx_trans_queue_depth, desc_elem_size, UHCI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(uhci_ctrl->tx_dir.trans_desc_pool, ESP_ERR_NO_MEM, err, TAG, "no mem for transaction desc pool");
uhci_transaction_desc_t *p_trans_desc = NULL;
for (int i = 0; i < config->tx_trans_queue_depth; i++) {
p_trans_desc = &uhci_ctrl->tx_dir.trans_desc_pool[i];
p_trans_desc = (uhci_transaction_desc_t *)((uint8_t *)uhci_ctrl->tx_dir.trans_desc_pool + i * desc_elem_size);
xQueueSend(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_READY], &p_trans_desc, 0);
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -14,6 +14,7 @@
#include "esp_private/periph_ctrl.h"
#include "esp_pm.h"
#include "sdkconfig.h"
#include "driver/uhci.h"
#ifdef __cplusplus
extern "C" {
@@ -33,8 +34,9 @@ typedef struct uhci_controller_t uhci_controller_t;
#endif
typedef struct {
void *buffer; // buffer for saving the received symbols
size_t buffer_size; // size of the buffer, in bytes
size_t total_size; // sum of all buffer segment sizes in this transaction, in bytes
size_t buf_info_count; // number of valid entries in buf_info actually used by the current transaction
uhci_transmit_buffer_info_t buf_info[]; // flexible array member, storage for this transaction's buffer segments. Its capacity is tx_dir.max_buf_count.
} uhci_transaction_desc_t;
typedef enum {
@@ -69,6 +71,9 @@ typedef struct {
size_t int_mem_align; // Alignment for internal memory
size_t ext_mem_align; // Alignment for external memory
atomic_int num_trans_inflight; // Indicates the number of transactions that are undergoing but not recycled to ready_queue
size_t max_transmit_size; // per-transaction max total size in bytes, from config->max_transmit_size; the DMA node pool is sized for this
size_t max_buf_count; // per-transaction max buffer segment count, from config->max_transmit_buffer_count (at least 1)
gdma_buffer_mount_config_t *mount_configs; // scratch array (capacity max_buf_count) reused by every transmit to mount buffer segments; avoids a VLA in ISR context
} uhci_tx_dir;
typedef struct {

View File

@@ -260,6 +260,112 @@ TEST_CASE("UHCI write and receive with length eof", "[uhci]")
vSemaphoreDelete(exit_sema);
}
static void uhci_fill_pattern(uint8_t *buf, size_t len, uint8_t start)
{
for (size_t i = 0; i < len; i++) {
buf[i] = (uint8_t)(start + i);
}
}
TEST_CASE("UHCI single buffer and multi buffer transmit interleaved", "[uhci]")
{
uart_config_t uart_config = {
.baud_rate = 2 * 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_XTAL,
};
TEST_ESP_OK(uart_param_config(EX_UART_NUM, &uart_config));
// Connect TX and RX together for testing self send-receive
TEST_ESP_OK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_TX_IO, -1, -1));
uhci_controller_config_t uhci_cfg = {
.uart_port = EX_UART_NUM,
.tx_trans_queue_depth = 30,
.max_receive_internal_mem = 10 * 1024,
.max_transmit_size = 10 * 1024,
.max_transmit_buffer_count = 3,
.dma_burst_size = 32,
.rx_eof_flags.idle_eof = 1,
};
uhci_controller_handle_t uhci_ctrl;
SemaphoreHandle_t exit_sema = xSemaphoreCreateBinary();
TEST_ESP_OK(uhci_new_controller(&uhci_cfg, &uhci_ctrl));
// 4 transactions in total: single buffer, multi buffer (3 segments), single buffer, multi buffer (2 segments)
int trans_count = 4;
void *args[] = { uhci_ctrl, exit_sema, &trans_count };
xTaskCreate(uhci_receive_test, "uhci_receive_test", 4096 * 2, args, 5, NULL);
uint8_t data_wr[DATA_LENGTH];
for (int i = 0; i < DATA_LENGTH; i++) {
data_wr[i] = i;
}
// 1) plain single buffer transmit
TEST_ESP_OK(uhci_transmit(uhci_ctrl, data_wr, DATA_LENGTH));
uhci_wait_all_tx_transaction_done(uhci_ctrl, portMAX_DELAY);
// Idle gap so RX side sees each transaction as a separate idle-eof event
vTaskDelay(200 / portTICK_PERIOD_MS);
// 2) multi buffer transmit with 3 discontinuous segments
size_t seg_sizes_a[3] = {128, 64, 108};
uint8_t *segs_a[3];
uhci_transmit_buffer_info_t buf_info_a[3];
size_t offset = 0;
for (int i = 0; i < 3; i++) {
segs_a[i] = heap_caps_calloc(1, seg_sizes_a[i], MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
assert(segs_a[i]);
uhci_fill_pattern(segs_a[i], seg_sizes_a[i], (uint8_t)offset);
buf_info_a[i].write_buffer = segs_a[i];
buf_info_a[i].buffer_size = seg_sizes_a[i];
offset += seg_sizes_a[i];
}
TEST_ESP_OK(uhci_multi_buffer_transmit(uhci_ctrl, buf_info_a, 3));
uhci_wait_all_tx_transaction_done(uhci_ctrl, portMAX_DELAY);
for (int i = 0; i < 3; i++) {
free(segs_a[i]);
}
vTaskDelay(200 / portTICK_PERIOD_MS);
// 3) plain single buffer transmit again
TEST_ESP_OK(uhci_transmit(uhci_ctrl, data_wr, 200));
uhci_wait_all_tx_transaction_done(uhci_ctrl, portMAX_DELAY);
vTaskDelay(200 / portTICK_PERIOD_MS);
// 4) multi buffer transmit with 2 discontinuous segments
size_t seg_sizes_b[2] = {256, 96};
uint8_t *segs_b[2];
uhci_transmit_buffer_info_t buf_info_b[2];
offset = 0;
for (int i = 0; i < 2; i++) {
segs_b[i] = heap_caps_calloc(1, seg_sizes_b[i], MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
assert(segs_b[i]);
uhci_fill_pattern(segs_b[i], seg_sizes_b[i], (uint8_t)offset);
buf_info_b[i].write_buffer = segs_b[i];
buf_info_b[i].buffer_size = seg_sizes_b[i];
offset += seg_sizes_b[i];
}
TEST_ESP_OK(uhci_multi_buffer_transmit(uhci_ctrl, buf_info_b, 2));
uhci_wait_all_tx_transaction_done(uhci_ctrl, portMAX_DELAY);
for (int i = 0; i < 2; i++) {
free(segs_b[i]);
}
// 5) exceeding max_transmit_buffer_count must be rejected with ESP_ERR_INVALID_ARG,
// without consuming a transaction descriptor or touching any buffer.
uhci_transmit_buffer_info_t buf_info_over[4] = {0};
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, uhci_multi_buffer_transmit(uhci_ctrl, buf_info_over, 4));
xSemaphoreTake(exit_sema, portMAX_DELAY);
vTaskDelay(2);
TEST_ESP_OK(uhci_del_controller(uhci_ctrl));
vSemaphoreDelete(exit_sema);
}
#if CONFIG_SPIRAM
#if GDMA_LL_GET(AHB_PSRAM_CAPABLE)
static void uhci_receive_test_in_psram(void *arg)

View File

@@ -53,7 +53,8 @@ If the configurations in :cpp:type:`uhci_controller_config_t` is specified, user
.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.
.max_transmit_size = 10 * 1024, // Maximum transfer size in one transaction, in bytes (including all buffers).
.max_transmit_buffer_count = 1, // Maximum number of buffers in one transmit transaction. 0 or 1 means only single-buffer transmit is used.
.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.
};
@@ -112,6 +113,30 @@ Data can be transmitted via UHCI as follows:
// Wait all transaction finishes
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
If the data to be sent is scattered across several separate buffers, you can send them as a single transaction without copying them into one contiguous buffer first, by using :cpp:func:`uhci_multi_buffer_transmit`. The buffer segments are described by an array of :cpp:type:`uhci_transmit_buffer_info_t` and are transmitted in the given order as one continuous UART stream (internally, the segments are assembled into one DMA link list and only the last segment is marked as the end of the transaction). To use this feature, :cpp:member:`uhci_controller_config_t::max_transmit_buffer_count` must be set to the maximum number of segments you intend to send in one call when creating the controller.
The following constraints apply:
- ``array_size`` must not exceed :cpp:member:`uhci_controller_config_t::max_transmit_buffer_count`.
- The total size of all segments must not exceed :cpp:member:`uhci_controller_config_t::max_transmit_size`.
- Every buffer segment must remain valid until the transmission is complete, same as :cpp:func:`uhci_transmit`.
.. code:: c
uint8_t header[8];
uint8_t payload[DATA_LENGTH];
// ... fill header and payload ...
uhci_transmit_buffer_info_t buffer_info[] = {
{ .write_buffer = header, .buffer_size = sizeof(header) },
{ .write_buffer = payload, .buffer_size = sizeof(payload) },
};
ESP_ERROR_CHECK(uhci_multi_buffer_transmit(uhci_ctrl, buffer_info, 2));
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
.. note::
When a transaction is submitted through :cpp:func:`uhci_multi_buffer_transmit` with more than one segment, :cpp:member:`uhci_tx_done_event_data_t::buffer` in the "trans-done" callback only points to the first segment and should be treated as an identifying handle for the transaction, not as the start of a ``sent_size``-byte contiguous region. :cpp:member:`uhci_tx_done_event_data_t::sent_size` is the sum of the sizes of all segments.
Initiating UHCI Reception
^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -53,7 +53,8 @@ UHCI 控制器需要通过 :cpp:type:`uhci_controller_config_t` 进行配置。
.uart_port = EX_UART_NUM, // 将指定 UART 端口连接到 UHCI 硬件
.tx_trans_queue_depth = 30, // 发送队列的队列深度
.max_receive_internal_mem = 10 * 1024, // 内部接收内存大小,更多信息请参考 API 注释。
.max_transmit_size = 10 * 1024, // 次传输的最大传输量,单位是字节
.max_transmit_size = 10 * 1024, // 次传输事务中的最大总字节数(包含该次传入的所有缓冲区)
.max_transmit_buffer_count = 1, // 一次传输事务中的最大缓冲区数量。设为 0 或 1 表示只使用单缓冲区传输。
.dma_burst_size = 32, // 突发传输大小
.rx_eof_flags.idle_eof = 1, // 结束帧的条件,用户可以选择 `idle_eof`, `rx_brk_eof` 和 `length_eof`, 关于更多信息请参考 API 注释.
};
@@ -112,6 +113,30 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义:
// 等待所有传输完成
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
如果待发送的数据分散在多个独立的缓冲区中,可以使用 :cpp:func:`uhci_multi_buffer_transmit` 将它们作为一次事务发送,而无需先拷贝到一块连续的缓冲区中。各缓冲区段通过一个 :cpp:type:`uhci_transmit_buffer_info_t` 数组描述,并按给定顺序作为一条连续的 UART 数据流发送(在内部,这些段会被组装成一条 DMA 链表,只有最后一段被标记为事务结束)。要使用该功能,需要在创建控制器时把 :cpp:member:`uhci_controller_config_t::max_transmit_buffer_count` 设为你在单次调用中打算发送的最大段数。
需要满足以下约束:
- ``array_size`` 不得超过 :cpp:member:`uhci_controller_config_t::max_transmit_buffer_count`
- 所有缓冲区的总字节数不得超过 :cpp:member:`uhci_controller_config_t::max_transmit_size`
-:cpp:func:`uhci_transmit` 一样,每个缓冲区段在传输完成前都必须保持有效。
.. code:: c
uint8_t header[8];
uint8_t payload[DATA_LENGTH];
// ... 填充 header 和 payload ...
uhci_transmit_buffer_info_t buffer_info[] = {
{ .write_buffer = header, .buffer_size = sizeof(header) },
{ .write_buffer = payload, .buffer_size = sizeof(payload) },
};
ESP_ERROR_CHECK(uhci_multi_buffer_transmit(uhci_ctrl, buffer_info, 2));
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
.. note::
当通过 :cpp:func:`uhci_multi_buffer_transmit` 提交的事务包含多个段时,“传输完成”回调中的 :cpp:member:`uhci_tx_done_event_data_t::buffer` 只指向第一个段,应把它当作该事务的标识句柄,而不是一段长度为 ``sent_size`` 字节的连续内存的起始地址。:cpp:member:`uhci_tx_done_event_data_t::sent_size` 是所有段大小之和。
启动 UHCI 接收
^^^^^^^^^^^^^^^^^^^^^^^^^