UART receive DMA, duplex DMA and electrical adaptation

UART retains public nx_uart_port_t and shared read-only nx_uart_ops_t. Configuration generates instances; callers retain only interface and context. New mechanisms are selected by mode; existing IRQ, dma-tx and irq-blocks state layouts and default operations-slot counts do not grow. Production remains C11 without heap, device scanning, hidden workers or default OS locks.

Modes and exact resources

Mode

TX

RX

Receive behavior

rx-dma-idle

IRQ

NORMAL DMA

Finite blocks; IDLE publishes a stable prefix; the owner explicitly services restart

dma-duplex

DMA

NORMAL DMA

Independent TX/RX engines; receive retains finite-block restart gaps

rx-dma-continuous

IRQ

Double-buffer DMA

Two preposted targets and a third available replacement block

dma-duplex-continuous

DMA

Double-buffer DMA

Independent duplex engines; continuous receive uses the same loan protocol

Initial support is STM32F407 USART1 and GD32F470 USART0 only. STM32 RX is DMA2 stream2 channel4 and TX DMA2 stream7 channel4; GD32 RX is physical DMA1 channel2 selector4 and TX physical DMA1 channel7 selector4. Controller numbers come from chip manuals, not public opaque resource-ID encodings. Configuration must exclusively own UART, DMA targets, vectors, clocks and pins; conflicts are rejected during generation.

Receive-block capacity is 1–65535 bytes. Continuous mode needs at least three equal-capacity blocks; finite mode permits different capacities. DMA targets must lie within authorized, writable, DMA-reachable regions; CCM on both chips is not receive-DMA-reachable SRAM. Neither MCU has CPU D-cache, and providers perform no invented cache clean/invalidate. Cache-equipped MCUs need new domain/maintenance contracts rather than reuse of these cache-free assumptions.

RX-only and duplex use separate read-only operations tables and direct TX calls. The TX hot path has no runtime dma_transmit branch; RX-only links retain no TX-DMA operations. Current providers share the explicitly selected receive-state layout, so RX-only cold storage still includes TX-DMA base fields; this is not a zero-field-overhead claim.

Block ownership and continuous receive

Callers initialize nx_stream_t and slots, then lend them through nx_uart_port_rx_start. Devices own producer responsibilities and callers consumer responsibilities. Ordinary producer and nx_stream_post_* preposted-producer APIs must not be mixed. Stream, slots, blocks and provider state do not overlap and remain valid until successful rx_stop and external-observer drain. Active TX payloads must not overlap the entire RX block pool.

Finite mode points DMA directly at caller payloads without CPU payload copies. On TC, IDLE or error, it first withdraws UART DMA requests and receive interrupts, then writes DMA disable. Actual EN, request and interrupt readbacks establish quiescence before stable remaining counts are read. Finally it rereads DMA/UART errors and performs the sole status/data clear order. Only reliable positive-length prefixes become READY; unreliable prefixes return FREE and record loss. Stop never falsely publishes an empty, unreceived block as successful data.

Continuous mode starts double-buffer DMA with two FILLING loans. After TC it reserves a third FREE block before replacing the completed inactive target. Before publishing the old block, it proves replacement address, other-target address, CT, EN, absence of new TC/errors and removal of the old address from both hardware targets. Failure never hands potentially writable memory to consumers: it withdraws sources, drains and rolls back future loans in LIFO order. Successful publication follows FIFO. IRQ copies no payload and performs bounded control with at most three loans.

Without a replacement FREE block, receive-write sources stop immediately and await actual drain while retaining consumer BORROWED blocks. Stable completed and partial blocks can still publish with one recorded stop gap, never a guessed missing-byte count. Failed disable retains every producer loan; later owner service retries boundedly. No block or state may be reused meanwhile.

TC is a flag, not a wrap counter. Continuous qualification requires worst-case IRQ latency plus target-replacement time below one block-fill interval, measured from actual baud, block size, interrupt priority, masked windows and concurrent DMA load. Software detects observable errors, target changes and CT anomalies but cannot recover arbitrarily many coalesced wraps. Violating latency budgets prevents claims of reliable sequencing even if all targets remain FILLING and consumer memory is untouched.

IDLE, finite-block restart and capacity-pressure recovery create receive gaps. Owner service periods are explicit resource budgets too. Observed UART overrun/error before restart records loss before status/data clear; software cannot infer exact omissions in gaps without error bits. Loss denotes observed boundaries, not exact wire-byte statistics. Upper protocols decide acceptable gaps and recovery.

TX, stop and calling context

TX DMA TC proves only payload delivery to the peripheral, not the last bit leaving the pin. TX settlement still waits for actual UART TC; cancellation withdraws sources before proving DMA/peripheral drain. RX DMA and TX DMA use separate targets and vectors; their completions never substitute for each other.

rx_start/rx_stop/service/stop run on the sole unmasked task executor. ISRs use only exact generated IRQ entries; protected spans save/restore incoming interrupt state. rx_stop closes stream admission and may return BUSY: published or borrowed blocks must be normally returned by consumers. Success requires withdrawn sources, quiescent engines and zero outstanding loans. Closed service never restarts RX. Complete stop first closes TX admission too, and may stop underlying clocks only after RX drain. Failure prohibits owner destruction or replacement.

GD32 IRQ TX cancellation uses shared USART reset and first drains active RX. If RX EN refuses stop, TX/RX loans remain and reset is deferred. Selected hardware-flow-control bits are restored after reset.

RTS/CTS and RS485

nx_*_uart_flow_configure is explicitly optional cold configuration. STM32 sets CR3 RTSE/CTSE and GD32 CTL2 RTSEN/CTSEN, rolling back failed readback. It runs after initialization but before active TX/RX and cannot modify live borrows. Chip signals are PA11 CTS, PA12 RTS, AF7; chip facts do not prove wiring on the three boards or peer handshake support. Board binding needs PCB, GPIO permission, voltage, multiplexing and peer review. Hardware RTS reflects UART data-register receive state, not remaining software-block capacity; it neither replaces protocol backpressure nor guarantees lossless operation without FREE blocks.

RS485 selects nx_uart_rs485_state_t, immutable nx_uart_rs485_config_t and nx_uart_rs485_ops; default UART state gains no DE, timer or task. Callers bind real GPIO, a uniquely authorized bit, DE polarity, setup/hold time and the exact provider’s pure TX validator. Validator and submit/start share rejection rules; validation writes no GPIO/MMIO, admits nothing and retains no references. Config, both faces, ops/context bindings and electrical facts remain immutable; all cold facts are separate from mutable request/payload/control storage. The same executor exclusively owns underlying TX; bypass submissions or transport changes are prohibited.

Rejection precedes DE assertion. After acceptance, setup starts only after successful DE assertion; the absolute transfer deadline includes setup and wire completion. Setup timeout never starts UART. Hold starts only after actual underlying SETTLED; successful DE withdrawal then removes internal payload/active references before outer SETTLED publication. Failed drain or DE withdrawal retains QUARANTINED outer loans and DE ownership for owner-service retries. Required drain/hold may extend borrows beyond transfer deadlines; expired deadlines never justify early memory or DE release.

Setup/hold uses the nx_time_now_us monotonic domain; worst-case service gaps determine boundary error. Hold guarantees at least declared duration, not exact-time DE withdrawal. Exact waveforms require explicit hardware-timer/electrical budgets; this implementation invents neither transceiver facts nor timing qualification.

Software evidence and board acceptance

GoogleTest/GoogleMock directly execute production providers, covering rejected bank replacement, CT flips, wrong address writes, new errors in W1C windows, refused disable, terminal DMA/UART errors, status/data-clear races, epoch exhaustion, duplex cancellation, flow configuration, RS485 setup/hold and DE failures. Native adapters additionally verify real request lifecycles. Results establish software contracts, not electrical waveforms, bus arbitration or physical latency.

Actual ARM ELF comparison before/after table splitting in the same no-LTO Release RX-only development fixture reduced STM32 text by 2310 bytes, GD32 by 2262 bytes, and both bss by 8 bytes. This describes section-GC costs only for that intermediate fixture, not final sources, arbitrary applications or all modes. Final candidates must freshly bind configuration, sources, ELF/map/nm and tool identity.

After terminal-error fixes, fresh production-graph ARM linking of the same fixture produced STM32 text 7866 bytes and GD32 text 9932 bytes, down 2218 and 2174 bytes from the old baseline; both bss reductions remain 8 bytes. All fourteen final mode fixtures linked; RX-only nm contains no nx_*_uart_dma_* symbols, while duplex retains real TX-DMA methods. This remains development-build software evidence; candidate sealing and board validation execute separately.

No physical boards are connected, and all physical acceptance remains unexecuted. Board exit conditions include both engines’ concurrent maximum baud/load, worst IRQ/service latency, target-replacement/wrap pressure, overflow/recovery after long consumer holds, IDLE/last-DMA-beat races, actual TC wire tails, RTS/CTS stall/resume, DE setup/hold polarity/edges, and drained clocks/vectors/SRAM loans after repeated stop/start. Record PCB revision, probes, UARTs, external wiring, raw logic-analyzer waveforms and the same ELF per board. Unexecuted items cannot pass.