feat(uhci): Add uhci_continuous api for example and doc

This commit is contained in:
C.S.M
2026-07-30 15:25:51 +08:00
parent fcd5c55194
commit ae64f66617
4 changed files with 68 additions and 20 deletions

View File

@@ -200,6 +200,30 @@ This mechanism allows the user to achieve continuous and fast reception using a
The parameter ``read_buffer`` of :cpp:func:`uhci_receive` cannot be freed until receive finishes.
Continuous Reception
^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`uhci_receive` is a one-shot API: it stops the DMA after a single frame and has to be re-armed for the next one. During that re-arm gap incoming bytes can be lost. For continuous streaming, use :cpp:func:`uhci_start_receive_continuous` instead. It keeps the GDMA running across EOFs, so the reception never idles and no data is dropped between frames.
The provided ``read_buffer`` is split across the DMA nodes and used as a ring: each finished frame (for example, a UART idle or length EOF) is delivered through the :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` callback with :cpp:member:`uhci_rx_event_data_t::flags::totally_received` set to 1, while a filled-but-not-yet-complete node is delivered with it set to 0. The callback hands out a pointer directly into the ring buffer (zero-copy), so the application must consume the data before the DMA wraps around and overwrites it. Size the buffer according to the expected throughput and consumer latency; overrun protection is not provided.
Call :cpp:func:`uhci_stop_receive` to end the session and return the controller to the idle state, after which it can be re-armed or deleted.
.. code:: c
// Register callback and start continuous reception.
ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx));
ESP_ERROR_CHECK(uhci_start_receive_continuous(uhci_ctrl, pdata, buffer_size));
// ... consume the frames delivered through on_rx_trans_event ...
// Stop the session before freeing pdata or deleting the controller.
ESP_ERROR_CHECK(uhci_stop_receive(uhci_ctrl));
.. note::
The parameter ``read_buffer`` of :cpp:func:`uhci_start_receive_continuous` must stay valid until :cpp:func:`uhci_stop_receive` returns.
Uninstall UHCI controller
^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -200,6 +200,30 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义:
在接收完成之前,:cpp:func:`uhci_receive` 的参数 ``read_buffer`` 不可被释放。
连续接收
^^^^^^^^
:cpp:func:`uhci_receive` 是一次性 (one-shot) 接口:它在接收完一帧后便会停止 DMA需要重新调用才能接收下一帧而在这个重新装载的间隙里可能会丢失到来的数据。若需要持续接收数据流请改用 :cpp:func:`uhci_start_receive_continuous`。它会让 GDMA 跨越 EOF 持续运行,接收过程不会中断,因此帧与帧之间不会丢数据。
传入的 ``read_buffer`` 会被拆分到各个 DMA 节点上,并作为环形缓冲区循环使用:每接收完一帧(例如 UART idle 或 length EOF都会通过 :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` 回调交付,且 :cpp:member:`uhci_rx_event_data_t::flags::totally_received` 置为 1而某个节点填满但整帧尚未结束时则以该标志为 0 交付。回调直接给出指向环形缓冲区内部的指针(零拷贝),因此应用必须在 DMA 绕回并覆盖数据之前将其消费掉。请根据预期的吞吐量与消费延迟来确定缓冲区大小;驱动不提供溢出保护。
调用 :cpp:func:`uhci_stop_receive` 可结束本次接收会话,并使控制器回到空闲状态,之后便可重新装载或删除控制器。
.. code:: c
// 注册回调并启动连续接收。
ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx));
ESP_ERROR_CHECK(uhci_start_receive_continuous(uhci_ctrl, pdata, buffer_size));
// ... 消费通过 on_rx_trans_event 交付的各帧数据 ...
// 在释放 pdata 或删除控制器之前,先停止接收会话。
ESP_ERROR_CHECK(uhci_stop_receive(uhci_ctrl));
.. note::
:cpp:func:`uhci_start_receive_continuous` 的参数 ``read_buffer`` 必须保持有效,直到 :cpp:func:`uhci_stop_receive` 返回。
卸载 UHCI 控制器
^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -76,7 +76,7 @@ Build the project and flash it to the board, then run monitor tool to view seria
idf.py -p PORT flash monitor
```
Then transmit the prepared bin to ESP-chips. Please note that the bin should be in one packet transaction which means there should not have any interval during the transaction in this example because it uses idle to judge whether the packet finishes or not.
Then transmit the prepared bin to ESP-chips. This example uses `uhci_start_receive_continuous()`, so the DMA keeps running across UART idle gaps and no data is dropped between frames. The transfer is considered finished once the RX line stays idle for about one second (`UART_DMA_OTA_IDLE_TIMEOUT_MS`), so make sure the whole bin is sent without such a long pause in the middle.
(To exit the serial monitor, type ``Ctrl-]``.)

View File

@@ -26,9 +26,11 @@ static const char *TAG = "uhci-example";
#define UART_DMA_OTA_BUFFER_SIZE (10 * 1024)
#define UART_DMA_OTA_RINGBUF_SIZE (10 * 1024)
// One second without any new data is treated as end-of-transfer.
#define UART_DMA_OTA_IDLE_TIMEOUT_MS 1000
typedef struct {
RingbufHandle_t ringbuf;
volatile bool rx_eof;
volatile bool rx_overflow;
} ota_rx_context_t;
@@ -37,22 +39,15 @@ static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_r
ota_rx_context_t *ctx = (ota_rx_context_t *)user_ctx;
BaseType_t xTaskWoken = pdFALSE;
// Copy each chunk out of the DMA storage buffer before it gets overwritten. In continuous mode
// the driver keeps the DMA running across frames, so this fires for every partial node and every
// frame EOF without any gap in between.
if (xRingbufferSendFromISR(ctx->ringbuf, edata->data, edata->recv_size, &xTaskWoken) != pdTRUE) {
ctx->rx_overflow = true;
}
if (edata->flags.totally_received) {
ctx->rx_eof = true;
}
return xTaskWoken == pdTRUE;
}
static bool rx_ringbuf_is_empty(RingbufHandle_t ringbuf)
{
UBaseType_t items_waiting = 0;
vRingbufferGetInfo(ringbuf, NULL, NULL, NULL, NULL, &items_waiting);
return items_waiting == 0;
}
static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_rx_context_t *ctx)
{
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
@@ -65,29 +60,35 @@ static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_rx_contex
ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle));
ESP_LOGI(TAG, "OTA process started");
// Storage buffer for continuous reception. The driver splits it across the DMA nodes and uses it
// as a ring, so the OTA image can be much larger than the buffer itself.
uint8_t *pdata = heap_caps_calloc(1, UART_DMA_OTA_BUFFER_SIZE, MALLOC_CAP_DEFAULT);
assert(pdata);
ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, UART_DMA_OTA_BUFFER_SIZE));
ESP_ERROR_CHECK(uhci_start_receive_continuous(uhci_ctrl, pdata, UART_DMA_OTA_BUFFER_SIZE));
size_t total_received_size = 0;
while (1) {
size_t item_size = 0;
uint8_t *data = xRingbufferReceive(ctx->ringbuf, &item_size, pdMS_TO_TICKS(1000));
uint8_t *data = xRingbufferReceive(ctx->ringbuf, &item_size, pdMS_TO_TICKS(UART_DMA_OTA_IDLE_TIMEOUT_MS));
if (data) {
ESP_ERROR_CHECK(esp_ota_write(ota_handle, data, item_size));
vRingbufferReturnItem(ctx->ringbuf, data);
total_received_size += item_size;
} else if (total_received_size > 0) {
// The ring buffer stayed empty for the whole timeout after data had started flowing:
// the sender is done. A NULL return only happens when the ring buffer is drained, so no
// received bytes are left behind here.
break;
}
if (ctx->rx_overflow) {
ESP_LOGE(TAG, "RX ring buffer overflow, please reduce the baud rate or increase the ring buffer size");
abort();
}
if (ctx->rx_eof && rx_ringbuf_is_empty(ctx->ringbuf)) {
break;
}
}
// Stop the continuous reception before releasing the storage buffer.
ESP_ERROR_CHECK(uhci_stop_receive(uhci_ctrl));
free(pdata);
ESP_LOGI(TAG, "Total received size: %zu", total_received_size);
@@ -115,7 +116,7 @@ void app_main(void)
.max_receive_internal_mem = UART_DMA_OTA_BUFFER_SIZE,
.max_transmit_size = UART_DMA_OTA_BUFFER_SIZE,
.dma_burst_size = 32,
.rx_eof_flags.idle_eof = 1, // receive finishes when rx line turns idle.
.rx_eof_flags.idle_eof = 1, // deliver a frame (EOF) whenever the rx line turns idle.
};
uhci_controller_handle_t uhci_ctrl;
@@ -125,7 +126,6 @@ void app_main(void)
ota_rx_context_t ctx = {
.ringbuf = xRingbufferCreate(UART_DMA_OTA_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF),
.rx_eof = false,
.rx_overflow = false,
};
assert(ctx.ringbuf);