Refactor arcade heading hold, reverse steering - #115
Conversation
Replace reset_heading/turning flags with a single _holding_heading flag and simplify arcade mixing logic. Use early returns, keep wheel-mixing/scaling behavior, and centralize IMU-assisted straight driving: capture heading on entry, hold it while straight, and clear the hold when turning. Improves readability and fixes heading recapture behavior when transitioning between turning and straight driving.
When driving backward the turn input is inverted so the robot steers toward the joystick the same way as when driving forward. Adds a check in DifferentialDrive to negate `turn` if `straight` is negative before mixing, preserving the existing mixing and scaling logic and preventing reversed steering behavior.
|
Deadbanding should not be handled in Arcade. That is for a program to
handle.
…On Thu, Aug 6, 2026 at 6:19 PM Jacob Williams ***@***.***> wrote:
Summary
Cleans up arcade() and its IMU heading-hold, and fixes two teleop feel
issues. Single file, differential_drive.py.
Changes
-
*Collapsed the heading-hold state machine.* The two flags turning +
reset_heading are replaced by one _holding_heading. Same behavior
across every transition, far less to reason about. arcade() is
flattened to early returns instead of 3-deep nesting, and the misnamed
left_speed/right_speed (they're efforts) become left/right.
-
*Joystick deadband (0.1).* Inputs below the threshold on either axis
snap to zero, so idle-stick drift no longer creeps or slowly turns the
robot. Also means near-center turn resolves to exactly 0, so heading-hold
engages instead of being disabled by drift.
-
*Reverse steering fix.* Pushing back-and-left used to curve the robot
*right* (the turn kept the same rotation direction as forward). Turn
is now flipped when reversing, so the robot steers toward the stick either
way.
-
*Stop forgets the held heading.* A neutral (0, 0) clears
_holding_heading, so resuming straight recaptures the current heading
rather than steering back toward the pre-stop one.
-
*Fresh PID per straight segment.* heading_pid.clear_history() on
entering straight prevents a prior turn's elapsed time from leaking into
the controller's first derivative/integral step.
-
*Per-board heading gains.* The single kp=0.075, kd=0.001 is replaced
with tuned values: non-Nano kp=0.064, kd=0.0045, Nano kp=0.014,
kd=0.001.
Notes
- Behavior changes worth knowing for existing code: the deadband, the
reverse turn-flip, and the new heading gains all change how teleop drives.
- self.turning and self.reset_heading are gone (nothing else in XRPLib
referenced them); the internal flag is now self._holding_heading.
Testing
Driven via gamepad teleop on hardware — deadband, reverse steering, and
straight-line heading hold all confirmed by feel. Autonomous straight()/
turn() are unaffected (they don't use heading_pid).
------------------------------
You can view, comment on, or merge this pull request online at:
#115
Commit Summary
- 9d4b585
<9d4b585>
Refactor IMU heading hold in differential_drive
- 43ba7d4
<43ba7d4>
Clear holding heading when drive stopped
- 6115cd9
<6115cd9>
Add joystick deadband and PID reset for heading
- 10538ad
<10538ad>
Add NanoXRP-specific heading PID gains
- aade436
<aade436>
Invert turn input when reversing
File Changes
(1 file <https://github.com/Open-STEM/XRP_MicroPython/pull/115/files>)
- *M* XRPLib/differential_drive.py
<https://github.com/Open-STEM/XRP_MicroPython/pull/115/files#diff-cb32d1534df39f42a083149b40f1dcb976cf643683e2f632c47258987cb6c4d0>
(79)
Patch Links:
- https://github.com/Open-STEM/XRP_MicroPython/pull/115.patch
- https://github.com/Open-STEM/XRP_MicroPython/pull/115.diff
—
Reply to this email directly, view it on GitHub
<#115?email_source=notifications&email_token=AAKMHRB6GUVB5OSN4CWCYGL5IUAABA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DEMRUGE4TIMBSGWTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVRTG633UMVZF6Y3MNFRWW>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKMHRDLV4IOM5MLSIGROY35IUAABAVCNFSNUABFKJSXA33TNF2G64TZHM3DAOJWG44TIOJVHNEXG43VMU5TKMBYGUYTAMZTGQZ2C5QC>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AAKMHRGF5LDTWKLZHVUUKLT5IUAABA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DEMRUGE4TIMBSGWTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVJTG633UMVZF62LPOM>
and Android
<https://github.com/notifications/mobile/android/AAKMHRBGHXYVITQOB7L6KHT5IUAABA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DEMRUGE4TIMBSGWTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVZTG633UMVZF6YLOMRZG62LE>.
Download it today!
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
|
My thinking was that without a deadband, there is not much point of having a control loop maintain heading. A stick will almost never be at exactly zero. Very few users are going to know to add a deadband. But I can remove it, if it is technically more correct. Future idea: I think a drivetrain class like this should make it easy for users to set and tune a min_input, max_input, deadband, and input exponential. Low priority but I want to revisit this at some point. |
|
The arcade function, in its original inception, was just meant to be an alternate version of set_effort, where you send a forward/back effort and a turning effort (which makes it really easy to program a line-follower and such). I think the changes that were made prior to this PR (adding the heading_pid) are not ideal. I think a better change for improving this function is to make a different version of this that uses speed control (including angular velocity control), which will result in the level of control without obfuscating the behavior in the default version of the function. Since XRPLib is a learning platform, I think hiding elements such as angular drift feels a little silly (which is why this is not the right place for a deadband) |
|
@KalticCodes I agree, I removed deadband from this PR. I'm with you in that I don't really think having heading_pid as part of arcade drive is ideal but it is outside the scope of this PR. The main purpose of this PR is the lib needs the per-board heading gains. I believe the reverse steering fix is a good improvement too, everything is else is just cleaning up the existing function/improving readability now. |
Summary
Cleans up
arcade()and its IMU heading-hold, and fixes two teleop feel issues. Single file,differential_drive.py.Changes
Per-board heading gains. The single
kp=0.075, kd=0.001is replaced with tuned values: non-Nanokp=0.064, kd=0.0045, Nanokp=0.014, kd=0.001.Reverse steering fix. Pushing back-and-left used to curve the robot right (the turn kept the same rotation direction as forward). Turn is now flipped when reversing, so the robot steers toward the stick either way.
Collapsed the heading-hold state machine. The two flags
turning+reset_headingare replaced by one_holding_heading. Same behavior across every transition, far less to reason about.arcade()is flattened to early returns instead of 3-deep nesting, and the misnamedleft_speed/right_speed(they're efforts) becomeleft/right.Stop forgets the held heading. A neutral
(0, 0)clears_holding_heading, so resuming straight recaptures the current heading rather than steering back toward the pre-stop one.Fresh PID per straight segment.
heading_pid.clear_history()on entering straight prevents a prior turn's elapsed time from leaking into the controller's first derivative/integral step.