Skip to content
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

Mixer smoothing (Motor Output Mixer) (Thrust Linearization) #398

Merged
merged 56 commits into from
Jan 2, 2021

Conversation

tylercorleone
Copy link
Member

@tylercorleone tylercorleone commented Oct 30, 2020

This PR brings different changes to the way the controller's output (roll, pitch and yaw) is mixed with throttle to obtain a motor output.

The needs of roll and pitch can interfere with yaw's needs, controller's output interferes with throttle demands etc.
Here are proposed two alternatives to the LEGACY mixer, SMOOTH and 2PASS.

Both SMOOTH and 2PASS can use an option called "laziness" that enables a smarter "clipping prevention" strategy, that is called lazy because it adds only the minimum required amount of offset to remain in the [0, 1] for each "motor group" (that is a group of opposite motors that can actually work in conjunction in order to obtain a certain amount of roll/pitch) instead of applying the biggest needed amount. In this way we avoid to add/increase the total thrust when not needed, obtaining a better "throttle authority".

The scope of the SMOOTH mixer is just to avoid the harsh clipping strategy that the legacy mixer uses to keep the sum of the controller's output (roll + pitch + yaw) and throttle in the [0, 1] range.

These graphs show the total thrust and the torque applied by two opposite motors with a respective output difference of 0.2 varying throttle and motorMixRange (maximum controller's output range).

AirMode OFF

1 - LEGACY
2 - SMOOTH - laziness OFF
3 - SMOOTH - laziness ON

airmode_off

AirMode ON

1 - LEGACY
2 - SMOOTH - laziness OFF
3 - SMOOTH - laziness ON

airmode_on

Laziness assumes that the torque obtained given a difference of X between two motors is independent from the offset they are working to. That is, it requires thrust linearization (TL).
This PR brings a TL formula that speculates that the thrust/RPM strategy can be expressed as the composition of a quadratic and a linear curve and uses its mathematical inverse to compensate for that non-linearity.

Here a tool to play with to calculate the correct parameters to use for low and high RPM compensation (0.65 and 0.3 is a good midpoint): https://www.desmos.com/calculator/zal1gyqrcn

IMG_20201221_065858

In this example you first have to match the thrust response curve of your quad (there are a couple already provided taken from www.miniquadtestbench.com), and then find the correct values to make it linear.

The two pass mixer (2PASS) handles yaw output and roll/pitch output differently. The first needs RPM linearity, hence motorOutput linearity, although it's not exactly a linear relationship, as you can see from other Mini Quad Bench graphs, while the seconds need thrust linearity.

This is the motorOutput at different throttle levels, when roll+pitch sum has the same magnitude of yaw (50/50):

image

yaw output is linear, roll+pitch output is linear too (what you see here is motor output, so motor and props will make it linear)

Here the roll+pitch/yaw rate is 90/10:

image

same roll and pitch authority while preserving a little more throttle authority

Here the roll+pitch/yaw rate is 10/90:

image

again yaw linearity while also preserving throttle authority

Here is the comparison between LECACY and 2PASS with laziness ON, that is the preferred combination so far (50/50 roll+pitch/yaw rate):

image

Keep in mind that the 0 axis represent the separation between different motors.


// Predictive AirMode actually predicts the need of greater authority based on stick movements
float calculatePredictiveAirModeAuthorityMultiplier() {
float maxStickMovement = MAX(stickMovement[ROLL], MAX(stickMovement[PITCH], stickMovement[YAW])); // [0, 1], the max r/p/y stick movement
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using stick input with only pt1 filter will make the motor outputs more jerky?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value is the filtered rc command. So we add an additional pt1 filter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If im not mistaken the filtered RC command you are referring to is what is being used here. Maybe im wrong though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right. You are getting it from rcCommandf, which is already filtered before being converted to the floats. Is extra filtering really needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra filtering gives us two advantages. It delays it slightly and keeps the effect going once we have stopped moving our sticks for a bit of time.

@tylercorleone
Copy link
Member Author

Hi guys, I updated both graphs updating plots order and labeling (I think that "authority" explains better the concept than "torque %", but I leaved it in the axis legend).

Also I updated the graphs of the AirMode OFF because in the current implementation we don't have a selectable authority value for the middle throttle, so it scales between the values for zero throttle and full throttle.

Maybe it can be useful to introduce a configurable third value for an authority value, e.g. 30% throttle -> 100% authority.
I say that because I think that having full authority at "medium" throttle is more important than having it at full throttle, especially in a quad that doesn't use thrust linearization, where having less authority at 100% throttle acts as a TPA.

@tylercorleone
Copy link
Member Author

I wanna stress out the Motor Mixer 2.0 only changes the behavior of motors not directly interested in the main/principal quad's movement.

These are the same graphs of above where the two opposite motors don't have a constant respective output difference, but is the motorMixRange itself.
V2.0 is the same as the normal smooth motor mixer:

AirMode OFF:

airmode_off_mitorMix_same_motorMixRange

AirMode ON:

airmode_on_mitorMix_same_motorMixRange

@gretel
Copy link
Contributor

gretel commented Nov 2, 2020

this pr what is shown in https://www.youtube.com/watch?v=A_rQs3Uqkq0 ?

@borisbstyle
Copy link
Contributor

Ok I finally managed to do some mixer checks. I did not fly yet. But your mixer strategy to keep the thrust equal seems fine. It gives the full authority, but the throttle offset is selected better. Just like shown in your graphs.
However I was getting exactly same results with 2_0 airmode and without
Basically in here I was getting exact same behaviour:

        if (useAirMode2_0) {
            motorMix[i] = scaleRangef(throttle, 0.0f, 1.0f, authorityZeroThrottle * (motorMix[i] + ABS(motorMix[i])), authorityFullThrottle * (motorMix[i] - ABS(motorMix[i])));
        } else {
            motorMix[i] = scaleRangef(throttle, 0.0f, 1.0f, authorityZeroThrottle * (motorMix[i] + motorMixDelta), authorityFullThrottle * (motorMix[i] - motorMixDelta));
        }

I do think you are trying to change too much in this single PR. In order to really flight test this linear mixer behavior it may be better to get rid of all other stuff, that you introduced in this PR.
I think it may be good to only keep applyMixerClipAdjustment() part of code and don't make it too much complicated yet.

Perhaps just keep the authorityZeroThrottle and authorityFullThrottle to configure for now?

@gretel
Copy link
Contributor

gretel commented Nov 2, 2020

have to agree with @borisbstyle here in terms of this being too much for one pr.

@gretel
Copy link
Contributor

gretel commented Nov 2, 2020

btw betaflight/betaflight#10294

@tylercorleone
Copy link
Member Author

tylercorleone commented Nov 2, 2020

I agree too, basically I should leave the applyMixerClipAdjustment() and the thrust linearization (it is needed by the 2.0 thing, it's something I really care about and it integrates well and simplifies the battery compensation logic) and leave out the @Quick-Flash part for the moment (predictive AirMode and axis lock). I didn't understand "Basically in here I was getting exact same behaviour".
Also I would rename AirMode 2.0 in something else, since it applies to the clipping issues. Idk but I was thinking to "lazy mixer" 🤣, but I don't like it actually.
Any ideas?

@gretel
Copy link
Contributor

gretel commented Nov 2, 2020

@tylercorleone i would like to do set mixer_laziness = ON just by the name of it :)

@borisbstyle
Copy link
Contributor

borisbstyle commented Nov 2, 2020

I agree too, basically I should leave the applyMixerClipAdjustment() and the thrust linearization (it is needed by the 2.0 thing, it's something I really care about and it integrates well and simplifies the battery compensation logic) and leave out the @Quick-Flash part for the moment. I didn't understand "Basically in here I was getting exact same behaviour".
Also I would rename AirMode 2.0 in something else, since it applies to the clipping issues. Idk but I was thinking to "lazy mixer" 🤣, but I don't like it actually.
Any ideas?

Well I wrote a small piece of simulation software, where I can play with different scenarios from stick inputs, gyro data and also test separate mixer scenarios.
For mixer I introduced some basic test cases, where mixer is tested under several circumstances, like different throttle inputs, mixdeltas and old vs new mixer behaviour. I pretty much wanted to verify behavior you claim in your graphs.
In these simple scenarios I didn't see any difference between these 2 calculations.
motorMix[i] = scaleRangef(throttle, 0.0f, 1.0f, authorityZeroThrottle * (motorMix[i] + ABS(motorMix[i])), authorityFullThrottle * (motorMix[i] - ABS(motorMix[i])));
behaved equal to:
motorMix[i] = scaleRangef(throttle, 0.0f, 1.0f, authorityZeroThrottle * (motorMix[i] + motorMixDelta), authorityFullThrottle * (motorMix[i] - motorMixDelta));

I am not sure why? Maybe I did something wrong and it also got late, but I will double check. I used (1.0 factor) for zero and full throttle authority for my tests and I didn't enable predictive airmode.

I think for naming convention it might be good to first flight test it and see how it feels :D. Than perhaps relate it to some "feeling". I would expect and hope it gives more organic feeling on sharper stick inputs, but that is just how I think it might be.
Back in the days, when I came with Airmode it was, because it gave a lot more authority on low throttle, compared to traditional mixer, where authority was being capped up to 50%. It opened up new opportunities in the air ;)
Don't also forget, that more "lazy" mixer might also need retuning of PID's. I would expect, that PID's generally could be increased.

@borisbstyle
Copy link
Contributor

@tylercorleone i would like to do set mixer_laziness = ON just by the name of it :)

Yeah that is what users want to see. Enable something and fly :D or make it default if it's good.

Then eventually additional stuff can be implemented later on as enhancements.

@tylercorleone
Copy link
Member Author

tylercorleone commented Nov 2, 2020

@borisbstyle yes, the "lazy" mode works exactly as the normal mode if you look only at the motors with the highest offset, like I wrote in a comment above (identical graphs). The difference is that not all the motors will receive the same amount of offset because ABS(motorMix[i]) is not equal to motorMixDelta for all the motors.

There is a discussion on Discord, from the first tests it seems that it behaves as it did on my quad. Less nervous, more predictable, no bounces when on the ground, no "vacuum effect" when you get hit the wall etc.

@borisbstyle
Copy link
Contributor

btw betaflight/betaflight#10294

@borisbstyle yes, the "lazy" mode works exactly as the normal mode if you look only at the motors with the highest offset, like I wrote in a comment above (identical graphs). The difference is that not all the motors will receive the same amount of offset because ABS(motorMix[i]) is not equal to motorMixDelta for all the motors.

There is a discussion on Discord, from the first tests it seems that it behaves as it did on my quad. Less nervous, more predictable, no bounces when on the ground, no "vacuum effect" when you get hit the wall etc.

Ok. That makes sense. I said it was simple test. I was only testing single axis.

@@ -623,6 +648,14 @@ static void calculateThrottleAndCurrentMotorEndpoints(timeUs_t currentTimeUs) {
#define CRASH_FLIP_DEADBAND 20
#define CRASH_FLIP_STICK_MINF 0.15f

static float mapThrustToMotorOutput(float thrust)
{
float vbatCompFactor = currentControlRateProfile->vbat_comp_type != VBAT_COMP_TYPE_OFF ? calculateBatteryCompensationFactor() : 1.0f;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: move this calculation out

@Quick-Flash
Copy link
Member

After more testing, I'm really unsure of the usefulness of predictive airmode. Axis lock (or whatever we want to call it) does however seem useful.

@tylercorleone
Copy link
Member Author

After more testing, I'm really unsure of the usefulness of predictive airmode. Axis lock (or whatever we want to call it) does however seem useful.

Yes, with the lazy mixer maybe we don't need Predictive AirMode. I didn't tried the axis lock, but I think the suggestion of Borisbstyle was just to make the PR smaller and easier to test.

@@ -623,6 +635,12 @@ static void calculateThrottleAndCurrentMotorEndpoints(timeUs_t currentTimeUs) {
#define CRASH_FLIP_DEADBAND 20
#define CRASH_FLIP_STICK_MINF 0.15f

static float mapThrustToMotorOutput(float thrust, float vbatCompFactor)
{
float linearizedThrust = vbatCompFactor * ((1.0f - thrust_linearization_level) * thrust + thrust_linearization_level * SIGN(thrust) * vbatCompFactor * sqrtf(ABS(thrust)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the sign management is necessary. I did it because of the 3D mode, but actually I don't know if here we can have a negative thrust value. If not the formula could be cleaned up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i didn't notice this, but your completely correct. Thrust should never be able to have a negative sign.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Quick 🙂

src/main/flight/mixer.c Outdated Show resolved Hide resolved
@tylercorleone
Copy link
Member Author

heres how I would write this code.
if (!currentPidProfile->mixer_thrust_linearization_level) { // TPA is not applied when Thrust Linearization is enabled
// calculating the PID sum and TPA and SPA
// multiply these things to the pidData so that logs shows the pid data correctly
pidData[axis].P = pidData[axis].P * getThrottlePAttenuation() * setPointPAttenuation;
pidData[axis].I = temporaryIterm[axis] * getThrottleIAttenuation() * setPointIAttenuation;
pidData[axis].D = pidData[axis].D * getThrottleDAttenuation() * setPointDAttenuation;
} else {
// calculating the PID sum and TPA and SPA
// multiply these things to the pidData so that logs shows the pid data correctly
pidData[axis].P = pidData[axis].P * setPointPAttenuation;
pidData[axis].I = temporaryIterm[axis] * setPointIAttenuation;
pidData[axis].D = pidData[axis].D * setPointDAttenuation;
}

done

@nerdCopter nerdCopter added the enhancement Minor enhancement to code label Dec 28, 2020
Copy link
Member

@Quick-Flash Quick-Flash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its time, defaults for thrust linear may still need to be played with though

@Quick-Flash Quick-Flash merged commit 36857ce into emuflight:master Jan 2, 2021
raemin pushed a commit to raemin/EmuFlight that referenced this pull request May 9, 2021
* feat(mixer_smoothing): smoothing out the motor output calculation removing the harsh motorMix constraining. Also introduced thrust linearization and 'AirMode 2.0'

* feat: QuickFlash's predictive AirMode, porting from another impl (emuflight#3)

* min/max fix, thanks to borisbstyle

* predictiveAirMode activation logic refactor

* applyAirMode become applyMixerClipAdjustment

* refactoring and simplification

* rolling back some useless changes

* cleared out thrust linearization formula and fixed the linear throttle (there was a division by 100 too much)

* removed unnecessary sign management

* removed unnecessary change

* fix

* fixes, removed throttle linearization for the moment

* moved PID scaling

* removed DEBUG_WRONG_PIDSUM_SIGN

* thrust linearization formula changed (actual matematical inverse) and reintroduced thruttle linearization

* removed duplicated code

* mixer_impl

* mixing yaw separately

* fixes and temporarily put mixerImpl on OSD

* mix & roll/pitch mix rate

* norm fix

* wip

* fixes

* final, maybe...

* cleanup

* unlinear throttle fix

* removing avg controller's caused thrust/motor

* backup

* two pass mixer. Version 1.0.0

* two level thrust linearization

* code cleaning

* TL from idle level and TPA disabled when TL is enabled

* fix SPA and motorOutputIdleLevel

* changed desmos link for TL graphs

* fix: applying AirMode level (so AirMode OFF) given throttleMotor, so that with boht linear_throttle ON and OFF the transition is the same

* removed mixer impl from OSD and another code cleaning

* mixerInitProfile called into pidInitConfig

* rewording

* fix: fixed thrust-linearization disabling. Code cleaning

* 65 default value for linear_thrust_low_output

* little code simplification

* 3d mode fix and cleanup

Co-authored-by: nerdCopter <56646290+nerdCopter@users.noreply.github.com>
Co-authored-by: Quick-Flash <46289813+Quick-Flash@users.noreply.github.com>
@nerdCopter nerdCopter changed the title Mixer smoothing Mixer smoothing (Motor Output Mixer) (Thrust Linearization) Feb 14, 2024
@nerdCopter
Copy link
Member

@tylercorleone , almost certain this PR broke 3D-mode. Can you investigate?
what is known is that motors spin the correct direction, but wrong motors push when commanded (roll for certain, maybe pitch, maybe yaw)

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Minor enhancement to code in-testing-needs-testing needs testing before merge consideration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants