Skip to content

Maintenance 9.x to master#11574

Merged
sensei-hacker merged 153 commits into
masterfrom
maintenance-9.x
May 20, 2026
Merged

Maintenance 9.x to master#11574
sensei-hacker merged 153 commits into
masterfrom
maintenance-9.x

Conversation

@sensei-hacker
Copy link
Copy Markdown
Member

No description provided.

gismo2004 and others added 30 commits December 18, 2025 12:07
This commit fixes the critical bugs that caused PR #11025 to be reverted
(PR #11139). The original implementation caused telemetry stream corruption
by emitting malformed frames when sensors were unavailable.

Root Cause:
The original PR scheduled telemetry frames unconditionally (if feature
compiled in) but frame functions only wrote data when sensors were available.
This resulted in frames containing only [SYNC][CRC] instead of the proper
[SYNC][LENGTH][TYPE][PAYLOAD][CRC] structure, corrupting the CRSF protocol
stream and breaking ALL telemetry (not just new frames).

Fixes Implemented:

1. RPM Frame (Bug #1 - CRITICAL):
   - Added conditional scheduling: only schedule if ESC_SENSOR_ENABLED
     and motorCount > 0
   - Added protocol limit enforcement: max 19 RPM values per CRSF spec
   - Location: src/main/telemetry/crsf.c:705-707, 337-343

2. Temperature Frame (Bug #2 - CRITICAL):
   - Added conditional scheduling: only schedule if temperature sources
     are actually available (ESC sensors OR temperature sensors)
   - Added bounds checking: prevent buffer overflow beyond 20 temperatures
   - Location: src/main/telemetry/crsf.c:709-732, 361-381

3. Buffer Overflow Protection (Bug #4):
   - Added MAX_CRSF_TEMPS constant and bounds checks in loops
   - Prevents array overflow if >20 temperature sources configured
   - Location: src/main/telemetry/crsf.c:361, 368, 376

4. Protocol Limit Enforcement (Bug #5):
   - Added MAX_CRSF_RPM_VALUES constant (19 per CRSF spec)
   - Clamp motorCount to protocol limit before sending
   - Location: src/main/telemetry/crsf.c:337, 342-344

Implementation Pattern:
Follows the GPS frame pattern:
  ✓ Conditional scheduling - only schedule if sensor available
  ✓ Unconditional writing - always write complete frame data when called

Testing:
- Code compiles successfully (verified with SITL build)
- No syntax errors or warnings
- All fixes follow existing code patterns in crsf.c

Related Issues:
- Original PR: #11025 (merged Nov 28, 2025, reverted same day)
- Revert PR: #11139
- Investigation: claude/developer/sent/pr11025-root-cause-analysis.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Correct the SPI register address masking in busWriteBuf() to clear the
MSB instead of setting it. For SPI protocol, the MSB indicates read (1)
or write (0) operations. Write operations should use (reg & 0x7F) to
clear the MSB, not (reg | 0x80) which sets it.

This matches the correct implementation in busWrite() at line 318 which
uses (reg & 0x7F) for write operations.

Note: This bug affects theoretical future SPI devices using buffer writes.
Current devices using busWriteBuf() are all I2C-based (VL53L0X, VL53L1X,
MLX90393, TERARANGER_EVO, US42 rangefinders) and are unaffected.

Fixes #10674
This approach doesn't prevent DShot interruption during EEPROM writes.
Committing for potential future refinement.

Changes:
- Added impl_timerPWMSetDMACircular() to switch DMA mode at runtime
- Modified processDelayedSave() to use circular mode during writeEEPROM()
- Called pwmCompleteMotorUpdate() 3x to latch DShot 0 packets

Issue: DShot still shows gaps during settings save on oscilloscope.

Next approach: Test with simple GPIO high instead of DShot.
Move circular DShot DMA code from processDelayedSave() to writeConfigToEEPROM().
This ensures the fix works for MSP_EEPROM_WRITE commands, not just delayed saves.

The MSP call path is:
MSP_EEPROM_WRITE → writeEEPROM() → writeConfigToEEPROM() → writeSettingsToEEPROM()

The previous commit (a6ba116) had circular DMA in processDelayedSave(),
which is only called for delayed saves (on disarm), not MSP commands.

Changes:
- Move circular DMA setup to writeConfigToEEPROM() in config_eeprom.c
- Remove unused pwmSetMotorPinsHigh() function
- Add pwm_output.h include to config_eeprom.c

Test method:
- MSP_EEPROM_WRITE command sent once per second
- DShot signal monitored on oscilloscope
- Confirmed: DShot no longer interrupted during settings save

Issue: #10913
Related: #9441
Based on code-reviewer agent feedback:

1. Add missing AT32 platform implementation
   - Implement impl_timerPWMSetDMACircular() for AT32F43x targets
   - Uses AT32 loop_mode (ctrl_bit.lm) instead of DMA_SxCR_CIRC

2. Remove duplicate circular DMA code from config.c
   - processDelayedSave() calls writeEEPROM() which calls writeConfigToEEPROM()
   - writeConfigToEEPROM() already has circular DMA protection
   - Removed redundant nested enable/disable from config.c

3. Add ATOMIC_BLOCK protection to DMA mode switch
   - Consistent with existing impl_timerPWMStopDMA() pattern
   - Prevents interrupt interference during DMA reconfiguration
   - Applied to HAL, StdPeriph, and AT32 implementations

Issue: #10913
Critical fixes for STM32H7 DMA circular mode:
- Wait for EN bit to actually clear before changing mode (was the primary bug)
- Disable/re-enable timer DMA requests during reconfiguration
- Reload DMA transfer count after mode change
- Clear pending DMA flags

Without these changes, the mode change was being ignored because the DMA
stream was still active when we tried to modify the configuration.
SITL doesn't have real PWM/motor hardware, so pwmSetMotorDMACircular()
and pwmCompleteMotorUpdate() don't exist in SITL builds.

Wrap these calls with #if \!defined(SITL_BUILD) to allow SITL builds to
compile while preserving the ESC spinup fix for hardware builds.
Address qodo-code-review feedback: Add defensive timeout checks when
waiting for DMA streams/channels to disable before reconfiguring.

Changes:
- H7 (timer_impl_hal.c): Check if timeout expired and abort if DMA
  still enabled
- F4/F7 (timer_impl_stdperiph.c): Add wait loop for EN bit to clear
  with timeout check
- AT32 (timer_impl_stdperiph_at32.c): Add wait loop for chen bit to
  clear with timeout check

This prevents potential race conditions where DMA configuration could
be modified while the stream is still active, which could cause
unstable behavior.
Some hardware targets don't compile DShot support, causing linker
errors when trying to call pwmSetMotorDMACircular() and
pwmCompleteMotorUpdate().

Change guard from:
  #if \!defined(SITL_BUILD)
To:
  #if \!defined(SITL_BUILD) && defined(USE_DSHOT)

This ensures the functions are only called on targets that actually
have DShot compiled in, fixing build failures on targets like
BEEROTORF4.
- config.c: Remove comment about circular DMA protection location
  (obvious from context)
- timer_impl_stdperiph_at32.c: Remove redundant comment about loop mode
  (already clear from 'Enable loop mode' / 'Disable loop mode' comments)
STM32F722-based target for the original (discontinued) Nexus,
distinct from the Nexus-X/XR (NEXUSX target). Pin mapping derived
from Rotorflight NEXUS_F7 dump and verified on hardware.

Key hardware support:
- ICM-42688-P IMU on SPI1 (CW90 alignment, EXTI PA15)
- SPL06 barometer on I2C1 (PB8/PB9, separate from external I2C2)
- W25N01G 128MB blackbox flash on SPI2
- 5 servo/motor outputs (S1-S4 + M1), expandable to 7
- UART4 with TX/RX swap for CRSF receiver on Port A
- UART1 on PA9/PA10 (not PB6/PB7 as on Nexus-X/XR)

Also adds USE_UART4_SWAP support in serial_uart_hal.c for the
STM32 HAL advanced UART pin swap feature.
GPS3DspeedFiltered was computed every PID cycle (1 kHz) from
gpsSol.velNED[], which only changes when the GPS task delivers a new
fix (~10 Hz). Move the sqrt and pt1 filter update into
imuCalculateTurnRateacceleration(), gate them on gpsHeartbeat change,
and make the filter state a static local. The variable is only
consumed in that function (fixed-wing + GPS-trustworthy path), so the
module-level FASTRAM variables and the imuInit() reset are also removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pidController is FAST_CODE. Its callees that lacked FAST_CODE created
SRAM veneers (trampolines from ITCM to flash) on every call. Annotate
each callee that was audited as genuinely hot:

  pid.c:
    pTermProcess, dTermProcess, applyItermLimiting,
    pidApplySetpointRateLimiting, checkItermLimitingActive,
    checkItermFreezingActive, pidRcCommandToAngle, pidRcCommandToRate

  smith_predictor.c: applySmithPredictor

  filter.c: pt1ComputeRC (static; called from FAST_CODE pt1FilterApply4)

  maths.c: constrain, constrainf, scaleRangef
    (called from the pid.c functions above; candidates for static inline
    in a future refactor, but FAST_CODE resolves the veneer for now)

  fc_core.c: getAxisRcCommand

Functions removed from FAST_CODE where no FAST_CODE caller existed
(sin_approx, cos_approx, fast_fsqrtf, failsafeShouldApplyControlInput)
were never added in this tree; no net change for those.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two independent optimisations for the main PID loop:

rcCommand caching (fc_core.c)
  getAxisRcCommand() and failsafeUpdateRcCommandValues() are now gated
  on isRXDataNew so they only run when the RX task delivers a fresh
  frame (~50 Hz) instead of every PID cycle (1 kHz / multirotor rate).
  rcCommand[] is updated from a small cache on every cycle so the rest
  of the code is unaffected.

pidSumLimit precomputation (pid.c)
  getPidSumLimit() returns 400 or 500 based on vehicle type and axis —
  values that are constant for the lifetime of a flight. Add a
  pidSumLimit field to pidState_t, initialise it once in pidInit(), and
  replace the two hot-path calls in pidApplyFixedWingRateController and
  pidApplyMulticopterRateController with a struct field read.
  getPidSumLimit() is retained for pid_autotune.c (not hot).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Address hardware testing feedback from daijoubu:
- VBAT_SCALE_DEFAULT 1100 → 320 (hardware-verified)
- Port C connector pinout: pin 3 is SDA/TX (PB11), pin 4 is SCL/RX (PB10)
- Clarify OG Nexus has no EXT-V input (Vin ADC only, unlike X/XR)
- Update verification checklist with confirmed test results

Co-Authored-By: Joshua Perry <josh@6bit.com>
The CW90 value was taken directly from the Rotorflight NEXUS_F7 target,
but Rotorflight does not enforce arrow-forward as the board reference
orientation. The Rotorflight CW90 was calibrated to an arbitrary
mounting, not the case arrow. iNAV convention is arrow = forward,
which requires CW180 on this hardware.

Same root cause as the NEXUSX fix in #11338 / #11325.

Co-Authored-By: Joshua Perry <josh@6bit.com>
FrSky F405 flight controller based on STM32F405RGT6.

Hardware: IIM-42688P gyro (SPI1), AT7456E OSD (SPI2), SD card (SPI3),
SPL06 barometer (I2C1), 9 motor outputs, hardware SBUS inverter on UART2,
LED strip on PA15. S7/S8 use TIM12 (PWM only, no DShot).
Signed-off-by: ncerzzk <huangcmzzk@gmail.com>
- Fix UART5_RX: PD12 → PD2 (only valid pin for UART5_RX on STM32F405)
- Move SPI3 to PB3/PB4/PB5, freeing PC10/PC11/PC12 for UART3 and UART5_TX
- Move UART3 to PC10/PC11 (was conflicting with I2C2 on PB10/PB11)
- Fix UART4_RX: PC5 → PA1 (PC5 is shared with RSSI ADC)
- Re-enable I2C2 on PB10/PB11 (now free after UART3 move)
- Add TARGET_IO_PORTD for PD2 (UART5_RX)
- Update SERIAL_PORT_COUNT to 7 (includes UART5)
- Remove config.c: UART2 has both standard and hardware-inverted SBUS pads
Per STM32F7 Reference Manual (RM0410 Section 8.3.5), writes to DMA_SxNDTR
and DMA_SxM0AR are silently ignored while the EN bit is asserted. After
calling LL_DMA_DisableStream(), EN does not clear synchronously -- the
hardware may still be completing an in-progress burst.

impl_timerPWMPrepareDMA() previously called LL_DMA_SetDataLength() and
LL_DMA_ConfigAddresses() immediately after LL_DMA_DisableStream() with no
wait. In the race window where EN is still 1, both writes are discarded,
leaving stale count and address in the DMA registers. The subsequent
LL_DMA_EnableStream() then fires DMA with the old (now incorrect) transfer
count and/or buffer address, producing garbled DShot frames.

Fix: add a bounded wait loop for EN to clear before reconfiguring. If EN
has not cleared after the timeout (indicating a hardware fault), skip the
reconfiguration entirely rather than proceeding with stale register values.

This race is most visible on STM32F765 targets, where the larger 16 KB
I-Cache keeps the interrupt handler hot path in cache, resulting in faster
execution that consistently lands within the ~5-18 ns EN clear window.
On STM32F745 (4 KB I-Cache), cache misses add stall cycles that naturally
extend the window enough for EN to clear before reconfiguration begins.
breadoven and others added 26 commits May 10, 2026 09:28
…wait

drivers: wait for DMA EN bit to clear before reconfiguring DShot DMA (F7/H7)
…ache

pid/filter: FAST_CODE annotations and RC command caching
Allow yaw reset to north when disarmed - for multirotors with no compass
…rs" (#11445)

Reverts PR #11445 (commit f6281a6, merge commit 4955d49) for 9.x
compatibility.

The two-pass priority algorithm is correct behavior but introduces a
backward-compatibility break: Configurator 9.0.1 re-implements the
firmware assignment algorithm in JS. Old Configurator + new firmware
(or vice versa) shows wrong pin assignments for targets with explicit
timerOverrides (e.g. OUTPUT_MODE_MOTORS / SERVOS in config.c).

To re-implement for 10.x without this problem:
  1. Merge this revert commit into maintenance-10.x (so both branches
     share the reverted state as their common ancestor).
  2. Apply the two-pass algorithm again as new commits on top of 10.x.
  3. Add MSP2_INAV_OUTPUT_ASSIGNMENT so the firmware reports final
     assignments directly — eliminates the JS/C algorithm duplication
     that caused this issue.
See: claude/developer/workspace/pwm-compat-project/project-plan-output-assignment-api.md
Revert "use explicitly assigned motor and servo timers before auto timers" (#11445)
…address

Please test on FRSKYPILOT.  Fix STM32F765xG linker scripts
added changes to max7456.c to add setting speed and removing spi_mod_0 flag with defines from target.h file
docs: Fix link case sensitivity in Telemetry.md
Add support for MS5525DSO digital airspeed sensor
Resolves blank OSD regression caused by missing multiFunctionWarning_t
definition from maintenance-9.x. Conflicts in osd.c resolved by:
- Keeping HEAD's osdWriteChar2/osdWriteChar optimization helpers
- Taking maintenance-9.x's brace style for if/else blocks
- Taking maintenance-9.x's strlen(buff) bug fix for RSSI stats itoa call
- Preserving HEAD's displayWrite call accidentally dropped by maintenance-9.x
Fix the issue where the default configuration parameters for the log
SITL target undefines USE_I2C, so the i2c member of the busdev union
does not exist in that build. Wrap the two address-assignment lines
with #ifdef USE_I2C so the code compiles without hardware I2C support.
fix(pitotmeter): guard MS5525 i2c address writes with USE_I2C for SITL builds
On HD displays with valid efficiency data and 4-digit (DJI compat) mode,
the efficiency string can reach 15 chars + null = 16 bytes, overflowing
outBuff[15] by one byte. Adding an explicit null-termination guard at the
end of all build paths prevents this and matches the inav3 reference.
Enables AddressSanitizer via -DASAN=ON. Use alongside -DDEBUG=ON for
readable stack traces. Follows the existing DEBUG flag pattern.
…terns

osd: replace common tfp_sprintf patterns with direct formatting helpers
@qodo-code-review
Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sensei-hacker sensei-hacker merged commit e630ecf into master May 20, 2026
32 of 33 checks passed
@github-actions
Copy link
Copy Markdown

Branch Targeting Suggestion

You've targeted the master branch with this PR. Please consider if a version branch might be more appropriate:

  • maintenance-9.x - If your change is backward-compatible and won't create compatibility issues between INAV firmware and Configurator 9.x versions. This will allow your PR to be included in the next 9.x release.

  • maintenance-10.x - If your change introduces compatibility requirements between firmware and configurator that would break 9.x compatibility. This is for PRs which will be included in INAV 10.x

If master is the correct target for this change, no action is needed.


This is an automated suggestion to help route contributions to the appropriate branch.

@github-actions
Copy link
Copy Markdown

Test firmware build ready — commit 0e298dd

Download firmware for PR #11574

234 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

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.