Skip to content

UART TX and RX on output pads - #12143

Open
MrScothh wants to merge 3 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:feature/uart-on-output-pads
Open

MrScothh wants to merge 3 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:feature/uart-on-output-pads

Conversation

@MrScothh

@MrScothh MrScothh commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

A UART's TX or RX can now move to a motor or servo pad that the processor also connects to that UART, for any function, on any board with the flash for it. This replaces #12087, which did it for one UART on three boards, and only for a Smart ESC.

Some uses: a port on a board whose UART pads are all taken, or a single-wire device (SmartAudio, Tramp, a Smart ESC, SmartPort) on the servo connector it's already plugged into, with no soldering.

How it works

  • Each UART driver gets a table of the pins every UART can reach. I took them from Betaflight's uartHardware tables, which follow the datasheets' alternate function tables. F4 and F7 keep one alternate function per UART, so there a pin is offered only when it uses the UART's own function (the F765's extra AF1/AF4/AF6/AF12 pins only when the target already sets that function).
  • New CLI command serialpad <port> <tx pad> <rx pad>, one word like serialpassthrough (the CLI would read serial_pad as serial). Pads are numbered as in the Outputs tab, and 0 keeps the UART's own pin. It's included in diff and dump, and on its own it marks any stored pad the UART isn't using (Serial.md lists why that can happen).
  • MSP2_INAV_SERIAL_PADS (0x2236) lists the pads each UART can reach and what each pad drives now. MSP2_INAV_SET_SERIAL_PAD (0x2237) sets one. The Configurator side is Ports: an output pad for a UART's TX or RX inav-configurator#2833: a Pins column in the Ports tab.
  • The choice is stored as the pin in a new parameter group (PG_SERIAL_PAD_CONFIG, 1050). Its default is zero, the UART's own pins, so nothing changes for anyone who doesn't use it. The pin moves before any port opens, and only while the port has a function. A pin that another active port holds is refused.
  • The moved pad keeps its output number, so the other motors and servos stay where they are. The existing skip for a UART pin that is also a timer pad would shift every later output instead. If the mixer drives a motor or servo on the pad, the board refuses to arm with a new PWM init error, "Motor or servo output used by a UART", rather than letting that output go dead. The LED strip, beeper and PINIO just lose the pad.
  • Soft-serial pins (with the feature on) and ADC pins are never offered, as checkPwmTimerConflicts() already keeps them off the outputs.
  • A UART that the target swaps (TX and RX exchanged) does not move.
  • On F4 and AT32, SBUS and SmartPort inversion uses an inverter on the UART's own pin, so a pad needs a receiver that doesn't need inverting. F7 and H7 invert inside the UART.

Where it's on

USE_SERIAL_PADS is on by default where the flash is larger than 512 KB: F405, F745, F765, H7 and AT32. That's 209 targets, and on 166 of them at least one UART can reach a pad. I counted with a script that crosses each target's timerHardware[] with the tables.

Target Flash RAM
MATEKF405 +3,108 B +24 B
SPEEDYBEEF405WING +3,484 B +24 B
KAKUTEF7 / KAKUTEF7HDV +2,888 / +3,000 B +64 / +48 B
MATEKF765 +3,020 B +64 B
TBS_LUCID_H7 / TBS_LUCID_H7_WING +3,104 / +3,088 B +96 B
NEUTRONRCF435WING +3,552 B +64 B
MAMBAF722_2022B (off) 0 0

Nothing new runs from the scheduler. The ITCM on KAKUTEF7 and KAKUTEF7HDV can still move by up to 40 B from one build to the next (+40 B on KAKUTEF7 and 0 on KAKUTEF7HDV in this one): when code elsewhere changes, LTO sometimes stops inlining gyroKalmanUpdate() into gyroFilter().

F722 and F411 targets can opt in with a define in target.h. The second commit does that for the three boards #12087 was written for. On the NEXUS, NEXUS X and Vantac RF007 the connector labelled ESC is a pad UART1's TX reaches (PB6, PA9, PA9). The commit also turns on the Smart ESC driver there, so a Smart ESC on that connector works with UART1 assigned to it and serialpad 0 5 0, as asked in #11184. Together that's about 7 KB of flash and 0.9 KB of RAM:

Target Flash RAM Flash used after
NEXUS +6,844 B +880 B 94.5 %
NEXUSX +7,236 B +864 B 97.1 %
VANTAC_RF007 +7,104 B +872 B 96.6 %

Testing

  • Unit tests cover the pad numbering, the routing rules (soft-serial pins included), the CLI and MSP checks and the MSP list (all 629 pass).

  • Bench, TBS Lucid H7 Wing, with no wiring. A bench-only command (not in this PR) counted the edges on a pin while the UART sent four 0x55 bytes, and bit-banged "OK" on a pin with its internal pull resistors for the UART to read back:

    serialpad 3 ... TX: edges on PA0 (S3) / PB9 (own) RX: "OK" read on PA1 (S4) / PB8 (own) Arming
    3 4 40 / 0 yes / no blocked, S3 and S4 are servos 1 and 2
    3 0 40 / 0 no / yes blocked
    0 4 0 / 40 yes / no blocked
    0 0 0 / 40 no / yes allowed

    Half duplex (SERIAL_BIDIR, as SmartAudio and the Smart ESC use it) with TX on S3: 40 edges on PA0, and "OK" read back on the same pad; nothing on PB9. The list the board sends over MSP was UART2 on S5/S6, UART4 on S3/S4 and UART7 on S13/S14, with S3 to S6 reported as servos 1 to 4.

Not tested, testing wanted

  • F4, F7 and AT32 boards, and the NEXUS family. If one of yours shows a pad in the Pins column of the Ports tab, please move a GPS, a receiver or a SmartAudio VTX there and check that it still works, and that the other outputs still drive what they drove before.
  • @UltraFly, on your NEXUS XR: Smart ESC on UART1, serialpad 0 5 0, and the ESC plugged into the ESC connector. The receiver has to be on another UART, since UART1 carries the ESC.

Overlaps with open PRs

I test-merged this with every open PR from #12000 up that touches the same files. These conflict, all of them additions next to each other where both sides stay:

#12090 is a draft test build that carries a copy of #12087.

PG id 1050 and MSP 0x2236/0x2237 skip numbers that open PRs already use (1048, 1049 and 0x2235).

On most boards the processor can also connect a UART's TX or RX to one of
the motor or servo pads. A port can now be moved there, from the Ports tab
or with the new CLI command serialpad <port> <tx pad> <rx pad>, which
gives a port to a board whose UART pads are taken, or puts a single-wire
device (SmartAudio, Tramp, a Smart ESC) on the servo connector it is
already plugged into.

Each driver has a table of the pins every UART reaches (from Betaflight's,
which follow the datasheets); the F4 and F7 drivers keep one alternate
function per UART, so their tables only offer pins with that function. The
choice is stored as the pin in a new parameter group, and applied before
any port opens, only while the port has a function.

A moved pad keeps its output number, so no other motor or servo moves. If
the mixer drives a motor or servo on it, arming is blocked with a new PWM
init error instead of that output silently going dead; the LED strip,
beeper and PINIO just lose the pad.

On by default where the flash is larger than 512 KB (F405, F745, F765, H7,
AT32): about 2.5 to 3.4 KB of flash and 24 to 96 bytes of RAM.
These helicopter boards have few connectors, and the one labelled ESC is a
pad UART1's TX can reach (PA9 on the NEXUS X and Vantac RF007, PB6 on the
NEXUS). With USE_SERIAL_PADS and the Smart ESC driver turned on for them, a
Smart ESC plugged into that connector works with UART1 assigned to it and
`serialpad 0 5 0`. Both are off on F722 by default for flash; these three
have room for them (94.4 to 97.0 % after, about 7 KB each).
@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

@MrScothh

Copy link
Copy Markdown
Contributor Author

You were right about #12087, the restrictions didn't make much sense. I reworked it: now any UART's TX or RX can go on any output pad the MCU can route it to. It's on for boards with more than 512 KB of flash (about 3 KB), plus the NEXUS boards. Is this closer to what you had in mind, @sensei-hacker?

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

Copy link
Copy Markdown

PR Summary by Qodo

Route UART TX and RX through motor and servo output pads

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Let supported boards move UART TX or RX to reachable output pads without rewiring.
• Add CLI and MSP configuration while preserving output numbering and blocking arming on motor or
 servo conflicts.
• Enable Smart ESC use on the ESC connectors of three helicopter boards.
Diagram

graph TD
  Interface["CLI and MSP"] --> Config["Pad configuration"] --> Routing["Boot routing"] --> UART["UART drivers"] --> Pads["Output pads"]
  Routing --> Mapping["PWM mapping"] --> Safety["Arming safety"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Define reachable pins separately for each board
  • ➕ Could include only pins exposed on that board, reducing table size.
  • ➖ Duplicates MCU alternate-function knowledge across targets.
  • ➖ Requires board definitions to stay synchronized with driver pin capabilities.

Recommendation: Keep the shared MCU-family alternate-function tables and intersect them with each board’s output pads. This centralizes hardware constraints and preserves a common configuration interface; board-specific maps are worth considering only if flash measurements make the shared tables prohibitive.

Files changed (28) +1317 / -12

Enhancement (13) +871 / -9
pwm_mapping.cPreserve outputs and reject driven-pad conflicts +70/-9

Preserve outputs and reject driven-pad conflicts

• Uses original UART pins when determining timer conflicts, keeps routed pads in their output positions, and prevents LED, beeper or PINIO use of those pads. Reports a PWM initialization error when a routed pad is assigned to a driven motor or servo, and exposes pad usage for MSP.

src/main/drivers/pwm_mapping.c

pwm_mapping.hExpose routed-output errors and pad usage +5/-0

Expose routed-output errors and pad usage

• Adds a PWM error for motor or servo outputs claimed by a UART and declares a pad-function query.

src/main/drivers/pwm_mapping.h

serial_uart.hDeclare the UART pin-routing interface +4/-0

Declare the UART pin-routing interface

• Adds a feature-gated API to check or apply a UART TX or RX pin choice.

src/main/drivers/serial_uart.h

serial_uart_at32f43x.cAdd AT32 UART alternate-pin routing +105/-0

Add AT32 UART alternate-pin routing

• Defines reachable pins and their mux settings, and applies validated TX or RX choices. Excludes swapped UARTs.

src/main/drivers/serial_uart_at32f43x.c

serial_uart_impl.hShare alternate-pin table definitions +13/-0

Share alternate-pin table definitions

• Introduces the alternate-pin record and macros used by MCU-specific UART routing tables.

src/main/drivers/serial_uart_impl.h

serial_uart_stm32f4xx.cAdd STM32F4 UART alternate-pin routing +75/-0

Add STM32F4 UART alternate-pin routing

• Lists supported TX and RX pins and permits routing to pins compatible with the driver's UART alternate function.

src/main/drivers/serial_uart_stm32f4xx.c

serial_uart_stm32f7xx.cAdd STM32F7 UART alternate-pin routing +108/-0

Add STM32F7 UART alternate-pin routing

• Adds family- and F765-specific pin choices, limited to the UART's configured alternate function. Refuses routing for the swapped UART4 configuration.

src/main/drivers/serial_uart_stm32f7xx.c

serial_uart_stm32h7xx.cAdd STM32H7 UART alternate-pin routing +97/-0

Add STM32H7 UART alternate-pin routing

• Defines reachable pins, updates per-direction alternate functions when routing, and excludes swapped UARTs.

src/main/drivers/serial_uart_stm32h7xx.c

cli.cAdd serialpad configuration and dumps +57/-0

Add serialpad configuration and dumps

• Adds a command to list or set TX and RX pads with validation. Includes serial-pad choices in CLI diff and dump output.

src/main/fc/cli.c

fc_init.cApply pad routing before serial initialization +5/-0

Apply pad routing before serial initialization

• Initializes serial-pad routing after timers and before ports open or outputs are assigned.

src/main/fc/fc_init.c

fc_msp.cExpose serial-pad listing and setting over MSP +23/-0

Expose serial-pad listing and setting over MSP

• Handles the feature-gated list request and validates set requests before changing configuration.

src/main/fc/fc_msp.c

serial_pads.cImplement persistent UART-to-output-pad routing +258/-0

Implement persistent UART-to-output-pad routing

• Registers pin choices, maps output numbers to physical pins, checks UART reachability and reserved pins, and applies eligible moves at boot. Also tracks routed pads and serializes reachable pads and their current usage for MSP.

src/main/io/serial_pads.c

serial_pads.hDeclare serial-pad configuration and APIs +51/-0

Declare serial-pad configuration and APIs

• Defines TX/RX directions, the persistent per-UART pin array, and routing, validation and listing interfaces.

src/main/io/serial_pads.h

Tests (3) +271 / -0
CMakeLists.txtBuild serial-pad unit tests +4/-0

Build serial-pad unit tests

• Registers the routing implementation and stream buffer as test dependencies and supplies feature and soft-serial pin definitions.

src/test/unit/CMakeLists.txt

platform.hProvide a USART type for unit tests +4/-0

Provide a USART type for unit tests

• Adds a minimal USART_TypeDef stub needed by the UART-related test headers.

src/test/unit/platform.h

serial_pads_unittest.ccTest pad numbering, routing rules and MSP list output +263/-0

Test pad numbering, routing rules and MSP list output

• Covers output numbering, active-port and pin-ownership rules, soft-serial reservations, configuration replacement, and serialized reachable-pad usage.

src/test/unit/serial_pads_unittest.cc

Documentation (5) +150 / -1
Cli.mdList the serialpad command +1/-0

List the serialpad command

• Adds the UART output-pad command to the CLI command reference.

docs/Cli.md

Serial.mdDocument UART pad routing and limitations +18/-0

Document UART pad routing and limitations

• Explains pad numbering, configuration, reboot behavior, output conflicts, inversion limitations and supported boards.

docs/Serial.md

Spektrum Smart ESC.mdDescribe Smart ESC routing through an ESC connector +8/-0

Describe Smart ESC routing through an ESC connector

• Documents using a UART-capable output pad for Smart ESC, including the UART1 and S5 settings for three helicopter boards.

docs/Spektrum Smart ESC.md

README.mdDocument serial-pad MSP messages +34/-0

Document serial-pad MSP messages

• Specifies the list and set messages, their payloads, pad usage values and save-and-reboot behavior.

docs/development/msp/README.md

msp_messages.jsonAdd machine-readable serial-pad MSP definitions +89/-1

Add machine-readable serial-pad MSP definitions

• Bumps the specification patch version and defines the list and set message payloads.

docs/development/msp/msp_messages.json

Other (7) +25 / -2
CMakeLists.txtInclude the serial-pad module in firmware builds +2/-0

Include the serial-pad module in firmware builds

• Adds the routing implementation and header to common source declarations.

src/main/CMakeLists.txt

parameter_group_ids.hReserve a persistent serial-pad parameter group +2/-1

Reserve a persistent serial-pad parameter group

• Assigns PG_SERIAL_PAD_CONFIG ID 1050 and advances the INAV parameter-group endpoint.

src/main/config/parameter_group_ids.h

msp_protocol_v2_inav.hAssign serial-pad MSP command IDs +3/-0

Assign serial-pad MSP command IDs

• Reserves 0x2236 for listing UART pad choices and 0x2237 for setting one.

src/main/msp/msp_protocol_v2_inav.h

target.hEnable UART pad routing and Smart ESC on NEXUS +5/-1

Enable UART pad routing and Smart ESC on NEXUS

• Opts the board into both features and notes that UART1 TX can reach the ESC header.

src/main/target/NEXUS/target.h

target.hEnable UART pad routing and Smart ESC on NEXUS X +4/-0

Enable UART pad routing and Smart ESC on NEXUS X

• Opts the board into both features for UART1 TX use on its ESC connector.

src/main/target/NEXUSX/target.h

target.hEnable UART pad routing and Smart ESC on Vantac RF007 +4/-0

Enable UART pad routing and Smart ESC on Vantac RF007

• Opts the board into both features for UART1 TX use on its ESC connector.

src/main/target/VANTAC_RF007/target.h

common.hDefault-enable serial pads on larger supported MCUs +5/-0

Default-enable serial pads on larger supported MCUs

• Enables the feature for supported F4, F7, H7 and AT32 targets with more than 512 KB of flash while allowing smaller targets to opt in.

src/main/target/common.h

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

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Absent ports appear configurable ✓ Resolved
Description
serialPadIsValid() accepts pad zero for any in-range UART identifier without checking whether that
port exists on the board. On a target without USART8, serialpad 7 0 0 or the equivalent MSP
request succeeds and stores a setting for an unavailable port, even though the pad list omits it.
Code

src/main/io/serial_pads.c[R190-195]

+{
+    if (identifier < 0 || identifier >= SERIAL_PAD_UART_COUNT || direction >= SERIAL_PAD_DIRECTION_COUNT) {
+        return false;
+    }
+    const timerHardware_t *timHw = padHardware(pad);
+    return pad == 0 || (timHw && isOffered(identifier, direction, timHw->tag));
Evidence
Validation returns true for pad zero after only range and direction checks; the setter persists
accepted requests. The listing path separately checks availability, while runtime serial
initialization includes only compiled port identifiers.

src/main/io/serial_pads.c[189-210]
src/main/io/serial_pads.c[223-231]
src/main/io/serial.c[454-474]
src/main/fc/fc_msp.c[3884-3897]

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

## Issue description
`serialPadIsValid()` accepts a zero-pad request for UART identifiers that are in range but unavailable on the target, so CLI and MSP report success for a nonexistent port.
## Fix Focus Areas
- src/main/io/serial_pads.c[189-195]
- src/test/unit/serial_pads_unittest.cc[195-209]
## Recommended Fix
Require `serialIsPortAvailable(identifier)` before accepting either a zero or nonzero pad. Add a test showing that an unavailable, in-range UART is rejected for pad zero.

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


2. A refused pad move goes unreported ✓ Resolved
Description
serialPadsInit() drops a configured pad without a log or status flag when the pin is reserved
(soft-serial with the feature on, or ADC), already held by another active UART, or not an output on
this board. The CLI serialpad listing and the MSP chosen byte both read serialPadConfig(), not
the pin actually applied, so they still show the pad after, say, feature SOFTSERIAL is turned on
or another port gets a function, while the device wired to that pad gets no signal.
Code

src/main/io/serial_pads.c[R152-155]

+            // a pin this board has no output on, or one already in use, leaves the UART where it was
+            if (doesConfigurationUsePort(i) && timerGetByTag(tag, TIM_USE_ANY) && !isReserved(tag) && !isHeldByUart(tag)) {
+                uartRoutePin(i, dir == SERIAL_PAD_TX, tag, true);
+            }
Evidence
In serialPadsInit the condition `doesConfigurationUsePort(i) && timerGetByTag(...) &&
!isReserved(tag) && !isHeldByUart(tag) only gates the uartRoutePin` call. Nothing records when it
fails. serialPadsWriteList sets chosen from serialPadConfig()->pin[i][dir] == timHw->tag, and
printSerialPads prints serialPadFind(config->pin[i][...]). Both report the stored choice, so a
refused move looks the same as an applied one. Turning on FEATURE_SOFTSERIAL after picking a
soft-serial pin, or giving a function to a port whose own pin is the chosen pad, hits this path.

src/main/io/serial_pads.c[141-158]
src/main/io/serial_pads.c[241-251]
src/main/fc/cli.c[952-968]

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

## Issue description
`serialPadsInit()` silently skips a configured pad when it is reserved, held by another UART or has no timer output. The CLI listing and the MSP `chosen` field show only the stored config, so a refused move cannot be told apart from an applied one.
## Fix Focus Areas
- src/main/io/serial_pads.c[141-158]
- src/main/io/serial_pads.c[241-251]
- src/main/fc/cli.c[952-968]
## Recommended Fix
In `serialPadsInit()`, when a non-zero configured tag is not applied (whether the port is unused, reserved, held or not an output), emit `LOG_WARNING(SYSTEM, ...)` with the port and reason. Also make the applied state readable: in the MSP list, set `chosen` to 2 (or add a field) when `currentPin(i, dir) == tag`, and have the CLI `serialpad` listing (DUMP_MASTER, no defaults) add a note such as `# not applied` when the configured pin differs from `currentPin()` while the port is in use.

ⓘ 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 copy the agent prompt from any finding and feed it to your IDE agent

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main/io/serial_pads.c
Comment thread src/main/io/serial_pads.c
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

RAM / Flash usage vs. base commit e87050f — commit 7a8775a

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

Target Flash Δ RAM Δ
MATEKF405 +3076 B (+0.43%) CCM: ±0 B (±0.00%)
RAM: +32 B (+0.03%)
MATEKF722 +56 B (+0.01%) ITCM_RAM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)
TCM: ±0 B (±0.00%)
MATEKF765 +3124 B (+0.41%) DTCM_RAM: ±0 B (±0.00%)
SRAM1: +64 B (+0.05%)
MATEKH743 +3096 B (+0.39%) D2_RAM: ±0 B (±0.00%)
DTCM_RAM: ±0 B (±0.00%)
ITCM_RAM: +72 B (+0.44%)
RAM: +200 B (+0.14%)

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

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Test firmware build ready — commit 7a8775a

Download firmware for PR #12143

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.

`serialpad` alone now adds a line for each stored pad the UART is not
using: the port has no function, the pad is not an output on this board,
soft serial, an ADC input or another port uses that pin, or the change is
waiting for a save. Serial.md lists those reasons; the firmware only marks
the line, which costs about 200 bytes instead of 500 with the reasons in
it. Only ports the board has accept a pad, own pin included.
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