Skip to content

fix: correct ICM-20689/MPU6500 SPI initialisation sequence#1289

Open
nerdCopter wants to merge 4 commits into
emuflight:masterfrom
nerdCopter:fix/icm20689-init
Open

fix: correct ICM-20689/MPU6500 SPI initialisation sequence#1289
nerdCopter wants to merge 4 commits into
emuflight:masterfrom
nerdCopter:fix/icm20689-init

Conversation

@nerdCopter

@nerdCopter nerdCopter commented Jun 26, 2026

Copy link
Copy Markdown
Member

AI Generated pull-request

Ports BF PR #11584 (merged 2022). Fixes initialisation bugs in the ICM-20689
gyro family that could leave the device in I2C mode or corrupt reserved
registers, causing permanent yaw-axis failure across power cycles.

Credit to @SteveCEvans for the original investigation and fix in Betaflight.
Thanks to @nikybiasion and @wrych who noted EmuFlight was affected in the BF PR comments.

Changes

accgyro_spi_icm20689.c

  • icm20689SpiDetect: write I2C_IF_DIS and SIGNAL_PATH_RESET only after
    WHO_AM_I confirms device identity (datasheet section 6.1 requirement — must
    be set immediately after reset to prevent accidental I2C switch)
  • icm20689GyroInit: remove redundant second device reset and signal-path
    write (already performed in detect); replace 15ms inter-register delays and
    100ms post-divider delay with ICM20689_CLKSEL_SETTLE_US (120µs) after
    clock source change only
  • Add named defines for all register bits and timing constants; move
    ICM20689_BIT_RESET from public header to file-private .c

accgyro_mpu.c

  • Add GYRO_SPI_STARTUP_MS (100ms) guard in detectSPISensorsAndUpdateDetectionResult
    so the SPI bus has settled before any device is probed

accgyro_spi_mpu6500.c

  • Change mpu6500SpiInit clock from SPI_CLOCK_FAST (~20MHz) to
    SPI_CLOCK_INITIALIZATION — the fast clock during detection could cause
    erroneous register accesses on MPU6500-family devices

Test plan

  • Build passes: SPEEDYBEEF7, YUPIF7, IFLIGHT_F722_TWING, IFLIGHT_H7_TWING, MATEKF405MINI, EXF722DUAL, FURYF7, BEEROTORF4 (ICM-20689-only targets)
  • Hardware verified on an ICM-20689 board — request testing from owners of:
    • SPEEDYBEEF7 (SpeedyBee F7 — ICM-20689 only)
    • YUPIF7 (YupiF7 — ICM-20689 only)
    • KAKUTEF7 / KAKUTEF7MINI / KAKUTEF7HDV (Kakute F7 v1.3 ships with ICM-20689)
    • IFLIGHT_F722_TWING / IFLIGHT_H7_TWING (iFlight Twig — ICM-20689 only)

Summary by CodeRabbit

  • Bug Fixes
    • Improved startup handling for SPI-based gyroscope detection to avoid early bus access issues.
    • Refined initialization timing for supported sensors, which should make device startup more reliable.
    • Updated SPI setup timing for one sensor model to better match initialization requirements.

Port of BF PR #11584. Fixes several initialisation bugs affecting the
ICM-20689 and related gyros that could corrupt reserved registers and
leave I2C mode active:

accgyro_spi_icm20689.c:
- Add named register defines for all magic numbers (ICM20689_BIT_RESET,
  ICM20689_I2C_IF_DIS, ICM20689_ACCEL_RST, ICM20689_TEMP_RST,
  ICM20689_INT_ANYRD_2CLEAR, ICM20689_CLKSEL_SETTLE_US,
  ICM20689_RESET_DELAY_MS, ICM20689_PATH_RESET_DELAY_MS)
- icm20689SpiDetect: write I2C_IF_DIS and SIGNAL_PATH_RESET only after
  WHO_AM_I confirms device identity (datasheet s6.1 requirement)
- icm20689GyroInit: remove redundant second reset and signal-path write
  (already done in detect); replace 15ms inter-register delays and
  100ms post-sample-rate delay with ICM20689_CLKSEL_SETTLE_US (120us)
  after clock source change; replace magic literals with named defines

accgyro_spi_icm20689.h:
- Remove ICM20689_BIT_RESET (now file-private in .c)

accgyro_mpu.c:
- Add GYRO_SPI_STARTUP_MS (100ms) guard before SPI detect loop so the
  bus has settled before any device is probed

accgyro_spi_mpu6500.c:
- Change mpu6500SpiInit clock from SPI_CLOCK_FAST (~20MHz) to
  SPI_CLOCK_INITIALIZATION to prevent erroneous register accesses
  during detection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0820f6a-6588-4a31-9d2e-63fd93eff5b3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

SPI gyro detection now waits before probing, mpu6500SpiInit uses the initialization SPI clock, and the ICM20689 driver replaces hardcoded reset values with named timing constants while splitting reset work between detection and gyro initialization.

Changes

SPI gyro startup timing

Layer / File(s) Summary
Startup delay and MPU6500 init clock
src/main/drivers/accgyro/accgyro_mpu.c, src/main/drivers/accgyro/accgyro_spi_mpu6500.c
detectSPISensorsAndUpdateDetectionResult waits 100 ms before SPI probing, and mpu6500SpiInit uses SPI_CLOCK_INITIALIZATION during setup.
ICM20689 detect reset timing
src/main/drivers/accgyro/accgyro_spi_icm20689.c, src/main/drivers/accgyro/accgyro_spi_icm20689.h
ICM20689-specific reset and path-reset constants are added, the probe reset delay uses ICM20689_RESET_DELAY_MS, and successful detection disables the primary I2C interface and resets accel/temp paths before waiting ICM20689_PATH_RESET_DELAY_MS.
ICM20689 gyro init sequencing
src/main/drivers/accgyro/accgyro_spi_icm20689.c
icm20689GyroInit now assumes detection handled the earlier reset work, sets SPI_CLOCK_SLOW, programs PWR_MGMT_1 with INV_CLK_PLL, waits ICM20689_CLKSEL_SETTLE_US, and then writes the gyro, accel, DLPF, sample-rate, and interrupt-pin configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • emuflight/EmuFlight#1118 — Also changes detectSPISensorsAndUpdateDetectionResult() in src/main/drivers/accgyro/accgyro_mpu.c.
  • emuflight/EmuFlight#1126 — Also updates the ICM20689 SPI detection and initialization path in src/main/drivers/accgyro/accgyro_spi_icm20689.c.
  • emuflight/EmuFlight#1145 — Also modifies detectSPISensorsAndUpdateDetectionResult() and SPI bus handling in the same startup flow.

Suggested labels

help wanted

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the SPI initialization fix for the ICM-20689/MPU6500 family.
Description check ✅ Passed The description includes the main summary, change breakdown, and test plan, with only template boilerplate missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

Requesting review of this ICM-20689 init backport from BF PR #11584. Key areas worth checking:

  1. SPI clock speed for new detect-tail writes (accgyro_spi_icm20689.c) — the new I2C_IF_DIS and SIGNAL_PATH_RESET writes happen after spiSetDivisor(SPI_CLOCK_STANDARD). On F4 (168MHz), SPI_CLOCK_STANDARD = 10.5MHz, above the ICM-20689 register write spec of 8MHz. BF 4.5-m uses spiCalculateDivider(ICM20689_MAX_SPI_CLK_HZ) = exactly 8MHz for these writes. EmuFlight uses the older enum-based SPI API with no Hz-level control.
  2. GyroInit SPI clock assumption (accgyro_spi_icm20689.c) — removed the SPI_CLOCK_INITIALIZATION set at GyroInit entry; now implicitly relies on detect having left the bus at SPI_CLOCK_STANDARD. BF 4.5-m explicitly sets spiSetClkDivisor(ICM20689_MAX_SPI_CLK_HZ) at GyroInit entry.
  3. GYRO_SPI_STARTUP_MS semantics (accgyro_mpu.c) — while (millis() < 100) is an absolute-boot-time guard, not a fixed delay. Comment says "allow 100ms" but the guard is a no-op if called after t=100ms from boot. Intentional copy of BF 4.5-m behaviour.
  4. mpu6500SpiInit clock changeSPI_CLOCK_FASTSPI_CLOCK_INITIALIZATION for detect; all MPU6500-family targets affected including ICM42688P path through USE_GYRO_SPI_MPU6500.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate relevant files =="
fd -i 'accgyro_spi_icm20689.c|accgyro_mpu.c|accgyro_spi_mpu6500.c|bus_spi|spi.*\.h|spi.*\.c'

echo
echo "== Outline accgyro_spi_icm20689.c =="
ICM_FILE=$(fd -i '^accgyro_spi_icm20689\.c$' | head -n1)
ast-grep outline "$ICM_FILE" --view expanded || true

echo
echo "== Relevant slice: accgyro_spi_icm20689.c =="
sed -n '1,260p' "$ICM_FILE"

echo
echo "== Outline accgyro_mpu.c =="
MPU_FILE=$(fd -i '^accgyro_mpu\.c$' | head -n1)
ast-grep outline "$MPU_FILE" --view expanded || true

echo
echo "== Relevant slice: accgyro_mpu.c =="
sed -n '1,260p' "$MPU_FILE"

echo
echo "== Outline accgyro_spi_mpu6500.c =="
MPU6500_FILE=$(fd -i '^accgyro_spi_mpu6500\.c$' | head -n1)
ast-grep outline "$MPU6500_FILE" --view expanded || true

echo
echo "== Relevant slice: accgyro_spi_mpu6500.c =="
sed -n '1,220p' "$MPU6500_FILE"

echo
echo "== Search for SPI clock enum/defines and divisor calculations =="
rg -n -C3 'SPI_CLOCK_(INITIALIZATION|STANDARD|FAST)|spiSetDivisor|spiCalculateDivider|spiSetClkDivisor|SPI_CLOCK_' .

echo
echo "== Search for millis guard and GYRO_SPI_STARTUP_MS =="
rg -n -C3 'GYRO_SPI_STARTUP_MS|millis\s*\(\)\s*<' "$MPU_FILE"

echo
echo "== Search for USE_GYRO_SPI_MPU6500 and ICM42688P relationships =="
rg -n -C4 'USE_GYRO_SPI_MPU6500|ICM42688P|accgyro_spi_mpu6500' .

Length of output: 167352


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ICM=src/main/drivers/accgyro/accgyro_spi_icm20689.c
MPU=src/main/drivers/accgyro/accgyro_mpu.c
MPU6500=src/main/drivers/accgyro/accgyro_spi_mpu6500.c

echo "== SPI clock definitions =="
rg -n -C4 'SPI_CLOCK_INITIALIZATION|SPI_CLOCK_STANDARD|SPI_CLOCK_FAST|typedef enum.*SPI_CLOCK|enum.*SPI_CLOCK' src/main

echo
echo "== spiSetDivisor / clock helper declarations =="
rg -n -C3 'spiSetDivisor\s*\(|spiCalculateDivider\s*\(|spiSetClkDivisor\s*\(' src/main

echo
echo "== accgyro_spi_icm20689.c matches =="
rg -n -C6 'icm20689SpiDetect|icm20689GyroInit|spiSetDivisor|I2C_IF_DIS|SIGNAL_PATH_RESET|WHO_AM_I|delayMicroseconds|delay\(' "$ICM"

echo
echo "== accgyro_spi_icm20689.c full relevant range =="
sed -n '1,240p' "$ICM"

echo
echo "== accgyro_mpu.c matches =="
rg -n -C6 'GYRO_SPI_STARTUP_MS|detectSPISensorsAndUpdateDetectionResult|millis\s*\(|mpu6500SpiDetect|icm426xxSpiDetect|icm20689SpiDetect' "$MPU"

echo
echo "== accgyro_mpu.c focused range =="
sed -n '320,530p' "$MPU"

echo
echo "== accgyro_spi_mpu6500.c matches =="
rg -n -C6 'mpu6500SpiInit|SPI_CLOCK_|WHO_AM_I|ICM42688P_WHO_AM_I_CONST|spiSetDivisor|busReadRegisterBuffer|spiReadRegMsk' "$MPU6500"

echo
echo "== accgyro_spi_mpu6500.c focused range =="
sed -n '1,140p' "$MPU6500"

Length of output: 151443



@nerdCopter I reviewed the four areas you called out. Short version: I’d flag 1 and 2, not 3 or 4.

  1. accgyro_spi_icm20689.c detect-tail write speed — valid issue

    • In src/main/drivers/bus_spi.h, Line 55, SPI_CLOCK_STANDARD is 10.5 MHz on STM32F4.
    • In src/main/drivers/accgyro/accgyro_spi_icm20689.c, Line 116, the code switches to SPI_CLOCK_STANDARD before writing MPU_RA_USER_CTRL and MPU_RA_SIGNAL_PATH_RESET on Lines 119 and 122.
    • So on F4 those writes do exceed the 8 MHz write limit you mentioned.

    One useful detail: EmuFlight does already have the plumbing for an exact divider via spiCalculateDivider() (src/main/drivers/bus_spi.h, Line 135), so this doesn’t have to stay enum-limited.

  2. icm20689GyroInit write-speed assumption — also valid

    • In src/main/drivers/accgyro/accgyro_spi_icm20689.c, icm20689GyroInit() writes config registers on Lines 149–160.
    • It only calls spiSetDivisor(..., SPI_CLOCK_STANDARD) at Line 163, i.e. after those writes.
    • That means init currently relies on detect having left the bus at a safe speed. It probably works in the common flow, but it’s an implicit cross-function contract and more brittle than BF’s explicit setup.
  3. GYRO_SPI_STARTUP_MS semantics — no bug, just misleading wording

    • src/main/drivers/accgyro/accgyro_mpu.c, Line 375 is while (millis() < GYRO_SPI_STARTUP_MS);
    • So yes, that is an absolute boot-time guard, not a fixed delay from the call site.
    • Given your note that this is an intentional BF 4.5-m copy, I wouldn’t flag the logic itself; I’d only suggest tightening the comment to reflect the real behavior.
  4. mpu6500SpiInit detect clock change — looks correct, and broader than it first appears

    • src/main/drivers/accgyro/accgyro_spi_mpu6500.c, Line 48 now uses SPI_CLOCK_INITIALIZATION for detect.
    • That probe still recognizes ICM42688P_WHO_AM_I_CONST on Lines 75–76.
    • In src/main/drivers/accgyro/accgyro_mpu.c, the USE_GYRO_SPI_MPU6500 probe runs before the dedicated USE_GYRO_SPI_ICM42688P probe (Lines 391–403 vs. Lines 486–499).
    • So your read is right: this lower-speed detect path also helps boards that rely on the MPU6500-family probe to identify an ICM42688P.

So if I were reducing this to actionable review feedback, I’d leave:

  • one required fix for the ICM20689 post-detect / init write clock handling, and
  • one non-blocking clarification for the GYRO_SPI_STARTUP_MS comment.

[review]


✏️ Learnings added
Learnt from: nerdCopter
URL: https://github.com/emuflight/EmuFlight/pull/1289

Timestamp: 2026-06-26T14:40:55.225Z
Learning: In `src/main/drivers/accgyro/accgyro_mpu.c`, `GYRO_SPI_STARTUP_MS` is intentionally implemented as an absolute boot-time guard using `while (millis() < GYRO_SPI_STARTUP_MS);`, matching Betaflight 4.5-m behavior, rather than as a fixed delay from the call site.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Three targeted fixes following code review:

1. icm20689SpiDetect: write I2C_IF_DIS and SIGNAL_PATH_RESET at
   SPI_CLOCK_SLOW (within ICM-20689 8MHz register write spec) rather
   than SPI_CLOCK_STANDARD (~10.5MHz on F4/~9MHz on H7, both over
   spec). Restore SPI_CLOCK_STANDARD explicitly before return.

2. accgyro_mpu.c GYRO_SPI_STARTUP_MS: add BF 4.5-m comment explaining
   this is a boot-time gate done once here, not a per-driver delay.

3. icm20689GyroInit: set SPI_CLOCK_SLOW at entry, matching BF 4.5-m
   intent of running init register writes at a safe clock speed.
   SPI_CLOCK_STANDARD restore at exit is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

This PR now has a second commit (cfa2e66197) addressing three issues flagged in review. Before accepting these as correct, I want your genuine technical analysis — not agreement for the sake of it. Please push back where you see problems.

What was changed in the second commit

1. icm20689SpiDetect() — SPI clock for post-identity writes
The detect tail now uses SPI_CLOCK_SLOW for the I2C_IF_DIS and SIGNAL_PATH_RESET writes, then restores SPI_CLOCK_STANDARD before returning. Rationale: SPI_CLOCK_STANDARD on F4 = 10.5 MHz, H7 = 9.0 MHz, both above the ICM-20689's 8 MHz register write spec. BF 4.5-m uses spiCalculateDivider(ICM20689_MAX_SPI_CLK_HZ) = exactly 8 MHz. EmuFlight has no 8 MHz enum — SPI_CLOCK_SLOW is the nearest-below option (F4 = 0.66 MHz, F7 = 0.42 MHz, H7 = 0.56 MHz).

Question for CRabbit: Is the 10–20× gap between SPI_CLOCK_SLOW (~0.65 MHz) and BF's target of 8 MHz a problem? The ICM-20689 datasheet does not specify a minimum SPI clock, but at sub-1 MHz the SPI frame takes ~130 µs per byte. Are there any known concerns (CS hold time violations, MISO line capacitance, bus contention timing) at this clock rate on typical F4/F7/H7 FCs? Also: SPI_CLOCK_STANDARD on F7 = 6.58 MHz which is already within spec — was the SLOW change necessary for F7 targets, or only F4 and H7?

2. icm20689GyroInit() — SPI clock at entry
Added spiSetDivisor(SPI_CLOCK_SLOW) at icm20689GyroInit() entry, restoring SPI_CLOCK_STANDARD at exit (unchanged). BF 4.5-m sets spiSetClkDivisor(ICM20689_MAX_SPI_CLK_HZ) = 8 MHz at GyroInit entry.

Question for CRabbit: icm20689GyroInit() writes MPU_RA_PWR_MGMT_1 = INV_CLK_PLL then waits ICM20689_CLKSEL_SETTLE_US = 120 µs for the PLL to lock. At SPI_CLOCK_SLOW = 0.65 MHz, each SPI byte takes ~12 µs, and a full 2-byte register write (~24 µs) completes before the 120 µs delay. Is there any concern about the SPI transaction at this clock rate causing the PLL settle time to be insufficient or the device to misinterpret the write? Is the ordering here safe: write PWR_MGMT_1 at 0.65 MHz → delay 120 µs → continue with remaining config registers also at 0.65 MHz?

3. accgyro_mpu.c — startup guard comment
Added the BF 4.5-m second comment line: "Do this once here rather than in each detection routine to speed boot." No code change.

Broader questions I want your honest assessment on

A. Retry loop vs BF single-attempt: EmuFlight's icm20689SpiDetect() uses a 20-attempt retry loop (up to 2 s total). BF 4.5-m does a single reset + delay(ICM20689_RESET_DELAY_MS) + one WHO_AM_I read and returns immediately. Is there a correctness or reliability argument for keeping the EF retry loop, or is it masking slow/unreliable hardware? Should EF move toward BF's single-attempt pattern?

B. I2C_IF_DIS as a detect side-effect: The original EF code never wrote I2C_IF_DIS during detection. Now icm20689SpiDetect() writes MPU_RA_USER_CTRL = 0x10 (USER_CTRL register) after confirming device identity. This is now a permanent state change during detect, not just a probe. If detect is ever called a second time (re-detect after error), the device is already in SPI mode. Is writing USER_CTRL here idempotent and safe? Are there any EF code paths that call detect more than once on the same device?

C. SIGNAL_PATH_RESET in detect vs init: The original EF code did the signal path reset in icm20689GyroInit(). It is now done in icm20689SpiDetect(). This means if the gyro is re-initialised at runtime (without re-detect), the signal path reset does not occur again. Is that correct? Does the ICM-20689 require signal path reset on every cold start, or only once after power-on reset?

Please give your actual assessment — flag anything that looks wrong, including if you think the SPI_CLOCK_SLOW choice was the wrong tradeoff.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Current branch / latest commits =="
git log --oneline -n 6

echo
echo "== Locate relevant files =="
fd '^accgyro_spi_icm20689\.c$|^accgyro_mpu\.c$|^accgyro_spi_mpu6500\.c$|^bus_spi\.h$|^bus_spi.*\.c$' src/main

ICM=src/main/drivers/accgyro/accgyro_spi_icm20689.c
MPU=src/main/drivers/accgyro/accgyro_mpu.c
MPU6500=src/main/drivers/accgyro/accgyro_spi_mpu6500.c
BUSSPIH=src/main/drivers/bus_spi.h

echo
echo "== Outline: accgyro_spi_icm20689.c =="
ast-grep outline "$ICM" --view expanded

echo
echo "== Focused content: accgyro_spi_icm20689.c =="
sed -n '1,260p' "$ICM"

echo
echo "== Outline: accgyro_mpu.c =="
ast-grep outline "$MPU" --view expanded

echo
echo "== Focused content: accgyro_mpu.c around detection =="
sed -n '340,540p' "$MPU"

echo
echo "== Focused content: accgyro_mpu.c around init / reinit if any =="
rg -n -C4 'gyro.*Init|initGyro|reinit|re-init|detectSPISensorsAndUpdateDetectionResult|icm20689SpiDetect|icm20689GyroInit' src/main

echo
echo "== Outline: accgyro_spi_mpu6500.c =="
ast-grep outline "$MPU6500" --view expanded

echo
echo "== Focused content: accgyro_spi_mpu6500.c =="
sed -n '1,140p' "$MPU6500"

echo
echo "== SPI clock enum / helper definitions =="
sed -n '1,220p' "$BUSSPIH"

echo
echo "== Search for spiSetDivisor/spiSetClkDivisor/spiCalculateDivider definitions/usages =="
rg -n -C3 'spiSetDivisor\s*\(|spiSetClkDivisor\s*\(|spiCalculateDivider\s*\(|SPI_CLOCK_(SLOW|INITIALIZATION|STANDARD|FAST)' src/main/drivers src/main

echo
echo "== Search for icm20689SpiDetect call sites =="
rg -n -C3 '\bicm20689SpiDetect\s*\(' src/main

echo
echo "== Search for icm20689GyroInit call sites =="
rg -n -C3 '\bicm20689GyroInit\s*\(' src/main

echo
echo "== Search for runtime gyro reinit paths =="
rg -n -C4 'gyroInitFn|initFn|reinit|reInit|gyro.*Config|signal path reset|SIGNAL_PATH_RESET|USER_CTRL|PWR_MGMT_1' src/main

echo
echo "== Search for repeated sensor detection / redetect paths =="
rg -n -C4 'detectSPISensorsAndUpdateDetectionResult|detectGyro|sensorsAutodetect|gyroDetect|redetect|re-detect|reprobe|probe' src/main

echo
echo "== Optional: show PR commit and patch for cfa2e66197 if available =="
if command -v gh >/dev/null 2>&1; then
  gh pr view 1289 --json title,headRefName,commits --jq '.commits[] | {oid: .oid, messageHeadline: .messageHeadline}' || true
  echo
  gh api repos/emuflight/EmuFlight/commits/cfa2e66197 --jq '{sha: .sha, files: [.files[] | {filename: .filename, status: .status, patch: .patch}]}' || true
fi

Length of output: 198596


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ICM=src/main/drivers/accgyro/accgyro_spi_icm20689.c
MPU=src/main/drivers/accgyro/accgyro_mpu.c
GYRO=src/main/sensors/gyro.c

echo "== Exact icm20689 detect/init slices =="
sed -n '77,170p' "$ICM"

echo
echo "== mpuResetFn declaration and usages =="
rg -n -C4 '\bmpuResetFn\b|\bresetFn\b' src/main/drivers src/main/sensors src/main/fc src/main

echo
echo "== mpuDetect and SPI detect flow slices =="
sed -n '369,575p' "$MPU"

echo
echo "== gyro init flow slice =="
sed -n '595,635p' "$GYRO"

echo
echo "== Any hardware gyro re-init calls via initFn after boot? =="
rg -n -C4 '\binitFn\s*\(&gyro|\bgyroSensor->gyroDev\.initFn\b|\bgyro\.initFn\b|\bgyroInitSensor\s*\(' src/main

echo
echo "== Any explicit gyro redetect / re-probe after startup? =="
rg -n -C4 'sensorsAutodetect\s*\(|gyroInit\s*\(|mpuDetect\s*\(|detectSPISensorsAndUpdateDetectionResult\s*\(|redetect|re-detect|reprobe|re-probe' src/main

Length of output: 40141



@nerdCopter I checked the current tree carefully, and my honest read is:

the second commit is technically safe, but conservative.
I would not push back on it as wrong. I also would not call it as clean as BF’s exact-8 MHz approach.

Bottom line

  • SPI_CLOCK_SLOW for the ICM-20689 writes is not a correctness problem in this codebase.
  • It is more conservative than necessary, especially on F7 where SPI_CLOCK_STANDARD is already within the stated write limit.
  • The PLL-settle ordering in icm20689GyroInit() looks safe.
  • Moving I2C_IF_DIS and SIGNAL_PATH_RESET into detect is safe in the current EmuFlight lifecycle, because I don’t see a normal runtime path that re-detects or re-initializes this gyro later without a full boot.
  • The retry loop is a policy/tradeoff question, not an obvious bug.

1) SPI_CLOCK_SLOW in icm20689SpiDetect()

Current code in src/main/drivers/accgyro/accgyro_spi_icm20689.c:

  • Line 79 sets SPI_CLOCK_INITIALIZATION
  • Line 83 issues device reset
  • Line 116 switches to SPI_CLOCK_SLOW
  • Line 119 writes MPU_RA_USER_CTRL = ICM20689_I2C_IF_DIS
  • Line 122 writes MPU_RA_SIGNAL_PATH_RESET
  • Line 125 restores SPI_CLOCK_STANDARD

My assessment:

  • I do not see a realistic failure mode caused by going slower here.
  • SPI timing limits like this are usually about maximum bus rate and minimum setup/hold widths. Slowing the clock down generally makes those easier to satisfy, not harder.
  • The concerns you listed like CS hold, line capacitance, or contention are not made worse by sub-1 MHz in a normal FC layout; if anything, they are usually more painful at the high end.
  • There’s also already a 100 ms path-reset delay immediately after these writes, so any extra tens of microseconds spent clocking the bytes out are irrelevant to the overall sequence.

So:

  • For F4/H7: the change is justified because SPI_CLOCK_STANDARD was above the intended ceiling.
  • For F7: no, it was probably not necessary, because SPI_CLOCK_STANDARD was already within spec by your reading.
  • But using SPI_CLOCK_SLOW uniformly across F4/F7/H7 is still a valid tradeoff if the goal is “one obviously-safe path everywhere.”

What I’d say technically is: safe, but blunt.
If EmuFlight wants BF parity later, an exact divider is cleaner. But I would not block this PR on using SLOW.

2) SPI_CLOCK_SLOW in icm20689GyroInit()

Current code:

  • Line 148 sets SPI_CLOCK_SLOW
  • Line 151 writes MPU_RA_PWR_MGMT_1 = INV_CLK_PLL
  • Line 152 delays ICM20689_CLKSEL_SETTLE_US
  • Lines 153–160 perform the remaining config writes
  • Line 163 restores SPI_CLOCK_STANDARD

I think this is safe.

Why:

  • The critical thing is when the 120 µs settle timer starts.
  • In this driver model, spiWriteReg() is synchronous. So the delayMicroseconds(ICM20689_CLKSEL_SETTLE_US) on Line 152 happens after the PWR_MGMT_1 transaction has completed, not while it is still on the wire.
  • That means the chip gets:
    1. the full register write at a slow-but-valid SPI speed,
    2. then a full 120 µs quiet period,
    3. then the next config writes.

So I do not think the slower bus causes the PLL delay to become insufficient.

Also, continuing the rest of init at SPI_CLOCK_SLOW is fine. There’s no sign in this sequence that the device requires those follow-up writes to happen quickly after the PLL switch; it just needs the post-CLKSEL settling delay, and the code still provides that.

So on your Question 2 specifically: I don’t see a problem with the ordering or the slower transaction time.

3) Startup guard comment in accgyro_mpu.c

Agreed with your earlier framing:

  • src/main/drivers/accgyro/accgyro_mpu.c, Line 375 is still an absolute boot-time guard:
    while (millis() < GYRO_SPI_STARTUP_MS);
  • The added second comment is harmless, but it does not change that semantic.

So I still view that as:

  • not a bug
  • not especially misleading anymore, but still only accurate if read as “centralize the one boot-time wait.”

A) Retry loop vs BF single-attempt

This is where I’d push back a bit against “BF does one attempt, therefore EF should too.”

Current ICM-20689 detect does:

  • one reset at Line 83
  • then up to 20 WHO_AM_I reads with delay(ICM20689_RESET_DELAY_MS) between them

That means it is not repeatedly resetting the chip. It is just waiting longer and retrying identity.

My read:

  • That is not inherently wrong.
  • It is a robustness vs boot-latency tradeoff.
  • It can absolutely help with slow power rails, slow sensor startup, or board-level timing weirdness.
  • But yes, it can also mask marginal hardware by turning “barely comes up late” into “seems fine.”

So I would phrase it like this:

  • If EmuFlight values wider board tolerance / boot robustness, keeping the retry loop is defensible.
  • If EmuFlight values fast fail / BF parity / simpler reasoning, a single-attempt path is cleaner.

I do not see a correctness argument forcing a change today.
I mostly see a product philosophy choice.

My own preference: don’t mix that policy decision into this PR unless there is evidence the long retry window is causing real problems.

B) I2C_IF_DIS as a detect side-effect

This is the one I checked most carefully.

From the current tree:

  • src/main/sensors/gyro.c, Line 599 calls mpuDetect(&gyroSensor->gyroDev)
  • then Line 626 calls gyroSensor->gyroDev.initFn(&gyroSensor->gyroDev)

I did not find a normal runtime path that:

  • re-runs mpuDetect() after boot, or
  • re-runs the hardware gyro initFn() later.

I also checked mpuResetFn:

  • it’s only used before system reset / bootloader / MSC transitions in:
    • src/main/drivers/system_stm32f4xx.c, Line 36 and Line 47
    • src/main/drivers/system_stm32f7xx.c, Line 40 and Line 48
    • src/main/drivers/usb_msc_f4xx.c, Line 135
    • src/main/drivers/usb_msc_f7xx.c, Line 138
  • and I only found an assignment for MPU9250 in src/main/drivers/accgyro/accgyro_mpu.c, Line 439
  • not for ICM-20689

So for ICM-20689 specifically, detect is effectively a boot-time one-shot in the current tree.

On the register write itself:

  • Writing MPU_RA_USER_CTRL = 0x10 here is idempotent enough for this use.
  • Rewriting I2C_IF_DIS on a device already in SPI-only mode should not be harmful.
  • The only caveat would be if some other USER_CTRL bits needed preserving at that moment, but right after reset and before any FIFO/DMP/I2C-master setup, I don’t see evidence that they do.

So my answer is:

  • Yes, detect now has a side effect.
  • In this codebase, that is acceptable and probably desirable.
  • I do not see a practical re-detect-on-live-device path that makes this risky.

C) SIGNAL_PATH_RESET moved from init to detect

Given the current lifecycle, I think this is also correct.

Important detail:

  • detect still performs the device reset first at Line 83
  • then, after identity succeeds, it does the signal path reset at Line 122
  • then icm20689GyroInit() only performs configuration writes

So this is not “reset disappeared”; it’s “reset moved earlier into the same boot bring-up flow.”

Because I could not find a runtime hardware re-init path for ICM-20689, I don’t think this creates a real regression today.

Where I would be cautious:

  • if EmuFlight later adds a true hardware gyro re-init path that calls icm20689GyroInit() without re-running detect,
  • then this assumption becomes fragile.

But in the current tree, that path doesn’t appear to exist. What you have now is effectively:

  1. detect/reset once at boot
  2. configure once at boot
  3. only software filter reinit later, not hardware sensor reinit

So I would say: correct for current architecture, but worth keeping in mind if future runtime reinit is added.

My final verdict

If you want the blunt version:

  • I would accept the second commit.
  • I do not think SPI_CLOCK_SLOW was the wrong tradeoff.
  • I do think it is overly conservative, not elegant.
  • The only thing I’d keep as a mental note is:
    this code now relies on detect being the one-and-only hardware reset phase for ICM-20689, which is true in the current tree.

So if I were summarizing review posture:

  • No blocker remaining from me on the second commit.
  • Non-blocking note only: exact ~8 MHz would be nicer than dropping all the way to SLOW, but SLOW is still technically sound here.

SPI_CLOCK_SLOW is used for ICM-20689 device-specific writes because EmuFlight's
SPI_CLOCK_STANDARD exceeds the chip's 8 MHz write limit on F4 (~10.5 MHz) and
H7 (~9 MHz). There is no ~8 MHz enum in EF's current SPI API; SPI_CLOCK_SLOW
(~0.65 MHz) is the nearest safe option. BF 4.5-m uses spiCalculateDivider(8000000)
for exact parity — a future SPI API upgrade could match that.

Also documents that icm20689GyroInit() assumes icm20689SpiDetect() has already
run, since reset, I2C_IF_DIS, and SIGNAL_PATH_RESET are now applied during detect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

Thank you for the thorough analysis. Addressed in commit 100a627b19:

SPI_CLOCK_SLOW rationale (your non-blocking note): Added a comment explaining that SPI_CLOCK_STANDARD exceeds the ICM-20689's 8 MHz write limit on F4 (~10.5 MHz) and H7 (~9 MHz), and that SPI_CLOCK_SLOW (~0.65 MHz) is the nearest available enum within spec. Notes that BF 4.5-m uses spiCalculateDivider(8000000) for exact parity — a future SPI API upgrade could match that. The conservative choice was intentional given EF's current enum-based API; not a wrong decision, just a current API limitation.

Detect-first assumption (your mental note): Strengthened the comment in icm20689GyroInit() to explicitly state that reset, I2C_IF_DIS, and SIGNAL_PATH_RESET are applied during detection, and that this function assumes icm20689SpiDetect() has already run. Documents the architectural assumption directly in code so a future developer adding a runtime re-init path sees it immediately.

No logic changes — the second commit stands as-is. These are documentation-only clarifications responding to your two non-blocking observations.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@nerdCopter Thanks — that sounds like the right resolution.

The added comments address exactly what I was concerned about:

  • they make the SPI_CLOCK_SLOW choice clearly intentional rather than accidental, and
  • they document the detect-first contract in icm20689GyroInit() so a future runtime re-init path won’t silently violate the current assumption.

Given that these were non-blocking documentation gaps rather than logic flaws, I’m satisfied with this follow-up. Nice cleanup.

🐇👍


✏️ Learnings added
Learnt from: nerdCopter
URL: https://github.com/emuflight/EmuFlight/pull/1289

Timestamp: 2026-06-26T17:52:23.248Z
Learning: In `src/main/drivers/accgyro/accgyro_spi_icm20689.c`, `icm20689GyroInit()` is documented to assume `icm20689SpiDetect()` has already run and applied reset, `I2C_IF_DIS`, and `SIGNAL_PATH_RESET`; this detect-first hardware initialization contract should be considered before adding any runtime re-initialization path for the ICM-20689 driver.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

Three commits now on this PR. Summarising what was done and asking for your final honest recommendation.

What the three commits do

Commit 1 (87df71f1ac) — core correctness fixes:

  • icm20689SpiDetect(): write I2C_IF_DIS (USER_CTRL 0x6a) after WHO_AM_I confirms identity; write SIGNAL_PATH_RESET post-identity; switch to SPI_CLOCK_INITIALIZATION for reset; restore SPI_CLOCK_STANDARD on return
  • icm20689GyroInit(): remove redundant second reset + 200ms delay; remove five unnecessary 15ms inter-register delays; add 120µs ICM20689_CLKSEL_SETTLE_US after INV_CLK_PLL; replace magic numbers with named defines; move ICM20689_BIT_RESET from header to file-private
  • accgyro_mpu.c: add GYRO_SPI_STARTUP_MS 100ms boot guard before detect loop
  • accgyro_spi_mpu6500.c: SPI_CLOCK_FASTSPI_CLOCK_INITIALIZATION in mpu6500SpiInit()

Commit 2 (cfa2e66197) — review fixes:

  • icm20689SpiDetect(): use SPI_CLOCK_SLOW for post-identity writes (I2C_IF_DIS, SIGNAL_PATH_RESET) since SPI_CLOCK_STANDARD exceeds ICM-20689's 8 MHz write limit on F4 (~10.5 MHz) and H7 (~9 MHz); restore SPI_CLOCK_STANDARD before returning
  • icm20689GyroInit(): add SPI_CLOCK_SLOW at entry, restore SPI_CLOCK_STANDARD at exit — matching BF 4.5-m intent of keeping config writes within spec

Commit 3 (100a627b19) — documentation only:

  • Comment explaining why SPI_CLOCK_SLOW (EF API constraint — no ~8 MHz enum; BF uses spiCalculateDivider(8000000); future parity opportunity)
  • Comment in icm20689GyroInit() explicitly stating the detect-first assumption

Remaining design decisions not changed

  • Retry loop (20 × 100ms = 2s): retained as a robustness/policy choice per your recommendation not to mix that decision into this PR
  • Exact ~8 MHz SPI: not implemented — EF's enum-based SPI API has no step between SLOW (~0.65 MHz) and STANDARD (~10.5 MHz on F4); porting spiCalculateDivider() from BF is out of scope for this fix PR

Question

Given the three commits as they now stand: do you honestly recommend any further change before this PR is merged? If yes, name it concisely. If no, say so plainly — we will act on your answer.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@nerdCopter No — I do not recommend any further code change before merge.

My honest final read:

  • the original correctness issues are addressed,
  • the SPI_CLOCK_SLOW choice is conservative but technically sound given EmuFlight’s current enum-based SPI API,
  • the detect-first assumption is now documented clearly enough,
  • and the remaining retry-loop / exact-8MHz questions are follow-up design choices, not merge blockers for this fix.

So my plain recommendation is: merge it as-is.

The only thing I’d still want, separately, is hardware verification on an affected ICM-20689 target. But that’s a test/validation step, not a further code change I’d require before accepting the implementation.

If you want future cleanup later, I’d track these in a separate PR:

  1. add exact-Hz SPI divider support for parity with BF’s ~8 MHz handling,
  2. revisit the 20-attempt retry policy on its own merits.

But for this PR: no further change recommended before merge.

@nerdCopter nerdCopter marked this pull request as ready for review June 26, 2026 18:46
@coderabbitai coderabbitai Bot added the help wanted Extra attention is needed label Jun 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/main/drivers/accgyro/accgyro_mpu.c (1)

92-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify that this is a boot-time guard, not a relative 100 ms delay.

while (millis() < GYRO_SPI_STARTUP_MS) only waits until system uptime reaches 100 ms, so calling this later is a no-op. The current comments read like “delay 100 ms here”, which is easy to misinterpret during future refactors.

Also applies to: 374-376

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/drivers/accgyro/accgyro_mpu.c` around lines 92 - 93, The comment and
constant around GYRO_SPI_STARTUP_MS in accgyro_mpu.c should describe a boot-time
guard, not a relative delay. Update the wording near the GYRO_SPI_STARTUP_MS
definition and the startup check in the MPU SPI init path (including the later
use around the same guard) so it’s clear the code waits until system uptime
reaches 100 ms and may be a no-op if called later. Keep the behavior unchanged,
but rename/comment the intent to match the millis() guard semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/drivers/accgyro/accgyro_mpu.c`:
- Around line 92-93: The comment and constant around GYRO_SPI_STARTUP_MS in
accgyro_mpu.c should describe a boot-time guard, not a relative delay. Update
the wording near the GYRO_SPI_STARTUP_MS definition and the startup check in the
MPU SPI init path (including the later use around the same guard) so it’s clear
the code waits until system uptime reaches 100 ms and may be a no-op if called
later. Keep the behavior unchanged, but rename/comment the intent to match the
millis() guard semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ca4c423-7ac2-419d-9ffd-e5a2ded0b75d

📥 Commits

Reviewing files that changed from the base of the PR and between 7cc284c and 100a627.

📒 Files selected for processing (4)
  • src/main/drivers/accgyro/accgyro_mpu.c
  • src/main/drivers/accgyro/accgyro_spi_icm20689.c
  • src/main/drivers/accgyro/accgyro_spi_icm20689.h
  • src/main/drivers/accgyro/accgyro_spi_mpu6500.c
💤 Files with no reviewable changes (1)
  • src/main/drivers/accgyro/accgyro_spi_icm20689.h
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Codacy Static Code Analysis
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-05-15T14:49:12.754Z
Learnt from: nerdCopter
Repo: emuflight/EmuFlight PR: 1189
File: src/main/drivers/bus_spi.c:631-631
Timestamp: 2026-05-15T14:49:12.754Z
Learning: In the EmuFlight repo (emuflight/EmuFlight), the STM32F303 (F303) targets currently do not compile on the master branch due to a pre-existing hardware RAM budget overflow (unrelated to any specific PR). When reviewing driver changes in shared driver files (e.g., src/main/drivers/bus_spi.c), avoid adding or fixing F303-specific driver behavior as part of the current PR; instead, defer those F303-targeted fixes to a dedicated F3-targeted PR and only revisit once the RAM overflow/build issue is resolved.

Applied to files:

  • src/main/drivers/accgyro/accgyro_spi_mpu6500.c
  • src/main/drivers/accgyro/accgyro_mpu.c
  • src/main/drivers/accgyro/accgyro_spi_icm20689.c
🔇 Additional comments (2)
src/main/drivers/accgyro/accgyro_spi_icm20689.c (1)

39-61: LGTM!

Also applies to: 79-88, 114-126, 150-167

src/main/drivers/accgyro/accgyro_spi_mpu6500.c (1)

48-48: LGTM!

…delay

The while (millis() < GYRO_SPI_STARTUP_MS) pattern spins until system uptime
reaches 100ms since boot — it is not a relative 100ms delay and is a no-op if
called after that point. Update comments at the define and the call site to make
this semantics clear. Addresses CodeRabbit nitpick on PR emuflight#1289.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

Addressed nitpick from review #4581948877 in commit `19d27ec918`:

GYRO_SPI_STARTUP_MS boot-time semantics — updated both comment sites in `accgyro_mpu.c`:

  • Define (line 92): was "Allow 100ms before attempting to access SPI bus" → now "Minimum system uptime (ms) before SPI bus access — boot-time guard, not a relative delay"
  • Call site (line 374): was "Allow 100ms before attempting to access gyro's SPI bus" → now "Spin until system uptime reaches GYRO_SPI_STARTUP_MS; no-op if already past that point."

No logic change. Comments now correctly reflect the absolute boot-clock semantics.

All four commits are comment/documentation only after the first. Are there any remaining concerns, or is this PR ready for hardware verification and merge?

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@nerdCopter No remaining concerns from me.

With the GYRO_SPI_STARTUP_MS comments corrected to explicitly describe the boot-time guard semantics, this PR looks ready for hardware verification on an affected ICM-20689 target and, assuming that passes, merge.

My honest final stance is unchanged:

  • the correctness fixes are in place,
  • the conservative SPI_CLOCK_SLOW choice is acceptable for this PR,
  • the detect-first assumption is now documented,
  • and the startup-guard wording now matches the actual behavior.

So: yes, ready for hardware verification and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant