@@ -169,6 +169,12 @@ void imuConfigure(void)
169169 imuRuntimeConfig .dcm_kp_mag = imuConfig ()-> dcm_kp_mag / 10000.0f ;
170170 imuRuntimeConfig .dcm_ki_mag = imuConfig ()-> dcm_ki_mag / 10000.0f ;
171171 imuRuntimeConfig .small_angle = imuConfig ()-> small_angle ;
172+ /* Precompute the Mahony anti-windup clamp. The PID loop reads this value
173+ * 1000× per second; the kP gains only change when the user saves settings,
174+ * so computing it here (called once per save) avoids one add, one multiply,
175+ * and one divide on every PID cycle. */
176+ imuRuntimeConfig .dcm_i_limit = DEGREES_TO_RADIANS (2.0f )
177+ * (imuRuntimeConfig .dcm_kp_acc + imuRuntimeConfig .dcm_kp_mag ) * 0.5f ;
172178}
173179
174180void imuInit (void )
@@ -364,7 +370,19 @@ static float imuCalculateMcCogAccWeight(void)
364370static void imuMahonyAHRSupdate (float dt , const fpVector3_t * gyroBF , const fpVector3_t * accBF , const fpVector3_t * magBF , const fpVector3_t * vCOG , const fpVector3_t * vCOGAcc , float accWScaler , float magWScaler )
365371{
366372 STATIC_FASTRAM fpVector3_t vGyroDriftEstimate = { 0 };
367- fpQuaternion_t prevOrientation = orientation ;
373+
374+ /* Opt 5: snapshot prevOrientation every 100 PID cycles instead of every cycle.
375+ * The snapshot is only used by the fault-recovery path in
376+ * imuCheckAndResetOrientationQuaternion(), which should never fire in normal
377+ * flight. Copying 4 floats 1000×/s just to support a near-zero-probability
378+ * reset path is wasteful; 100 ms staleness is a safe recovery point. */
379+ static uint8_t prevOrientationSnapshotCount = 0 ;
380+ static fpQuaternion_t prevOrientation = { .q0 = 1.0f }; // identity quaternion safe default
381+ if (++ prevOrientationSnapshotCount >= 100 ) {
382+ prevOrientationSnapshotCount = 0 ;
383+ prevOrientation = orientation ;
384+ }
385+
368386 fpVector3_t vRotation = * gyroBF ;
369387
370388 /* Calculate general spin rate (rad/s) */
@@ -497,11 +515,17 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
497515
498516 /* Step 2: Roll and pitch correction - use measured acceleration vector */
499517 if (accBF ) {
500- static const fpVector3_t vGravity = { .v = { 0.0f , 0.0f , 1.0f } };
501518 fpVector3_t vEstGravity , vAcc , vErr ;
502519
503- // Calculate estimated gravity vector in body frame
504- quaternionRotateVector (& vEstGravity , & vGravity , & orientation ); // EF -> BF
520+ /* Opt 1: imuComputeRotationMatrix() is called at the END of every
521+ * imuMahonyAHRSupdate() and keeps rMat in sync with orientation.
522+ * Rotating the constant unit-gravity vector {0,0,1} from EF to BF by
523+ * the current orientation quaternion yields exactly the third row of rMat
524+ * (rMat[2][0..2]). Reading those three floats replaces a quaternionRotateVector()
525+ * call that costs 2× quaternionMultiply = ~32 multiplies + 24 adds. */
526+ vEstGravity .x = rMat [2 ][0 ];
527+ vEstGravity .y = rMat [2 ][1 ];
528+ vEstGravity .z = rMat [2 ][2 ];
505529
506530 // Error is sum of cross product between estimated direction and measured direction of gravity
507531 vectorNormalize (& vAcc , accBF );
@@ -524,7 +548,9 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
524548 vectorAdd (& vRotation , & vRotation , & vErr );
525549 }
526550 // Anti wind-up
527- float i_limit = DEGREES_TO_RADIANS (2.0f ) * (imuRuntimeConfig .dcm_kp_acc + imuRuntimeConfig .dcm_kp_mag ) / 2.0f ;
551+ /* Opt 4: dcm_i_limit is computed once in imuConfigure() (called on settings save),
552+ * not recomputed each PID cycle. The kP gains do not change at runtime. */
553+ const float i_limit = imuRuntimeConfig .dcm_i_limit ;
528554 vGyroDriftEstimate .x = constrainf (vGyroDriftEstimate .x , - i_limit , i_limit );
529555 vGyroDriftEstimate .y = constrainf (vGyroDriftEstimate .y , - i_limit , i_limit );
530556 vGyroDriftEstimate .z = constrainf (vGyroDriftEstimate .z , - i_limit , i_limit );
@@ -547,7 +573,10 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
547573 // Proper quaternion from axis/angle involves computing sin/cos, but the formula becomes numerically unstable as Theta approaches zero.
548574 // 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 -
549575 // then we can safely use the "low angle" approximated version without loss of accuracy.
550- if (thetaMagnitudeSq < fast_fsqrtf (24.0f * 1e-6f )) {
576+ /* Opt 2: original condition was "thetaMagnitudeSq < sqrt(24e-6)".
577+ * Squaring both sides (both are non-negative) gives an equivalent
578+ * condition without a sqrt() call: thetaMagnitudeSq² < 24e-6. */
579+ if (thetaMagnitudeSq * thetaMagnitudeSq < 24.0e-6f ) {
551580 quaternionScale (& deltaQ , & deltaQ , 1.0f - thetaMagnitudeSq / 6.0f );
552581 deltaQ .q0 = 1.0f - thetaMagnitudeSq / 2.0f ;
553582 }
@@ -559,7 +588,20 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
559588
560589 // Calculate final orientation and renormalize
561590 quaternionMultiply (& orientation , & orientation , & deltaQ );
562- quaternionNormalize (& orientation , & orientation );
591+ /* Opt 3: first-order Newton renormalization avoids sqrt() and 4 divides.
592+ * At 1 kHz the quaternion norm drifts by < 1e-6 per step, so normSq = 1 + ε
593+ * with |ε| ≪ 1. The identity 1/sqrt(x) ≈ (3-x)/2 is accurate to O(ε²) ≈ 1e-12
594+ * — well within float precision. imuCheckAndResetOrientationQuaternion() below
595+ * catches any catastrophic norm deviation that this approximation cannot correct. */
596+ {
597+ const float normSq = orientation .q0 * orientation .q0 + orientation .q1 * orientation .q1
598+ + orientation .q2 * orientation .q2 + orientation .q3 * orientation .q3 ;
599+ const float scale = (3.0f - normSq ) * 0.5f ;
600+ orientation .q0 *= scale ;
601+ orientation .q1 *= scale ;
602+ orientation .q2 *= scale ;
603+ orientation .q3 *= scale ;
604+ }
563605 }
564606
565607 // Check for invalid quaternion and reset to previous known good one
0 commit comments