Skip to content

feat(throttle): unify cruise max at 70% and raise chill max to 80% - #128

Merged
PaulDWhite merged 2 commits into
masterfrom
feat/unified-cruise-max
Oct 7, 2026
Merged

PaulDWhite merged 2 commits into
masterfrom
feat/unified-cruise-max

Conversation

@PaulDWhite

@PaulDWhite PaulDWhite commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

For 8.1: gives chill mode more top-end thrust, and makes cruise work the same way in both modes. Cruise now has one maximum shared by chill and sport, and it always holds exactly the throttle you were flying at when you set it.

All percentages below are of the stock ESC throttle window (normal_pwm_start 1050 µs to normal_pwm_end 1950 µs, curve set to "Linear Thrust"), so output % = (µs − 1050) / 900. The old CHILL_MODE_MAX_PWM comment said "70%", but 1600 µs was actually 61%.

What changes

v8.0 / master This PR
Chill max 1600 µs (61%) 1770 µs (80%)
Sport max 1950 µs (100%) 1950 µs (100%)
Cruise max, sport 1584 µs (59%), clamped 1680 µs (70%)
Cruise max, chill 1430 µs (42%), limited by the 70% lever gate 1680 µs (70%)
Engaging cruise above the max Sport: output is clamped down (up to 69% → 59%) Refused: icon flashes, output unchanged
  • One cruise max for both modes. CRUISE_MAX_PWM (1680 µs) replaces CRUISE_MAX_PERCENTAGE (the 60% clamp) and CRUISE_ACTIVATION_MAX_POT_PERCENTAGE (the 70% lever gate). The limit is on output, so sport reaches it at about 70% lever and chill at about 88%.
  • Cruise holds the exact output being flown. If you engage above the max, cruise is refused instead of clamped, so it can no longer drop your power when it engages.
  • The BLE cruise setpoint write (ThrottleValueCallbacks) respects the same cap. Before, it accepted anything up to 1950 µs, which bypassed the cruise limit entirely.
  • Chill ramp rate is unchanged (8 µs/tick), so idle to full chill now takes about 1.8 s instead of 1.4 s. Response at any given output feels the same.
  • The cruise cancel rule is unchanged: squeeze back to 80% of the set lever position, after the 2 s grace period.

Race fix: engaging cruise

changeDeviceState() set ARMED_CRUISING and released the mutex before afterCruiseStart() recorded cruisedPotVal and cruisedAtMillis and queued the cruise PWM. If the 50 Hz throttle task ran in that gap, one of two things happened:

  • It sent the stale currentCruiseThrottlePWM for a tick. On the first cruise after boot that's ESC_MIN_PWM, i.e. idle.
  • It ran the cancel check against the reset cruisedPotVal = 0 and an old cruisedAtMillis, which cancels cruise immediately.

Now toggleCruise() records the cruise references before the state changes. The throttle task latches its own last commanded output when it sees ARMED_CRUISING for the first time, so no other task has to hand it a value. The throttle task also reads currentState once per tick, so the latch and the switch always see the same state. afterCruiseStart() has been removed.

Relation to pot-travel-cruise

This covers the same area as the unmerged pot-travel-cruise branch (chill 75%, 70% lever gate, "+20 points past" cancel). It differs in three ways: the limit is on output rather than lever position so both modes share one cruise max, chill goes to 80%, and the existing cancel rule is kept. It includes that branch's fix for reading the lever once when cruise engages.

Validation

  • pio test -e native-test: 56 passed. calculateCruisePwm and isPotInCruiseActivationRange were retired along with their 7 tests. 3 new tests cover the 80%/70% values, refusing just above the cruise max, and the exact lever position where cruise stops engaging in each mode (sport 2891/4095, chill 3599/4095).
  • pio run -e OpenPPG-CESP32S3-CAN-SP140: target build succeeded, no new warnings; RAM and flash unchanged (36.8% / 36.3%).
  • Flashed to a controller on USB: boots cleanly, throttle task running. No ESC was connected, so throttle output hasn't been checked on hardware yet.
  • The screenshot suite doesn't compile or include any changed file.

Before merging: bench test (prop off, watching ESC input µs)

  • Chill at full lever reads about 1770 µs.
  • Sport: engaging cruise below about 70% lever holds the same µs with no dip. Above that, the icon flashes and cruise doesn't engage.
  • Chill: cruise engages up to about 88% lever and is refused above that.
  • Cruise cancel still works after the 2 s grace period.
  • Engaging and cancelling cruise several times in a row never cancels immediately or dips at the moment it engages.

Chill max goes from 1600us (61% ESC output) to 1770us (80%). Cruise max
is now a single output level, CRUISE_MAX_PWM = 1680us (70%), shared by
chill and sport. Percentages are of the stock ESC throttle window,
1050-1950us.

Cruise holds exactly the output being flown when it engages. Above the
cruise max, activation is refused (icon flashes) instead of clamped,
which removes the sport-mode drop when engaging at 60-70% lever. The
old 60% clamp and 70% lever gate are gone, and BLE cruise updates now
respect the same cap (previously they accepted up to 1950us).

Fix a cruise-engage race: the state flipped to ARMED_CRUISING before
the cruise PWM, start time and pot reference were set, so a throttle
tick in that gap could command a stale setpoint (idle on the first
cruise after boot) or cancel cruise immediately. The references are
now recorded before the state change and the throttle task latches
its own last output on entry.
Resolve the conflict with #127 (ESP-IDF time shim): keep this branch's
removal of afterCruiseStart(), whose work moved to toggleCruise() and
the throttle task, and use timeMillis() for the cruise start time that
toggleCruise() now records.
@PaulDWhite
PaulDWhite merged commit 0890f61 into master Oct 7, 2026
8 checks passed
@PaulDWhite
PaulDWhite deleted the feat/unified-cruise-max branch October 7, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant