refactor(uhci): improve uart_dma_ota example & uhci docs

add const to UHCI rx callback edata->data
This commit is contained in:
Hu Rui
2026-05-15 20:28:57 +08:00
parent 8f3d8b41c3
commit 98128e8b56
8 changed files with 88 additions and 75 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
*/
@@ -48,11 +48,11 @@ typedef bool (*uhci_tx_done_callback_t)(uhci_controller_handle_t uhci_ctrl, cons
* @brief UHCI RX Done Event Data Structure
*/
typedef struct {
uint8_t *data; /*!< Pointer to the received data buffer */
const uint8_t *data; /*!< Pointer to the received data buffer. Data pointed to by this pointer is typically only guaranteed to be readable during the callback. If you need to use it after callback returns, copy it to external buffer first or refer to advanced zero-copy usage. */
size_t recv_size; /*!< Number of bytes received */
struct {
uint32_t totally_received: 1; /*!< 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. */
} flags; /*!< I2C master config flags */
} flags; /*!< UHCI RX event flags */
} uhci_rx_event_data_t;
/**
@@ -60,7 +60,8 @@ typedef struct {
* @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.
* status information relevant to the operation. The `edata` pointer is only valid
* during the callback. So do not save this pointer and use it outside the callback.
* @param user_ctx User-defined context passed during the callback registration.
* It can be used to maintain application-specific state or data.
*

View File

@@ -131,7 +131,7 @@ static bool uhci_gdma_rx_callback_done(gdma_channel_handle_t dma_chan, gdma_even
if (cache_line > 0) {
// The per-node buffer base is aligned to cache_line (see uhci_receive), and rx_size here
// equals buffer_size_per_desc_node[] which is also a multiple of cache_line.
esp_cache_msync(evt_data.data, rx_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
esp_cache_msync((void *)evt_data.data, rx_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
}
if (uhci_ctrl->rx_dir.on_rx_trans_event) {
need_yield |= uhci_ctrl->rx_dir.on_rx_trans_event(uhci_ctrl, &evt_data, uhci_ctrl->user_data);
@@ -167,7 +167,7 @@ static bool uhci_gdma_rx_callback_done(gdma_channel_handle_t dma_chan, gdma_even
// is harmless.
if (cache_line > 0) {
size_t sync_size = (rx_size + cache_line - 1) & ~(cache_line - 1);
esp_cache_msync(evt_data.data, sync_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
esp_cache_msync((void *)evt_data.data, sync_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
}
if (uhci_ctrl->rx_dir.on_rx_trans_event) {
need_yield |= uhci_ctrl->rx_dir.on_rx_trans_event(uhci_ctrl, &evt_data, uhci_ctrl->user_data);

View File

@@ -87,14 +87,18 @@ The TX event data is defined in :cpp:type:`uhci_tx_done_event_data_t`:
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::data` points to the received data. The data is stored in the buffer specified by the ``buffer`` parameter of :cpp:func:`uhci_receive`, so users should not free this receive buffer before the callback returns. Data pointed to by ``edata->data`` is typically only guaranteed to be readable during the callback. If application code needs to use the received data after callback returns, copy it to a external buffer first.
- :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.
.. note::
Forwarding ``edata->data`` pointer to another task without copying is an advanced zero-copy usage. To keep it safe, user code must understand the chunking and overwrite behavior of the underlying circular DMA buffer, and guarantee the consumer can process data before it gets overwritten.
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.
: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 block the thread until all transactions are finished.
Data can be transmitted via UHCI as follows:
@@ -111,9 +115,9 @@ Data can be transmitted via UHCI as follows:
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.
: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_event_callbacks_t::on_rx_trans_event` 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:
Data can be received via UHCI as follows:
.. code:: c
@@ -137,7 +141,7 @@ Data can be transmitted via UHCI as follows:
ctx->p_receive_data += edata->recv_size;
}
xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken);
xQueueSendFromISR(uhci_queue, &evt, &xTaskWoken);
return xTaskWoken;
}
@@ -153,7 +157,7 @@ Data can be transmitted via UHCI as follows:
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 (xQueueReceive(uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
if (evt == UHCI_EVT_EOF) {
printf("Received size: %d\n", ctx->receive_size);
break;

View File

@@ -87,16 +87,20 @@ TX 事件数据在 :cpp:type:`uhci_tx_done_event_data_t` 中定义:
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::data` 指向接收到的数据。数据保存在 :cpp:func:`uhci_receive` 函数 ``buffer`` 参数指定的缓冲区中,因此用户在回调返回之前不应释放此接收缓冲区。``edata->data`` 所指向的数据通常仅保证在回调期间可读。若回调返回后仍需使用该数据,请先拷贝到外部缓冲区。
- :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` 指示当前接收缓冲区是否是事务中的最后一个。
.. note::
如果希望不拷贝而在回调外继续使用 ``edata->data`` (例如把指针通过队列传给任务处理),属于高级零拷贝用法。用户需要理解底层 DMA 环形缓冲区的分块和覆盖行为,并保证消费者处理速度快于覆盖速度。
启动 UHCI 传输
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`uhci_transmit` 是一个非阻塞函数,这意味着在调用后会立即返回。您可以通过 :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` 相关回调指示事务完成。我们还提供了一个函数 :cpp:func:`uhci_wait_all_tx_transaction_done` 来阻塞线程,等待所有事务完成。
以下代码显示了如何通过 UHCI 接收数据:
以下代码显示了如何通过 UHCI 传输数据:
.. code:: c
@@ -111,9 +115,9 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义:
启动 UHCI 接收
^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`uhci_receive` 是一个非阻塞函数,这意味着该函数在调用后会立即返回。用户可以通过 :cpp:member:`uhci_rx_event_data_t::recv_size` 获取相关的回调,以指示接收事件并判断事务是否完成。
:cpp:func:`uhci_receive` 是一个非阻塞函数,这意味着该函数在调用后会立即返回。用户可以通过 :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` 获取相关的回调,以指示接收事件并判断事务是否完成。
以下代码展示了如何通过 UHCI 传输数据:
以下代码展示了如何通过 UHCI 接收数据:
.. code:: c
@@ -146,13 +150,13 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义:
.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 抛出的事件
// 在任务中队列用接收 UHCI 抛出的事件
if (xQueueReceive(uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
if (evt == UHCI_EVT_EOF) {
printf("Received size: %d\n", ctx->receive_size);
@@ -199,7 +203,7 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义:
通过启用 Kconfig 选项 :ref:`CONFIG_UHCI_ISR_CACHE_SAFE`,可实现以下功能:
1. 即使缓存被禁用,中断也能被服务
1. 即使缓存被禁用,中断也能被及时处理
2. 将 ISR 使用的所有函数放入 IRAM [1]_
3. 将驱动对象放入 DRAM防止其意外映射到 PSRAM。

View File

@@ -626,6 +626,12 @@ examples/peripherals/twai/twai_utils:
examples/peripherals/uart/uart_dma_ota:
disable:
- if: SOC_UHCI_SUPPORTED != 1
depends_components:
- esp_driver_uart
- esp_driver_dma
- app_update
- esp_ringbuf
- soc
examples/peripherals/uart/uart_echo_rs485:
enable:

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "uart_dma_ota_example_main.c"
REQUIRES esp_driver_uart app_update
REQUIRES esp_driver_uart app_update esp_ringbuf
INCLUDE_DIRS ".")

View File

@@ -1,20 +1,22 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/ringbuf.h"
#include "driver/uart.h"
#include "driver/uhci.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
static const char *TAG = "uhci-example";
@@ -22,48 +24,36 @@ static const char *TAG = "uhci-example";
#define EXAMPLE_UART_BAUD_RATE CONFIG_UART_BAUD_RATE
#define EXAMPLE_UART_RX_IO CONFIG_UART_RX_IO
#define UART_DMA_OTA_BUFFER_SIZE (10 * 1024)
typedef enum {
UHCI_EVT_PARTIAL_DATA,
UHCI_EVT_EOF,
} uhci_event_t;
#define UART_DMA_OTA_RINGBUF_SIZE (10 * 1024)
typedef struct {
QueueHandle_t uhci_queue;
size_t receive_size;
uint8_t *ota_data1;
uint8_t *ota_data2;
bool use_ota_data1;
} ota_example_context_t;
RingbufHandle_t ringbuf;
volatile bool rx_eof;
volatile bool rx_overflow;
} ota_rx_context_t;
static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx)
{
ota_example_context_t *ctx = (ota_example_context_t *)user_ctx;
BaseType_t xTaskWoken = 0;
uhci_event_t evt = 0;
ota_rx_context_t *ctx = (ota_rx_context_t *)user_ctx;
BaseType_t xTaskWoken = pdFALSE;
if (xRingbufferSendFromISR(ctx->ringbuf, edata->data, edata->recv_size, &xTaskWoken) != pdTRUE) {
ctx->rx_overflow = true;
}
if (edata->flags.totally_received) {
evt = UHCI_EVT_EOF;
} else {
evt = UHCI_EVT_PARTIAL_DATA;
ctx->rx_eof = true;
}
// Choose the buffer to store received data
ctx->receive_size = edata->recv_size;
if (ctx->use_ota_data1) {
ctx->ota_data1 = edata->data;
} else {
ctx->ota_data2 = edata->data;
}
// Toggle the buffer for the next receive
ctx->use_ota_data1 = !ctx->use_ota_data1;
xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken);
return xTaskWoken;
return xTaskWoken == pdTRUE;
}
static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_example_context_t *ctx)
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);
if (!ota_partition) {
@@ -75,25 +65,32 @@ static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_example_c
ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle));
ESP_LOGI(TAG, "OTA process started");
uhci_event_t evt;
uint32_t received_size = 0;
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));
while (1) {
if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
uint8_t *data_to_write = ctx->use_ota_data1 ? ctx->ota_data2 : ctx->ota_data1;
ESP_ERROR_CHECK(esp_ota_write(ota_handle, data_to_write, ctx->receive_size));
received_size += ctx->receive_size;
if (evt == UHCI_EVT_EOF) {
break;
}
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));
if (data) {
ESP_ERROR_CHECK(esp_ota_write(ota_handle, data, item_size));
vRingbufferReturnItem(ctx->ringbuf, data);
total_received_size += item_size;
}
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;
}
}
free(pdata);
ESP_LOGI(TAG, "Total received size: %ld", received_size);
ESP_LOGI(TAG, "Total received size: %zu", total_received_size);
ESP_ERROR_CHECK(esp_ota_end(ota_handle));
ESP_ERROR_CHECK(esp_ota_set_boot_partition(ota_partition));
}
@@ -126,25 +123,24 @@ void app_main(void)
ESP_LOGI(TAG, "UHCI initialized, baud rate is %d, rx pin is %d", uart_config.baud_rate, EXAMPLE_UART_RX_IO);
ota_example_context_t *ctx = calloc(1, sizeof(ota_example_context_t));
assert(ctx);
ctx->uhci_queue = xQueueCreate(2, sizeof(uhci_event_t));
assert(ctx->uhci_queue);
ctx->use_ota_data1 = true; // Start with ota_data1
ota_rx_context_t ctx = {
.ringbuf = xRingbufferCreate(UART_DMA_OTA_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF),
.rx_eof = false,
.rx_overflow = false,
};
assert(ctx.ringbuf);
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_register_event_callbacks(uhci_ctrl, &uhci_cbs, &ctx));
perform_ota_update(uhci_ctrl, ctx);
perform_ota_update(uhci_ctrl, &ctx);
ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl));
free(ctx);
vRingbufferDelete(ctx.ringbuf);
ESP_LOGI(TAG, "OTA update successful. Rebooting...");
esp_restart();
}

View File

@@ -48,9 +48,11 @@ def test_uart_dma_ota(dut: Dut) -> None:
# We OTA the same binary to another partition and switch to there.
binary_path = os.path.join(dut.app.binary_path, 'uart_dma_ota.bin')
assert os.path.exists(binary_path), f'OTA binary not found at {binary_path}'
binary_size = os.path.getsize(binary_path)
buad_rate = dut.app.sdkconfig.get('UART_BAUD_RATE')
send_file_via_uart(FLASH_PORT, buad_rate, binary_path, PACKET_SIZE)
dut.expect_exact(f'uhci-example: Total received size: {binary_size}')
dut.expect('OTA update successful. Rebooting', timeout=10)
dut.expect('ESP-ROM:', timeout=10)