Skip to content

refactor(imuf9001): migrate gyro driver from USE_DMA_SPI_DEVICE to spiSequence/imufIntCallback#1283

Draft
nerdCopter wants to merge 9 commits into
emuflight:masterfrom
nerdCopter:refactor/imuf9001-dma-migration
Draft

refactor(imuf9001): migrate gyro driver from USE_DMA_SPI_DEVICE to spiSequence/imufIntCallback#1283
nerdCopter wants to merge 9 commits into
emuflight:masterfrom
nerdCopter:refactor/imuf9001-dma-migration

Conversation

@nerdCopter

@nerdCopter nerdCopter commented Jun 23, 2026

Copy link
Copy Markdown
Member

AI Generated [pull-request]

Summary

  • Removes the legacy USE_DMA_SPI_DEVICE / dma_spi.* infrastructure (~710 lines deleted) and replaces it with the standard EmuFlight spiSequence + imufIntCallback bus model
  • Moves the blocking 32-byte IMUF9001 SPI transfer out of the EXTI ISR and into imufSpiGyroRead (called in GYROPID task context), reducing EXTI ISR latency from ~15–20µs to <1µs
  • Moves imufTxBuf/imufRxBuf from CCM SRAM to AHB SRAM (prerequisite for future true DMA migration; DMA cannot read CCM on F4)
  • Consolidates all IMUF-specific globals (imufQuat, isImufCalibrating, crcErrorCount, imufTransferPending) exclusively in accgyro_imuf9001.h; fixes ODR violation on isImufCalibrating
  • Restores SPI Mode 0 explicit init in imuf9001SpiDetect (was silently inheriting bus state → zero gyro output)
  • Hardware-verified on HELIOSPRING at 16kHz/16kHz defaults: smooth continuous gyro + accel waveforms, no MSP starvation

Files Changed

Area Change
drivers/dma_spi.c, dma_spi.h, dma_spi_hal.c, dma_spi_hal.h Deleted — IMUF9001-only, incompatible with standard bus abstraction
drivers/accgyro/accgyro_imuf9001.c imufSpiGyroRead performs deferred SPI transfer; SPI Mode 0 explicit init
drivers/accgyro/accgyro_mpu.c EXTI ISR sets imufTransferPending only (<1µs); buffers to AHB SRAM
drivers/accgyro/accgyro_imuf9001.h All IMUF globals consolidated here; extern ODR fix
drivers/accgyro/accgyro_mpu.h Removed IMUF globals (now in imuf9001.h)
target/HELIOSPRING, STRIXF10, MODE2FLUX Removed USE_DMA_SPI_DEVICE defines
fc/fc_tasks.c, sensors/gyro.c Removed isDmaSpiDataReady checkFunc; dataReady gate is now inside imufSpiGyroRead

Known Limitation

32kHz gyro loop produces stepped waveforms and MSP starvation on IMUF9001 targets. This is a pre-existing condition also present on master — not a regression of this PR. Tracked in #1281. Fix requires true non-blocking DMA for the SPI transfer; the AHB SRAM buffer migration in this PR is the prerequisite for that work.

Test Plan

  • Flash HELIOSPRING (or derivative IMUF9001 target), defaults (16kHz gyro / 16kHz PID loop)
  • Verify smooth gyro + accel waveforms in EmuConfigurator sensors tab (no staircase artifacts)
  • Verify EmuConfigurator tabs respond normally (no MSP starvation)
  • Repeat at 8kHz gyro / 8kHz PID loop (gyro_sync_denom=4)
  • Flights with blackbox logging enabled — review logs at both 16kHz and 8kHz loop rates for noise, gyro dropout, or unexpected behavior
  • Confirm gyro calibration completes on boot
  • (Optional) Test 32kHz — expected to exhibit pre-existing stepping per IMUF9001: 32kHz gyro loop produces stepped waveforms and MSP starvation on HELIOSPRING and derivatives #1281

Related Issues

Summary by CodeRabbit

  • New Features

    • Improved IMUF9001 sensor handling with more reliable read timing and data acquisition.
    • Added CRC/error tracking for IMUF9001 communication.
  • Bug Fixes

    • Reduced risk of sensor read issues by moving SPI work out of interrupt handling.
    • Improved gyro update behavior so sensor reads happen more consistently.
    • Removed a legacy DMA-SPI path and updated target builds accordingly for cleaner operation.

nerdCopter and others added 6 commits June 22, 2026 15:49
Replace the legacy USE_DMA_SPI_DEVICE code path with the general
spiSequence/busSegment_t infrastructure introduced in the Stage M
SPI refactor (PRs emuflight#1121, emuflight#1128emuflight#1131).

Key changes:
- accgyro_mpu.c: new #ifdef USE_GYRO_IMUF9001 block with
  imufTxBuf[58]/imufRxBuf[58] (FAST_RAM_ZERO_INIT), crcErrorCount,
  imufIntCallback (DMA completion: CRC-validate, copy gyroADCf/ADCRaw),
  imufPrepareDmaRead (build TX command before each EXTI-triggered read),
  mpuImufSetupDma (wire gyro->segments at init time).
- mpuIntExtiHandler: IMUF path now calls imufPrepareDmaRead + spiSequence
  instead of the legacy gyroDmaSpiStartRead; other SPI gyros keep the
  existing GYRO_EXTI_INT_DMA spiSequence call unchanged.
- accgyro_imuf9001.c: add imufSpiGyroRead (trivial readFn satisfying the
  gyroUpdateSensor call now unconditionally present), wire readFn and call
  mpuImufSetupDma from imufSpiGyroInit.
- bus_spi.c: remove USE_DMA_SPI_DEVICE fallback in spiSequence() that
  routed IMUF's SPI bus through dmaSpiTransmitReceive; IMUF now uses the
  standard non-blocking segment queue path.
- gyro.c: remove #ifndef USE_DMA_SPI_DEVICE guard around readFn call in
  gyroUpdateSensor; remove gyroDmaSpiFinishRead/gyroDmaSpiStartRead wrappers.
- nvic.h: NVIC_PRIO_MPU_INT_EXTI unconditionally NVIC_BUILD_PRIORITY(0x0f,0x0f)
  (matches BF 4.5-m; was elevated when USE_DMA_SPI_DEVICE was defined).
- dma_stm32f4xx/f7xx: restore DMA2_ST2/ST3 IRQ handlers (previously
  excluded to avoid conflicting with dma_spi's raw IRQ handler).
- fc_tasks: GYROPID task reverts to TASK_PRIORITY_REALTIME; timing is
  now driven by EXTI→spiSequence→imufIntCallback.
- crcErrorCount moved from dma_spi.c/dma_spi_hal.c to accgyro_mpu.c
  under USE_GYRO_IMUF9001; exported via accgyro_mpu.h and
  accgyro_imuf9001.h for cli.c visibility.

Closes emuflight#1143 (partial — commit 2 removes the legacy driver files).

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

Delete the legacy dma_spi driver layer now that all IMUF9001 targets
have been migrated to the general spiSequence infrastructure:

Deleted files:
- drivers/dma_spi.c       — F4 StdPeriph custom DMA driver (HELIOSPRING/MODE2FLUX)
- drivers/dma_spi.h       — shared header (dmaTxBuffer, dmaRxBuffer, dmaSpiReadStatus)
- drivers/dma_spi_hal.c   — F7 HAL custom DMA driver (STRIXF10/F722)
- drivers/dma_spi_hal.h   — HAL variant header

Target changes (HELIOSPRING, MODE2FLUX, STRIXF10):
- Remove USE_DMA_SPI_DEVICE and all DMA_SPI_* pin/stream/channel/flag
  defines from target.h (45–47 lines per target).
- Remove dma_spi.c / dma_spi_hal.c from TARGET_SRC in target.mk.

No functional change — commit 1 already switched all call sites to
the spiSequence path. This commit makes the codebase consistent by
removing the now-unreachable dead driver files and their build hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two hardware-verified fixes on HELIOSPRING:

1. SPI mode mismatch: the old USE_DMA_SPI_DEVICE spiSequence fallback bypassed
   spiSequenceStart entirely and called dmaSpiTransmitReceive on SPI1 hardware
   that dmaSpiInit had set to Mode 0 (CPOL_Low/CPHA_1Edge, Prescaler_4 = 21 MHz).
   The new polling path uses spiSequenceStart which applies the per-device
   leadingEdge setting — which defaulted to false (Mode 3) since no explicit
   spiSetClkPhasePolarity call existed for IMUF. Fix: set leadingEdge=true and
   divisor=4 in imuf9001SpiDetect to match original dmaSpiInit settings.

2. CRC gate removed: imufIntCallback was gating all data updates on CRC match.
   The original mpuGyroDmaSpiReadFinish had no CRC gate — it always copied data.
   Restore that behavior: always copy, tally errors diagnostically only.

Both bugs together caused: 50+ second init (all 10 SETUP attempts timing out),
setArmingDisabled(NO_GYRO), zero gyro/accel movement.

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

imufSpiGyroRead previously returned true unconditionally, allowing GYROPID
to run every targetLooptime regardless of whether imufIntCallback had
deposited a fresh IMUF sample.  This was a regression from the old
TASK_PRIORITY_TRIGGER + isDmaSpiDataReady path, which only allowed GYROPID
to execute when the DMA transfer completion ISR set dmaSpiDeviceDataReady.

With the unconditional-true readFn:
- At gyro_sync_denom=1 (32 kHz targetLooptime) and imuf_rate=IMUF_16000,
  every other GYROPID cycle processed stale data.  Filters saw duplicate
  input samples; the PID integrator accumulated at 2x the sensor rate.
- The 32 kHz loop capability of HELIOSPRING/STRIXF10 was effectively halved
  in data quality, even though the task fired at the correct period.

Fix: return gyro->dataReady from imufSpiGyroRead.  imufIntCallback (called
from the EXTI ISR's synchronous SPI completion) sets dataReady = true after
each fresh sample; gyroUpdateSensor clears it after consuming the sample.
GYROPID now returns early when no new sample is available, exactly matching
the old isDmaSpiDataReady gate.  The loop naturally runs at the actual IMUF
output rate (16 kHz default, 32 kHz when imuf_rate = IMUF_32000 with
gyro_sync_denom = 1).

Also fix a pre-existing ODR violation: accgyro_imuf9001.h declared
isImufCalibrating without extern, creating a tentative definition in every
translation unit that included the header.  Changed to extern; the single
definition remains in accgyro_imuf9001.c.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
At 16 kHz IMUF rate the EXTI ISR was firing every 62.5 µs and executing a
full 32-byte synchronous SPI polling transfer (~15-20 µs at 21 MHz).  That
burned ~32% of the F405 CPU in interrupt context, starving TASK_SERIAL and
all other lower-priority tasks.  Symptoms: very slow EmuConfigurator MSP
responses, stepped/blocky sensor display (configurator only received updates
every several hundred ms instead of every 10 ms).

Fix: reduce the EXTI ISR to a single flag write (< 1 µs):
    imufTransferPending = true;

imufSpiGyroRead (called by gyroUpdateSensor each GYROPID cycle) now owns
the transfer.  When imufTransferPending is set it clears the flag, calls
imufPrepareDmaRead, and runs spiSequence synchronously in task context.
spiSequence invokes imufIntCallback on completion, which sets dataReady;
gyroUpdateSensor proceeds with the fresh sample and clears dataReady.
If imufTransferPending is clear, imufSpiGyroRead returns dataReady=false
and gyroUpdateSensor skips the cycle -- naturally gating the GYROPID loop
to IMUF's actual data rate (same behaviour as the old isDmaSpiDataReady
checkFunc with TASK_PRIORITY_TRIGGER).

Also move imufTxBuf/imufRxBuf out of FAST_RAM_ZERO_INIT (CCM SRAM).  They
are now in regular AHB SRAM.  The CCM buffers had no functional impact on
the polling path but would block a future DMA migration (DMA cannot read
from CCM on F4).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve duplicate crcErrorCount extern

crcErrorCount was declared in both accgyro_imuf9001.h and accgyro_mpu.h
after commit c5b3b08 added it to the MPU header alongside the new
imufTransferPending extern.  Duplicate extern declarations are legal C
but create two apparent sources of truth and were flagged by CodeRabbit.

Fix: move both imuf-specific globals (crcErrorCount, imufTransferPending)
to accgyro_imuf9001.h where they semantically belong.  Remove them from
accgyro_mpu.h, leaving that header focused on the generic MPU/SPI bridge
interface (mpuImufSetupDma, imufPrepareDmaRead).

cli.c includes accgyro_imuf9001.h and uses crcErrorCount — no include
change needed there.

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

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

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: 19a82553-4eef-4368-b451-34e8413a12b8

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

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

unsoldered 16k/16k
image

unsoldered 8k/8k
image

@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.

Actionable comments posted: 2

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

191-192: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Restore the IMUF buffer/transfer-size contract.

The callback assumes xferLen >= 4, word alignment, and enough received bytes for sizeof(imufData_t), while the buffers are fixed at 58 bytes and the previous static sizing check was removed. Please re-add compile-time buffer assertions and keep imuf_mode normalized before assigning segments[0].len.

Proposed guard for the fixed buffers
 static uint8_t imufTxBuf[58];
 static uint8_t imufRxBuf[58];
+STATIC_ASSERT(sizeof(imufTxBuf) >= sizeof(imufCommand_t), imuf_tx_buffer_too_small);
+STATIC_ASSERT(sizeof(imufRxBuf) >= sizeof(imufData_t), imuf_rx_buffer_too_small);

Also applies to: 203-210, 244-246

🤖 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 191 - 192, Restore the
IMUF transfer-size contract in the IMUF SPI path: the fixed buffers imufTxBuf
and imufRxBuf must be validated at compile time against the assumptions used by
the callback (xferLen >= 4, word alignment, and enough space for
sizeof(imufData_t)). Re-add static assertions near the buffer declarations and
ensure the receive path still guards the transfer length before using the data.
Also normalize imuf_mode before assigning segments[0].len so the segment length
logic stays consistent with the mode selection.

Source: Linters/SAST tools

🤖 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.

Inline comments:
In `@src/main/drivers/accgyro/accgyro_mpu.c`:
- Around line 225-243: Clear the stale IMUF transfer state in imufPrepareDmaRead
so old commands are not resent on later reads. After handling
IMUF_COMMAND_SETPOINT, make sure imufTxBuf is reset to a no-command payload (and
CRC cleared if needed) before the next DMA transfer, similar to the
IMUF_DONE_CALIBRATING branch. Use the existing symbols imufPrepareDmaRead,
imufTxBuf, imufCommand_t, and isSetpointNew to locate the fix.
- Around line 146-153: The EXTI handler in accgyro_mpu.c is gating the IMUF
deferral only on USE_GYRO_IMUF9001, which prevents the generic GYRO_USES_SPI DMA
path from running for non-IMUF SPI gyros in the same build. Update the decision
logic around the EXTI callback so the imufTransferPending deferral is applied
only when the detected sensor is IMUF9001, while all other SPI gyros still reach
the existing spiSequence() path via gyro->gyroModeSPI. Use the symbols
USE_GYRO_IMUF9001, GYRO_USES_SPI, gyro->gyroModeSPI, and spiSequence() to keep
the fix localized.

---

Nitpick comments:
In `@src/main/drivers/accgyro/accgyro_mpu.c`:
- Around line 191-192: Restore the IMUF transfer-size contract in the IMUF SPI
path: the fixed buffers imufTxBuf and imufRxBuf must be validated at compile
time against the assumptions used by the callback (xferLen >= 4, word alignment,
and enough space for sizeof(imufData_t)). Re-add static assertions near the
buffer declarations and ensure the receive path still guards the transfer length
before using the data. Also normalize imuf_mode before assigning segments[0].len
so the segment length logic stays consistent with the mode selection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15fcbb25-fe2b-4cdc-94f0-331f1512509c

📥 Commits

Reviewing files that changed from the base of the PR and between 7cc284c and 0f8b036.

📒 Files selected for processing (24)
  • src/main/drivers/accgyro/accgyro_imuf9001.c
  • src/main/drivers/accgyro/accgyro_imuf9001.h
  • src/main/drivers/accgyro/accgyro_mpu.c
  • src/main/drivers/accgyro/accgyro_mpu.h
  • src/main/drivers/bus_spi.c
  • src/main/drivers/dma_spi.c
  • src/main/drivers/dma_spi.h
  • src/main/drivers/dma_spi_hal.c
  • src/main/drivers/dma_spi_hal.h
  • src/main/drivers/dma_stm32f4xx.c
  • src/main/drivers/dma_stm32f7xx.c
  • src/main/drivers/nvic.h
  • src/main/fc/fc_core.c
  • src/main/fc/fc_init.c
  • src/main/fc/fc_tasks.c
  • src/main/interface/cli.c
  • src/main/sensors/gyro.c
  • src/main/sensors/gyro.h
  • src/main/target/HELIOSPRING/target.h
  • src/main/target/HELIOSPRING/target.mk
  • src/main/target/MODE2FLUX/target.h
  • src/main/target/MODE2FLUX/target.mk
  • src/main/target/STRIXF10/target.h
  • src/main/target/STRIXF10/target.mk
💤 Files with no reviewable changes (19)
  • src/main/drivers/dma_stm32f7xx.c
  • src/main/drivers/nvic.h
  • src/main/drivers/dma_spi_hal.h
  • src/main/drivers/dma_spi.h
  • src/main/sensors/gyro.h
  • src/main/target/MODE2FLUX/target.mk
  • src/main/drivers/dma_stm32f4xx.c
  • src/main/interface/cli.c
  • src/main/drivers/dma_spi_hal.c
  • src/main/fc/fc_tasks.c
  • src/main/target/MODE2FLUX/target.h
  • src/main/drivers/dma_spi.c
  • src/main/target/STRIXF10/target.h
  • src/main/fc/fc_init.c
  • src/main/target/HELIOSPRING/target.mk
  • src/main/drivers/bus_spi.c
  • src/main/target/HELIOSPRING/target.h
  • src/main/fc/fc_core.c
  • src/main/sensors/gyro.c
📜 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_imuf9001.c
  • src/main/drivers/accgyro/accgyro_mpu.c
🪛 GitHub Check: Codacy Static Code Analysis
src/main/drivers/accgyro/accgyro_mpu.c

[failure] 210-210: src/main/drivers/accgyro/accgyro_mpu.c#L210
Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data.

🔇 Additional comments (5)
src/main/target/STRIXF10/target.mk (1)

4-5: LGTM!

src/main/drivers/accgyro/accgyro_imuf9001.h (1)

190-192: LGTM!

src/main/drivers/accgyro/accgyro_mpu.h (1)

250-252: LGTM!

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

46-49: LGTM!

Also applies to: 88-88, 249-260, 524-524

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

202-215: LGTM!

Also applies to: 420-422, 511-511, 538-538

Comment on lines +146 to 153
#if defined(USE_GYRO_IMUF9001)
// Keep EXTI ISR < 1 µs: SPI transfer is done by imufSpiGyroRead in GYROPID task context.
imufTransferPending = true;
#elif defined(GYRO_USES_SPI)
if (gyro->gyroModeSPI == GYRO_EXTI_INT_DMA) {
spiSequence(&gyro->dev, gyro->segments);
}
#endif

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gate the IMUF deferral by the detected sensor, not just the build flag.

With USE_GYRO_IMUF9001 enabled, this #if skips the generic GYRO_USES_SPI DMA read path for every MPU EXTI callback in that firmware image. Non-IMUF SPI gyros would only set imufTransferPending and never start their SPI sequence.

Proposed fix
 `#if` defined(USE_GYRO_IMUF9001)
-    // Keep EXTI ISR < 1 µs: SPI transfer is done by imufSpiGyroRead in GYROPID task context.
-    imufTransferPending = true;
-#elif defined(GYRO_USES_SPI)
+    if (gyro->mpuDetectionResult.sensor == IMUF_9001_SPI) {
+        // Keep EXTI ISR < 1 µs: SPI transfer is done by imufSpiGyroRead in GYROPID task context.
+        imufTransferPending = true;
+    }
+#if defined(GYRO_USES_SPI)
+    else if (gyro->gyroModeSPI == GYRO_EXTI_INT_DMA) {
+        spiSequence(&gyro->dev, gyro->segments);
+    }
+#endif
+#elif defined(GYRO_USES_SPI)
     if (gyro->gyroModeSPI == GYRO_EXTI_INT_DMA) {
         spiSequence(&gyro->dev, gyro->segments);
     }
 `#endif`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#if defined(USE_GYRO_IMUF9001)
// Keep EXTI ISR < 1 µs: SPI transfer is done by imufSpiGyroRead in GYROPID task context.
imufTransferPending = true;
#elif defined(GYRO_USES_SPI)
if (gyro->gyroModeSPI == GYRO_EXTI_INT_DMA) {
spiSequence(&gyro->dev, gyro->segments);
}
#endif
`#if` defined(USE_GYRO_IMUF9001)
if (gyro->mpuDetectionResult.sensor == IMUF_9001_SPI) {
// Keep EXTI ISR < 1 µs: SPI transfer is done by imufSpiGyroRead in GYROPID task context.
imufTransferPending = true;
}
`#if` defined(GYRO_USES_SPI)
else if (gyro->gyroModeSPI == GYRO_EXTI_INT_DMA) {
spiSequence(&gyro->dev, gyro->segments);
}
`#endif`
`#elif` defined(GYRO_USES_SPI)
if (gyro->gyroModeSPI == GYRO_EXTI_INT_DMA) {
spiSequence(&gyro->dev, gyro->segments);
}
`#endif`
🤖 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 146 - 153, The EXTI
handler in accgyro_mpu.c is gating the IMUF deferral only on USE_GYRO_IMUF9001,
which prevents the generic GYRO_USES_SPI DMA path from running for non-IMUF SPI
gyros in the same build. Update the decision logic around the EXTI callback so
the imufTransferPending deferral is applied only when the detected sensor is
IMUF9001, while all other SPI gyros still reach the existing spiSequence() path
via gyro->gyroModeSPI. Use the symbols USE_GYRO_IMUF9001, GYRO_USES_SPI,
gyro->gyroModeSPI, and spiSequence() to keep the fix localized.

Comment on lines +225 to 243
FAST_CODE void imufPrepareDmaRead(gyroDev_t *gyro) {
imufCommand_t *txCmd = (imufCommand_t *)imufTxBuf;
if (isImufCalibrating == IMUF_IS_CALIBRATING) {
memset(imufTxBuf, 0, sizeof(imufCommand_t));
txCmd->command = IMUF_COMMAND_CALIBRATE;
txCmd->crc = getCrcImuf9001((uint32_t *)imufTxBuf, 11);
isImufCalibrating = IMUF_DONE_CALIBRATING;
} else if (isImufCalibrating == IMUF_DONE_CALIBRATING) {
txCmd->command = 0;
txCmd->crc = 0;
imufEndCalibration();
} else if (isSetpointNew) {
txCmd->command = IMUF_COMMAND_SETPOINT;
txCmd->param1 = getSetpointRateInt(0);
txCmd->param2 = getSetpointRateInt(1);
txCmd->param3 = getSetpointRateInt(2);
txCmd->crc = getCrcImuf9001((uint32_t *)imufTxBuf, 11);
isSetpointNew = 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clear stale IMUF commands before preparing each transfer.

After a setpoint update, isSetpointNew is cleared but imufTxBuf still contains IMUF_COMMAND_SETPOINT, so later “no command” reads keep resending the last setpoint command.

Proposed fix
 FAST_CODE void imufPrepareDmaRead(gyroDev_t *gyro) {
     imufCommand_t *txCmd = (imufCommand_t *)imufTxBuf;
+    memset(imufTxBuf, 0, sizeof(imufCommand_t));
     if (isImufCalibrating == IMUF_IS_CALIBRATING) {
-        memset(imufTxBuf, 0, sizeof(imufCommand_t));
         txCmd->command = IMUF_COMMAND_CALIBRATE;
         txCmd->crc     = getCrcImuf9001((uint32_t *)imufTxBuf, 11);
         isImufCalibrating = IMUF_DONE_CALIBRATING;
     } else if (isImufCalibrating == IMUF_DONE_CALIBRATING) {
-        txCmd->command = 0;
-        txCmd->crc     = 0;
         imufEndCalibration();
     } else if (isSetpointNew) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FAST_CODE void imufPrepareDmaRead(gyroDev_t *gyro) {
imufCommand_t *txCmd = (imufCommand_t *)imufTxBuf;
if (isImufCalibrating == IMUF_IS_CALIBRATING) {
memset(imufTxBuf, 0, sizeof(imufCommand_t));
txCmd->command = IMUF_COMMAND_CALIBRATE;
txCmd->crc = getCrcImuf9001((uint32_t *)imufTxBuf, 11);
isImufCalibrating = IMUF_DONE_CALIBRATING;
} else if (isImufCalibrating == IMUF_DONE_CALIBRATING) {
txCmd->command = 0;
txCmd->crc = 0;
imufEndCalibration();
} else if (isSetpointNew) {
txCmd->command = IMUF_COMMAND_SETPOINT;
txCmd->param1 = getSetpointRateInt(0);
txCmd->param2 = getSetpointRateInt(1);
txCmd->param3 = getSetpointRateInt(2);
txCmd->crc = getCrcImuf9001((uint32_t *)imufTxBuf, 11);
isSetpointNew = 0;
}
FAST_CODE void imufPrepareDmaRead(gyroDev_t *gyro) {
imufCommand_t *txCmd = (imufCommand_t *)imufTxBuf;
memset(imufTxBuf, 0, sizeof(imufCommand_t));
if (isImufCalibrating == IMUF_IS_CALIBRATING) {
txCmd->command = IMUF_COMMAND_CALIBRATE;
txCmd->crc = getCrcImuf9001((uint32_t *)imufTxBuf, 11);
isImufCalibrating = IMUF_DONE_CALIBRATING;
} else if (isImufCalibrating == IMUF_DONE_CALIBRATING) {
imufEndCalibration();
} else if (isSetpointNew) {
txCmd->command = IMUF_COMMAND_SETPOINT;
txCmd->param1 = getSetpointRateInt(0);
txCmd->param2 = getSetpointRateInt(1);
txCmd->param3 = getSetpointRateInt(2);
txCmd->crc = getCrcImuf9001((uint32_t *)imufTxBuf, 11);
isSetpointNew = 0;
}
🤖 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 225 - 243, Clear the
stale IMUF transfer state in imufPrepareDmaRead so old commands are not resent
on later reads. After handling IMUF_COMMAND_SETPOINT, make sure imufTxBuf is
reset to a no-command payload (and CRC cleared if needed) before the next DMA
transfer, similar to the IMUF_DONE_CALIBRATING branch. Use the existing symbols
imufPrepareDmaRead, imufTxBuf, imufCommand_t, and isSetpointNew to locate the
fix.

…serts

CodeRabbit PR emuflight#1283 review findings:
- imufPrepareDmaRead unconditionally clears imufTxBuf before each transfer,
  so a stale IMUF_COMMAND_SETPOINT is no longer resent after isSetpointNew
  is cleared (matches behavior gap present since the original USE_DMA_SPI_DEVICE
  code, now fixed in the migrated path)
- Restore STATIC_ASSERT guards on imufTxBuf/imufRxBuf sizes against
  imufCommand_t/imufData_t, equivalent to the asserts removed with dma_spi.c
@nerdCopter

Copy link
Copy Markdown
Member Author

@coderabbitai, AI generated comment

Addressed findings from review #4603838966:

  • Stale setpoint command (Major, accgyro_mpu.c:225-243): Fixed in 9b25898. imufPrepareDmaRead now unconditionally memsets imufTxBuf to zero at function entry before the calibration/setpoint branches, so a stale IMUF_COMMAND_SETPOINT (with old param/CRC values) is no longer resent on subsequent reads after isSetpointNew clears. Applied your suggested diff as-is.

  • Missing buffer-size static asserts (Nitpick, accgyro_mpu.c:191-192): Fixed in 9b25898. Restored STATIC_ASSERT(sizeof(imufTxBuf) >= sizeof(imufCommand_t), ...) and the RX equivalent, matching the asserts that existed against dmaTxBuffer/dmaRxBuffer before dma_spi.c was removed.

  • EXTI gating only on USE_GYRO_IMUF9001 build flag (Major, accgyro_mpu.c:146-153): Confirmed false positive — not fixed. Checked all three IMUF9001 targets (HELIOSPRING, STRIXF10, MODE2FLUX); none define a second SPI gyro or USE_DUAL_GYRO alongside USE_GYRO_IMUF9001, so the generic GYRO_USES_SPI path is never reachable in the same firmware image as IMUF9001. This also mirrors master's exact pre-existing pattern: master's mpuIntExtiHandler gates exclusively on #ifdef USE_DMA_SPI_DEVICE (build flag, not runtime sensor detection) with an identical all-or-nothing #else for the generic SPI path. No regression introduced by this PR; deferring the runtime-detection refactor as out of scope for a migration PR.

Build-verified on HELIOSPRING after both fixes (clean compile, RAM/FLASH unchanged).

@coderabbitai

coderabbitai Bot commented Jun 30, 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
fd accgyro_mpu.c
echo "----"
sed -n '1,260p' src/main/drivers/accgyro/accgyro_mpu.c | grep -n "imufTxBuf\|imufRxBuf\|STATIC_ASSERT\|imufPrepareDmaRead\|memset" 
echo "----diff context----"
git log --oneline -10

Length of output: 2345


@nerdCopter, thanks for the detailed follow-up. I verified both fixes directly against commit 9b25898:

  • Stale setpoint command: Confirmed. imufPrepareDmaRead now unconditionally zeroes imufTxBuf at line 229 before the calibration/setpoint branches (lines 232/241), so a previously-set IMUF_COMMAND_SETPOINT payload can no longer leak into a later, unrelated transfer.
  • Missing buffer-size static asserts: Confirmed. Lines 193-194 restore STATIC_ASSERT(sizeof(imufTxBuf) >= sizeof(imufCommand_t), ...) and the RX equivalent, matching the protection that existed against the old dmaTxBuffer/dmaRxBuffer.

On the EXTI gating point: your reasoning is sound — since none of the three IMUF9001 targets pair USE_GYRO_IMUF9001 with a second SPI gyro or USE_DUAL_GYRO, the generic GYRO_USES_SPI branch is indeed unreachable in those builds, and this matches master's pre-existing USE_DMA_SPI_DEVICE all-or-nothing gating pattern. I agree this isn't a regression introduced by this PR and is fine to defer as out of scope for this migration. Withdrawing that finding.

Thanks for the build verification on HELIOSPRING as well.


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

Thank you for using CodeRabbit!

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.

1 participant