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

Initial cut on full quaternion IMU conversion #2894

Merged
merged 7 commits into from
Mar 14, 2018

Conversation

digitalentity
Copy link
Member

No description provided.

@adrianmiriuta
Copy link

@digitalentity
nice adoption !

why ?
QUATERNION_INIT_ZERO
why ?
quaternionMultiply

@digitalentity
Copy link
Member Author

digitalentity commented Mar 8, 2018

nice adoption !

@adrianmiriuta while it may look similar, this is not an adoption of your work, see my comment here betaflight/betaflight#5182 (comment)

quaternionMultiply

Read the code

@digitalentity
Copy link
Member Author

@HaukeRa we're finally making some use of your implementation. Quaternion PID controller will follow.

@adrianmiriuta
Copy link

@digitalentity

Read the code

I did

More accurate quaternion integration
t_fp_vector axis;
float angle;
} fpAxisAngle_t;

Copy link
Contributor

Choose a reason for hiding this comment

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

With LTO, it may be better to define these function in .c file and let gcc decide when inlining is worth it ...

Copy link
Member Author

Choose a reason for hiding this comment

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

LTO will take out optimizations that GCC could do when inlining the functions (i.e. optimizing out multiplications where one operand is known to be zero). Also, in all cases a function call will be more expensive than inlined code at expense of marginal increase of text segment.

Copy link
Contributor

Choose a reason for hiding this comment

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

Inlining is possible with LTO and (IIRC) actual function may be eliminated when all calls are inlined.
So it is not worse than static inline in '.h'

(Not that sure) Using static inline, static will force one instance per source file. Gcc can still decide that it is not worth inlining, producing multiple copies of identical code ...

Zero-member optimization is interesting question, i'll have to look at it ...

return sq(q->q0) + sq(q->q1) + sq(q->q2) + sq(q->q3);
}

static inline fpQuaternion_t * quaternionCrossProduct(fpQuaternion_t * result, const fpQuaternion_t * a, const fpQuaternion_t * b)
Copy link
Contributor

Choose a reason for hiding this comment

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

As @adrianmiriuta pointed out, this is quaternion multiplication (and vector cross product - cross product is defined only for 3D and 7D)

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 actually a Hamilton product, but for simplicity we can rename it to quaternionMultiply.

vectQuat.q2 = vect->V.Y;
vectQuat.q3 = vect->V.Z;

quaternionConjugate(&refConj, ref);
Copy link
Contributor

Choose a reason for hiding this comment

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

(this is slowest way to rotate vector ... https://geometrictools.com/Documentation/RotationIssues.pdf, p9)

Copy link
Member Author

@digitalentity digitalentity Mar 9, 2018

Choose a reason for hiding this comment

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

Agreed, I'll optimize it later. Are you suggesting converting to rotation matrix and applying it to a vector?

return result;
}

static inline t_fp_vector * rotateVectorByQuaternion(t_fp_vector * result, const t_fp_vector * vect, const fpQuaternion_t * ref)
Copy link
Contributor

Choose a reason for hiding this comment

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

(maybe quaternionRotateVector ? )

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea


// Floating point 3 vector.
typedef struct fp_vector {
float X;
Copy link
Contributor

Choose a reason for hiding this comment

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

(normally field names are lower-case)

Copy link
Member Author

Choose a reason for hiding this comment

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

This is how it is now, will refactor in a separate PR.

t_fp_vector vCoG = { .V = { -cos_approx(courseOverGround), sin_approx(courseOverGround), 0.0f } };

// Rotate Forward vector from BF to EF - will yield Heading vector in Earth frame
rotateVectorByQuaternionInv(&vHeadingEF, &vNorth, &orientation);
Copy link
Contributor

Choose a reason for hiding this comment

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

it's vForward, not north (value is the same)


// integral error scaled by Ki
vectorScale(&vTmp, &vErr, imuRuntimeConfig.dcm_ki_mag * dt);
vectorAdd(&vIntegralMag, &vIntegralMag, &vTmp);
Copy link
Contributor

Choose a reason for hiding this comment

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

There should probably be only one integral error accumulator.
Two integrators may drift in opposite direction - total error will be small, but error in each integrator large, possibly causing problem.

Also integrator is here to remove gyro drift. Not sure, but it's gain is more related to gyro properties (maximum drift) than to reference source (and its noise)...

ey = (az * rMat[2][0] - ax * rMat[2][2]);
ez = (ax * rMat[2][1] - ay * rMat[2][0]);
vectorNormalize(&vAcc, accBF);
vectorCrossProduct(&vErr, &vAcc, &vEstGravity);
Copy link
Contributor

Choose a reason for hiding this comment

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

Possible improvement:
Cross product will be small when vectors are close to antiparallel (initial convergence problem). Using dot product &vAcc, &vEstGravity, it is easy to detect large error and update error estimation accordingly (for example normalize vErr when dot(&vAcc, &vEstGravity) < 0

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'll try that

Copy link
Contributor

Choose a reason for hiding this comment

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

It will usually apply only on initial guess. But it is also metric to detect 'large deviation' that is done explicitly to reset orientation in iNav ...


// Calculate quaternion delta:
// Theta is a axis/angle rotation. Direction of a vector is axis, magnitude is angle/2.
// Proper quaternion from axis/angle involves computing sin/cos, but the formula becomes numerically unstable as Theta approaches zero.
Copy link
Contributor

Choose a reason for hiding this comment

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

The instability is strange - even sin_approx should be reasonably accurate for sin(x)/x
(BF approximation polynomials were designed with this in mind)

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is /x part. If our sin(x) was perfect it wouldn't matter, but since our sin(x) is an estimate, with x -> 0 result of sin_approx(x)/x diverges from actual value creating a problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

Tried it, sin_approx(x)/x correctly converges to 1 and is within 1e-6 absolute error ...
(skipped normalization, valid from -pi to pi )

function y = sin_approx(x)
  if(1)
    sinPolyCoef3 =-1.666568107e-1;
    sinPolyCoef5 = 8.312366210e-3;
    sinPolyCoef7 = -1.849218155e-4;
    sinPolyCoef9 = 0;
  else
    sinPolyCoef3 = -1.666665710e-1;                                          % Double: -1.666665709650470145824129400050267289858e-1
    sinPolyCoef5 = 8.333017292e-3;                                          % Double:  8.333017291562218127986291618761571373087e-3
    sinPolyCoef7 = -1.980661520e-4;                                          % Double: -1.980661520135080504411629636078917643846e-4
    sinPolyCoef9 = 2.600054768e-6;                                          % Double:  2.600054767890361277123254766503271638682e-6
  endif

  x(x > .5*pi) = pi - x(x > .5*pi);
  x(x < -.5*pi) = -pi - x(x < -.5*pi);

  x2 = x .* x;
  y= x + x .* x2 .* (sinPolyCoef3 + x2 .* (sinPolyCoef5 + x2 .* (sinPolyCoef7 + x2 .* sinPolyCoef9)));

Copy link
Member Author

@digitalentity digitalentity Mar 9, 2018

Choose a reason for hiding this comment

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

I prefer to be on a safe side here. Besides, I would still need to handle a case of no motion (gyroBF == 0)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I was just surprised. Approximation functions were designed to behave nicely around these points.

Actually, the approximation is faster and accurate enough. From my experiments even linear approximation is quite OK (when running IMU ar 300Hz and assuming 2k IMU as reference).

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not about update rate, it's about delta rotation magnitude. At 300Hz IMU if quad is rotating 720deg/s (2.4deg per update). Linear approximation of sin(x) has only 0.03% error at this rotation. After 300 iterations (1 second) it will yield 0.8 deg error.

At 100Hz IMU and 720 deg/s rotation error after 1s would be 2 deg already.

In reality there are also measurement errors, delta time jitter etc. All this adds up to rapidly growing error at slow update rates. If we can take out at least interpolation error - I'd say it's worth the effort

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm (slowly) looking into this ... IMO balance is necessary - there is no point using math many orders more accurate than input data / drift ...
At least it's useful to have both available ...

// Proper quaternion from axis/angle involves computing sin/cos, but the formula becomes numerically unstable as Theta approaches zero.
// For near-zero cases we use the first 3 terms of the Taylor series expansion for sin/cos. We check if fourth term is less than machine precision -
// then we can safely use the "low angle" approximated version without loss of accuracy.
if (thetaMagnitudeSq * thetaMagnitudeSq / 24.0f < 1e-6f) {
Copy link
Contributor

Choose a reason for hiding this comment

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

you can sqrt both sides ... -> thetaMagnitudeSq < sqrt(24*1e-6)

Copy link
Member Author

Choose a reason for hiding this comment

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

sqrtf if more expensive than multiplication

Copy link
Contributor

Choose a reason for hiding this comment

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

But square root of constant is free ...

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops 😄

@digitalentity
Copy link
Member Author

@ledvinap please have a look at 765495f

@ledvinap
Copy link
Contributor

@digitalentity : Some error estimate is here: betaflight/betaflight#5182 (comment)

@digitalentity
Copy link
Member Author

@ledvinap yep, that's one main reason why INAV is running IMU at gyro rate.

@digitalentity
Copy link
Member Author

Finally magnetic declination is applied to compass and RPY angles now accurately represent internal orientation quaternion.

@digitalentity
Copy link
Member Author

This should be now ready for testing

@ledvinap
Copy link
Contributor

@digitalentity : There is some update rate that is 'good enough', with no measurable influence on IMU estimate (or error well below noise)

@digitalentity
Copy link
Member Author

@ledvinap indeed it is, but proper rate is yet to be figured out.

@@ -277,6 +290,9 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
// Ignore magnetic inclination
vMag.z = 0.0f;

// Apply magnetic declination correction
rotationMatrixRotateVector(&vMag, &vMag, &magDeclinationRMat);
Copy link
Contributor

Choose a reason for hiding this comment

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

It may be easier (and actually cleaner) to use true north vector ( [cos(declination, sin(declination), 0] (?) ) as reference

Copy link
Member Author

Choose a reason for hiding this comment

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

@ledvinap you're a genius! It didn't occur to me that I can just figure our true North vector from declination 👍

@digitalentity
Copy link
Member Author

Attitude and heading seems correct. Merging

@digitalentity digitalentity merged commit e174e5a into development Mar 14, 2018
@digitalentity digitalentity deleted the de_quaternion_math branch March 14, 2018 14:20
@stronnag
Copy link
Collaborator

stronnag commented Mar 14, 2018 via email

nmaggioni pushed a commit to nmaggioni/inav that referenced this pull request May 18, 2018
* Initial cut on full quaternion/vector IMU conversion
* More accurate quaternion integration
* Refactor vector struct per @ledvinap suggection
* Implement rotation matrix from axis/angle; Refactor mag declination to have orientation correspond to RPY angles
* Use magnetic North vector as a reference
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants