libxr
Embedded C++ framework for bare-metal, RTOS and Linux
Loading...
Searching...
No Matches
esp_uart.cpp
1#include "esp_uart.hpp"
2
3#include <algorithm>
4#include <cstring>
5
6#include "esp_attr.h"
7#include "esp_clk_tree.h"
8#include "esp_err.h"
9#include "esp_heap_caps.h"
10#include "esp_private/periph_ctrl.h"
11#include "esp_rom_gpio.h"
12#include "hal/uart_ll.h"
13#include "soc/gpio_sig_map.h"
14#include "soc/uart_periph.h"
15
16namespace
17{
18// FIFO 接收路径处理的 RX 中断原因。
19// RX interrupt reasons handled by the FIFO receive path.
20constexpr uint32_t UART_RX_INTR_MASK =
21 UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_OVF;
22
23// FIFO 发送路径使用的 TX 中断原因。
24// TX interrupt reason used by the FIFO transmit path.
25constexpr uint32_t UART_TX_INTR_MASK = UART_INTR_TXFIFO_EMPTY;
26constexpr uint32_t UART_TX_IDLE_INTR_MASK = UART_INTR_TX_DONE;
27
28// 使用最小非零 timeout,让短 RX 尾包尽量贴近 idle 风格边界被冲刷出来。
29// Use the minimum non-zero timeout so short RX tails are flushed as close as
30// possible to an idle-style boundary.
31constexpr uint8_t RX_TOUT_THRESHOLD = 1;
32
33// 当硬件 FIFO 下降到约一半深度时,请求补充更多 TX 字节。
34// Ask for more TX bytes once the hardware FIFO drops to roughly half depth.
35constexpr uint16_t TX_EMPTY_THRESHOLD = SOC_UART_FIFO_LEN / 2U;
36
37// 该后端不能与 ESP-IDF 的控制台 UART 占用同时存在。
38// This backend cannot coexist with the ESP-IDF console UART reservation.
39bool IsConsoleUartInUse(uart_port_t uart_num)
40{
41#if defined(CONFIG_ESP_CONSOLE_UART) && CONFIG_ESP_CONSOLE_UART
42 return static_cast<int>(uart_num) == CONFIG_ESP_CONSOLE_UART_NUM;
43#else
44 (void)uart_num;
45 return false;
46#endif
47}
48
49bool IsBaudrateRepresentable(uint32_t baudrate, uint32_t source_clock_hz)
50{
51 if ((baudrate == 0U) || (source_clock_hz == 0U))
52 {
53 return false;
54 }
55
56#if defined(UART_SCLK_DIV_NUM_V) || defined(PCR_UART0_SCLK_DIV_NUM_V) || \
57 defined(HP_SYS_CLKRST_REG_UART0_SCLK_DIV_NUM_V)
58#if defined(UART_SCLK_DIV_NUM_V)
59 constexpr uint32_t max_source_divider = UART_SCLK_DIV_NUM_V + 1U;
60#elif defined(PCR_UART0_SCLK_DIV_NUM_V)
61 constexpr uint32_t max_source_divider = PCR_UART0_SCLK_DIV_NUM_V + 1U;
62#else
63 constexpr uint32_t max_source_divider = HP_SYS_CLKRST_REG_UART0_SCLK_DIV_NUM_V + 1U;
64#endif
65
66 const uint64_t max_uart_divider = UART_CLKDIV_V;
67 const uint64_t denominator = max_uart_divider * baudrate;
68 const uint64_t source_divider =
69 (static_cast<uint64_t>(source_clock_hz) + denominator - 1U) / denominator;
70 if ((source_divider == 0U) || (source_divider > max_source_divider))
71 {
72 return false;
73 }
74
75 const uint64_t clock_divider = (static_cast<uint64_t>(source_clock_hz) << 4U) /
76 (static_cast<uint64_t>(baudrate) * source_divider);
77 return (clock_divider >> 4U) <= max_uart_divider;
78#else
79 const uint64_t clock_divider =
80 (static_cast<uint64_t>(source_clock_hz) << 4U) / baudrate;
81 return (clock_divider >> 4U) <= UART_CLKDIV_V;
82#endif
83}
84
85bool GetUartClockFrequency(uint32_t& out_frequency_hz)
86{
87 out_frequency_hz = 0U;
88 return (esp_clk_tree_src_get_freq_hz(static_cast<soc_module_clk_t>(UART_SCLK_DEFAULT),
89 ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED,
90 &out_frequency_hz) == ESP_OK) &&
91 (out_frequency_hz != 0U);
92}
93} // namespace
94
95namespace LibXR
96{
97
99{
100 owner_.service_.Invoke(ESP32UART::EVENT_RX_WORK, in_isr,
101 [this](uint32_t events, bool owner_isr)
102 { owner_.ServiceEvents(events, owner_isr); });
103}
104
105// 优先使用对齐的 DMA 可访问分配;若失败,再退回到更宽松的 DMA heap。
106// Prefer aligned DMA-capable allocation first, then fall back to the broader
107// DMA-capable heap if the strict aligned allocation API is unavailable.
108uint8_t* ESP32UART::AllocateTxStorage(size_t size)
109{
110 void* aligned = heap_caps_aligned_alloc(
111 4, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
112 if (aligned != nullptr)
113 {
114 return static_cast<uint8_t*>(aligned);
115 }
116
117 return static_cast<uint8_t*>(
118 heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT));
119}
120
121// 将公开 UART 序号转换为 ESP 外设模块门控对象。
122// Translate the public UART index into the ESP peripheral module gate.
123ErrorCode ESP32UART::ResolveUartPeriph(uart_port_t uart_num, periph_module_t& out)
124{
125 switch (uart_num)
126 {
127 case UART_NUM_0:
128 out = PERIPH_UART0_MODULE;
129 return ErrorCode::OK;
130 case UART_NUM_1:
131 out = PERIPH_UART1_MODULE;
132 return ErrorCode::OK;
133#if SOC_UART_HP_NUM > 2
134 case UART_NUM_2:
135 out = PERIPH_UART2_MODULE;
136 return ErrorCode::OK;
137#endif
138 default:
140 }
141}
142
143// 先构造队列连接,再把 storage 绑定到 TX 双缓冲视图,最后再触碰硬件。
144// Construct queue plumbing first, then bind the storage into the TX
145// double-buffer view before touching hardware.
146ESP32UART::ESP32UART(uart_port_t uart_num, int tx_pin, int rx_pin, int rts_pin,
147 int cts_pin, size_t rx_buffer_size, size_t tx_buffer_size,
148 uint32_t tx_queue_size, UART::Configuration config, bool enable_dma)
149 : UART(&_read_port, &_write_port),
150 uart_num_(uart_num),
151 tx_pin_(tx_pin),
152 rx_pin_(rx_pin),
153 rts_pin_(rts_pin),
154 cts_pin_(cts_pin),
155 config_(config),
156 rx_isr_buffer_(new uint8_t[rx_buffer_size]),
157 rx_isr_buffer_size_(rx_buffer_size),
158 tx_storage_(AllocateTxStorage(tx_buffer_size * 2)),
159 dma_requested_(enable_dma),
160 _read_port(rx_buffer_size, *this),
161 _write_port(tx_queue_size, tx_buffer_size)
162{
163 ASSERT(!IsConsoleUartInUse(uart_num_));
164 ASSERT(uart_num_ < UART_NUM_MAX);
165 ASSERT(uart_num_ < SOC_UART_HP_NUM);
166 ASSERT(rx_isr_buffer_size_ > 0);
167 ASSERT(tx_buffer_size > 0);
168 ASSERT(tx_queue_size > 0U);
169
170 INIT_CRIT_SECTION_LOCK_RUNTIME(&irq_lock_);
171
172 tx_dma_buffer_.Init({tx_storage_, tx_buffer_size * 2U});
173
174 _write_port = WriteFun;
175
176 if (InitUartHardware() != ErrorCode::OK)
177 {
178 REQUIRE(false);
179 return;
180 }
181
182#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
183 if (dma_requested_)
184 {
185 if (InitDmaBackend() != ErrorCode::OK)
186 {
187 REQUIRE(false);
188 return;
189 }
190 }
191
192 if (!dma_backend_enabled_)
193 {
194 if (InstallUartIsr() != ErrorCode::OK)
195 {
196 REQUIRE(false);
197 return;
198 }
199 ConfigureRxInterruptPath();
200 }
201#else
202 if (InstallUartIsr() != ErrorCode::OK)
203 {
204 REQUIRE(false);
205 return;
206 }
207 ConfigureRxInterruptPath();
208#endif
209}
210
211// FIFO 模式下使用半 FIFO 的 RX 阈值,剩余短尾包交给上面的最小非零 RX timeout。
212// Use a half-FIFO RX threshold in FIFO mode. Short residual tails are handled
213// by the minimum non-zero RX timeout above.
214void ESP32UART::ConfigureRxInterruptPath()
215{
216 const uint16_t full_thr = static_cast<uint16_t>(SOC_UART_FIFO_LEN / 2U);
217
218 uart_hal_set_rxfifo_full_thr(&uart_hal_, full_thr);
219 uart_hal_set_rx_timeout(&uart_hal_, RX_TOUT_THRESHOLD);
220 ClearUartInterrupt(UART_RX_INTR_MASK);
221 EnableUartInterrupt(UART_RX_INTR_MASK);
222}
223
224// 验证并发布单个待应用配置,在当前传输模式的边界应用。
225// Validate and publish one configuration for the mode-specific transfer boundary.
227{
228 if (!uart_hw_enabled_)
229 {
231 }
232
233 uart_word_length_t word_length = UART_DATA_8_BITS;
234 uart_stop_bits_t stop_bits = UART_STOP_BITS_1;
235 if (config.baudrate == 0U || !ResolveWordLength(config.data_bits, word_length) ||
236 !ResolveStopBits(config.stop_bits, stop_bits) ||
238 config.parity != UART::Parity::ODD))
239 {
240 return ErrorCode::ARG_ERR;
241 }
242
243 // 使用初始化时的时钟快照,校验时不访问硬件。
244 // Validate against the initial clock snapshot without touching hardware.
245 if (!IsBaudrateRepresentable(config.baudrate, uart_sclk_hz_))
246 {
247 return ErrorCode::ARG_ERR;
248 }
249
250 uint8_t expected = static_cast<uint8_t>(ConfigState::EMPTY);
251 if (!config_state_.compare_exchange_strong(
252 expected, static_cast<uint8_t>(ConfigState::RESERVED),
253 std::memory_order_acq_rel, std::memory_order_acquire))
254 {
255 return ErrorCode::BUSY;
256 }
257
258 requested_config_ = config;
259 config_state_.store(static_cast<uint8_t>(ConfigState::PUBLISHED),
260 std::memory_order_release);
261 service_.Invoke(EVENT_CONFIG, in_isr, [this](uint32_t events, bool owner_isr)
262 { ServiceEvents(events, owner_isr); });
263 return ErrorCode::OK;
264}
265
266ErrorCode ESP32UART::ApplyConfig(const UART::Configuration& config)
267{
268 if (!uart_hw_enabled_)
269 {
271 }
272
273 uart_word_length_t word_length = UART_DATA_8_BITS;
274 uart_stop_bits_t stop_bits = UART_STOP_BITS_1;
275 [[maybe_unused]] const auto resolve_word_length_result =
276 ResolveWordLength(config.data_bits, word_length);
277 DEV_ASSERT(resolve_word_length_result);
278 [[maybe_unused]] const auto resolve_stop_bits_result =
279 ResolveStopBits(config.stop_bits, stop_bits);
280 DEV_ASSERT(resolve_stop_bits_result);
281
282 const uart_sclk_t sclk = UART_SCLK_DEFAULT;
283 uart_hal_set_sclk(&uart_hal_, static_cast<soc_module_clk_t>(sclk));
284
285 if (uart_sclk_hz_ == 0U)
286 {
287 return ErrorCode::INIT_ERR;
288 }
289
290 if (!uart_hal_set_baudrate(&uart_hal_, config.baudrate, uart_sclk_hz_))
291 {
292 return ErrorCode::INIT_ERR;
293 }
294
295 uart_hal_set_data_bit_num(&uart_hal_, word_length);
296 uart_hal_set_stop_bits(&uart_hal_, stop_bits);
297 uart_hal_set_parity(&uart_hal_, ResolveParity(config.parity));
298 uart_hal_set_hw_flow_ctrl(&uart_hal_, UART_HW_FLOWCTRL_DISABLE, 0);
299 uart_hal_set_mode(&uart_hal_, UART_MODE_UART);
300 uart_hal_set_txfifo_empty_thr(&uart_hal_, TX_EMPTY_THRESHOLD);
301 uart_hal_rxfifo_rst(&uart_hal_);
302 ClearUartInterrupt(UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT);
303 return ErrorCode::OK;
304}
305
306void IRAM_ATTR ESP32UART::EnableUartInterrupt(uint32_t mask)
307{
308 esp_os_enter_critical_safe(&irq_lock_);
309 uart_hal_ena_intr_mask(&uart_hal_, mask);
310 esp_os_exit_critical_safe(&irq_lock_);
311}
312
313void IRAM_ATTR ESP32UART::DisableUartInterrupt(uint32_t mask)
314{
315 esp_os_enter_critical_safe(&irq_lock_);
316 uart_hal_disable_intr_mask(&uart_hal_, mask);
317 esp_os_exit_critical_safe(&irq_lock_);
318}
319
320void IRAM_ATTR ESP32UART::ClearUartInterrupt(uint32_t mask)
321{
322 esp_os_enter_critical_safe(&irq_lock_);
323 uart_hal_clr_intsts_mask(&uart_hal_, mask);
324 esp_os_exit_critical_safe(&irq_lock_);
325}
326
327void IRAM_ATTR ESP32UART::DisableAndClearUartInterrupt(uint32_t mask)
328{
329 esp_os_enter_critical_safe(&irq_lock_);
330 uart_hal_disable_intr_mask(&uart_hal_, mask);
331 uart_hal_clr_intsts_mask(&uart_hal_, mask);
332 esp_os_exit_critical_safe(&irq_lock_);
333}
334
335bool IRAM_ATTR ESP32UART::TryApplyPublishedConfig(bool in_isr)
336{
337 if (config_state_.load(std::memory_order_acquire) !=
338 static_cast<uint8_t>(ConfigState::PUBLISHED))
339 {
340 return false;
341 }
342
343 bool tx_idle = !tx_busy_.IsSet() && !tx_active_valid_
344#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
345 && !tx_dma_buffer_.HasPending()
346#endif
347 ;
348#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
349 if (!dma_backend_enabled_)
350#endif
351 {
352 tx_idle = tx_idle && (_write_port.Size() == 0U) && uart_hal_is_tx_idle(&uart_hal_);
353 }
354
355 if (!tx_idle)
356 {
357#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
358 if (!dma_backend_enabled_)
359#endif
360 {
361 // Keep a line-idle carrier armed while FIFO data is draining. The source
362 // is disabled again when the published configuration is applied.
363 EnableUartInterrupt(UART_TX_IDLE_INTR_MASK);
364 }
365 return false;
366 }
367
368#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
369 if (!dma_backend_enabled_)
370#endif
371 {
372 DisableAndClearUartInterrupt(UART_TX_IDLE_INTR_MASK);
373 }
374
375#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
376 if (dma_backend_enabled_)
377 {
378 // 在 ApplyConfig 重置 UART RX FIFO 前,先停止 RX DMA 消费者。
379 // Stop the RX DMA consumer before ApplyConfig resets the UART RX FIFO.
380 StopDmaRx(in_isr);
381 }
382#endif
383 [[maybe_unused]] const auto apply_config_result = ApplyConfig(requested_config_);
384 REQUIRE_FROM_CALLBACK(apply_config_result == ErrorCode::OK, in_isr);
385 config_ = requested_config_;
386 config_state_.store(static_cast<uint8_t>(ConfigState::EMPTY),
387 std::memory_order_release);
388#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
389 if (dma_backend_enabled_)
390 {
391 ResetDmaRxDescriptors(in_isr);
392 StartDmaRx(in_isr);
393 }
394#endif
395 return true;
396}
397
398void IRAM_ATTR ESP32UART::ServiceEvents(uint32_t events, bool in_isr)
399{
400 if ((events & EVENT_RX_ERROR) != 0U)
401 {
402#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
403 if (dma_backend_enabled_)
404 {
405 HandleDmaRxError(in_isr);
406 }
407#endif
408 }
409
410 const bool rx_error = (events & EVENT_RX_ERROR) != 0U;
411 const bool tx_error = (events & EVENT_TX_ERROR) != 0U;
412 if (tx_error)
413 {
414#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
415 if (dma_backend_enabled_)
416 {
417 HandleDmaTxError(in_isr);
418 }
419#endif
420 }
421
422 if (!tx_error && (events & EVENT_TX_DONE) != 0U)
423 {
424 OnTxTransferDone(in_isr, ErrorCode::OK);
425 }
426
427 if ((events & (EVENT_WRITE | EVENT_TX_DONE | EVENT_TX_ERROR | EVENT_TX_WORK |
428 EVENT_CONFIG)) != 0U)
429 {
430 (void)TryStartTx(in_isr);
431 }
432
433 // Re-evaluate after TX progress. In particular, a coalesced CONFIG/TX_DONE
434 // snapshot may become idle only after OnTxTransferDone().
435 if (TryApplyPublishedConfig(in_isr))
436 {
437 // A GDMA queue front intentionally stayed in WriteQueue behind the
438 // configuration barrier; resume ordinary loading under the new config.
439 (void)TryStartTx(in_isr);
440 }
441
442 if (!rx_error && (events & EVENT_RX_WORK) != 0U)
443 {
444#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
445 if (dma_backend_enabled_)
446 {
447 HandleDmaRxDone(nullptr, in_isr);
448 }
449 else
450#endif
451 {
452 DrainRxFifo(in_isr);
453 }
454 }
455}
456
457// UART 内部环回直接作为外设开关暴露,用于后端自测和无需外部短接的链路检查。
458// Internal UART loopback is exposed as a direct peripheral toggle for backend
459// self-test and board-free link checks.
461{
462 if (!uart_hw_enabled_)
463 {
465 }
466
467 uart_ll_set_loop_back(uart_hal_.dev, enable);
468 return ErrorCode::OK;
469}
470
471// `WritePort` 只需要一个回跳到所属 UART 实例的跳板。
472// `WritePort` only needs a trampoline back into the owning UART instance.
473void IRAM_ATTR ESP32UART::WriteFun(WritePort& port, bool in_isr)
474{
475 auto* uart = LibXR::ContainerOf(&port, &ESP32UART::_write_port);
476 uart->service_.Invoke(ESP32UART::EVENT_WRITE, in_isr,
477 [uart](uint32_t events, bool owner_isr)
478 { uart->ServiceEvents(events, owner_isr); });
479}
480
481// 将 libxr 数据位语义转换为 ESP HAL 取值。
482// Convert libxr data-bit semantics into the ESP HAL value set.
483bool ESP32UART::ResolveWordLength(uint8_t data_bits, uart_word_length_t& out)
484{
485 switch (data_bits)
486 {
487 case 5:
488 out = UART_DATA_5_BITS;
489 return true;
490 case 6:
491 out = UART_DATA_6_BITS;
492 return true;
493 case 7:
494 out = UART_DATA_7_BITS;
495 return true;
496 case 8:
497 out = UART_DATA_8_BITS;
498 return true;
499 default:
500 return false;
501 }
502}
503
504// 将 libxr 停止位语义转换为 ESP HAL 取值。
505// Convert libxr stop-bit semantics into the ESP HAL value set.
506bool ESP32UART::ResolveStopBits(uint8_t stop_bits, uart_stop_bits_t& out)
507{
508 switch (stop_bits)
509 {
510 case 1:
511 out = UART_STOP_BITS_1;
512 return true;
513 case 2:
514 out = UART_STOP_BITS_2;
515 return true;
516 default:
517 return false;
518 }
519}
520
521// 将 libxr 校验位语义转换为 ESP HAL 取值。
522// Convert libxr parity semantics into the ESP HAL value set.
523uart_parity_t ESP32UART::ResolveParity(UART::Parity parity)
524{
525 switch (parity)
526 {
528 return UART_PARITY_DISABLE;
530 return UART_PARITY_EVEN;
532 return UART_PARITY_ODD;
533 default:
534 return UART_PARITY_DISABLE;
535 }
536}
537
538// 在挂接更高层 ISR 或 DMA 连接前,先把 UART 模块拉到已知空闲状态。
539// Bring the UART block into a known idle state before higher-level ISR or DMA
540// plumbing is attached.
541ErrorCode ESP32UART::InitUartHardware()
542{
543 if (uart_num_ >= UART_NUM_MAX)
544 {
546 }
547
548 periph_module_t uart_module = PERIPH_MODULE_MAX;
549 if (ResolveUartPeriph(uart_num_, uart_module) != ErrorCode::OK)
550 {
552 }
553
554 uart_hal_.dev = UART_LL_GET_HW(uart_num_);
555 if (uart_hal_.dev == nullptr)
556 {
558 }
559
560 periph_module_enable(uart_module);
561 periph_module_reset(uart_module);
562
563 uart_ll_sclk_enable(uart_hal_.dev);
564 uart_hal_init(&uart_hal_, uart_num_);
565
566 const uart_sclk_t sclk = UART_SCLK_DEFAULT;
567 uart_hal_set_sclk(&uart_hal_, static_cast<soc_module_clk_t>(sclk));
568 if (!GetUartClockFrequency(uart_sclk_hz_))
569 {
570 return ErrorCode::INIT_ERR;
571 }
572
573 uart_hw_enabled_ = true;
574 if (SetConfig(config_) != ErrorCode::OK)
575 {
576 uart_hw_enabled_ = false;
577 return ErrorCode::INIT_ERR;
578 }
579
580 if (ConfigurePins() != ErrorCode::OK)
581 {
582 uart_hw_enabled_ = false;
583 return ErrorCode::INIT_ERR;
584 }
585
586 uart_hal_txfifo_rst(&uart_hal_);
587 uart_hal_rxfifo_rst(&uart_hal_);
588 DisableAndClearUartInterrupt(UINT32_MAX);
589
590 return ErrorCode::OK;
591}
592
593// GPIO 映射保持显式写法,因为 ESP UART 路由是逐引脚可配置的。
594// GPIO mapping stays explicit because ESP UART routing is per-pin configurable.
595ErrorCode ESP32UART::ConfigurePins()
596{
597 if (tx_pin_ >= 0)
598 {
599 if (!GPIO_IS_VALID_OUTPUT_GPIO(tx_pin_))
600 {
601 return ErrorCode::ARG_ERR;
602 }
603 esp_rom_gpio_pad_select_gpio(static_cast<uint32_t>(tx_pin_));
604 esp_rom_gpio_connect_out_signal(
605 tx_pin_, UART_PERIPH_SIGNAL(uart_num_, SOC_UART_TX_PIN_IDX), false, false);
606 }
607
608 if (rx_pin_ >= 0)
609 {
610 if (!GPIO_IS_VALID_GPIO(rx_pin_))
611 {
612 return ErrorCode::ARG_ERR;
613 }
614 gpio_input_enable(static_cast<gpio_num_t>(rx_pin_));
615 esp_rom_gpio_connect_in_signal(
616 rx_pin_, UART_PERIPH_SIGNAL(uart_num_, SOC_UART_RX_PIN_IDX), false);
617 }
618
619 if (rts_pin_ >= 0)
620 {
621 if (!GPIO_IS_VALID_OUTPUT_GPIO(rts_pin_))
622 {
623 return ErrorCode::ARG_ERR;
624 }
625 esp_rom_gpio_pad_select_gpio(static_cast<uint32_t>(rts_pin_));
626 esp_rom_gpio_connect_out_signal(
627 rts_pin_, UART_PERIPH_SIGNAL(uart_num_, SOC_UART_RTS_PIN_IDX), false, false);
628 }
629
630 if (cts_pin_ >= 0)
631 {
632 if (!GPIO_IS_VALID_GPIO(cts_pin_))
633 {
634 return ErrorCode::ARG_ERR;
635 }
636 gpio_pullup_en(static_cast<gpio_num_t>(cts_pin_));
637 gpio_input_enable(static_cast<gpio_num_t>(cts_pin_));
638 esp_rom_gpio_connect_in_signal(
639 cts_pin_, UART_PERIPH_SIGNAL(uart_num_, SOC_UART_CTS_PIN_IDX), false);
640 }
641
642 return ErrorCode::OK;
643}
644
645// Active 状态由 FIFO 和 DMA 两条 TX 后端共用。
646// Active state is shared by FIFO and DMA TX backends.
647void IRAM_ATTR ESP32UART::ClearActiveTx()
648{
649 tx_active_length_ = 0U;
650 tx_active_valid_ = false;
651}
652
653// 启动当前半区,检查启动结果;请求完成已在装载时结算。
654// Start the active half; request completion was settled during loading.
655bool IRAM_ATTR ESP32UART::StartAndReportActive(bool in_isr)
656{
657 const bool started = StartActiveTransfer(in_isr);
658 REQUIRE_FROM_CALLBACK(started, in_isr);
659 return true;
660}
661
662// TX 发起路径只做两件事:
663// 1. TX 完全空闲时,启动一个 active 请求;
664// 2. DMA TX 已忙且还没有预装 pending 时,补装一个。
665// 配置处于 RESERVED 或 PUBLISHED 时,只关闭从队列装入 DMA 的入口;
666// 已经认领的 active/ready 存储仍由 owner 继续推进。
667// TX start only does two things:
668// 1. when TX is fully idle, start one active request;
669// 2. when DMA TX is busy and no pending request is preloaded yet, preload one.
670// A reserved or published configuration closes only the queue-to-DMA load points;
671// already claimed active/ready storage is still progressed by the owner.
672ErrorCode IRAM_ATTR ESP32UART::TryStartTx(bool in_isr)
673{
674#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
675 if (dma_backend_enabled_)
676 {
677 if (!tx_busy_.IsSet())
678 {
679 if (!tx_active_valid_ && config_state_.load(std::memory_order_acquire) !=
680 static_cast<uint8_t>(ConfigState::EMPTY))
681 {
682 return ErrorCode::PENDING;
683 }
684 if (!tx_active_valid_ && !LoadActiveTxFromQueue(in_isr))
685 {
686 return ErrorCode::PENDING;
687 }
688 if (tx_active_valid_ && !StartAndReportActive(in_isr))
689 {
690 return ErrorCode::PENDING;
691 }
692 }
693 if (tx_busy_.IsSet() && !tx_dma_buffer_.HasPending() &&
694 config_state_.load(std::memory_order_acquire) ==
695 static_cast<uint8_t>(ConfigState::EMPTY))
696 {
697 (void)LoadPendingTxFromQueue(in_isr);
698 }
699 return ErrorCode::PENDING;
700 }
701#endif
702
703 FillTxFifo(in_isr);
704 return ErrorCode::PENDING;
705}
706
707// GDMA 装载完整请求并记录半区占用,随后由队列接口析构完成请求。
708// Load a complete GDMA request and record occupancy before scope completion.
709bool IRAM_ATTR ESP32UART::LoadActiveTxFromQueue(bool in_isr)
710{
711#if !(SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED)
712 UNUSED(in_isr);
713 return false;
714#else
715 if (!dma_backend_enabled_)
716 {
717 return false;
718 }
719
720 auto queue = _write_port.GetWriteQueue(in_isr);
721 if (queue.Empty())
722 {
723 return false;
724 }
725
726 const size_t size = queue.AvailableSize();
727 DEV_ASSERT_FROM_CALLBACK(size <= tx_dma_buffer_.Size(), in_isr);
728 queue.PopAll(tx_dma_buffer_.ActiveBuffer());
729 tx_dma_buffer_.SetActiveLength(size);
730 tx_active_length_ = size;
731 tx_active_valid_ = true;
732 return true;
733#endif
734}
735
736// Pending 预装只存在于 DMA 后端,因为 FIFO 模式可以直接从队列流式发送,
737// 不需要再暂存第二块 payload。
738// Pending preload exists only for the DMA backend because FIFO mode can stream
739// directly from the queue without staging a second payload block.
740bool IRAM_ATTR ESP32UART::LoadPendingTxFromQueue(bool in_isr)
741{
742#if !(SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED)
743 UNUSED(in_isr);
744 return false;
745#else
746 if (!dma_backend_enabled_ || tx_dma_buffer_.HasPending())
747 {
748 return false;
749 }
750
751 auto queue = _write_port.GetWriteQueue(in_isr);
752 if (queue.Empty())
753 {
754 return false;
755 }
756
757 const size_t size = queue.AvailableSize();
758 DEV_ASSERT_FROM_CALLBACK(size <= tx_dma_buffer_.Size(), in_isr);
759 queue.PopAll(tx_dma_buffer_.PendingBuffer());
760 tx_dma_buffer_.SetPendingLength(size);
761 tx_dma_buffer_.EnablePending();
762 return true;
763#endif
764}
765
766// 提升步骤只负责把一个已经预装的 pending 请求转成 active;
767// 真正启动硬件传输是单独一步。
768// Promotion only moves one preloaded pending request into the active slot.
769// Hardware start is a separate step.
770bool IRAM_ATTR ESP32UART::PromotePendingTxToActive()
771{
772#if !(SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED)
773 return false;
774#else
775 if (!dma_backend_enabled_)
776 {
777 return false;
778 }
779
780 if (tx_active_valid_ || !tx_dma_buffer_.HasPending())
781 {
782 return false;
783 }
784
785 const size_t pending_length = tx_dma_buffer_.GetPendingLength();
786 tx_dma_buffer_.Switch();
787 tx_dma_buffer_.SetActiveLength(pending_length);
788
789 tx_active_length_ = pending_length;
790 tx_active_valid_ = true;
791
792 return true;
793#endif
794}
795
796// 启动已准备的数据;GDMA 路径使用当前半区。
797// Start prepared data using the active half for GDMA.
798bool IRAM_ATTR ESP32UART::StartActiveTransfer(bool)
799{
800 if (!tx_active_valid_)
801 {
802 return false;
803 }
804
805 if (tx_busy_.TestAndSet())
806 {
807 return true;
808 }
809
810#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
811 if (dma_backend_enabled_)
812 {
813 if (StartDmaTx())
814 {
815 return true;
816 }
817
818 tx_busy_.Clear();
819 return false;
820 }
821#endif
822
823 ClearUartInterrupt(UART_TX_INTR_MASK);
824 EnableUartInterrupt(UART_TX_INTR_MASK);
825 FillTxFifo(false);
826
827 return true;
828}
829
830// RX 字节会尽量推进软件队列,直到队列满;完成后按批次触发待读回调。
831// RX bytes are pushed opportunistically until the software queue is full, then
832// pending read callbacks are serviced once per batch.
833void IRAM_ATTR ESP32UART::PushRxBytes(const uint8_t* data, size_t size, bool in_isr)
834{
835 auto queue = _read_port.GetReadQueue(in_isr);
836 size_t offset = 0;
837 while (offset < size)
838 {
839 const size_t free_space = queue.EmptySize();
840 if (free_space == 0)
841 {
842 break;
843 }
844
845 const size_t chunk = std::min(free_space, size - offset);
846 [[maybe_unused]] const auto push_batch_result = queue.PushBatch(data + offset, chunk);
847 DEV_ASSERT_FROM_CALLBACK(push_batch_result == ErrorCode::OK, in_isr);
848
849 offset += chunk;
850 }
851 queue.Publish();
852}
853
854// 释放已结束的传输存储,推进后续半区;不重复完成写请求。
855// Release finished transfer storage and advance halves without completing again.
856void IRAM_ATTR ESP32UART::OnTxTransferDone(bool in_isr, ErrorCode result)
857{
858 tx_busy_.Clear();
859
860 ClearActiveTx();
861
862#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
863 if (dma_backend_enabled_)
864 {
865 if (result != ErrorCode::OK)
866 {
867 tx_dma_buffer_.Reset();
868 (void)TryStartTx(in_isr);
869 return;
870 }
871
872 if (tx_dma_buffer_.HasPending())
873 {
874 [[maybe_unused]] const auto promote_pending_tx_to_active_result =
875 PromotePendingTxToActive();
876 DEV_ASSERT_FROM_CALLBACK(promote_pending_tx_to_active_result, in_isr);
877 [[maybe_unused]] const auto start_and_report_active_result =
878 StartAndReportActive(in_isr);
879 REQUIRE_FROM_CALLBACK(start_and_report_active_result, in_isr);
880 }
881 else if (config_state_.load(std::memory_order_acquire) ==
882 static_cast<uint8_t>(ConfigState::EMPTY) &&
883 LoadActiveTxFromQueue(in_isr))
884 {
885 [[maybe_unused]] const auto start_and_report_active_result =
886 StartAndReportActive(in_isr);
887 REQUIRE_FROM_CALLBACK(start_and_report_active_result, in_isr);
888 }
889
890 if (tx_busy_.IsSet() && !tx_dma_buffer_.HasPending() &&
891 config_state_.load(std::memory_order_acquire) ==
892 static_cast<uint8_t>(ConfigState::EMPTY))
893 {
894 (void)LoadPendingTxFromQueue(in_isr);
895 }
896 return;
897 }
898#endif
899
900 if (result != ErrorCode::OK)
901 {
902 return;
903 }
904
905 if (LoadActiveTxFromQueue(in_isr))
906 {
907 (void)StartAndReportActive(in_isr);
908 }
909}
910
911} // namespace LibXR
void SetActiveLength(size_t length)
设置当前活动缓冲区的数据长度 Sets the size of the active buffer
void Reset()
重置 active/pending 状态,但保留已绑定的 backing storage Resets active/pending state while keeping bound backing ...
void SetPendingLength(size_t length)
设置备用缓冲区的数据长度 Sets the size of the pending buffer
void EnablePending()
手动启用 pending 状态 Manually sets the pending state to true
bool HasPending() const
判断是否有待切换的缓冲区 Checks whether a pending buffer is ready
size_t Size() const
获取每个缓冲区的大小(单位:字节) Gets the size of each buffer in bytes
uint8_t * ActiveBuffer() const
获取当前正在使用的缓冲区指针 Returns the currently active buffer
void Init(const LibXR::RawData &raw_data)
绑定连续 backing storage 并重置双缓冲状态 Binds continuous backing storage and resets double-buffer state
uint8_t * PendingBuffer() const
获取备用缓冲区的指针 Returns the pending (inactive) buffer
void Switch()
切换到备用缓冲区(若其有效) Switches to the pending buffer if it's valid
size_t GetPendingLength() const
获取 pending 缓冲区中准备好的数据长度 Gets the size of valid data in pending buffer
ErrorCode SetLoopback(bool enable)
切换 UART 内部环回模式 / Toggle UART internal loopback.
Definition esp_uart.cpp:460
static void WriteFun(WritePort &port, bool in_isr)
WritePort 使用的队列驱动 TX 入口 / Queue-driven TX entry used by WritePort.
Definition esp_uart.cpp:473
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
ErrorCode SetConfig(UART::Configuration config, bool in_isr=false) override
提交帧格式和波特率配置 / Submit framing and baud configuration.
Definition esp_uart.cpp:226
void OnReadQueueSpaceAvailable(bool in_isr) override
通知后端处理接收空间 / Notify the backend of RX space.
Definition esp_uart.cpp:98
bool TestAndSet() noexcept
测试并置位:置位并返回旧状态 / Test-and-set: set and return previous state
Definition flag.hpp:146
bool IsSet() const noexcept
判断是否已置位 / Check whether the flag is set
Definition flag.hpp:138
void Clear() noexcept
清除标志 / Clear the flag
Definition flag.hpp:130
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
bool Invoke(uint32_t events, bool in_isr, Handler &&handler) noexcept
发布事件并尝试取得执行权 / Publish events and attempt to claim execution.
通用异步收发传输(UART)基类 / Abstract base class for Universal Asynchronous Receiver-Transmitter (UART)
Definition uart.hpp:19
Parity
奇偶校验模式 / Parity mode
Definition uart.hpp:29
@ NO_PARITY
无校验 / No parity
@ ODD
奇校验 / Odd parity
@ EVEN
偶校验 / Even parity
size_t AvailableSize() const
获取当前队头的剩余长度 / Get the current front remainder.
基于 SPSC 字节队列的写端口 / Write endpoint backed by an SPSC byte queue.
WriteQueue GetWriteQueue(bool in_isr=false)
获取已提交队头的出队接口 / Obtain a consumer interface for the released front.
size_t Size() const
获取字节队列当前数据量 / Get current byte queue occupancy.
LibXR 命名空间 / LibXR namespace.
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举 / Defines an enumeration for error codes
@ INIT_ERR
初始化错误 / Initialization error
@ STATE_ERR
状态错误 / State error
@ BUSY
忙碌 / Busy
@ NOT_SUPPORT
不支持 / Not supported
@ PENDING
等待中 / Pending
@ OK
操作成功 / Operation successful
@ ARG_ERR
参数错误 / Argument error
void(* WriteFun)(WritePort &port, bool in_isr)
写入进度通知函数类型 / Write progress notification function type.
OwnerType * ContainerOf(MemberType *ptr, MemberType OwnerType::*member) noexcept
通过成员指针恢复其所属对象指针 Recover the owning object pointer from a member pointer
UART 配置结构体 / UART configuration structure.
Definition uart.hpp:44
uint8_t stop_bits
停止位长度 / Number of stop bits
Definition uart.hpp:53
uint32_t baudrate
波特率 / Baud rate
Definition uart.hpp:45