Repository navigation
Conversation
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.
taskRunRealtimeCallbacks() calls afatfs_poll() from scheduler(), which is FAST_CODE, and LTO inlines the file system's state machine into it. On targets with an SD card that puts 800 to 980 bytes of afatfs code in ITCM, depending on the target, where KAKUTEF7 and KAKUTEF7HDV are at 99%. The poll does a little work at most once per scheduler loop, so it runs from flash like the rest of afatfs.
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.
The H7's ITCM is 64 KB and about a quarter used, so the poll can stay inlined in the scheduler there, as on maintenance-10.x. F7 builds are identical to the previous commit; H7, F4 and AT32 builds are identical to maintenance-10.x.
…ne goes idle Receivers were the one kind of port left on the byte interrupt, one interrupt per byte, because CRSF and SBUS time every byte. A receiver that asks for it with SERIAL_RX_BURSTS now gets a receive stream: the idle line interrupt, and the stream's half and full ring interrupts for longer bursts, hand the new bytes to its callback in one go at the UART's priority. Each byte is timed back from the end of the burst one character apart, and CRSF and SBUS read that time with serialRxByteTimeUs(), so their gap checks see what they saw before. Half-duplex receivers and the other protocols stay on the byte interrupt. uartOpen() now stops the receive stream as well before it resets a port, a new ring restarts the stream in the same atomic block, and an MSP passthrough drops the receiver's callback, as the CLI one does.
…art-rx-dma-receivers KAKUTEF7 and KAKUTEF7HDV need the ITCM it frees: without it this branch leaves them at exactly 100 %.
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.
# Conflicts: # src/main/drivers/serial_uart_stm32f7xx.c
|
ⓘ 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 QodoReceive CRSF and SBUS frames through UART DMA bursts
AI Description
Diagram
High-Level Assessment
Files changed (26)
|
Code Review by Qodo
1.
|
… docs on receivers with DMA A session ended with +++ left the receiver without its callback, so it stayed in failsafe until a reboot. The target guide still said every serial receiver stays on the interrupt; CRSF and SBUS in full duplex now take a stream.
|
Test firmware build ready — commit Download firmware for PR #12132 251 targets built. Find your board's
|
|
RAM / Flash usage vs. base commit
See RAM/flash optimization guide for techniques to reduce usage. |
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.
Builds on #12129 (DMA on every UART, which builds on #12128, #12062, #12033 and #12001) and on #12118 (
afatfs_pollout of ITCM on F7), merged in because KAKUTEF7 and KAKUTEF7HDV need the ITCM it frees. Please review those first; once they are merged, only this PR's commits are left here.Problem
Receivers are the one kind of port the UART DMA work leaves on the byte interrupt: every byte of a CRSF or SBUS frame is one interrupt, 26 per CRSF RC frame, so 26 000 a second with ELRS at 1 kHz. They were left out because their parsers time each byte: CRSF starts a new frame after a gap, SBUS waits for a quiet line before a frame and drops one that pauses for 3 bytes in the middle. A stream that hands the bytes over later would break those times.
Change
A receiver that asks for it with
SERIAL_RX_BURSTS(CRSF and SBUS here) gets a receive stream like any other port.serialRxByteTimeUs()instead ofmicros(), so their gap checks see what they saw with one interrupt per byte. A pause shorter than a character inside a burst is not seen, but it never mattered to them either.Half-duplex receivers and the other protocols stay on the byte interrupt. The CRSF port's transmitter (telemetry), and SBUS's when it shares the port with telemetry, get a stream too, like every other port since #12129.
Around it:
uartOpen()stops the receive stream before it resets a port, as it already did the transmit one; a port given a new ring restarts its stream in the same atomic block; and an MSP serial passthrough drops the receiver's callback, as the CLI one always did, so the session reads the ring alone, and gives it back when the session ends with +++. The target guide (timer-dma-conflicts.md) says which receivers take a stream.The costs:
Tests
MAMBAF722_2022B with TX3 wired to RX3: the receiver on UART3, and a bench-only CLI command sending frames out of the receiver's own port at a fixed rate. The same runs on a baseline image with the receive stream refused (byte interrupt). Every frame checked by its CRC.
The cycles count everything at interrupt level for that port, the parser's callback and the bench sender's transmit stream included, which are the same in both images: the difference, about 10 000 cycles per frame, is the byte interrupts. At 216 MHz that is about 47 us per frame, 4.7 % of the CPU with CRSF at 1 kHz. With this PR the stream's half and full ring interrupts add about 0.2 per frame. The last column is the time the parser was given for each frame's first byte, after the sender started writing it: timed back from the end of the burst it lands 2 to 7 us after the byte interrupt's own reading, well within a character (24 us for CRSF, 120 us for SBUS).
serial_uart_impl.hlet a half-duplex receiver take bursts; the stream, the idle line and the timing are the code of this PR. CRSF at 150, 500 and 1000 Hz and in bursts of 6 frames all intact, 1 UART interrupt per frame, 11 590 -> 3 826 interrupt cycles per frame at 480 MHz; SBUS kept and dropped exactly as on the byte interrupt.uartOpen(), the ring swap, MSP passthrough) the Mamba runs again: CRSF at 150 and 1000 Hz, bursts of 6 and 8, SBUS at 100 Hz, every frame intact.RAM is the two new fields in every serial port's state. Measured before 39bddd3, on #12129 alone KAKUTEF7 and KAKUTEF7HDV ended at exactly 100 % ITCM: none of the new code goes there, but LTO lays
scheduler()and the rate controller out differently. That is why #12118 is merged in.Not tested, testers wanted
If you fly CRSF or SBUS on F4, H7 or AT32, a flight or a bench check with this build (RX rate, link quality, no failsafe) would help; a report of the board and the receiver is enough.