Skip to content

Commit 2ce3f0a

Browse files
sensei-hackerclaude
andcommitted
imu: optimize imuMahonyAHRSupdate() hot path (5 micro-opts)
Five cycle-saving changes to imuMahonyAHRSupdate(), which accounts for ~205 µs (30%) of the PID loop on RP2350. Each change is safe on all targets; the gains are proportionally smaller on F7/H7 where the function already lives in ITCM RAM. 1. Replace quaternionRotateVector({0,0,1}) with rMat[2][*] reads. imuComputeRotationMatrix() is called at the end of every invocation and keeps rMat in sync with orientation. Rotating the constant gravity vector EF→BF yields exactly the third row of rMat, so three float loads replace ~56 floating-point multiply/add operations. 2. Eliminate sqrt() from the Taylor-series threshold. Original: thetaMagnitudeSq < sqrt(24e-6). Squaring both sides (both non-negative) gives the equivalent condition thetaMagnitudeSq² < 24e-6 with no sqrt call. 3. First-order Newton quaternion renormalization replaces quaternionNormalize() (sqrt + 4 divides, ~35 cycles) with scale = (3 - normSq) * 0.5 (~14 cycles). At 1 kHz the per-step drift |ε| < 1e-6, making the O(ε²) error < 1e-12. imuCheckAndResetOrientationQuaternion() remains as the catastrophic-failure safety net. 4. Precompute the anti-windup i_limit in imuConfigure() (called only on settings save). The hot path now reads a single float instead of performing an add, a multiply, and a divide every PID cycle. Adds dcm_i_limit to imuRuntimeConfig_t and imuConfigure(). 5. Reduce prevOrientation snapshot frequency from every PID cycle to every 100 cycles (~100 ms at 1 kHz). The snapshot is only used by the fault-recovery path which should never fire in normal flight; 16 bytes of unnecessary SRAM write every ms is not justified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c428cc9 commit 2ce3f0a

2 files changed

Lines changed: 54 additions & 7 deletions

File tree

src/main/flight/imu.c

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ void imuConfigure(void)
171171
imuRuntimeConfig.dcm_kp_mag = imuConfig()->dcm_kp_mag / 10000.0f;
172172
imuRuntimeConfig.dcm_ki_mag = imuConfig()->dcm_ki_mag / 10000.0f;
173173
imuRuntimeConfig.small_angle = imuConfig()->small_angle;
174+
/* Precompute the Mahony anti-windup clamp. The PID loop reads this value
175+
* 1000× per second; the kP gains only change when the user saves settings,
176+
* so computing it here (called once per save) avoids one add, one multiply,
177+
* and one divide on every PID cycle. */
178+
imuRuntimeConfig.dcm_i_limit = DEGREES_TO_RADIANS(2.0f)
179+
* (imuRuntimeConfig.dcm_kp_acc + imuRuntimeConfig.dcm_kp_mag) * 0.5f;
174180
}
175181

176182
void imuInit(void)
@@ -368,7 +374,19 @@ static float imuCalculateMcCogAccWeight(void)
368374
static 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)
369375
{
370376
STATIC_FASTRAM fpVector3_t vGyroDriftEstimate = { 0 };
371-
fpQuaternion_t prevOrientation = orientation;
377+
378+
/* Opt 5: snapshot prevOrientation every 100 PID cycles instead of every cycle.
379+
* The snapshot is only used by the fault-recovery path in
380+
* imuCheckAndResetOrientationQuaternion(), which should never fire in normal
381+
* flight. Copying 4 floats 1000×/s just to support a near-zero-probability
382+
* reset path is wasteful; 100 ms staleness is a safe recovery point. */
383+
static uint8_t prevOrientationSnapshotCount = 0;
384+
static fpQuaternion_t prevOrientation = { .q0 = 1.0f }; // identity quaternion safe default
385+
if (++prevOrientationSnapshotCount >= 100) {
386+
prevOrientationSnapshotCount = 0;
387+
prevOrientation = orientation;
388+
}
389+
372390
fpVector3_t vRotation = *gyroBF;
373391

374392
/* Calculate general spin rate (rad/s) */
@@ -501,11 +519,17 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
501519

502520
/* Step 2: Roll and pitch correction - use measured acceleration vector */
503521
if (accBF) {
504-
static const fpVector3_t vGravity = { .v = { 0.0f, 0.0f, 1.0f } };
505522
fpVector3_t vEstGravity, vAcc, vErr;
506523

507-
// Calculate estimated gravity vector in body frame
508-
quaternionRotateVector(&vEstGravity, &vGravity, &orientation); // EF -> BF
524+
/* Opt 1: imuComputeRotationMatrix() is called at the END of every
525+
* imuMahonyAHRSupdate() and keeps rMat in sync with orientation.
526+
* Rotating the constant unit-gravity vector {0,0,1} from EF to BF by
527+
* the current orientation quaternion yields exactly the third row of rMat
528+
* (rMat[2][0..2]). Reading those three floats replaces a quaternionRotateVector()
529+
* call that costs 2× quaternionMultiply = ~32 multiplies + 24 adds. */
530+
vEstGravity.x = rMat[2][0];
531+
vEstGravity.y = rMat[2][1];
532+
vEstGravity.z = rMat[2][2];
509533

510534
// Error is sum of cross product between estimated direction and measured direction of gravity
511535
vectorNormalize(&vAcc, accBF);
@@ -528,7 +552,9 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
528552
vectorAdd(&vRotation, &vRotation, &vErr);
529553
}
530554
// Anti wind-up
531-
float i_limit = DEGREES_TO_RADIANS(2.0f) * (imuRuntimeConfig.dcm_kp_acc + imuRuntimeConfig.dcm_kp_mag) / 2.0f;
555+
/* Opt 4: dcm_i_limit is computed once in imuConfigure() (called on settings save),
556+
* not recomputed each PID cycle. The kP gains do not change at runtime. */
557+
const float i_limit = imuRuntimeConfig.dcm_i_limit;
532558
vGyroDriftEstimate.x = constrainf(vGyroDriftEstimate.x, -i_limit, i_limit);
533559
vGyroDriftEstimate.y = constrainf(vGyroDriftEstimate.y, -i_limit, i_limit);
534560
vGyroDriftEstimate.z = constrainf(vGyroDriftEstimate.z, -i_limit, i_limit);
@@ -551,7 +577,10 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
551577
// Proper quaternion from axis/angle involves computing sin/cos, but the formula becomes numerically unstable as Theta approaches zero.
552578
// 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 -
553579
// then we can safely use the "low angle" approximated version without loss of accuracy.
554-
if (thetaMagnitudeSq < fast_fsqrtf(24.0f * 1e-6f)) {
580+
/* Opt 2: original condition was "thetaMagnitudeSq < sqrt(24e-6)".
581+
* Squaring both sides (both are non-negative) gives an equivalent
582+
* condition without a sqrt() call: thetaMagnitudeSq² < 24e-6. */
583+
if (thetaMagnitudeSq * thetaMagnitudeSq < 24.0e-6f) {
555584
quaternionScale(&deltaQ, &deltaQ, 1.0f - thetaMagnitudeSq / 6.0f);
556585
deltaQ.q0 = 1.0f - thetaMagnitudeSq / 2.0f;
557586
}
@@ -563,7 +592,20 @@ static void imuMahonyAHRSupdate(float dt, const fpVector3_t * gyroBF, const fpVe
563592

564593
// Calculate final orientation and renormalize
565594
quaternionMultiply(&orientation, &orientation, &deltaQ);
566-
quaternionNormalize(&orientation, &orientation);
595+
/* Opt 3: first-order Newton renormalization avoids sqrt() and 4 divides.
596+
* At 1 kHz the quaternion norm drifts by < 1e-6 per step, so normSq = 1 + ε
597+
* with |ε| ≪ 1. The identity 1/sqrt(x) ≈ (3-x)/2 is accurate to O(ε²) ≈ 1e-12
598+
* — well within float precision. imuCheckAndResetOrientationQuaternion() below
599+
* catches any catastrophic norm deviation that this approximation cannot correct. */
600+
{
601+
const float normSq = orientation.q0 * orientation.q0 + orientation.q1 * orientation.q1
602+
+ orientation.q2 * orientation.q2 + orientation.q3 * orientation.q3;
603+
const float scale = (3.0f - normSq) * 0.5f;
604+
orientation.q0 *= scale;
605+
orientation.q1 *= scale;
606+
orientation.q2 *= scale;
607+
orientation.q3 *= scale;
608+
}
567609
}
568610

569611
// Check for invalid quaternion and reset to previous known good one

src/main/flight/imu.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ typedef struct imuRuntimeConfig_s {
6565
float dcm_ki_acc;
6666
float dcm_kp_mag;
6767
float dcm_ki_mag;
68+
/* Precomputed anti-windup limit for imuMahonyAHRSupdate(): equals
69+
* DEGREES_TO_RADIANS(2) * (dcm_kp_acc + dcm_kp_mag) / 2.
70+
* Updated once by imuConfigure() whenever settings are saved, so the
71+
* hot PID path reads a single float instead of doing arithmetic. */
72+
float dcm_i_limit;
6873
uint8_t small_angle;
6974
} imuRuntimeConfig_t;
7075

0 commit comments

Comments
 (0)