Skip to content

Add generic test for inverted-Heli flying#33547

Open
peterbarker wants to merge 1 commit into
ArduPilot:masterfrom
peterbarker:pr-claude/sitl-heliquad-then-generic-heli-inverted-test
Open

Add generic test for inverted-Heli flying#33547
peterbarker wants to merge 1 commit into
ArduPilot:masterfrom
peterbarker:pr-claude/sitl-heliquad-then-generic-heli-inverted-test

Conversation

@peterbarker

@peterbarker peterbarker commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Iterates the Heli frames, trying to fly each inverted

Classification & Testing (check all that apply and add your own)

  • Checked by a human programmer
  • Non-functional change
  • No-binary change
  • Infrastructure change (e.g. unit tests, helper scripts)
  • Automated test(s) verify changes (e.g. unit test, autotest)
  • Tested manually, description below (e.g. SITL)
  • Tested on hardware
  • Logs attached
  • Logs available on request

Description

Follow-up I promised in #33521 (and currently has patches from that PR in here). It's actually only a single patch on top! I rebased

Iterates the Heli frames and changes the tuning a little to allow for inverted flight

  Per-frame inverted-flight performance

  ┌───────────────┬───────────┬──────────────────┬──────────────────┬────────────────────┬───────────────────────────┐
  │     frame     │ inverted? │ alt drop (tuned) │ headroom (tuned) │ alt drop (default) │          result           │
  ├───────────────┼───────────┼──────────────────┼──────────────────┼────────────────────┼───────────────────────────┤
  │ heli          │ yes       │ 0.0 m            │ 140 µs (0% sat)  │ 6.1 m              │ tuned PASS / default FAIL │
  ├───────────────┼───────────┼──────────────────┼──────────────────┼────────────────────┼───────────────────────────┤
  │ heli-blade360 │ yes       │ 0.1 m            │ 115 µs (0%)      │ 6.2 m              │ PASS / FAIL               │
  ├───────────────┼───────────┼──────────────────┼──────────────────┼────────────────────┼───────────────────────────┤
  │ heli-ddfptail │ yes       │ 0.1 m            │ 140 µs (0%)      │ 6.2 m              │ PASS / FAIL               │
  ├───────────────┼───────────┼──────────────────┼──────────────────┼────────────────────┼───────────────────────────┤
  │ heli-ddvptail │ yes       │ 0.0 m            │ 140 µs (0%)      │ 6.7 m              │ PASS / FAIL               │
  ├───────────────┼───────────┼──────────────────┼──────────────────┼────────────────────┼───────────────────────────┤
  │ heli-dual     │ yes       │ 1.2 m            │ 106 µs (0%)      │ 6.4 m              │ PASS / FAIL               │
  ├───────────────┼───────────┼──────────────────┼──────────────────┼────────────────────┼───────────────────────────┤
  │ heli-gas      │ yes       │ 0.1 m            │ 105 µs (0%)      │ 22.9 m             │ PASS / FAIL               │
  └───────────────┴───────────┴──────────────────┴──────────────────┴────────────────────┴───────────────────────────┘

  The story: every frame rolls inverted fine in both configs — attitude control isn't the limiter. What separates pass from fail is collective authority. With the test's tuned range
  (−12°/H_COL_MIN 1260), all six settle into steady inverted hover with ~105–140 µs of collective headroom to spare and 0% saturation, holding altitude within ~1 m. With the default range
  (−2°/1460), the collective pins at its floor (100% saturated) — it physically can't make enough negative thrust — and altitude collapses 6–7 m (heli-gas worst at 23 m; its throttle
  governor behaves differently, only 33% saturated but still loses the most). So the maneuver is gated by negative-collective range, not the controllers, and all six frames are comfortably
  capable once given the range — none is marginal.
image image image image
  ┌───────────────┬───────────────────────────┬────────────────────┬────────────┐
  │     frame     │ peak yaw drift (inverted) │ yaw actuator range │ saturated? │
  ├───────────────┼───────────────────────────┼────────────────────┼────────────┤
  │ heli          │ 40°                       │ 1542–1846          │ no         │
  ├───────────────┼───────────────────────────┼────────────────────┼────────────┤
  │ heli-blade360 │ 42°                       │ 1255–1430          │ no         │
  ├───────────────┼───────────────────────────┼────────────────────┼────────────┤
  │ heli-ddfptail │ 28°                       │ 1284–1969          │ no         │
  ├───────────────┼───────────────────────────┼────────────────────┼────────────┤
  │ heli-ddvptail │ 40°                       │ 1541–1833          │ no         │
  ├───────────────┼───────────────────────────┼────────────────────┼────────────┤
  │ heli-dual     │ 179°                      │ 1410–1776          │ no         │
  ├───────────────┼───────────────────────────┼────────────────────┼────────────┤
  │ heli-gas      │ 43°                       │ 1604–1675          │ no         │
  └───────────────┴───────────────────────────┴────────────────────┴────────────┘

  The yaw story (and the justification for not gating the generic test on heading):
  - Every frame's heading drifts while inverted — typically ~30–45°, because a tail rotor's yaw torque direction effectively reverses relative to the airframe when upside down, so the
  controller is fighting a re-trimmed plant. The drift is bounded and slow, not a spin-out — the yaw actuator works steadily and never saturates (stays off the 1000/2000 stops on all
  frames), so yaw control is retained, just with a standing heading offset.
  - heli-dual is the outlier: ~179° — it essentially yaws around while inverted. A tandem/dual-swash heli has fundamentally different yaw authority (differential collective, no tail rotor),
  and inverted it loses heading completely even though it holds altitude fine (1.2 m drop, passes the test). This is exactly why heading is excluded — heli-dual would otherwise fail on an
  axis orthogonal to "can it sustain inverted flight."

@peterbarker peterbarker requested a review from bnsgeyer June 24, 2026 06:45
@peterbarker

Copy link
Copy Markdown
Contributor Author
  "tuned" / passing = the params your test sets (what you gave me):
  - H_COL_MIN 1260, H_COL_ANG_MIN -12 → the swashplate can drive collective down to −12° blade pitch
  - (IM_STB_COL_* too, but those only matter in STABILIZE; the maneuver is ALT_HOLD)

  "default" / "untuned" / failing = the stock heli defaults (from copter-heli.parm, with nothing extra applied):
  - H_COL_MIN 1460, H_COL_ANG_MIN -2 → collective only reaches −2° blade pitch

  That's the entire difference between the two runs — just how far negative the collective can go. Same airframes, same controllers, same maneuver, same everything else. To get the "failing"
  data I temporarily reverted those two values to the stock defaults, flew, captured the logs, and restored the file.

  So it's not "tuned" in the sense of PID/gain tuning — it's purely collective travel range. −2° can't make enough downward (inverted: upward) thrust to hold the vehicle up while upside
  down, so altitude collapses; −12° can, so it holds. If the labels are misleading I can relabel the graphs to something exact like "−12° collective (test params)" vs "−2° collective (stock 
  defaults)". Want me to do that?

Refactor the inverted-flight maneuver out of HeliQuadInvertedFlight into
a shared fly_inverted_flight() helper, and add a generic InvertedFlight
test that attempts inverted flight on each heli frame and asserts the set
that succeeds matches an expected-pass list: a frame that flies inverted
but is not expected to, or an expected frame that does not, fails the
test.  heli-quad has its own dedicated test.

The frames need a more negative collective range to hold inverted, so the
test sets H_COL_MIN, H_COL_ANG_MIN and the IM_STB_COL_* points for the
duration of the test.

Heading is not checked for the traditional frames: a single tail rotor
re-trims when inverted and drifts slowly, which is orthogonal to whether
the frame can hold inverted altitude (the property under test). The quad
keeps its strict heading and per-rotor negative-collective checks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@peterbarker peterbarker force-pushed the pr-claude/sitl-heliquad-then-generic-heli-inverted-test branch from 844dd03 to 128b79e Compare June 25, 2026 00:18
@bnsgeyer

Copy link
Copy Markdown
Contributor

@peterbarker Wow! You have gone above and beyond on your analysis. I didn't expect the default values to pass and that is why I provided the collective parameter changes. there is one other parameter that probably should be changed to aid in better transition. The default setup has the ATC_RATE_P_MAX and ATC_RATE_R_MAX set to 0 (no limit) but the models don't have infinite pitch and roll rate capability. So I always set a limit to allow the aircraft to keep up with the command model as it commands the roll to inverted and back. I think it would be safe to set those both to 120 deg/s. I don't think it will affect the performance.

I really didn't intend to check all frames. I think it is redundant to check Blade360, ddfp tail, ddvp tail, and the gas versions. They are all single main rotor/tail rotor configs. The dual heli is interesting. the yaw problem may be a factor of control reversal since the thrust vector is negative and dual heli's use the lateral component of thrust to generate yaw moments.

@bnsgeyer

Copy link
Copy Markdown
Contributor

Just tested Dual heli and I think you should drop that one from the inverted flight test. I don't have a good fix for dual heli in order for it to have proper yaw control while inverted. So that would just leave you with the one traditional heli config. which I think is all that is needed for now. Sorry, I didn't mean for you to do all that work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants