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

MC: improve manual stick input handling #7940

Merged
merged 5 commits into from
Sep 19, 2017
Merged

MC: improve manual stick input handling #7940

merged 5 commits into from
Sep 19, 2017

Conversation

bkueng
Copy link
Member

@bkueng bkueng commented Sep 8, 2017

I was quite surprised when I noticed the following in manual mode:

  1. The maximum tilt angle (set via MPC_MAN_TILT_MAX) can easily be exceeded just by using a combination of roll & pitch (e.g. by moving the stick to the right upper corner). The formula for the tilt angle is acos(cos(roll) * cos(pitch)). By inserting the maximum angle, the limit is exceeded by roughly 15 degrees (with the default maximum tilt of 35 degrees). The full graph can be seen here: https://www.desmos.com/calculator/5zykbcnn93
  2. I would have expected the vehicle to move towards 45 degrees when I move the stick towards a corner (equal roll & pitch). However this is not the case. For a maximum tilt angle of 35 degrees, the direction is off by a bit more than 5 degrees. What's worse is that the direction depends on the tilt angle, meaning if you were to move the stick perfectly straight from the center towards a corner, you would fly a curve. The formula for the direction of maximum tilt is atan(sin(roll) / (sin(pitch) * cos(roll))). For roll == pitch, this is the graph by how much the direction is off from the diagonal: https://www.desmos.com/calculator/3poijoayhx. With a larger maximum tilt angle, this becomes noticeable as well.

I confirmed both points by looking at logs & doing simulations.

At first I only noticed 1. and implemented a solution for that, when I noticed the second point. They cannot be solved independently, so I propose a completely new approach to handle manual input. x and y input are used to control two (different) angles:

  • the tilt angle (given by sqrt(x*x + y*y)). This allows a simple limitation of the maximum tilt (it adds a small dead-zone in the corners of the stick range)
  • the direction of the tilt in the xy-plane, which defines the (eventual) direction of the motion

Benefits:

  • for roll/pitch only inputs (zero pitch/roll) the behavior is exactly the same as before
  • maximum tilt is correctly limited in every direction
  • the flight direction matches with the direction of the stick input
  • changes are linear
  • improved flight experience. The vehicle does what you would expect it to do. This is a bit subjective, and I suggest you test it yourself. I made a test branch manual_input_rework_testing. It makes it possible to switch between the old & new behavior in-flight by mapping the aux1 channel.

However there is one point to note: the new behavior changes the yaw as well, when roll & pitch are combined. This can be seen if you pick up an x-quad at 2 diagonal motors, and start rotating it around those 2 motors: the yaw changes a bit.
If a is the tilt angle and b is the angle of the direction of the tilt angle, then the yaw angle is given by yaw = atan((-2 * sin(b) * cos(b) * sin^2(a/2)) / (1 - 2 * cos^2(b) * sin^2(a/2))), and as a graph (x=tilt angle): https://www.desmos.com/calculator/y2mnebvl0j

Testing
I did extensive testing in HIL, including combinations of yawing and maximum tilt angles up to 90 degrees. I noticed that the yaw becomes very uncontrolled (overshoots and slow convergence) at high angles (around 80+). But this is a separate issue, I had the same behavior before this PR.
Then I tested on 2 different quads. So far, with a maximum tilt angle up to 55 degrees:

@bkueng
Copy link
Member Author

bkueng commented Sep 11, 2017

More flights at higher angles:

I noticed the same yawing issues as with HIL, especially with 75 degrees max tilt. This makes it pretty much unflyable. I created an issue in #7949.

@Stifael
Copy link
Contributor

Stifael commented Sep 17, 2017

The math looks ok, but in the computation you only compute q_tilt and ignoring q_twist(represents yaw).
an attitude qcan be represented as follow:
q = q_tilt * q_twist,
where q_tilt= [cos(0.5*a), sin(0.5*a)*nx, sin(0.5*a)*ny, sin(0.5*a)*nz]with a= tilt angle. In your case, you have q_sp_rpy = q_sp_tilt, where q_sp_tiltis the tilt attitude computed from tilt angle setpoint and rotation axis, but what we ultimately want to have is
q_sp_rpy = q_sp_tilt * q_sp_twist

q_sp_twistis simply just the current attitude twist q_current_twist because we compute the attitude setpoint from roll and pitch and therefore would like to have the same twist attitude without adding yaw to it.

Either we compute the twist attitude setpoint directly from yaw, or we compute the twist attitude setpoint from quaternions:

  • b= tilt angle between z_body_axis and [0,0,-1]
  • q_current_tilt = [cos(0.5*b), sin(0.5*b)*nx, sin(0.5*b)*ny, sin(0.5*b)*nz] , where [nx,ny,nz] is the rotation axis to get from [0,0,-1] to z_body_axis
  • q_current_twist = inverse(q_current_tilt) * q_current
  • q_sp_twist = q_current_twist

@bkueng
Copy link
Member Author

bkueng commented Sep 18, 2017

@Stifael Thanks for the review. You probably have overlooked _att_sp.yaw_body += euler_sp(2); (https://github.com/PX4/Firmware/pull/7940/files#diff-fc77c3ef569029d45764664c75d4b0c1R2156). It adds q_tilt to the yaw setpoint, doing the same as q_sp_rpy = q_sp_tilt * q_sp_twist just in Euler angles.

@Stifael
Copy link
Contributor

Stifael commented Sep 18, 2017

@bkueng, yes I missed that. However, it is also kind of an interesting order of implementation.
First one computes q_sp, then it gets transformed into euler where yaw is added, and then it gets again transformed into quaternion here https://github.com/PX4/Firmware/blob/96cb5760ae3180f65ea514da9a32e4ef7fb420c3/src/modules/mc_pos_control/mc_pos_control_main.cpp#L2197.
I guess it would be nicer to compute the entire quaternion first, and then compute roll, pitch and yaw setpoints, which primarily only should be used for logging. Anyway, looks good.

Stifael
Stifael previously approved these changes Sep 18, 2017
@bkueng
Copy link
Member Author

bkueng commented Sep 18, 2017

I agree and I first had it with quaternions only, but the next block (https://github.com/PX4/Firmware/blob/96cb5760ae3180f65ea514da9a32e4ef7fb420c3/src/modules/mc_pos_control/mc_pos_control_main.cpp#L2159:L2193) then again uses Eulers, so I kept Eulers as well to reduce the number of CPU cycles.

I rebased to master.

At 90 degrees the yaw is extremely unstable (tested with HIL), it
overshoots and only very slowly converges to the correct value.
This behavior is also noticable with lower angles, but not so extreme.
It definitely needs to be looked into further, but for now this makes it
safer.
@bkueng bkueng force-pushed the manual_input_rework branch from ee2580c to b2a6624 Compare September 18, 2017 11:36
Copy link
Member

@MaEtUgR MaEtUgR left a comment

Choose a reason for hiding this comment

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

Thanks a lot for thinking this through. I just have some implementation order comments.
The idea behind your implementation is very nice!

What I still don't get exactly is why it should influence yaw at all... I need to think this through again.

I would favor the most quaternion near implementation as possible (without unnecessary conversions) as well.

* ----------------------------------------
* This simplest thing to do is map the y & x inputs directly to roll and pitch, and scale according to the max
* tilt angle.
* But this has several issue:
Copy link
Member

Choose a reason for hiding this comment

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

typo? "issues"

*/
const float x = _manual.x * _params.man_tilt_max;
const float y = _manual.y * _params.man_tilt_max;
const float tilt_angle_sp = math::min(_params.man_tilt_max, sqrtf(x * x + y * y));
Copy link
Member

Choose a reason for hiding this comment

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

Nitpicking: You could calculate v and v_norm before this line and use v_norm instead of sqrtf(x * x + y * y).

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, this is much simpler now: 773df45

float v_norm = v.norm();

if (v_norm > FLT_EPSILON) {
v /= v_norm;
Copy link
Member

Choose a reason for hiding this comment

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

v.normalize() ?

v /= v_norm;
}

v *= tilt_angle_sp; // the norm of v defines the tilt angle
Copy link
Member

Choose a reason for hiding this comment

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

you're basically multiplying by v_norm here again which means you could only treat the case v_norm is bigger than _params.man_tilt_max to safe steps.

MaEtUgR
MaEtUgR previously approved these changes Sep 19, 2017
Copy link
Member

@MaEtUgR MaEtUgR left a comment

Choose a reason for hiding this comment

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

Nice work!

}

matrix::Quatf q_sp_rpy = matrix::AxisAnglef(v(0), v(1), 0.f);
// The axis angle can change the yaw as well (but only at higher tilt angles).
Copy link
Member

Choose a reason for hiding this comment

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

I think these comments are very useful. What made me finally understand why the yaw changes here in your spoken explanation is the hint "it's yaw in world frame". Maybe this hint could also help others reading the comments.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added that.

@MaEtUgR MaEtUgR merged commit b00c93a into master Sep 19, 2017
@MaEtUgR MaEtUgR deleted the manual_input_rework branch September 19, 2017 09:44
@MaEtUgR MaEtUgR mentioned this pull request Sep 23, 2017
6 tasks
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.

4 participants