Skip to content

motor_srxl2: a smaller ESC state - #12086

Open
MrScothh wants to merge 8 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:opt/srxl2-driver-ram
Open

MrScothh wants to merge 8 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:opt/srxl2-driver-ram

Conversation

@MrScothh

@MrScothh MrScothh commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

The SRXL2 driver keeps 200 bytes of state per ESC, 800 bytes for the four it allows, in the main RAM that fills first on F405 and AT32 boards. This applies the RAM and flash guide to it (ram-and-flash-optimization, the draft from #11718): its buffers are sized for what the bus really carries and its fields are packed, so an ESC takes 144 bytes. The state stays in the main RAM: FASTRAM is better kept for the main loop.

This builds on #12085 (Smart ESC at power-up): it reorders the struct that one changes.

The change

  • The receive buffer held the protocol's 80-byte maximum. It now holds SRXL2_CONTROL_FRAME_MAX, the longest control frame the driver can build (every one of its channels, 34 bytes), so our own echo always fits. The frames it actually sends carry the throttle and the reverse channel, 16 to 18 bytes, and the ESC's replies are 22 bytes at most (telemetry) and 14 (handshake). A longer frame is dropped as corrupt, as one over 80 bytes was. The control frame's buffer on the stack is sized the same way.
  • The fields of the ESC state and of its telemetry are ordered by size, which removes their padding.
  • The diagnostics written to the debug channels are gathered only when debug_mode is ALWAYS, instead of on every run of the task at 200 Hz.

Cost

Against #12085 (Smart ESC at power-up):

Target Main RAM Flash
MATEKF405SE -224 B, 79.82% -> 79.65% -24 B
BLUEBERRYF435WING -256 B, 91.04% -> 90.85% -32 B
TBS_LUCID_H7_WING -256 B -56 B, ITCM -8 B

FASTRAM (CCM, RAM1, DTCM) is unchanged. The F7 targets build no SRXL2 driver, which is off there for flash, so they are not affected.

Tested

  • TBS Lucid H7 Wing with an Avian 70A driving its motor (no propeller), with the first version of this PR, which also moved the state to FASTRAM; the struct and the buffers are the same since, only their section changed. Flashed with the ESC powered, it linked again, its telemetry came through (24.76 V, 42 degC), and 1120 us turned the motor at about 4830 rpm, as before this change, for 4 s and then stopped at 1000 us. Our control frames' echo, the 22-byte telemetry and the handshakes all went through the new buffer.
  • Builds of the current version: MATEKF405SE, BLUEBERRYF435WING and TBS_LUCID_H7_WING.

Related

Not tested, testing wanted

  • F405 and AT32 boards with a Smart ESC.

An Avian powered with the board announces itself 43 to about 345 ms after
the board's reset, links only once those handshakes are over and only if
control frames are still coming, and never links if left unanswered. The
ports opened at 325 ms, after the USB wait, and the driver first ran from
the task at about 6 s, so a battery connected to board and ESC together
left the motor dead until it was unplugged and plugged in again.

Timer and serial init move ahead of the USB wait, and a board set to SRXL2
opens its ports there and runs the driver until every ESC has replied to a
telemetry request (349 ms on the bench), gives up on one that has not
spoken by 200 ms, and stops at 700 ms whatever happens. Init's fixed waits
keep the ESCs answered instead of sleeping, so one powered a little after
the board links too. Other motor protocols only see the two calls move.
The keepalive answer waits 500 ms into a link: answered again during its
handshakes, the Avian did not link when they ended.

The rest of init leaves the ESC without frames for about 4 s. The Avian
announces itself through them and the task links it again; one still
starting begins its startup anew, so the arming check counts its
readiness again from when the frames return. The check also asks for a
recent telemetry reply rather than any frame, since an ESC that has lost
its frames still sends handshakes.

The docs and two comments said a board restarting under a powered ESC
never gets it back: a linked Avian that loses its frames announces itself
again, and the task links it.
The state of each ESC took 200 bytes, 800 for the four the driver allows,
in the main RAM that fills first on F405 and AT32 boards. Following the RAM
and flash guide, it moves to FASTRAM, which only the CPU touches here. AT32
does not zero that section, so srxl2MotorInitialize() clears it, as it
already did, and escCount keeps every reader off it until then.

The receive buffer held the protocol's 80-byte maximum. It now holds the
longest control frame the driver can build, 34 bytes with every channel,
so our own echo always fits, while the ESC's replies are 22 bytes at most;
the control frame's stack buffer is sized the same way. With the fields of
the ESC state and of its telemetry ordered by size, an ESC takes 144
bytes.

The diagnostics written to the debug channels are gathered only when
debug_mode is ALWAYS, instead of on every run of the task.
@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

Reduce SRXL2 RAM use and link Smart ESCs during boot

🐞 Bug fix ✨ Enhancement 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Move compacted ESC state to FASTRAM and shrink frame buffers to reduce main RAM use.
• Link Smart ESCs before startup waits so their brief power-up announcements are not missed.
• Require recent telemetry and a 10-second startup period before permitting arming.
Diagram

graph TD
    INIT["Boot initialization"] --> PORTS["Serial ports"] --> DRIVER["SRXL2 driver"] --> RAM[("FASTRAM state")]
    DRIVER --> ESC["Smart ESC"]
    DRIVER --> ARM["Arming gate"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Event-driven ESC servicing throughout startup
  • ➕ Could handle ESC announcements without dedicated blocking service loops.
  • ➖ Requires broader changes to startup scheduling and serial processing.

Recommendation: The focused early-link and startup-wait servicing approach is preferable for this change: it addresses the ESC's short announcement window without restructuring initialization. Review the timing limits and early-initialization dependencies carefully, particularly on F405 and AT32 hardware.

Files changed (5) +210 / -72

Enhancement (2) +136 / -37
motor_srxl2.cCompact ESC state and service boot-time links +122/-36

Compact ESC state and service boot-time links

• Moves per-ESC state to FASTRAM, reorders fields, and sizes receive and control buffers to the longest frame the driver builds. Adds bounded boot-link servicing and initialization reuse, tracks telemetry replies and renewed announcements for arming readiness, extends the ready delay to 10 seconds, and skips debug aggregation unless requested.

src/main/io/motor_srxl2.c

motor_srxl2.hExpose boot-link servicing and compact telemetry +14/-1

Expose boot-link servicing and compact telemetry

• Reorders telemetry fields to reduce padding. Declares the early-link and timed-service entry points used during initialization.

src/main/io/motor_srxl2.h

Bug fix (1) +29 / -7
fc_init.cStart SRXL2 linking before USB initialization +29/-7

Start SRXL2 linking before USB initialization

• Moves timer and serial initialization ahead of USB startup, then opens and services configured SRXL2 ports during the ESC's announcement window. Replaces selected fixed GPS/magnetometer startup delays with waits that continue servicing the ESC.

src/main/fc/fc_init.c

Documentation (2) +45 / -28
Spektrum Smart ESC.mdDocument boot-time linking and arming behavior +38/-21

Document boot-time linking and arming behavior

• Explains when an Avian announces itself, how early startup servicing establishes its link, and which power-on timings can still miss it. Documents the 10-second readiness block and recovery after lost frames or a board restart.

docs/Spektrum Smart ESC.md

fc_core.cClarify the SRXL2 arming safety check +7/-7

Clarify the SRXL2 arming safety check

• Updates the arming-check rationale to reflect both link availability and the ESC's throttle-ready delay. The arming condition itself is unchanged.

src/main/fc/fc_core.c

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

qodo-free-for-open-source-projects Bot commented Sep 30, 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. A shared port can lose setup access 🐞 Bug ≡ Correctness
Description
srxl2MotorAwaitLink() now opens SRXL2 ports before mspSerialInit(), so an SRXL2 entry claims a
UART that is also listed in a separate MSP entry. When those duplicate entries exist, the later MSP
open fails; previously MSP opened first, leaving setup access available even though the motor port
could not open.
Code

src/main/fc/fc_init.c[R304-307]

+    if (motorConfig()->motorPwmProtocol == PWM_TYPE_SRXL2) {
+        srxl2MotorSetReverseChannel(motorConfig()->srxl2ReverseChannel);
+        srxl2MotorSetTelemetryRate(motorConfig()->srxl2TelemetryRate);
+        srxl2MotorAwaitLink();
Evidence
Serial validation checks function combinations per entry but does not reject two entries with the
same identifier. SRXL2 enumerates and opens its entries during the newly added early call;
openSerialPort() refuses an already occupied identifier, and the subsequent MSP initializer skips
ports it cannot open. Before this change, MSP initialization preceded the motor preconfiguration
that opened SRXL2 ports.

src/main/io/serial.c[271-314]
src/main/io/motor_srxl2.c[622-647]
src/main/io/serial.c[327-334]
src/main/msp/msp_serial.c[52-76]
src/main/fc/fc_init.c[298-332]
src/main/drivers/pwm_output.c[704-715]

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

## Issue description
Opening SRXL2 ports before MSP changes allocation priority for duplicate UART entries and can remove the user's setup connection.
## Fix Focus Areas
- src/main/fc/fc_init.c[298-332]
- src/main/io/serial.c[271-314]
## Recommended Fix
Ensure MSP retains priority for a UART configured in both services, either by rejecting duplicate identifiers during configuration validation with a recoverable path or by reserving MSP ports before early SRXL2 allocation.

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


2. Short stalls block arming for 10 seconds ✓ Resolved
Description
In the SRXL2_RUNNING case, srxl2ProcessEsc() resets runningSinceMs whenever more than
SRXL2_STARVED_MS (150 ms) passes between control frames. srxl2HandleHandshake() does the same on
each re-announcement, in both cases however long ago the ESC finished starting. A blocking settings
save, flash erase or long MSP operation on a disarmed, fully started aircraft then makes
srxl2MotorIsConnected() return false for SRXL2_READY_DELAY_MS, which raises the hardware-failure
arming block and its OSD warning. The PR's own documentation says a started Avian resumes the
throttle by itself when frames return.
Code

src/main/io/motor_srxl2.c[R932-934]

+            if (now - e->lastControlMs >= SRXL2_STARVED_MS) {
+                e->runningSinceMs = now;
+            }
Evidence
srxl2MotorIsConnected() returns false while now - runningSinceMs < SRXL2_READY_DELAY_MS, and
updateArmingStatus() turns that into ARMING_DISABLED_HARDWARE_FAILURE. The new resets have no
condition on whether the ESC has already finished starting.

src/main/io/motor_srxl2.c[930-937]
src/main/io/motor_srxl2.c[430-435]
src/main/io/motor_srxl2.c[1063-1081]
src/main/fc/fc_core.c[321-334]

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 readiness timer `runningSinceMs` is reset on any control-frame gap of 150 ms or more, and on every handshake from the linked ESC, even after the ESC has long finished starting. The arming check then blocks arming for 10 s after every short main-loop stall.
## Fix Focus Areas
- src/main/io/motor_srxl2.c[931-935]
- src/main/io/motor_srxl2.c[431-435]
## Recommended Fix
Guard both resets so they apply only while the ESC is still in its startup window: `if (now - e->runningSinceMs < SRXL2_READY_DELAY_MS) e->runningSinceMs = now;`. Alternatively, keep a per-ESC flag that is set once the ready delay has passed and cleared only on a new negotiation, and skip the resets while it is set.

ⓘ 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 add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main/fc/fc_init.c
Comment thread src/main/io/motor_srxl2.c
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

RAM / Flash usage vs. base branch — commit d8e7f01

No size baseline is available yet for this PR's base commit (no per-commit baseline has been published for it). This comment will show deltas once one exists — rebasing the PR refreshes its base commit.

Target Flash Δ RAM Δ
MATEKF405 723983 B (no baseline) 136572 B (no baseline)
MATEKF722 480763 B (no baseline) 112124 B (no baseline)
MATEKF765 756159 B (no baseline) 153948 B (no baseline)
MATEKH743 800207 B (no baseline) 158344 B (no baseline)

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

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Test firmware build ready — commit 80ac111

Download firmware for PR #12086

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.

# Conflicts:
#	docs/Spektrum Smart ESC.md
#	src/main/io/motor_srxl2.c
…tarts

A 150 ms gap in the board's frames restarted the 10 s arming wait whatever
the ESC's state, so a pause of the board's own, long after the ESC had
started, blocked arming again for 10 s with the OSD's hardware warning. Only
an Avian still starting when its frames stop starts over; one already started
takes the throttle back at once. The state is judged when the frames stopped,
so a gap that begins during the startup restarts the wait however long it is.
@sensei-hacker

Copy link
Copy Markdown
Member

I think I might rather put more widely-used main loop code in FASTRAM. But leaving this open for now.

FASTRAM is better kept for the main loop's data, which every board runs; the smaller state stays.
@MrScothh MrScothh changed the title motor_srxl2: keep the ESC state out of the main RAM motor_srxl2: a smaller ESC state Oct 5, 2026
@MrScothh

MrScothh commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Fair point, FASTRAM is better spent on the main loop. I took it out in 80ac111: the ESC state stays in the main RAM. What is left is the smaller state (fields ordered by size, buffers sized for what the bus carries): 224 B less main RAM on F405, 256 B on AT32 and H7, FASTRAM untouched. Title and description updated.

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.

2 participants