Repository navigation
Conversation
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
PR Summary by QodoAdd target-configured DMA reception for UART ports
AI Description
Diagram
High-Level Assessment
Files changed (17)
|
Code Review by Qodo
1. Satellite details update too slowly
|
|
RAM / Flash usage vs. base commit
See RAM/flash optimization guide for techniques to reduce usage. |
|
Test firmware build ready — commit Download firmware for PR #12033 251 targets built. Find your board's
|
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.
b415c3f to
86745b2
Compare
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.
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.
The F7 UART receive rings of iNavFlight#12033 are compiled only then; without the D-cache the port's own ring does.
This builds on #12001, the GPS port's own receive buffer. Its four commits show here until it is merged; the last three are this PR, the third from its review.
Stacked on this one: #12062 (send through DMA), #12128 (the stream rule), #12129 (DMA on every UART) and #12132 (CRSF and SBUS receivers on DMA). Merged from here up, each one's diff shows only its own commits.
What this adds
A UART can receive through a DMA stream instead of an interrupt per byte. A target turns it on per port with
UARTx_RX_DMA; a port without one, and a target that names none, build and behave as before.How
rxCallback(the serial receiver protocols, which frame by timing) stays on the interrupt even when a stream is named.DMA_RAM), out of the data cache and within reach of DMA1 and DMA2.serialSetRxBuffer()from gps: satellite details twice a second, and a 512 byte receive buffer for the GPS port #12001 goes through a new optional vtable entry. A UART receiving through DMA restarts its stream on the new ring. On targets with RX DMA the GPS ring is placed where a stream can reach it: D2 SRAM on H7, plain RAM on F4, F7 and AT32F43x (CCM is out of reach on F4, INAV's F7 linker scripts say DMA cannot reach DTCM, and I don't know that RAM1 is reachable on AT32). Everywhere else it stays in FASTRAM.docs/development/targets/timer-dma-conflicts.mdgets a section on choosing a stream.Measured
TBS Lucid H7 Wing, NEO-F10N on UART2 at 115200, one minute, a bench build of this change with and without
UART2_RX_DMA. That build sits on an older base and carries interrupt counters that are not part of this PR:At 480 MHz the load was small to begin with. What the stream adds is that it keeps receiving while interrupts are masked, where the byte interrupt has to be served before the next byte arrives.
With this branch, GPS on UART2 through DMA: the receiver is identified with no errors or timeouts at 115200 and after autobaud to 230400, and NAV-PVT arrives at the configured 10 Hz. Over ten reboots at 230400 there were 1 error and 2 timeouts at startup, against 1 and 1 over nine reboots with the byte interrupt, so that remainder is not the stream's.
Against #12001, built from the same base:
The size comment below is measured against an older base (86a0441) and includes #12001: the +512 B it shows in CCM, TCM or DTCM on targets that do not receive through DMA is #12001's GPS ring, not this PR.
Not tested
The H743 and an F7 (Diatone Mamba F722, see Tested on an F7 below) have received through DMA. F4 and AT32 build, and their stream tables follow the reference manuals, but no board of those families has run it.
After review
On F7 the GPS ring is in plain RAM when the port receives through DMA: it was in DTCM, which INAV's F7 linker scripts say DMA cannot reach.
Rebased on the current
maintenance-10.xwith gps: satellite details twice a second, and a 512 byte receive buffer for the GPS port #12001.A receive stream restarted on a new ring sets the UART's DMA request again (28ef068). On F7 and H7 stopping
the stream cleared it and only a reprogramming of the port set it back, so after
serialSetRxBuffer()the portheard nothing until the next baud, mode or option change. Today's only caller, the GPS, changes the baud rate
right after, so it never showed. With a test patch that swaps a larger GPS ring in 10 s after boot:
F4 and AT32 already set the request when the stream starts. TBS_LUCID_H7_WING +16 B of flash; no other target
compiles it.
On F7 the GPS ring goes in
DMA_RAMas on H7 (79dcdb5). A port's receive stream gets a ring of its own there only whereDMA_RAMis uncached, that is with F7: enable the D-cache, DMA buffers in a non-cacheable SRAM2 region #12119's D-cache, which setsDMA_RAM_UNCACHED(d2cafb6, after a Qodo point on serial: leave a UART only the DMA streams a timer output will use #12128): without itDMA_RAMis plain RAM and that ring would only duplicate the port's own. With the D-cache, a ring left in cached RAM could hand the CPU a line cached before the stream filled it. I did not see that fail at 115200 (FLYWOOF722PRO with F7: enable the D-cache, DMA buffers in a non-cacheable SRAM2 region #12119, the GPS sending on the interrupt, 0-1 errors per boot with the rings in cached RAM and inDMA_RAMalike), so this follows the cache rule rather than a measured failure. No target in the tree names an F7 receive stream today.Built on F4, F7, H7, AT32 and SITL; KAKUTEF7 and KAKUTEF7HDV fit their ITCM. On the TBS Lucid H7 Wing after review: the GPS on UART2 works through receive DMA.
Tested on an F7 (2026-10-06)
Diatone Mamba F722 2022B, image of #12062 (which carries this PR) merged on the current maintenance-10.x, with
UART1_RX_DMA DMA_TAG(2, 5, 4),UART3_RX_DMA DMA_TAG(1, 1, 4)andUART3_TX_DMA DMA_TAG(1, 3, 4)added to the target (DMA1 streams 1 and 3 belong to no timer of this board; UART1's transmit stream, DMA2 stream 7, is TIM8_CH4's, so UART1 cannot send through DMA there).serialpassthroughon UART3 with TX3 and RX3 joined, random bytes written and read back at the same time from the host:The interrupt counters were a local test commit, not part of the PR. A CRSF receiver on UART1 with
UART1_RX_DMAnamed keeps its byte callback and works (RX rate 994, channels and RSSI as expected). F4 and AT32 still untested.Note for testers: the branch has maintenance-10.x merged in since 28ef068, #11943 (flash flush) included, so an image from this head builds as tested.