-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor feedforward, use acro Rates in Angle mode #12578
Conversation
This comment has been minimized.
This comment has been minimized.
AUTOMERGE: (FAIL)
|
This comment has been minimized.
This comment has been minimized.
76d3aa9
to
2f3edb8
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Need help with feedforward
2948828
to
9d69801
Compare
This comment has been minimized.
This comment has been minimized.
It flies really well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the move to dividing the calculation from the get, and triggering the calculations from RC rather than the PID loop.
I have mostly looked at the feedforward calculation function and maybe got into a bit too much detail.
I'll probably have more comments later
} | ||
} | ||
|
||
FAST_CODE_NOINLINE void calculateFeedforward (int axis) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be made static or STATIC_UNIT_TESTED
? It's not supposed to be called from outside rc.c, right?
Also, having the actual arguments as arguments rather than an index to a static array would make it a lot easier to write tests for this function. Same with returning the feedForward delta rather than just storing it.
There are a lot of de facto arguments, so maybe a struct containing all arguments for an axis would be a good idea.
e. g. instead of
static float prevRcCommand[3];
static float prevRcCommandDeltaAbs[3]; // for duplicate interpolation
etc etc
there would be a struct like
typedef struct feedforwardState_s {
float prevCommand;
float prevCommandDeltaAbs;
etc...
} feedforwardState;
static feedforwardState feedforwardStates[XYZ_AXIS_COUNT];
and then the signature of the function would be something like
float calculateFeedforward(feedForwardState* ffState)
Just a thought (Not sure every argument should be in the struct)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this function could definitely be STATIC.
As for unit tested, IDK what STATIC_UNIT_TESTED changes over just STATIC?
Using a struct may help tidy things up, for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
STATIC_UNIT_TESTED
translates to static
unless Betaflight is built for unit testing, then it translates to nothing. That way the function can be reached from outside the translation unit when being tested (i.e. from outside the same .c file).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'd like to implement the struct with some advice exactly how, since some parameters come from pid.c, and the main benefit would be if we could simplify getting those multiple values using a struct or something.
I'll add STATIC_UNIT_TESTED
once I've figured how to restore the feedforward unit tests (currently all deleted).
src/main/fc/rc.c
Outdated
float setpointAcceleration = 0.0f; | ||
|
||
// calculate an attenuator from average of two most recent rcCommand deltas vs jitter threshold | ||
float ffAttenuator = 1.0f; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change the name from jitterAttenuator
? That was more descriptive to me at least. Adding ff in the feed forward calculation function doesn't really give me any more information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
Ah, it's also used to cause attenuation to zero in some other places, that are not specifically jitter-related. ie it's not exclusively used for jitter control.
If I rename it to feedforwardAttenuator
which is shorter than feedforwardJitterAttenuator
.
I'll do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feedforwardAttenuator
doesn't really tell me more either. That we are in a function called calculateFeedforward
is enough of a hint that the variable has something to do with feedforward.
Maybe call it jitterAttenuator
again and use different variables for the other cases?
It might look inefficient but the code will probably be easier to follow and the compiler will probably sort things out in the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After checking the code functionally, I needed a value to force some lines to zero, independently of a value to attenuate on the basis of the jitter attenuation code. So we're back to the original jitterAttenuator
again :-)
src/main/fc/rc.c
Outdated
// DEBUG_SET(DEBUG_FEEDFORWARD, 2, lrintf(setpoint)); // un-smoothed setpoint after interpolations | ||
// } | ||
|
||
float absSetpointSpeed = fabsf(feedforwardDelta); // unsmoothed for kick prevention |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
absSetpointDelta
might be a better name.
A speed should always be absolute and calling this a speed when it's a difference between two setpoints, with no invlovement of the time between them is a bit odd to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do a bunch of re-naming, hopefully to tidy up these loose ends. Thanks for finding them.
src/main/fc/rc.c
Outdated
float absSetpointSpeed = fabsf(feedforwardDelta); // unsmoothed for kick prevention | ||
|
||
// calculate acceleration, smooth and attenuate it | ||
setpointAcceleration = feedforwardDelta - prevSetpointDelta[axis]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling this an acceleration makes me a bit confused.
In the current code it was an acceleration since it was calculated as
setpointAcceleration = setpointSpeed - prevSetpointSpeed[axis];
setpointAcceleration *= rxRate * 0.01f;
and setpointSpeed
was the derivative of the setpoint, including the multiplication with rxRate
(so really it was setpoint velocity but...).
Here setpointAcceleration
is the difference of a difference. Not really sure what to call that, but calling it an acceleration instantly have me looking for a multiplication with rxRate and assume that there is a bug when I can't find one.
In the current master code the acceleration is calculated based on the smoothed velocity.
Here the delta is smoothed after the "acceleration" is calculated. Is that intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setpointSpeed or setpointDelta is the rate of change of setpoint over time, or setpoint 'velocity'.
The rate of change in setpointDelta over time is the second derivative, or 'acceleration' of setpoint.
I'll look at the smoothing etc again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem I have is that the over time part is missing to make either value a rate of change.
If the rxRate was factored in there I wouldn't have any problem with these names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes good point, I was too quick with the original draft. I'm back to using RxRate to convert the delta to a 'velocity', and have set the names accordingly. This was necessary to ensure that the 'velocity' was the same at different RC rates, and to make the interpolation work correctly.
src/main/fc/rc.c
Outdated
feedforwardDelta += feedforwardBoost; | ||
feedforwardDelta *= rxRate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this value should use another name since it no longer a difference between setpoints?
Something like:
feedforwardDelta += feedforwardBoost; | |
feedforwardDelta *= rxRate; | |
float feedforwardValue = (feedforwardDelta + feedforwardBoost) * rxRate; |
and then keep using the new name further down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, thanks.
src/main/flight/pid.c
Outdated
pidSetpointDelta = feedforwardApply(axis, newRcFrame, pidRuntime.feedforwardAveraging, rawSetpoint, rawSetpointIsSmoothed); | ||
#endif | ||
// -----calculate D component | ||
float pidSetpointDelta = currentPidSetpoint - pidRuntime.previousPidSetpoint[axis]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't this make a pretty significant change to the behavior of D_MIN?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
I re-arranged it a bit so that it will now work exactly as it did before.
The 'setpoint' value to Dmin will, as before, be the feedforward value calculated from the feedforward code. This will be true except when the axis is under Angle mode control, because setpoint then does not come direct from rates, but from the angle control code. Mostly those setpoint changes are slow, so Dmin would not likely be boosted much towards DMax, but for quick stick inputs where the quad was given a fast setpoint change, Dmin would rise as it would in acro.
src/main/flight/pid.c
Outdated
// these axes will already have stick based feedforward in the input to their angle setpoint | ||
// we can use the simple setpointDelta from Dterm to generate a pidF element to help control motor lag | ||
// this will not apply in Horizon mode; it is not needed because the acro normal setpoint feedforward is present | ||
pidSetpointDelta = pidSetpointDelta * pidRuntime.pidFrequency * pidRuntime.angleFeedforwardGain; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe create a new variable here instead of reusing pidSetpointDelta?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done :-)
src/main/flight/pid.c
Outdated
pidSetpointDelta = getFeedforwardDelta(axis); | ||
} | ||
#endif | ||
|
||
#ifdef USE_ABSOLUTE_CONTROL | ||
// include abs control correction in feedforward | ||
pidSetpointDelta += setpointCorrection - pidRuntime.oldSetpointCorrection[axis]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it hasn't been changed, but this part raises a warning flag for me. It looks like it assumes that setpointDelta is still a difference between setpoints, which it is not. I'm a bit to tired right now to do a through analysis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your comment, it's a good point.
In pid.c, pidSetpointDelta
is simply currentPidSetpoint - pidRuntime.previousPidSetpoint[axis]
.
As such it is the difference in setpoint per PID loop interval, and PID loop interval can vary.
I have checked that, in this PR, changing loootime does not affect the reported pidSetpointDelta
value. To be honest, I was concerned, but there is definitely no problem:
src/main/flight/pid.c
Outdated
const float feedforwardMaxRate = pidRuntime.feedforwardMaxRate[axis]; | ||
const float Kp = pidRuntime.pidCoefficient[axis].Kp; | ||
if (axis < FD_YAW && feedforwardMaxRate != 0.0f) { | ||
if (feedForward * currentPidSetpoint > 0.0f) { | ||
if (fabsf(currentPidSetpoint) <= feedforwardMaxRate) { | ||
feedForward = constrainf(feedForward, (-feedforwardMaxRate - currentPidSetpoint) * Kp, (feedforwardMaxRate - currentPidSetpoint) * Kp); | ||
} else { | ||
feedForward = 0; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this new or old functionality?
It looks a bit out of place compared to all other FF calculations goes pretty far to avoid using the pid rate setpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the original function, and hasn't been changed in a long time. I just moved it here from where it was. It works as it did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed this from pid.c and refactored it within the feedforward() calculation in rc.c. It ends up a bit simpler there, and doesn't need recalculating every pid loop.
This comment has been minimized.
This comment has been minimized.
64539d1
to
069828c
Compare
This comment has been minimized.
This comment has been minimized.
069828c
to
c3a978e
Compare
This comment has been minimized.
This comment has been minimized.
c3a978e
to
dda025c
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
dc7efcb
to
5143f93
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
setpoint noise gets through the simple delta
Do you want to test this code? Here you have an automated build: |
Closing since code now incorporated into #12605 |
This is a big refactoring of our Feedforward, Angle and Horizon code. It flies really well and fixes several bugs.
Apart from not having the unit test, the code is, I think, ready to merge. I am very confident about the changes and we need them tested more widely.
Fixes:
Main functional change:
Notes:
This PR inherits the earth referencing and input feedforward of the previous Angle PR's for 4.5. There is no change to these factors, although smoothing may be improved / more reliable.
On defaults, Angle mode has become much quicker to respond than before. It definitely requires expo to attenuate the speed of those responses. Hence, this PR removes the old angle-specific
angle_roll_expo
andangle_pitch_expo
settings; the user must use Acro Rates to modify Angle/Horizon stick responsiveness. When moving from Angle to Horizon, Angle to Acro, or between pitch and roll in Level Race mode, the stick 'feel' is more consistent.Pilots who previously flew older Angle code, with customised angle expo settings, may not like this. Please give this Rates method a good try, however. The old Angle expo settings can be emulated using our current Rates tools - it's easy adjust the curves, and see what is happening in the Rates panel; there's no need to go to the CLI to adjust angle_expo and get an uncertain outcome.
As before, Angle feedforward 'strength' is adjustable with the
angle_feedforward
factor. When set to zero, there is no angle feedforward, bringing the responsiveness back to the slow 'old way', with its overshoot and lag. Likewise, the earth referencing can also be disabled by setting it to zero, but then you'll get the wobbles after quick yaw inputs again.Detailed changes:
processRcCommand()
function, which handles incoming rc data and calculates setpoints. This will make it much easier to provide duplicate interpolation on setpoint, to remove duplicate glitches on P as well as feedforward (later PR, only benefits Rx like FrSky, ELRS doesn't send duplicates).angle_feedforward
strength factor is user-adjustable, as before. Default is 50, or 50% of the amount of feedforward that would apply in Acro, but that doesn't mean it is twice as weak in the Angle mode setting.Limitations and issues:
To Do:
Confirmed: