Skip to content

serial: DMA on every UART, the stream picked at runtime where the target names none - #12129

Open
MrScothh wants to merge 26 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:feature/uart-dma-default
Open

MrScothh wants to merge 26 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:feature/uart-dma-default

Conversation

@MrScothh

@MrScothh MrScothh commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #12128 (the stream rule), #12062 (UART TX DMA), #12033 (UART RX DMA) and #12001 (the GPS port's own receive buffer); only the commits after #12128's are this PR.

With #12033/#12062 a UART uses DMA only where its target names a stream, and today one target does (TBS_LUCID_H7_WING,
UART2). This PR turns it on for every UART on F4, F7, H7 and AT32F43x: a port whose target names no stream picks a
free one when it opens.

The target guide (timer-dma-conflicts.md) describes the runtime pick and the opt-out.

Tested

FLYWOOF722PRO (F722), DSHOT300 on four motors, GPS (u-blox M10) on UART5:

MAMBAF722_2022B (F722), DSHOT300 in burst on DMA2 S1/S4, a GPS port on UART3 (no module fitted, so it keeps
sending while it looks for one): the port took DMA1 S1/S3. Disarmed motor steps alternating this PR and the base
image, with the ESC powered throughout: the same current within 0.04 A at every step.

KAKUTEH7MINI (H743), GPS (SAM-M10Q) on UART2: on DMA1 S2/S3 with no motors, on DMA1 S4/S5 with four DSHOT motors on
S0-S3; UART1 on DMA2 S3/S4. No timeouts in 10 reboots, one checksum error at startup in one of them.

TBS_LUCID_H7_WING (H743, the target that names DMA2 S5/S7 for UART2), on two boards. A second Lucid with the
final code, GPS on UART2, MSP on UART1 and UART3, SD card fitted: the GPS on its named DMA2 S5/S7, MSP on DMA1 S0/S1
and S2/S3, the SD card mounted, no GPS timeout and a fix in each of 10 reboots. The bench Lucid, GPS (u-blox M10)
on UART2, MSP on UART1, SD card fitted, on the previous round of this branch (the changes since, #12033's restart fix, #12062's cache
clean and the streams given back, do not change which stream a port gets at boot):

  • motors on SRXL2: MSP took DMA1 S0/S1, the GPS its named S5/S7, the SD card mounted;
  • DSHOT300: when MSP opened, before the motors, every output's stream was reserved, so MSP took DMA2 S6 to receive
    and stayed on the interrupt to send, leaving S5/S7 to the GPS; the motor then took DMA1 S0;
  • GPS 0 errors in both setups and 15 s after each of 10 reboots.

Streams given back (39bddd3), on #12132's tree with bench commands that list each stream's owner and open, write
and close a spare UART:

  • MAMBAF722_2022B (F722): a spare UART took DMA1 S0/S7, sent 256 bytes in 3 transfers and, closed, left both free;
    opened again it took them back and sent again. Without this commit the closed port kept both. UART3 with MSP shared
    with LTM, armed and disarmed 3 times: arming, MSP gave back S1/S3 and LTM (send only) took S3 again; disarming, the
    reverse. LTM sent 278 transfers; the ELRS receiver on UART1 had no bad frame.
  • KAKUTEH7MINI (H743) with four DSHOT motors, the GPS, the ELRS receiver and two MSP ports, which leave one stream
    free (DMA2 S7): UART6 took it and, when it closed, UART7, opened next, took it; without this commit UART7 would have
    stayed on the interrupt. UART3 with MSP shared with MAVLink and a serial blackbox on UART6, armed and disarmed 3
    times: the blackbox took the stream MSP had just given back and MAVLink the next ones, in an order that changed
    between cycles, and every port sent on the stream it got (blackbox 6425 transfers, MAVLink 27). After the last
    disarm the streams were as at boot.

All 237 release targets build, none over its regions; 645 of 645 unit tests pass. uart_dma_stream_unittest covers
the pick (named streams) on plain data, with source checks that pin the live code it mirrors: the candidate tables,
the named list for every port and direction, the common_post.h defaults, each driver's two calls and each driver's
stops giving their stream back. Each check was seen to fail on a deliberate change to the code it pins.

Not tested on hardware: F4 and AT32F43x. The F4 and AT32 DMA paths of #12033/#12062 have not run on a board
either. Tests on an F405/F411/F722-class board with a GPS and MSP, and on an AT32F435, would be very welcome: what to
look at is GPS and MSP working as before, and status free of errors.

Size

Against the stream rule PR (#12128), on the final tree:

Target Flash RAM
MATEKF722SE (fullest F722) +1924 B, 98.83 % +568 B, DTCM -512 B
SYNERDUINOSTM_F405 (fullest F405 RAM) +1672 B +560 B, 84.66 %; CCM -512 B
BETAFPVF435 (AT32) +2064 B +576 B, 91.73 %; RAM1 -512 B
KAKUTEH7MINI +1920 B D2 +3.5 KB, DTCM -512 B
TBS_LUCID_H7_WING +312 B D2 +3 KB
KAKUTEF7HDV +2096 B, ITCM unchanged +576 B, DTCM -512 B
KAKUTEF7 +2048 B, ITCM +40 B (16 272 of 16 384) +588 B, DTCM -512 B

The RAM is the GPS ring moving out of the fast RAM to where #12033 puts a ring a stream fills, and the larger port structure. Until 8ae0509 the F7 rows also carried a duplicate receive ring for every port, 1280 to 1536 B, now built only with #12119's D-cache. KAKUTEF7's 40 B of ITCM are not serial code: gyroFilter reaches its variables through other offsets once the GPS ring leaves DTCM.

Related PRs

Merge order and conflicts

NAV-SIG (M9, M10) and NAV-SAT (M8) were requested at every navigation
epoch. They feed only the satellite list in the CLI, yet they are most of
what the receiver sends: NAV-SIG carries 16 bytes for each tracked signal,
several hundred bytes per epoch on a multi-constellation receiver, against
about 100 for the NAV-PVT that navigation runs on.

They are now requested every (gps_ublox_nav_hz + 1) / 2 epochs, which is
about twice a second at any navigation rate.
A receive buffer has to hold what arrives between two runs of the task
that drains it. For the GPS task, 20 ms apart, that is up to 230 bytes at
115200 and 460 at 230400, and a reply to a poll has to fit whole: MON-VER
alone can be 258 bytes. With the 256 bytes every port has, a NEO-F10N at
230400 was never identified, because its MON-VER reply was always cut.

The GPS port now gets a 512 byte buffer of its own, in FASTRAM, where
there is room: CCM on F405, RAM1 on AT32F43x. serialSetRxBuffer() swaps
it in right after the port is opened. Every other port keeps its 256
bytes.

The note that occupied sizes are returned as uint8_t no longer held: the
driver functions and the ring indices are all 32 bit.
The satellite details also feed the OSD's GPS extra stats, not only
gpssats, so the comment now says both. The comments are one line each,
the buffer states the rate it covers (230400; at 460800 and above a
NAV-PVT plus a large NAV-SIG can still overflow it), its size is asserted
to be a power of two because softserial masks with size - 1, and it only
exists where the u-blox provider does, the only one that opens a port.
The note in serial_uart.h about sending a UBLOX SVINFO was stale.
A UART receiving through a DMA stream costs no interrupt per byte, and keeps
the bytes that arrive while interrupts are held off. A target turns it on per
port with UARTx_RX_DMA; a port without one, and a target that names none,
build and behave exactly as before.

The stream fills the port's own ring, whose head is read back from the
transfer counter, so nothing above the driver changes. Ports whose owner
takes each byte through rxCallback (the serial receivers, which frame by
timing) stay on the interrupt even when a stream is named.

F4 and F7 wire each receiver to fixed streams, so a tag naming another is a
build error. H7 and AT32 route through the DMAMUX. On H7 the ring moves to
D2 SRAM, out of the data cache. A stream any timer output is mapped to is
left to the timers, since MSP ports open before the motors do.

On F7, H7 and AT32 the interrupt handler tested the RX flag without the
enable, so a port receiving through DMA could have lost a byte to the
interrupt raised for what it sends; it now leaves the data register alone.

Enabled on TBS_LUCID_H7_WING for UART2, on DMA2 stream 5.
serialSetRxBuffer() only swapped the ring's pointer and size, but a DMA
stream keeps writing to the memory it was started on, so a port receiving
through DMA would have gone on filling its old ring while the reader looked
at the new one. The serial vtable gets an optional setRxBuffer, which the
UARTs implement by swapping the ring and, if a stream is running, starting
it again on the new one.

The GPS ring must then be memory a stream can reach on targets that receive
through DMA: D2 SRAM on H7 rather than DTCM, and on F4 and AT32F43x plain
RAM rather than CCM or RAM1. Everywhere else it stays in FASTRAM, so a
target without UARTx_RX_DMA builds exactly as before.

On the TBS Lucid H7 Wing, GPS on UART2 through DMA2 stream 5, NEO-F10N:
identified with no errors or timeouts at 115200 and after autobaud to
230400, NAV-PVT at the configured 10 Hz. Over ten reboots, 1 error and 2
timeouts at startup, against 1 and 1 over nine with the byte interrupt.
A UART sends one byte per interrupt. A target can now name a stream for a
port's transmitter (UARTx_TX_DMA), as it can for the receiver: the stream
empties the transmit ring and the port takes one interrupt per transfer.
writeBuf() queues a message whole, so MSP and MAVLink send each in one
transfer; what is queued while one runs goes in the next.

On F4, F7, H7 and AT32. The transmit ring moves to D2 SRAM on H7, as the
receive ring does. The stream check the receiver used now serves both, and
F4/F7 targets naming a stream the transmitter is not wired to fail to
build. The TBS Lucid H7 Wing sends on UART2 through DMA2 stream 7.
iNavFlight#12119 turns the F7 D-cache on and makes DMA_RAM an uncached SRAM2
region. A ring left in cached RAM could then hand the CPU a line it
cached before the stream filled it. The rings go in DMA_RAM, as on H7;
on F7 that is plain RAM until iNavFlight#12119, so on its own this changes nothing.

Not seen to fail at 115200: FLYWOOF722PRO with iNavFlight#12119 and the GPS
sending on the interrupt had 0-1 errors per boot with the rings in
cached RAM and in DMA_RAM alike.
On F7 and H7, stopping the stream clears USART_CR3_DMAR and only
uartReconfigure() set it again. After serialSetRxBuffer() the stream ran
but the UART never fed it, and with the byte interrupt off for a DMA port
the port stayed deaf until the next baud, mode or option change. The GPS
got away with it because the u-blox driver changes the baud rate right
after swapping its ring. F4 and AT32 already set the request when the
stream starts.

With a test patch that swaps a larger GPS ring in 10 s after boot,
FLYWOOF722PRO and KAKUTEH7MINI had a timeout on every boot without this
and none with it.
# Conflicts:
#	src/main/drivers/serial_uart_stm32f7xx.c
MSP DisplayPort and the serial gimbal swap their own TX buffer into the port after opening it. On
H7 that buffer is in AXI SRAM, which the CPU caches, so the stream sends what is in RAM rather than
what was just written. Kakute H7 Mini with the GPS port given such a buffer (a test patch): the
u-blox never got its configuration (3 timeouts, no navigation messages); with this, a navigation
message every 120 ms and 0 errors over 3 reboots. On F7 every ring is cached once iNavFlight#12119 turns its
D-cache on: FLYWOOF722PRO with iNavFlight#12119 had 11-14 GPS errors and no fix on every boot, and 0 with this.

The lines the transfer covers are written back first. Without a D-cache, or on the uncached D2
rings, that does nothing.
The stream check refused every stream any timer output is mapped to, so
a target naming a UART stream on F4 or F7, where each UART has one or
two fixed streams, would find most of them taken. Now it refuses a
stream while DSHOT motors that may take it have not started, and the LED
strip's stream when that feature is on. Started motors already own their
streams, so a port opened after them gets the streams of outputs that
ended up as servos or unused, and every stream but the LED strip's when
the motors are not on DSHOT. The choice is made at each boot.
…get names none

On F4, F7, H7 and AT32F43x a UART whose target names no stream now picks
a free one when it opens: on F4 and F7 the streams the reference manual
wires it to, on H7 and AT32 any stream the DMAMUX can route except the
ADC's. It skips the streams any target names for a port, the SD card's,
its own other direction's, and those a timer output will use; a port
opened again takes back a stream it already holds first, since none is
ever given back. Serial receivers keep receiving on the interrupt, and
half-duplex ports get no stream unless the target names one. A target
keeps a port on the interrupt by setting UARTx_RX_DMA or UARTx_TX_DMA to
DMA_NONE.

Tested on F722 (Flywoo F722 Pro, also with the D-cache of iNavFlight#12119) and
H743 (Kakute H7 Mini, TBS Lucid H7 Wing) with GPS, MSP and DSHOT motors.
F4 and AT32 are untested on hardware.
@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Enable runtime UART DMA selection and enlarge the GPS receive buffer

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Select free UART DMA streams at port open while protecting motor outputs and named assignments.
• Keep callback-driven and unsupported ports on interrupts; enlarge GPS reception to prevent
 truncated replies.
• Reduce u-blox satellite-detail traffic and document DMA selection, opt-outs, and conflicts.
Diagram

graph TD
  Config["Target DMA settings"] --> Open["UART port open"] --> Pick{"Stream available?"} --> DMA["DMA stream"] --> Ring["Serial rings"] --> GPS["GPS processing"]
  Reserved["Timer reservations"] --> Pick
  Pick --> IRQ["Byte interrupts"] --> Ring
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Require target-defined UART DMA streams
  • ➕ Assignments are explicit and predictable for each board.
  • ➕ Avoids runtime candidate search and reservation logic.
  • ➖ Requires maintaining mappings across targets.
  • ➖ Cannot adapt as readily to streams claimed by boot-time motor and LED configurations.

Recommendation: Prefer runtime selection with named-stream overrides: stream availability depends on board configuration and initialization order, so static mappings alone would leave usable DMA capacity idle. Retain interrupt fallback and seek F4 and AT32 hardware validation before relying on those paths.

Files changed (22) +2463 / -39

Enhancement (15) +1635 / -39
dma.hDefine the automatic DMA tag +2/-0

Define the automatic DMA tag

• Adds DMA_TAG_AUTO to distinguish runtime stream selection from a named stream or DMA_NONE.

src/main/drivers/dma.h

pwm_mapping.cReserve streams needed by timer outputs +23/-0

Reserve streams needed by timer outputs

• Tracks motor initialization and exposes whether a stream must remain available for prospective DSHOT outputs or an enabled LED strip.

src/main/drivers/pwm_mapping.c

pwm_mapping.hExpose timer DMA reservation query +2/-0

Expose timer DMA reservation query

• Declares the reservation query used to keep UART selection from taking a timer output's future stream.

src/main/drivers/pwm_mapping.h

serial.cSupport replacing a port's receive buffer +25/-0

Support replacing a port's receive buffer

• Adds serialSetRxBuffer with a driver hook for DMA-backed ports and an atomic ring reset for other ports.

src/main/drivers/serial.c

serial.hAdd the receive-buffer replacement API +4/-0

Add the receive-buffer replacement API

• Extends the serial vtable with an optional buffer-replacement callback and declares serialSetRxBuffer.

src/main/drivers/serial.h

serial_uart.cIntegrate DMA with the non-HAL UART lifecycle +50/-9

Integrate DMA with the non-HAL UART lifecycle

• Stops outstanding TX DMA on reopen, starts eligible RX/TX DMA paths, and falls back to interrupts. Reads DMA-backed ring positions and queues complete buffer writes before starting TX.

src/main/drivers/serial_uart.c

serial_uart.hTrack per-port UART DMA state +10/-4

Track per-port UART DMA state

• Adds RX/TX DMA descriptors and an in-flight TX count to UART ports; clarifies the standard buffer-size comment.

src/main/drivers/serial_uart.h

serial_uart_at32f43x.cImplement AT32F43x UART DMA transfers +241/-2

Implement AT32F43x UART DMA transfers

• Adds per-UART DMAMUX requests, circular RX DMA, queued TX DMA with completion handling, and guards that leave DMA-backed directions out of byte-interrupt handling.

src/main/drivers/serial_uart_at32f43x.c

serial_uart_hal.cPreserve STM32 HAL UART DMA mode +61/-9

Preserve STM32 HAL UART DMA mode

• Restores UART DMA request bits after HAL reconfiguration, starts DMA when ports open, and adds DMA-aware receive accounting and buffered writes.

src/main/drivers/serial_uart_hal.c

serial_uart_hal_at32f43x.cIntegrate AT32 UART DMA with port opens +49/-9

Integrate AT32 UART DMA with port opens

• Stops TX transfers before reopening, selects DMA or interrupt paths for each direction, and adds DMA-aware receive accounting and buffered writes.

src/main/drivers/serial_uart_hal_at32f43x.c

serial_uart_impl.hShare runtime UART DMA selection rules +317/-0

Share runtime UART DMA selection rules

• Defines platform candidate streams, named-stream protection, peripheral exclusions, ownership checks, and reopened-port preference. Also provides DMA ring-position, cache-clean, and receive-buffer restart helpers.

src/main/drivers/serial_uart_impl.h

serial_uart_stm32f4xx.cImplement STM32F4 UART RX and TX DMA +228/-0

Implement STM32F4 UART RX and TX DMA

• Adds fixed-channel stream configuration, circular RX transfers, TX completion handling, and runtime stream selection for each UART direction.

src/main/drivers/serial_uart_stm32f4xx.c

serial_uart_stm32f7xx.cImplement STM32F7 UART DMA with accessible RX rings +299/-2

Implement STM32F7 UART DMA with accessible RX rings

• Adds RX/TX stream setup and completion handling, places DMA-capable RX rings in DMA_RAM, and cleans cached TX data before transfer.

src/main/drivers/serial_uart_stm32f7xx.c

serial_uart_stm32h7xx.cImplement STM32H7 DMAMUX UART transfers +316/-2

Implement STM32H7 DMAMUX UART transfers

• Adds per-UART DMAMUX RX/TX setup and DMA-aware interrupt handling. Places eligible RX and TX rings in DMA_RAM and cleans TX data before transfers.

src/main/drivers/serial_uart_stm32h7xx.c

gps_ublox.cRequest satellite details about twice per second +8/-2

Request satellite details about twice per second

• Reduces NAV-SIG and NAV-SAT message frequency relative to the navigation epoch, lowering serial traffic without changing the navigation update rate.

src/main/io/gps_ublox.c

Bug fix (1) +20 / -0
gps.cGive u-blox GPS ports a larger receive ring +20/-0

Give u-blox GPS ports a larger receive ring

• Installs a dedicated 512-byte receive buffer after opening a u-blox GPS port, using memory appropriate for UART DMA where enabled.

src/main/io/gps.c

Tests (3) +629 / -0
gps_heartbeat_unittest.ccStub the new serial buffer API in GPS heartbeat tests +1/-0

Stub the new serial buffer API in GPS heartbeat tests

• Adds a serialSetRxBuffer stub so the GPS heartbeat test links with the updated GPS initialization path.

src/test/unit/gps_heartbeat_unittest.cc

gps_null_port_unittest.ccStub receive-buffer replacement in null-port tests +7/-0

Stub receive-buffer replacement in null-port tests

• Adds a serialSetRxBuffer stub for the GPS null-port test harness.

src/test/unit/gps_null_port_unittest.cc

uart_dma_stream_unittest.ccTest UART DMA reservations and runtime selection +621/-0

Test UART DMA reservations and runtime selection

• Exercises mirrored timer-reservation and stream-picking scenarios, including named assignments, fallback, and reopened ports. Source-synchronization checks pin candidate tables, defaults, and driver calls to the live implementation.

src/test/unit/uart_dma_stream_unittest.cc

Documentation (1) +112 / -0
timer-dma-conflicts.mdDocument UART DMA selection and conflicts +112/-0

Document UART DMA selection and conflicts

• Explains automatic and named RX/TX stream selection, interrupt fallback, DMA_NONE opt-outs, candidate mappings, and conflicts with timers, ADC, and SDIO.

docs/development/targets/timer-dma-conflicts.md

Other (2) +67 / -0
target.hPin Lucid UART2 to its RX and TX DMA streams +3/-0

Pin Lucid UART2 to its RX and TX DMA streams

• Names DMA2 streams 5 and 7 for UART2 so automatic assignments cannot take the target's intended GPS streams.

src/main/target/TBS_LUCID_H7_WING/target.h

common_post.hDefault supported UART directions to automatic DMA +64/-0

Default supported UART directions to automatic DMA

• Assigns DMA_TAG_AUTO to unspecified UART RX/TX settings on F4, F7, H7, and AT32F43x, while preserving explicit target choices and enabling the relevant DMA code.

src/main/target/common_post.h

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. DMA errors can stall serial output 🐞 Bug ☼ Reliability
Description
On STM32F4/F7/H7, uartTxDmaHandler() clears the direct-mode error flag but only resets
txDmaCount for transfer-complete or transfer-error flags, while TX setup enables interrupts only
for those latter flags. If a direct-mode error stops a transfer without either enabled completion
event, the recorded count prevents another transfer from starting and a buffered write can wait
indefinitely when the ring fills.
Code

src/main/drivers/serial_uart_stm32f7xx.c[R434-435]

+#define UART_TX_DMA_FLAGS   (DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_DMEIF | DMA_IT_FEIF)
+#define UART_TX_DMA_ENDED   (DMA_IT_TCIF | DMA_IT_TEIF)
Evidence
The three drivers include DMA_IT_DMEIF among flags to clear but exclude it from
UART_TX_DMA_ENDED, and their setup enables only TC and TE interrupts. Each handler clears
txDmaCount and restarts transmission only for an ended event; uartStartTxDMA() requires that
count to be zero, while uartWriteBuf() waits for ring space.

src/main/drivers/serial_uart_stm32f4xx.c[340-341]
src/main/drivers/serial_uart_stm32f4xx.c[363-370]
src/main/drivers/serial_uart_stm32f4xx.c[380-389]
src/main/drivers/serial_uart_stm32f4xx.c[432-433]
src/main/drivers/serial_uart_stm32f7xx.c[434-435]
src/main/drivers/serial_uart_stm32f7xx.c[479-488]
src/main/drivers/serial_uart_stm32f7xx.c[534-536]
src/main/drivers/serial_uart_stm32h7xx.c[492-493]
src/main/drivers/serial_uart_stm32h7xx.c[537-546]
src/main/drivers/serial_uart_stm32h7xx.c[592-594]
src/main/drivers/serial_uart_hal.c[300-317]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The F4, F7 and H7 TX DMA drivers clear direct-mode errors without enabling an interrupt or clearing the active transfer state for them. A stopped transfer can leave buffered serial output unable to progress.
## Fix Focus Areas
- src/main/drivers/serial_uart_stm32f4xx.c[340-341]
- src/main/drivers/serial_uart_stm32f7xx.c[434-435]
- src/main/drivers/serial_uart_stm32h7xx.c[492-493]
## Recommended Fix
Enable the direct-mode error interrupt in each driver and handle that flag as a failed transfer. Clear the active count and apply the existing error-path tail and restart policy so queued output can continue.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Findings visible per group, which tucks the rest behind a View link

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +434 to +435
#define UART_TX_DMA_FLAGS (DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_DMEIF | DMA_IT_FEIF)
#define UART_TX_DMA_ENDED (DMA_IT_TCIF | DMA_IT_TEIF)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Dma errors can stall serial output 🐞 Bug ☼ Reliability

On STM32F4/F7/H7, uartTxDmaHandler() clears the direct-mode error flag but only resets
txDmaCount for transfer-complete or transfer-error flags, while TX setup enables interrupts only
for those latter flags. If a direct-mode error stops a transfer without either enabled completion
event, the recorded count prevents another transfer from starting and a buffered write can wait
indefinitely when the ring fills.
Agent Prompt
## Issue description
The F4, F7 and H7 TX DMA drivers clear direct-mode errors without enabling an interrupt or clearing the active transfer state for them. A stopped transfer can leave buffered serial output unable to progress.

## Fix Focus Areas
- src/main/drivers/serial_uart_stm32f4xx.c[340-341]
- src/main/drivers/serial_uart_stm32f7xx.c[434-435]
- src/main/drivers/serial_uart_stm32h7xx.c[492-493]

## Recommended Fix
Enable the direct-mode error interrupt in each driver and handle that flag as a failed transfer. Clear the active count and apply the existing error-path tail and restart policy so queued output can continue.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A direct mode error doesn't end the transfer, only a transfer error does. The HAL in this tree handles it the same way: HAL_DMA_IRQHandler() disables the stream only on HAL_DMA_ERROR_TE (stm32f7xx_hal_dma.c:928-936) and just records a direct mode error, after which the transfer runs on to its transfer complete. uartTxDmaHandler() gets that interrupt and clears DMEIF along with the other flags, so the count is reset there. No change.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

RAM / Flash usage vs. base commit e87050f — commit 3034909

Using the nearest available size baseline — the PR's exact base commit has no stored baseline yet.

Target Flash Δ RAM Δ
MATEKF405 +1816 B (+0.25%) CCM: ±0 B (±0.00%)
RAM: +568 B (+0.55%)
MATEKF722 +2120 B (+0.44%) ITCM_RAM: +24 B (+0.19%)
RAM: +572 B (+0.66%)
TCM: +4 B (+0.02%)
MATEKF765 +2632 B (+0.35%) DTCM_RAM: ±0 B (±0.00%)
SRAM1: +604 B (+0.48%)
MATEKH743 ⚠️ +2448 B (+0.31%) D2_RAM: +4096 B (+125.49%)
DTCM_RAM: ±0 B (±0.00%)
ITCM_RAM: ±0 B (±0.00%)
RAM: +80 B (+0.06%)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Test firmware build ready — commit 3034909

Download firmware for PR #12129

251 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

On F7 DMA_RAM is plain RAM until iNavFlight#12119 turns the D-cache on and makes
it an uncached SRAM2 section, so until then each ring only duplicated
the port's own, 256 B per UART with a receive stream. They are compiled
only where build_config.h says DMA_RAM is uncached (DMA_RAM_UNCACHED,
set together with the D-cache), so either PR can go in first.
@MrScothh

MrScothh commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

The warnings are RAM. On F7 they were the receive rings from the thread on #12128: since 8ae0509 they are compiled only with #12119's D-cache, which gives back 1280 to 1536 B on the F722 targets. On H7 the D2 increase is the receive and transmit rings in uncached D2 SRAM, 4 KB of the H743's 288 KB.

A stopped stream was kept by its UART, so a port closed at runtime held streams
another port could have used. Every stop now frees its stream and closing a port
stops its streams; the pick no longer needs to look for a port's own streams first.
Since streams are given back only the F4/F7 candidate tables read it, and CI builds with warnings as errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant