Skip to content

Commit 1637e27

Browse files
committed
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
2 parents eb81a85 + 093db55 commit 1637e27

File tree

8 files changed

+622
-4
lines changed

8 files changed

+622
-4
lines changed

components/esp_driver_uart/include/driver/uhci.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ typedef struct {
2222
uart_port_t uart_port; /*!< UART port that connect to UHCI controller */
2323
size_t tx_trans_queue_depth; /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background */
2424
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 */
25-
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. */
26-
size_t dma_burst_size; /*!< DMA burst size, in bytes */
25+
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. */
26+
size_t dma_burst_size; /*!< DMA burst size, in bytes. Set to 0 to disable data burst. Otherwise, use a power of 2. */
2727
size_t max_packet_receive; /*!< Max receive size, auto stop receiving after reach this value, only valid when `length_eof` set true */
2828

2929
struct {

components/esp_driver_uart/src/uhci.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ static esp_err_t uhci_gdma_initialize(uhci_controller_handle_t uhci_ctrl, const
203203
// Initialize DMA RX channel
204204
gdma_channel_alloc_config_t rx_alloc_config = {
205205
.direction = GDMA_CHANNEL_DIRECTION_RX,
206-
.sibling_chan = uhci_ctrl->tx_dir.dma_chan,
207206
#if CONFIG_UHCI_ISR_CACHE_SAFE
208207
.flags.isr_cache_safe = true,
209208
#endif

docs/conf_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109

110110
CLK_TREE_DOCS = ['api-reference/peripherals/clk_tree.rst']
111111

112-
UART_DOCS = ['api-reference/peripherals/uart.rst']
112+
UART_DOCS = ['api-reference/peripherals/uart.rst', 'api-reference/peripherals/uhci.rst']
113113

114114
SDMMC_DOCS = ['api-reference/peripherals/sdmmc_host.rst']
115115

docs/doxygen/Doxyfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ INPUT = \
149149
$(PROJECT_PATH)/components/esp_driver_tsens/include/driver/temperature_sensor_etm.h \
150150
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uart.h \
151151
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uart_vfs.h \
152+
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uhci.h \
153+
$(PROJECT_PATH)/components/esp_driver_uart/include/driver/uhci_types.h \
152154
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_com.h \
153155
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_driver.h \
154156
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_mac.h \
@@ -270,6 +272,7 @@ INPUT = \
270272
$(PROJECT_PATH)/components/hal/include/hal/efuse_hal.h \
271273
$(PROJECT_PATH)/components/hal/include/hal/eth_types.h \
272274
$(PROJECT_PATH)/components/hal/include/hal/lp_core_types.h \
275+
$(PROJECT_PATH)/components/hal/include/hal/uhci_types.h \
273276
$(PROJECT_PATH)/components/heap/include/esp_heap_caps_init.h \
274277
$(PROJECT_PATH)/components/heap/include/esp_heap_caps.h \
275278
$(PROJECT_PATH)/components/heap/include/esp_heap_task_info.h \

docs/en/api-reference/peripherals/uart.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ Each UART controller is independently configurable with parameters such as baud
1818

1919
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>`__]).
2020

21+
.. toctree::
22+
:hidden:
23+
24+
uhci
25+
26+
.. only:: SOC_UHCI_SUPPORTED
27+
28+
The {IDF_TARGET_NAME} chip also supports using DMA with UART. For details, see to :doc:`uhci`.
29+
2130
Functional Overview
2231
-------------------
2332

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
UART DMA (UHCI)
2+
===============
3+
4+
:link_to_translation:`zh_CN:[中文]`
5+
6+
This document describes the functionality of the UART DMA(UHCI) driver in ESP-IDF. The table of contents is as follows:
7+
8+
.. contents::
9+
:local:
10+
:depth: 2
11+
12+
Introduction
13+
------------
14+
15+
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.
16+
17+
.. note::
18+
19+
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.
20+
21+
Quick Start
22+
-----------
23+
24+
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:
25+
26+
Creating and Enabling the UHCI controller
27+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
29+
UHCI controller requires the configuration specified by :cpp:type:`uhci_controller_config_t`.
30+
31+
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.
32+
33+
.. code:: c
34+
35+
#define EX_UART_NUM 1 // Define UART port number
36+
37+
// For uart port configuration, please refer to UART programming guide.
38+
// Please double-check as the baud rate might be limited by serial port chips.
39+
uart_config_t uart_config = {
40+
.baud_rate = 1 * 1000 * 1000,
41+
.data_bits = UART_DATA_8_BITS,
42+
.parity = UART_PARITY_DISABLE,
43+
.stop_bits = UART_STOP_BITS_1,
44+
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
45+
.source_clk = UART_SCLK_DEFAULT,
46+
};
47+
48+
//UART parameter config
49+
ESP_ERROR_CHECK(uart_param_config(EX_UART_NUM, &uart_config));
50+
ESP_ERROR_CHECK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_RX_IO, -1, -1));
51+
52+
uhci_controller_config_t uhci_cfg = {
53+
.uart_port = EX_UART_NUM, // Connect uart port to UHCI hardware.
54+
.tx_trans_queue_depth = 30, // Queue depth of transaction queue.
55+
.max_receive_internal_mem = 10 * 1024, // internal memory usage, for more information, please refer to API reference.
56+
.max_transmit_size = 10 * 1024, // Maximum transfer size in one transaction, in bytes.
57+
.dma_burst_size = 32, // Burst size.
58+
.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.
59+
};
60+
61+
uhci_controller_handle_t uhci_ctrl;
62+
63+
ESP_ERROR_CHECK(uhci_new_controller(&uhci_cfg, &uhci_ctrl));
64+
65+
Register Event Callbacks
66+
^^^^^^^^^^^^^^^^^^^^^^^^
67+
68+
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.
69+
70+
The UHCI event callbacks are listed in the :cpp:type:`uhci_event_callbacks_t`:
71+
72+
- :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`.
73+
74+
- :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`.
75+
76+
.. note::
77+
78+
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`.
79+
80+
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.
81+
82+
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.
83+
84+
The TX event data is defined in :cpp:type:`uhci_tx_done_event_data_t`:
85+
86+
- :cpp:member:`uhci_tx_done_event_data_t::buffer` indicates the buffer has been sent out.
87+
88+
The RX event data is defined in :cpp:type:`uhci_rx_event_data_t`:
89+
90+
- :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.
91+
- :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.
92+
- :cpp:member:`uhci_rx_event_data_t::flags::totally_received` indicates whether the current received buffer is the last one in the transaction.
93+
94+
Initiating UHCI Transmission
95+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
96+
97+
: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.
98+
99+
Data can be transmitted via UHCI as follows:
100+
101+
.. code:: c
102+
103+
uint8_t data_wr[DATA_LENGTH];
104+
for (int i = 0; i < DATA_LENGTH; i++) {
105+
data_wr[i] = i;
106+
}
107+
ESP_ERROR_CHECK(uhci_transmit(uhci_ctrl, data_wr, DATA_LENGTH));
108+
// Wait all transaction finishes
109+
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
110+
111+
Initiating UHCI Reception
112+
^^^^^^^^^^^^^^^^^^^^^^^^^
113+
114+
: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.
115+
116+
Data can be transmitted via UHCI as follows:
117+
118+
.. code:: c
119+
120+
// global variable: handle of queue.
121+
QueueHandle_t uhci_queue;
122+
123+
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)
124+
{
125+
// parameter `user_ctx` is parsed by the third parameter of function `uhci_register_event_callbacks`
126+
uhci_context_t *ctx = (uhci_context_t *)user_ctx;
127+
BaseType_t xTaskWoken = 0;
128+
uhci_event_t evt = 0;
129+
if (edata->flags.totally_received) {
130+
evt = UHCI_EVT_EOF;
131+
ctx->receive_size += edata->recv_size;
132+
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
133+
} else {
134+
evt = UHCI_EVT_PARTIAL_DATA;
135+
ctx->receive_size += edata->recv_size;
136+
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
137+
ctx->p_receive_data += edata->recv_size;
138+
}
139+
140+
xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken);
141+
return xTaskWoken;
142+
}
143+
144+
// In task
145+
uhci_event_callbacks_t uhci_cbs = {
146+
.on_rx_trans_event = s_uhci_rx_event_cbs,
147+
};
148+
149+
// Register callback and start reception.
150+
ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx));
151+
ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, 100));
152+
153+
uhci_event_t evt;
154+
while (1) {
155+
// A queue in task for receiving event triggered by UHCI.
156+
if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
157+
if (evt == UHCI_EVT_EOF) {
158+
printf("Received size: %d\n", ctx->receive_size);
159+
break;
160+
}
161+
}
162+
}
163+
164+
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.
165+
166+
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.
167+
168+
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.
169+
170+
.. note::
171+
172+
The parameter `read_buffer` of :cpp:func:`uhci_receive` cannot be freed until receive finishes.
173+
174+
Uninstall UHCI controller
175+
^^^^^^^^^^^^^^^^^^^^^^^^^
176+
177+
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.
178+
179+
.. code:: c
180+
181+
ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl));
182+
183+
Advanced Features
184+
-----------------
185+
186+
As the basic usage has been covered, it's time to explore more advanced features of the UHCI driver.
187+
188+
Power Management
189+
^^^^^^^^^^^^^^^^
190+
191+
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.
192+
193+
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.
194+
195+
Cache Safe
196+
^^^^^^^^^^
197+
198+
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.
199+
200+
There is a Kconfig option :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` that has the following features:
201+
202+
1. Enable the interrupt being serviced even when the cache is disabled
203+
2. Place all functions used by the ISR into IRAM [1]_
204+
3. Place the driver object into DRAM in case it is mapped to PSRAM by accident
205+
206+
This Kconfig option allows the interrupt handler to run while the cache is disabled but comes at the cost of increased IRAM consumption.
207+
208+
Resource Consumption
209+
^^^^^^^^^^^^^^^^^^^^
210+
211+
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):
212+
213+
**Note that the following data are not exact values and are for reference only; they may differ on different chip models.**
214+
215+
Resource consumption when :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` is enabled:
216+
217+
.. list-table:: Resource Consumption
218+
:widths: 10 10 10 10 10 10 10 10 10
219+
:header-rows: 1
220+
221+
* - Component Layer
222+
- Total Size
223+
- DIRAM
224+
- .bss
225+
- .data
226+
- .text
227+
- Flash Code
228+
- Flash Data
229+
- .rodata
230+
* - UHCI
231+
- 5733
232+
- 680
233+
- 8
234+
- 34
235+
- 638
236+
- 4878
237+
- 175
238+
- 175
239+
240+
Resource consumption when :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` is disabled:
241+
242+
.. list-table:: Resource Consumption
243+
:widths: 10 10 10 10 10 10 10 10 10 10
244+
:header-rows: 1
245+
246+
* - Component Layer
247+
- Total Size
248+
- DIRAM
249+
- .bss
250+
- .data
251+
- .text
252+
- Flash Code
253+
- .text
254+
- Flash Data
255+
- .rodata
256+
* - UHCI
257+
- 5479
258+
- 42
259+
- 8
260+
- 34
261+
- 0
262+
- 5262
263+
- 5262
264+
- 175
265+
- 175
266+
267+
Performance
268+
^^^^^^^^^^^
269+
270+
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.
271+
272+
.. note::
273+
274+
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`.
275+
276+
Thread Safety
277+
^^^^^^^^^^^^^
278+
279+
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.
280+
281+
Other Kconfig Options
282+
^^^^^^^^^^^^^^^^^^^^^
283+
284+
- :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.
285+
286+
Application Examples
287+
--------------------
288+
289+
- :example:`peripherals/uart/uart_dma_ota` demonstrates how to use the uart dma for fast OTA the chip firmware with 1M baud rate speed.
290+
291+
API Reference
292+
-------------
293+
294+
.. include-build-file:: inc/uhci.inc
295+
.. include-build-file:: inc/components/esp_driver_uart/include/driver/uhci_types.inc
296+
.. include-build-file:: inc/components/hal/include/hal/uhci_types.inc
297+
298+
.. [1]
299+
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.

0 commit comments

Comments
 (0)