libxr
Embedded C++ framework for bare-metal, RTOS and Linux
Loading...
Searching...
No Matches
esp_uart_dma.cpp
1#include "esp_uart.hpp"
2
3#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
4
5#include <algorithm>
6#include <array>
7
8#include "esp_attr.h"
9#include "esp_err.h"
10#include "esp_heap_caps.h"
11#include "esp_memory_utils.h"
12#include "esp_private/periph_ctrl.h"
13#include "hal/uhci_ll.h"
14#include "soc/ext_mem_defs.h"
15
16namespace
17{
18// RX 使用循环 DMA 描述符环,语义接近 STM/CH 的循环 RX DMA:持续接收,
19// 软件侧维护消费索引。
20// RX uses a circular DMA descriptor ring, similar to STM/CH circular RX DMA
21// behavior (continuous receive + software consumer index).
22constexpr uint32_t DMA_RX_NODE_COUNT = 8;
23
24// 当前 ESP GDMA link item 单节点最多只能描述 4095 字节。
25// Current ESP GDMA link items cannot describe more than 4095 bytes in one node.
26constexpr size_t DMA_MAX_BUFFER_SIZE_PER_LINK_ITEM = 4095U;
27
28// 为就地修改描述符长度而保留的 GDMA link descriptor 最小本地视图。
29// Minimal local view of the GDMA link descriptor layout used for in-place patching.
30struct GdmaLinkItem
31{
32 struct
33 {
34 uint32_t size : 12;
35 uint32_t length : 12;
36 uint32_t reserved24 : 4;
37 uint32_t err_eof : 1;
38 uint32_t reserved29 : 1;
39 uint32_t suc_eof : 1;
40 uint32_t owner : 1;
41 } dw0;
42 void* buffer;
43 GdmaLinkItem* next;
44};
45
46constexpr uint32_t GDMA_OWNER_CPU = 0U;
47constexpr uint32_t GDMA_OWNER_DMA = 1U;
48
49// 用于 DMA storage 和 node 大小对齐计算的辅助函数。
50// Helper used for DMA storage and node-size alignment calculations.
51size_t AlignUp(size_t value, size_t align)
52{
53 if (align <= 1)
54 {
55 return value;
56 }
57 return ((value + align - 1) / align) * align;
58}
59
60// 当目标 SoC 提供 non-cache alias 时,把 ESP-IDF 返回的 cache 地址转换成
61// GDMA 描述符使用的 non-cache 地址。
62// Convert the cached address returned by ESP-IDF into the non-cache alias used
63// by the GDMA descriptors when the target SoC exposes one.
64uintptr_t CacheAddrToNonCache(uintptr_t addr)
65{
66#if SOC_NON_CACHEABLE_OFFSET
67 return addr + SOC_NON_CACHEABLE_OFFSET;
68#else
69 return addr;
70#endif
71}
72
73// 从 GDMA list head 地址恢复首个 link item。
74// Recover the first link item from a GDMA list head address.
75GdmaLinkItem* LinkItemFromHeadAddr(uintptr_t head_addr)
76{
77 return reinterpret_cast<GdmaLinkItem*>(CacheAddrToNonCache(head_addr));
78}
79
80#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
81extern "C" esp_err_t esp_cache_msync(void* addr, size_t size, int flags);
82#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
83extern "C" esp_err_t esp_cache_get_alignment(uint32_t heap_caps, size_t* out_alignment);
84#endif
85
86constexpr int CACHE_SYNC_FLAG_UNALIGNED = (1 << 1);
87constexpr int CACHE_SYNC_FLAG_DIR_C2M = (1 << 2);
88constexpr int CACHE_SYNC_FLAG_DIR_M2C = (1 << 3);
89
90// 当当前内存区域可缓存时,同步一个 DMA 窗口。
91// Synchronize one DMA window when the active memory region is cacheable.
92bool CacheSyncDmaBuffer(const void* addr, size_t size, bool cache_to_mem)
93{
94 if ((addr == nullptr) || (size == 0U))
95 {
96 return true;
97 }
98
99#if SOC_PSRAM_DMA_CAPABLE && !SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
100 if (!esp_ptr_external_ram(addr))
101 {
102 return true;
103 }
104#endif
105
106 // M2C 不接受 UNALIGNED;RX 调用方传入完整且按 cache line 对齐的 descriptor。
107 // M2C does not accept UNALIGNED; RX callers pass a complete cache-line-aligned
108 // descriptor.
109 int flags = cache_to_mem ? (CACHE_SYNC_FLAG_DIR_C2M | CACHE_SYNC_FLAG_UNALIGNED)
110 : CACHE_SYNC_FLAG_DIR_M2C;
111
112 const esp_err_t ret = esp_cache_msync(const_cast<void*>(addr), size, flags);
113 // Non-cacheable regions can return ESP_ERR_INVALID_ARG; treat as no-op success.
114 return (ret == ESP_OK) || (ret == ESP_ERR_INVALID_ARG);
115}
116#endif
117} // namespace
118
119namespace LibXR
120{
121
122// TX EOF 表示当前暂存的 active payload 已经完整离开 DMA 引擎。
123// TX EOF means the current staged active payload has fully left the DMA engine.
124bool IRAM_ATTR ESP32UART::DmaTxEofCallback(gdma_channel_handle_t, gdma_event_data_t*,
125 void* user_data)
126{
127 auto* uart = static_cast<ESP32UART*>(user_data);
128 if (uart != nullptr)
129 {
130 uart->service_.Invoke(ESP32UART::EVENT_TX_DONE, true,
131 [uart](uint32_t events, bool in_isr)
132 { uart->ServiceEvents(events, in_isr); });
133 }
134 return false;
135}
136
137// TX 描述符错误会被上报为后端 TX 失败。
138// TX descriptor error is surfaced as a backend TX failure.
139bool IRAM_ATTR ESP32UART::DmaTxDescrErrCallback(gdma_channel_handle_t, gdma_event_data_t*,
140 void* user_data)
141{
142 auto* uart = static_cast<ESP32UART*>(user_data);
143 if (uart != nullptr)
144 {
145 uart->service_.Invoke(ESP32UART::EVENT_TX_ERROR, true,
146 [uart](uint32_t events, bool in_isr)
147 { uart->ServiceEvents(events, in_isr); });
148 }
149 return false;
150}
151
152// RX 完成回调只负责把事件转发到 UART 对象状态机。
153// RX done callback only forwards the event into the UART object state machine.
154bool IRAM_ATTR ESP32UART::DmaRxDoneCallback(gdma_channel_handle_t,
155 gdma_event_data_t* event_data,
156 void* user_data)
157{
158 auto* uart = static_cast<ESP32UART*>(user_data);
159 if (uart != nullptr)
160 {
161 const uint32_t event = (event_data != nullptr && event_data->flags.abnormal_eof)
162 ? ESP32UART::EVENT_RX_ERROR
163 : ESP32UART::EVENT_RX_WORK;
164 uart->service_.Invoke(event, true, [uart](uint32_t events, bool in_isr)
165 { uart->ServiceEvents(events, in_isr); });
166 }
167 return false;
168}
169
170// RX 描述符错误要求完整恢复 RX 环。
171// RX descriptor error requests a full RX ring recovery.
172bool IRAM_ATTR ESP32UART::DmaRxDescrErrCallback(gdma_channel_handle_t, gdma_event_data_t*,
173 void* user_data)
174{
175 auto* uart = static_cast<ESP32UART*>(user_data);
176 if (uart != nullptr)
177 {
178 uart->service_.Invoke(ESP32UART::EVENT_RX_ERROR, true,
179 [uart](uint32_t events, bool in_isr)
180 { uart->ServiceEvents(events, in_isr); });
181 }
182 return false;
183}
184
185// DMA 后端初始化做三件事:
186// 1. 把 UHCI 绑定到选定 UART。
187// 2. 为双缓冲两半各准备一条 TX 描述符链。
188// 3. 准备一条循环 RX 描述符环。
189// DMA backend bring-up does three things:
190// 1. Bind UHCI to the selected UART.
191// 2. Prepare two TX descriptor lists, one per double-buffer half.
192// 3. Prepare one circular RX descriptor ring.
193ErrorCode ESP32UART::InitDmaBackend()
194{
195 if (dma_backend_enabled_)
196 {
197 return ErrorCode::OK;
198 }
199
200 periph_module_enable(PERIPH_UHCI0_MODULE);
201 periph_module_reset(PERIPH_UHCI0_MODULE);
202
203 uhci_hal_init(&uhci_hal_, 0);
204 uhci_ll_attach_uart_port(uhci_hal_.dev, uart_num_);
205
206 uhci_seper_chr_t sep_chr = {};
207 sep_chr.sub_chr_en = 0;
208 uhci_ll_set_seper_chr(uhci_hal_.dev, &sep_chr);
209 uhci_ll_rx_set_eof_mode(uhci_hal_.dev, UHCI_RX_IDLE_EOF);
210
211 gdma_channel_alloc_config_t tx_cfg = {
212 .sibling_chan = nullptr,
213 .direction = GDMA_CHANNEL_DIRECTION_TX,
214 .flags = {},
215 };
216 if (gdma_new_ahb_channel(&tx_cfg, &tx_dma_channel_) != ESP_OK)
217 {
218 return ErrorCode::INIT_ERR;
219 }
220
221 if (gdma_connect(tx_dma_channel_, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)) !=
222 ESP_OK)
223 {
224 return ErrorCode::INIT_ERR;
225 }
226
227 gdma_transfer_config_t transfer_cfg = {
228 .max_data_burst_size = 0,
229 .access_ext_mem = true,
230 };
231 if (gdma_config_transfer(tx_dma_channel_, &transfer_cfg) != ESP_OK)
232 {
233 return ErrorCode::INIT_ERR;
234 }
235
236 size_t tx_int_alignment = 1;
237 size_t tx_ext_alignment = 1;
238 if (gdma_get_alignment_constraints(tx_dma_channel_, &tx_int_alignment,
239 &tx_ext_alignment) != ESP_OK)
240 {
241 return ErrorCode::INIT_ERR;
242 }
243 const size_t tx_dma_alignment =
244 std::max<size_t>(1, std::max(tx_int_alignment, tx_ext_alignment));
245
246 gdma_strategy_config_t tx_strategy = {
247 .owner_check = true,
248 .auto_update_desc = true,
249 .eof_till_data_popped = true,
250 };
251 if (gdma_apply_strategy(tx_dma_channel_, &tx_strategy) != ESP_OK)
252 {
253 return ErrorCode::INIT_ERR;
254 }
255
256 gdma_link_list_config_t tx_link_cfg = {
257 .num_items = 1,
258 .item_alignment = 4,
259 .flags = {},
260 };
261 gdma_link_list_handle_t tx_dma_links[2] = {nullptr, nullptr};
262
263 for (int i = 0; i < 2; ++i)
264 {
265 if (gdma_new_link_list(&tx_link_cfg, &tx_dma_links[i]) != ESP_OK)
266 {
267 return ErrorCode::INIT_ERR;
268 }
269
270 gdma_buffer_mount_config_t tx_mount = {
271 .buffer = tx_dma_buffer_.Buffer(i),
272 .buffer_alignment = tx_dma_alignment,
273 .length = 1,
274 .flags =
275 {
276 .mark_eof = 1,
277 .mark_final = 1,
278 .bypass_buffer_align_check = 0,
279 },
280 };
281
282 if (gdma_link_mount_buffers(tx_dma_links[i], 0, &tx_mount, 1, nullptr) != ESP_OK)
283 {
284 return ErrorCode::INIT_ERR;
285 }
286
287 tx_dma_head_addr_[i] = gdma_link_get_head_addr(tx_dma_links[i]);
288 if (tx_dma_head_addr_[i] == 0U)
289 {
290 return ErrorCode::INIT_ERR;
291 }
292 }
293
294 gdma_tx_event_callbacks_t tx_callbacks = {
295 .on_trans_eof = DmaTxEofCallback,
296 .on_descr_err = DmaTxDescrErrCallback,
297 };
298 if (gdma_register_tx_event_callbacks(tx_dma_channel_, &tx_callbacks, this) != ESP_OK)
299 {
300 return ErrorCode::INIT_ERR;
301 }
302
303 gdma_channel_alloc_config_t rx_cfg = {
304 .sibling_chan = nullptr,
305 .direction = GDMA_CHANNEL_DIRECTION_RX,
306 .flags = {},
307 };
308 if (gdma_new_ahb_channel(&rx_cfg, &rx_dma_channel_) != ESP_OK)
309 {
310 return ErrorCode::INIT_ERR;
311 }
312
313 if (gdma_connect(rx_dma_channel_, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)) !=
314 ESP_OK)
315 {
316 return ErrorCode::INIT_ERR;
317 }
318
319 if (gdma_config_transfer(rx_dma_channel_, &transfer_cfg) != ESP_OK)
320 {
321 return ErrorCode::INIT_ERR;
322 }
323
324 gdma_strategy_config_t rx_strategy = {
325 .owner_check = true,
326 .auto_update_desc = false,
327 .eof_till_data_popped = false,
328 };
329 if (gdma_apply_strategy(rx_dma_channel_, &rx_strategy) != ESP_OK)
330 {
331 return ErrorCode::INIT_ERR;
332 }
333
334 size_t rx_int_alignment = 1;
335 size_t rx_ext_alignment = 1;
336 if (gdma_get_alignment_constraints(rx_dma_channel_, &rx_int_alignment,
337 &rx_ext_alignment) != ESP_OK)
338 {
339 return ErrorCode::INIT_ERR;
340 }
341 const size_t rx_dma_alignment =
342 std::max<size_t>(1, std::max(rx_int_alignment, rx_ext_alignment));
343
344 size_t rx_cache_alignment = 1U;
345#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
346 if ((esp_cache_get_alignment(MALLOC_CAP_INTERNAL, &rx_cache_alignment) != ESP_OK) ||
347 (rx_cache_alignment == 0U))
348 {
349 return ErrorCode::INIT_ERR;
350 }
351#endif
352
353 gdma_link_list_config_t rx_link_cfg = {
354 .num_items = DMA_RX_NODE_COUNT,
355 .item_alignment = 4,
356 .flags = {},
357 };
358 if (gdma_new_link_list(&rx_link_cfg, &rx_dma_link_) != ESP_OK)
359 {
360 return ErrorCode::INIT_ERR;
361 }
362
363 // 保持单个环窗口适度偏大,以降低高波特率下的 ISR 压力。
364 // Keep one ring window reasonably large to lower ISR pressure at high baud.
365 const size_t rx_chunk_target = std::min<size_t>(
366 std::max<size_t>(32, rx_isr_buffer_size_ / DMA_RX_NODE_COUNT), 512);
367 const size_t rx_storage_alignment =
368 std::max<size_t>(4, std::max(rx_dma_alignment, rx_cache_alignment));
369 rx_dma_chunk_size_ =
370 std::max<size_t>(AlignUp(rx_chunk_target, rx_storage_alignment), 32);
371 const size_t rx_storage_bytes =
372 AlignUp(rx_dma_chunk_size_ * DMA_RX_NODE_COUNT, rx_storage_alignment);
373
374 rx_dma_storage_ = static_cast<uint8_t*>(
375 heap_caps_aligned_alloc(rx_storage_alignment, rx_storage_bytes,
376 MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT));
377 if (rx_dma_storage_ == nullptr)
378 {
379 return ErrorCode::NO_MEM;
380 }
381
382 std::array<gdma_buffer_mount_config_t, DMA_RX_NODE_COUNT> rx_mount = {};
383 for (uint32_t i = 0; i < DMA_RX_NODE_COUNT; ++i)
384 {
385 rx_mount[i] = gdma_buffer_mount_config_t{
386 .buffer = rx_dma_storage_ + (static_cast<size_t>(i) * rx_dma_chunk_size_),
387 .buffer_alignment = rx_dma_alignment,
388 .length = rx_dma_chunk_size_,
389 .flags =
390 {
391 .mark_eof = 0,
392 .mark_final = 0,
393 .bypass_buffer_align_check = 0,
394 },
395 };
396 }
397
398 if (gdma_link_mount_buffers(rx_dma_link_, 0, rx_mount.data(), DMA_RX_NODE_COUNT,
399 nullptr) != ESP_OK)
400 {
401 return ErrorCode::INIT_ERR;
402 }
403
404 gdma_rx_event_callbacks_t rx_callbacks = {
405 .on_recv_eof = nullptr,
406 .on_descr_err = DmaRxDescrErrCallback,
407 .on_recv_done = DmaRxDoneCallback,
408 };
409 if (gdma_register_rx_event_callbacks(rx_dma_channel_, &rx_callbacks, this) != ESP_OK)
410 {
411 return ErrorCode::INIT_ERR;
412 }
413
414 if (gdma_reset(rx_dma_channel_) != ESP_OK)
415 {
416 return ErrorCode::INIT_ERR;
417 }
418
419 rx_dma_node_index_ = 0;
420 dma_backend_enabled_ = true;
421 if (gdma_start(rx_dma_channel_, gdma_link_get_head_addr(rx_dma_link_)) != ESP_OK)
422 {
423 return ErrorCode::INIT_ERR;
424 }
425 return ErrorCode::OK;
426}
427
428// TX DMA 启动时只修改预挂载描述符链的动态字段,避免每次请求都重建描述符。
429// TX DMA start only patches the dynamic fields of the pre-mounted descriptor
430// list, so the hot path avoids rebuilding descriptors for every request.
431bool IRAM_ATTR ESP32UART::StartDmaTx()
432{
433 if ((tx_dma_channel_ == nullptr) || !tx_active_valid_)
434 {
435 return false;
436 }
437
438 uint8_t* const active_buffer = tx_dma_buffer_.ActiveBuffer();
439 const size_t active_len = tx_active_length_;
440 if ((active_buffer == nullptr) || (active_len == 0) ||
441 (active_len > DMA_MAX_BUFFER_SIZE_PER_LINK_ITEM))
442 {
443 return false;
444 }
445
446 const int link_index = tx_dma_buffer_.ActiveBlock();
447 if ((link_index != 0) && (link_index != 1))
448 {
449 return false;
450 }
451
452 if (tx_dma_head_addr_[link_index] == 0U)
453 {
454 return false;
455 }
456
457 auto* desc = LinkItemFromHeadAddr(tx_dma_head_addr_[link_index]);
458 if (desc == nullptr)
459 {
460 return false;
461 }
462
463 // 描述符链保持预挂载,只就地更新本次传输长度。
464 // Keep descriptor list pre-mounted and only patch the dynamic transfer length in-place.
465 desc->buffer = active_buffer;
466 desc->dw0.size = static_cast<uint32_t>(active_len);
467 desc->dw0.length = static_cast<uint32_t>(active_len);
468 desc->dw0.err_eof = 0U;
469 desc->dw0.suc_eof = 1U;
470 desc->dw0.owner = GDMA_OWNER_DMA;
471 desc->next = nullptr;
472 std::atomic_thread_fence(std::memory_order_release);
473
474#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
475 if (!CacheSyncDmaBuffer(active_buffer, active_len, true))
476 {
477 return false;
478 }
479#endif
480
481 return gdma_start(tx_dma_channel_, tx_dma_head_addr_[link_index]) == ESP_OK;
482}
483
484// RX DMA 完成以描述符 owner 位为准,而不是依赖短命回调指针。已完成描述符按
485// 最老顺序消费,按 ReadQueue 实时容量复制,然后在同一串行 owner 中交还 DMA。
486// RX DMA completion is derived from descriptor ownership, not from a transient
487// callback pointer. Completed descriptors are consumed oldest-first, copied up
488// to live ReadQueue capacity, and returned to DMA in the same serialized owner.
489void IRAM_ATTR ESP32UART::HandleDmaRxDone(gdma_event_data_t* event_data, bool in_isr)
490{
491 UNUSED(event_data);
492 if ((rx_dma_storage_ == nullptr) || (rx_dma_chunk_size_ == 0))
493 {
494 return;
495 }
496
497 auto* first = LinkItemFromHeadAddr(gdma_link_get_head_addr(rx_dma_link_));
498 DEV_ASSERT_FROM_CALLBACK(first != nullptr, in_isr);
499 auto* item = first;
500 for (uint32_t i = 0U; i < rx_dma_node_index_; ++i)
501 {
502 DEV_ASSERT_FROM_CALLBACK(item != nullptr, in_isr);
503 item = item->next;
504 }
505
506 auto queue = _read_port.GetReadQueue(in_isr);
507 for (uint32_t processed = 0U; processed < DMA_RX_NODE_COUNT; ++processed)
508 {
509 DEV_ASSERT_FROM_CALLBACK(item != nullptr, in_isr);
510 if (item->dw0.owner != GDMA_OWNER_CPU)
511 {
512 break;
513 }
514
515 std::atomic_thread_fence(std::memory_order_acquire);
516 const size_t received = std::min<size_t>(item->dw0.length, rx_dma_chunk_size_);
517#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
518 [[maybe_unused]] const auto cache_sync_dma_buffer_result =
519 CacheSyncDmaBuffer(item->buffer, rx_dma_chunk_size_, false);
520 REQUIRE_FROM_CALLBACK(cache_sync_dma_buffer_result, in_isr);
521#endif
522 if (received != 0U && queue.EmptySize() != 0U)
523 {
524 const size_t fitting = std::min(received, queue.EmptySize());
525 [[maybe_unused]] const auto push_batch_result =
526 queue.PushBatch(static_cast<const uint8_t*>(item->buffer), fitting);
527 DEV_ASSERT_FROM_CALLBACK(push_batch_result == ErrorCode::OK, in_isr);
528 }
529
530 std::atomic_thread_fence(std::memory_order_release);
531 item->dw0.owner = GDMA_OWNER_DMA;
532 rx_dma_node_index_ = (rx_dma_node_index_ + 1U) % DMA_RX_NODE_COUNT;
533 item = item->next;
534 }
535 queue.Publish();
536}
537
538void IRAM_ATTR ESP32UART::StopDmaRx(bool in_isr)
539{
540 DEV_ASSERT_FROM_CALLBACK(rx_dma_channel_ != nullptr, in_isr);
541 [[maybe_unused]] const auto gdma_stop_result = gdma_stop(rx_dma_channel_);
542 REQUIRE_FROM_CALLBACK(gdma_stop_result == ESP_OK, in_isr);
543 [[maybe_unused]] const auto gdma_reset_result = gdma_reset(rx_dma_channel_);
544 REQUIRE_FROM_CALLBACK(gdma_reset_result == ESP_OK, in_isr);
545}
546
547void IRAM_ATTR ESP32UART::ResetDmaRxDescriptors(bool in_isr)
548{
549 DEV_ASSERT_FROM_CALLBACK(rx_dma_link_ != nullptr, in_isr);
550 auto* item = LinkItemFromHeadAddr(gdma_link_get_head_addr(rx_dma_link_));
551 DEV_ASSERT_FROM_CALLBACK(item != nullptr, in_isr);
552 for (uint32_t i = 0U; i < DMA_RX_NODE_COUNT; ++i)
553 {
554 DEV_ASSERT_FROM_CALLBACK(item != nullptr, in_isr);
555 item->dw0.size = static_cast<uint32_t>(rx_dma_chunk_size_);
556 item->dw0.length = static_cast<uint32_t>(rx_dma_chunk_size_);
557 item->dw0.err_eof = 0U;
558 item->dw0.suc_eof = 0U;
559 std::atomic_thread_fence(std::memory_order_release);
560 item->dw0.owner = GDMA_OWNER_DMA;
561 item = item->next;
562 }
563
564 rx_dma_node_index_ = 0;
565}
566
567void IRAM_ATTR ESP32UART::StartDmaRx(bool in_isr)
568{
569 DEV_ASSERT_FROM_CALLBACK(rx_dma_channel_ != nullptr, in_isr);
570 DEV_ASSERT_FROM_CALLBACK(rx_dma_link_ != nullptr, in_isr);
571 [[maybe_unused]] const auto gdma_start_result =
572 gdma_start(rx_dma_channel_, gdma_link_get_head_addr(rx_dma_link_));
573 REQUIRE_FROM_CALLBACK(gdma_start_result == ESP_OK, in_isr);
574}
575
576// RX DMA 恢复会从节点零重新启动整个环。
577// RX DMA recovery restarts the circular ring from node zero.
578void IRAM_ATTR ESP32UART::HandleDmaRxError(bool in_isr)
579{
580 if ((rx_dma_channel_ == nullptr) || (rx_dma_link_ == nullptr))
581 {
582 return;
583 }
584
585 StopDmaRx(in_isr);
586 ResetDmaRxDescriptors(in_isr);
587 StartDmaRx(in_isr);
588}
589
590// TX DMA 恢复会中止当前硬件传输,再交给公共 TX 完成路径清理软件状态。
591// TX DMA recovery aborts the current hardware transfer and lets the common TX
592// completion path clean up the software state.
593void IRAM_ATTR ESP32UART::HandleDmaTxError(bool in_isr)
594{
595 if (tx_dma_channel_ != nullptr)
596 {
597 gdma_stop(tx_dma_channel_);
598 gdma_reset(tx_dma_channel_);
599 }
600 OnTxTransferDone(in_isr, ErrorCode::FAILED);
601}
602
603} // namespace LibXR
604
605#endif
int ActiveBlock() const
获取当前活动缓冲区编号 Returns the current active block index
uint8_t * Buffer(int block) const
获取指定编号缓冲区的指针 Returns the pointer of the specified block
uint8_t * ActiveBuffer() const
获取当前正在使用的缓冲区指针 Returns the currently active buffer
ESP32UART(uart_port_t uart_num, int tx_pin, int rx_pin, int rts_pin=PIN_NO_CHANGE, int cts_pin=PIN_NO_CHANGE, size_t rx_buffer_size=1024, size_t tx_buffer_size=512, uint32_t tx_queue_size=5, UART::Configuration config={115200, UART::Parity::NO_PARITY, 8, 1}, bool enable_dma=true)
构造并初始化 ESP UART / Construct and initialize an ESP UART.
Definition esp_uart.cpp:146
void Publish()
结束本次写入并通知挂起读 / End production and notify a pending read.
Definition read_port.cpp:42
ReadQueue GetReadQueue(bool in_isr=false)
获取后端接收入队接口 / Obtain the backend RX producer interface.
Definition read_port.cpp:14
LibXR 命名空间 / LibXR namespace.
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举 / Defines an enumeration for error codes
@ INIT_ERR
初始化错误 / Initialization error
@ NO_MEM
内存不足 / Insufficient memory
@ FAILED
操作失败 / Operation failed
@ OK
操作成功 / Operation successful