Sitelet https://github.com/betaflight/betaflight/pull/15214
Skip to content

STM32N6 platform support - #15214

Merged
blckmn merged 1 commit into
betaflight:masterfrom
blckmn:feat/n6570dk
May 13, 2026
Merged

blckmn merged 1 commit into
betaflight:masterfrom
blckmn:feat/n6570dk

Conversation

@blckmn

@blckmn blckmn commented May 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds STM32N6 platform support, with STM32N657DK_DSK320 as the first
working target. End-to-end on the dev kit: boot ROM → Betaflight
OpenBootloader (lib/main/STM32/n6_obl) → BF running from XSPI at
600 MHz, USB VCP enumerates as 0483:5740, CLI accessible. Companion
target-config PR: betaflight/config#1096.

What's new

  • lib/main/STM32N6/: CubeN6 v1.3.0 vendor SDK (HAL, CMSIS, USBD library, OpenBootloader middleware).
  • lib/main/CMSIS/Core/Include/: ARMv8.1-M / Cortex-M55 core headers (core_cm55.h, cachel1_armv7.h, pmu_armv8.h).
  • src/platform/STM32 (N6 family): startup, system init, linker scripts (XIP/LRUN), USB OTG_HS PHY bring-up (USB33RDY, FSEL, RIF master/slave attrs), SDIO RIF wiring, secure-alias DBGMCU writes (RIFSC drops NS-tagged writes), IO subsystem extended through GPIO port O.
  • lib/main/STM32/n6_obl/: Betaflight OpenBootloader. Custom signed FSBL loaded from XSPI nor0 0x0; sets up clocks, memory-maps XSPI, decides DFU-vs-jump from RCC->RSR (IWDGRSTF / LCKRSTF / WWDGRSTF), arms a 10 s IWDG before jumping to BF. DFU mode re-flashes BF over USB without touching BOOT0. Unbricking.md covers the full TSV recovery path via boot ROM system DFU.
  • BF ↔ OBL contract (ENABLE_BF_OBL, default off; N6 target.h opts in): BF refreshes OBL's IWDG from TASK_SERIAL; bl rom from CLI / MSP DFU spins with IRQs masked so IWDG routes the next boot to DFU; visible 1 Hz LED1 heartbeat in run().

Status

Working: boot path through OBL into BF, USB VCP + CLI, virtual gyro/acc/baro/mag stack so the scheduler runs, ADC1 + VBAT sense.

Not yet wired (TODOs in the per-board config):

  • Real LSM6DSK320X gyro/acc on SPI1 (RIF + secure peripheral alias setup pending).
  • LIS2MDL mag + LPS22DF baro on I2C1.
  • SDMMC2 (driver init wedges on STAR; pin/RIF infra is in).
  • LTDC LCD console (panel bring-up wedges the chip).

N6-specific quirks left as TODOs in the code:

  • unusedPinsInit() skipped on N6 — IOTraversePins wedges on at least one RIFSC-restricted port; needs the IO layer to learn about restricted ports.
  • .persistent_data doesn't survive NVIC_SystemReset(); CONFIG_IN_RAM CLI saves don't persist across reboot. Bake defaults into per-board config.h for now.

Marked draft until the real-sensor / SDIO / LCD TODOs are closed out.

Summary by CodeRabbit

  • New Features

    • Added Betaflight OpenBootloader integration with watchdog management
    • Expanded STM32N6 platform support with multiple boot modes (XIP, RAM-only, FSBL)
    • Added debug CLI memory read/write commands for advanced diagnostics
  • Bug Fixes

    • Fixed fault handler linker optimization issues
    • Corrected ADC DMA initialization sequence
  • Chores

    • Extended GPIO port support and configuration
    • Updated boot sequence and system initialization
    • Expanded USB/VCP configuration flexibility

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This pull request adds comprehensive STM32N6 microcontroller support including OpenBootloader (OBL) integration for bootloader handoff, multiple linker variants for different boot modes, enhanced system initialization with security configuration, expanded interrupt vector coverage, platform driver updates (ADC/USB/SDIO/OctoSPI/UART), GPIO port expansion from A-I to A-O, and diagnostic CLI commands for memory inspection.

Changes

STM32N6 Enablement and OpenBootloader Integration

Layer / File(s) Summary
Build system and feature gates
Makefile, src/platform/STM32/mk/STM32N6.mk, src/main/target/common_post.h
Makefile Git-revision guard updated to check for .git/ directory; STM32N6 build includes conditional linker-script selection (FSBL_FULL, XIP, RAM, or default LRUN), variant-specific DEVICE_FLAGS (N6_BF_AS_FSBL_BUILD, N6_XIP_BUILD, N6_RAM_ONLY_BUILD), and optimization flags; MMFLASH placement macros redirect code/data to RAM sections under USE_FLASH_MEMORY_MAPPED; new feature gates ENABLE_DEBUG_CLI_COMMANDS and ENABLE_BF_OBL with conditional include of the OBL contract header.
OBL watchdog contract and fault handling
src/main/drivers/bf_obl_contract.h, src/main/fc/faults.c, src/platform/common/stm32/fault_handlers.c
New OBL↔BF IWDG contract header defines BF_OBL_IWDG_REFRESH() macro that writes 0x0000AAAAU to IWDG->KR to trigger watchdog reload; systemFaultAction marked with __attribute__((used)) to prevent LTO removal; HardFault_Handler converted to __attribute__((naked)) assembly to capture fault registers (CFSR/HFSR/BFAR/MMFAR), stacked PC/LR, and faulting SP into AXISRAM2 before tail-branching to fault action.
System and clock initialization
src/platform/STM32/system_stm32n6xx.c, src/platform/STM32/target/STM32N657/target.h
SystemCoreClockUpdate() now decodes live RCC register state (HSI/HSE/IC1/PLL); memoryMappedModeInit() enables XSPI2 clock before reading memory-mapped-mode boot status; systemInit() expanded with secure-alias bring-up (debug/APB3/VTOR/NVIC/cache/FPU setup), RIFSC/GPIO TrustZone configuration, analog/voltage-monitor reconfiguration; new static SystemClock_Config() configures power/oscillator/PLL/IC routing and peripheral clocks; under ENABLE_BF_OBL, systemResetToBootloader() disables interrupts and spins indefinitely to trigger IWDG reset; target header removes unconditional USE_VCP/USE_USB_DETECT defaults (now per-board), removes TARGET_IO_PORTP/Q definitions, gates flash-chip macros, defaults ENABLE_BF_OBL to 1.
Startup assembly and interrupt vectors
src/platform/STM32/startup/startup_stm32n657xx.s
Reset_Handler gains early IRQ masking (NVIC/SysTick clear), MSPLIM/SP setup, AXISRAM clock enable via RCC NS-alias, and debug markers at key stages; external interrupt vector table rebuilt to follow strict CMSIS IRQn_Type ordering with .word 0 reserved slots; 50+ new external vectors added (security/crypto/display/DMA/I2C/timers/audio/USB/Ethernet/FDCAN); 100+ new weak IRQ aliases map symbols to Default_Handler.
Multiple linker scripts for boot variants
src/platform/STM32/link/STM32N657XX_FSBL_FULL.ld, src/platform/STM32/link/STM32N657XX_LRUN.ld, src/platform/STM32/link/STM32N657XX_RAM.ld, src/platform/STM32/link/STM32N657XX_XIP.ld
Four linker scripts support different boot modes: FSBL_FULL loads full BF as bootloader into AXISRAM; LRUN updated with revised XSPI flash map (FSBL/XIP/config regions) and LMA switches from FLASH1 to FLASH; RAM executes entirely from SRAM for debug (VMA=LMA); XIP places code in XSPI flash and copies mutable sections to AXISRAM at reset with contiguous copy mechanism.
Platform driver updates
src/platform/STM32/adc_stm32n6xx.c, src/platform/STM32/bus_octospi_stm32n6xx.c, src/platform/STM32/sdio_n6xx.c, src/platform/STM32/serial_uart_stm32n6xx.c, src/platform/STM32/vcp_hal/usbd_conf_stm32n6xx.c, src/platform/STM32/vcp_hal/usbd_cdc_interface.c
ADC disables after calibration before per-channel config; resolves DMA spec early and deactivates device if missing. OctoSPI skips sanity test when memory-mapped already enabled; allows graceful continue on RAM_ONLY_BUILD. SDIO expands pin routing, adds RIFSC/TrustZone secure/private GPIO and SDMMC configuration, updates HAL init. UART1 gains PE5/PE6 alternate pins. USB renames IRQ handler to USB1_OTG_HS_IRQHandler, rewrites MSP init with power monitor and PHY setup, updates PHY interface selection. VCP conditionally compiles CDC functions when USE_VCP is defined.
IO port expansion
src/platform/STM32/io_stm32.c, src/platform/common/stm32/io_def_generated.h, src/utils/def_generated.pl
Extend GPIO support from A-I to A-O: ioPortDefs for STM32N6 uses designated-index initializer mapping ports A-H to indices 0-7, skip 8-12, place N-O at 13-14, omit P-Q due to ioTag_t overflow; io_def_generated.h adds mask/count/offset macros and per-pin families for J-O; generator expands port list and output directory.
Application layer integration
src/main/main.c, src/main/fc/init.c, src/main/fc/tasks.c
Relocate LCD console printf setup from early init to post-initPhase3; add STM32N6-only LED1 heartbeat toggling at intervals; skip unusedPinsInit() for STM32N6; call BF_OBL_IWDG_REFRESH() in serial task to keep watchdog alive.
Debug CLI memory commands
src/main/cli/cli.c
Add dxr (read) and dxw (write) CLI commands under ENABLE_DEBUG_CLI_COMMANDS: dxr parses hex address and optional count, forces 4-byte alignment, caps count at 64, performs volatile reads, prints each address:value; dxw parses address and value, forces alignment, performs write and readback, prints result.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

RN: NEW TARGET, RN: TARGET UPDATE

Suggested reviewers

  • haslinghuis
  • sugaarK
  • SteveCEvans
  • KarateBrot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% 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 and concisely summarizes the main addition: STM32N6 platform support, which is the primary focus of this changeset.
Description check ✅ Passed The pull request provides a comprehensive description covering summary, what's new, status, and working/pending features, though it diverges from the repository template by not following its required sections.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@github-actions

Copy link
Copy Markdown

Do you want to test this code? You can flash it directly from the Betaflight App:

  • Simply put #15214 (this pull request number) in the Select commit field in the Firmware Flasher tab (you need to Enable expert mode, Show release candidates and Development).

WARNING: It may be unstable. Use only for testing!

@blckmn
blckmn marked this pull request as ready for review May 10, 2026 22:28
Copilot AI review requested due to automatic review settings May 10, 2026 22:28
@blckmn blckmn self-assigned this May 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds initial STM32N6 (STM32N657) platform bring-up support, including XIP/LRUN linker variants and OpenBootloader (OBL) integration so Betaflight can boot from XSPI and provide USB VCP/DFU workflows on the N6570-DK.

Changes:

  • Extends STM32 IO/generation and N6 target configuration (including BF↔OBL contract enablement).
  • Adds/updates N6-specific startup, USB OTG_HS PHY + RIFSC configuration, SDMMC2 pin routing, and build/link selection logic.
  • Introduces N657 XIP linker script and updates OBL DFU descriptors to support BF-slot flashing and a debug upload region.

Reviewed changes

Copilot reviewed 63 out of 66 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Makefile Adjusts revision detection logic used for embedding build revision.
src/utils/def_generated.pl Extends GPIO port generation (A–O) and changes the output directory for generated IO defs.
src/main/main.c Changes init ordering for LCD console redirect and adds a scheduler-driven LED heartbeat in run().
src/platform/common/stm32/fault_handlers.c Adds BF↔OBL hardfault debug capture to a fixed RAM address when ENABLE_BF_OBL is enabled.
src/platform/STM32/vcp_hal/usbd_conf_stm32n6xx.c Fixes N6 USB IRQ vector name and adds N6 OTG_HS PHY power/reset/FSEL and RIFSC configuration.
src/platform/STM32/target/STM32N657/target.h Makes USB/VCP per-board config, adds OCTOSPI/XIP-related defines, and defaults ENABLE_BF_OBL on N657.
src/platform/STM32/startup/stm32n6xx_hal_conf.h Enables LTDC HAL module and includes the LTDC header.
src/platform/STM32/serial_uart_stm32n6xx.c Adds additional USART1 RX/TX pin options (PE6/PE5).
src/platform/STM32/sdio_n6xx.c Adds SDMMC2 pin routes for N6570-DK and configures RIFSC/GPIO security attributes to unblock SD init.
src/platform/STM32/mk/STM32N6.mk Adds N6 link-mode selection (RAM_ONLY/BF_AS_FSBL/XIP/LRUN) and related build-flag/optimization tweaks.
src/platform/STM32/link/STM32N657XX_LRUN.ld Updates LRUN linker script; DMA-related section handling changes.
src/platform/STM32/link/STM32N657XX_XIP.ld Adds N657 XIP linker script with a NOLOAD debug exchange region and RAM-resident clock-switch code path.
lib/main/STM32/n6_obl/openbootloader_conf.h Overrides OBL config to avoid CMSE-only secure alias macros and defines memory layout for OBL.
lib/main/STM32/n6_obl/usbd_conf.h Overrides OBL USBD config and documents DFU alt layout for BF flashing + debug upload.
lib/main/STM32/n6_obl/usbd_dfu_if.c Implements BF-slot DFU memory descriptor switching and exposes a fixed RAM region for debug uploads.
Comments suppressed due to low confidence (2)

src/platform/STM32/link/STM32N657XX_LRUN.ld:296

  • The .DMA_RW_D2 section is no longer marked NOLOAD. DMA scratch/descriptor regions are typically not loaded from FLASH (and startup code won’t initialize them), so making this a loadable section can create unexpected load/zeroing behavior and larger images. Align this with the other DMA sections by marking it NOLOAD (and only placing it in D2_RAM).
    Makefile:203
  • Revision detection now checks for a .git/ directory. In git worktrees/submodules, .git is commonly a file (not a directory), so this can incorrectly leave REVISION as norevision even though git metadata is available. Consider checking for .git (file or dir) or using git rev-parse --is-inside-work-tree to decide whether to compute REVISION.
REVISION := norevision
ifneq ($(wildcard .git/),)
ifeq ($(shell git diff --shortstat),)
REVISION := $(shell git rev-parse --short=9 HEAD)
endif
endif

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/main.c Outdated
Comment thread src/platform/common/stm32/fault_handlers.c
Comment thread lib/main/STM32/n6_obl/openbootloader_conf.h Outdated
Comment thread lib/main/STM32/n6_obl/usbd_conf.h
Comment thread lib/main/STM32/n6_obl/usbd_dfu_if.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/platform/STM32/startup/stm32n6xx_hal_conf.h (1)

23-37: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consolidate duplicated LTDC macro and include declarations.

HAL_LTDC_MODULE_ENABLED is defined twice (lines 23 and 36) and its include block appears twice (lines 157–159 and 197–199). Remove the first occurrence at line 23 and the first include block to eliminate redundancy and reduce preprocessor-warning risk.

Proposed cleanup diff
-#define HAL_LTDC_MODULE_ENABLED
 `#define` HAL_PCD_MODULE_ENABLED
-#ifdef HAL_LTDC_MODULE_ENABLED
-#include "stm32n6xx_hal_ltdc.h"
-#endif /* HAL_LTDC_MODULE_ENABLED */
-
 `#ifdef` HAL_PCD_MODULE_ENABLED
🤖 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/platform/STM32/startup/stm32n6xx_hal_conf.h` around lines 23 - 37, Remove
the duplicate HAL_LTDC_MODULE_ENABLED definition and its first corresponding
include block to avoid redundant preprocessor definitions and duplicate
includes; specifically, delete the earlier occurrence of the
HAL_LTDC_MODULE_ENABLED macro and the first LTDC-related include section so only
the later LTDC definition and its single include block (the LTDC + RIF block)
remain, ensuring references to HAL_RIF_MODULE_ENABLED and the intended LTDC
include are preserved.
src/platform/STM32/link/STM32N657XX_LRUN.ld (1)

286-296: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

.DMA_RW_D2 is loadable now, but it still has no flash LMA.

Dropping NOLOAD here does not make initialized .DMA_RW data work in the LRUN image. Without AT > FLASH plus a matching startup copy path, the section still has no XSPI-backed load image, so any non-zero initializer placed there will come up garbage after reset. Either keep it NOLOAD, or wire it up like the other initialized RAM sections.

🤖 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/platform/STM32/link/STM32N657XX_LRUN.ld` around lines 286 - 296, The
.DMA_RW_D2 section is currently loadable but lacks a flash LMA so initialized
data placed into .DMA_RW will be garbage at boot; either mark the section NOLOAD
to leave it uninitialized or give it an LMA and startup copy like other
initialized RAM sections by changing the .DMA_RW_D2 linker stanza to include "AT
> FLASH" (or mirror the existing pattern used for other initialized RAM regions)
and ensure the startup code performs a copy from the flash LMA to the RAM VMA
using the provided symbols (dmarw_start/dmarw_end/_sdmarw/_edmarw) so LRUN
images get a valid XSPI-backed load image and initializers are preserved.
🧹 Nitpick comments (13)
src/main/fc/init.c (1)

1073-1078: ⚡ Quick win

TODO acknowledged — consider raising this as a tracked issue.

Skipping unusedPinsInit() wholesale on N6 leaves any genuinely unused but writable pin floating, which is a small EMI / current-draw risk on production hardware and a footgun if someone later writes target code that depends on the floor-clean state this function provides on other STM32 families. The right long-term fix is probably for IOTraversePins (or its callees) to consult RIFSC RISC_SECCFGR/PRIVCFGR before poking MODER, but that's clearly outside the scope of this PR.

The #if !defined(STM32N6) guard is fine as a short-term workaround given the PR is already marked draft. Worth tracking explicitly so it doesn't get lost.

Want me to draft a small RIFSC-aware filter that IOTraversePins can call to skip restricted GPIO banks, and/or open a tracking issue for this TODO?

🤖 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/fc/init.c` around lines 1073 - 1078, The current `#if`
!defined(STM32N6) skips unusedPinsInit() on N6 which risks floating writable
pins; create a tracked issue for implementing a RIFSC-aware filter (so
IOTraversePins/IOConfigGPIO consult RISC_SECCFGR/PRIVCFGR and skip restricted
banks) and add a concise TODO comment in init.c referencing that issue ID and
explaining why the guard is temporary; include the symbols unusedPinsInit,
IOTraversePins, IOConfigGPIO, RISC_SECCFGR, and PRIVCFGR in the comment so
future work knows exactly what's required.
src/platform/STM32/sdio_n6xx.c (2)

747-750: 💤 Low value

Both SDMMCx_IRQHandler dispatch to the same hsd1 — fine today, fragile later.

Functionally correct given the active-controller selection in sdioPinConfigure(), but if anyone ever enables both SDMMC1 and SDMMC2 simultaneously (or stubs in a second handle), this collapses to a single handler and the wrong instance will be polled in IRQ context. Worth a // only one of these is wired up at a time — sdioHardware->instance picks the active controller comment so a future maintainer doesn't paste a second handle here without thinking.

🤖 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/platform/STM32/sdio_n6xx.c` around lines 747 - 750, Both SDMMC1/SDMMC2
IRQ handlers currently call HAL_SD_IRQHandler(&hsd1), which works now but is
fragile if both controllers are enabled; add a brief explanatory comment in
SDMMC2_IRQHandler (and SDMMC1_IRQHandler if present) noting that only one
controller is wired at a time and sdioHardware->instance selects the active
controller in sdioPinConfigure(), so the handler intentionally uses hsd1; if
future work enables both controllers, replace the hardcoded hsd1 usage with
instance-aware dispatching (e.g., choose the correct SD handle based on
sdioHardware->instance) rather than copying the same handle.

197-206: ⚡ Quick win

Fix the misleading comment on PRIVCFGR.

The trailing comment // NPRIV = 0 reads as if there is a field called NPRIV that you're setting to 0. The GPIOx_PRIVCFGR field is PRIV — clearing the bit means "non-privileged access allowed". The current text inverts the polarity of what you're actually writing.

📝 Suggested wording
-    port->SECCFGR  |=  mask;     // SEC = 1
-    port->PRIVCFGR &= ~mask;     // NPRIV = 0
+    port->SECCFGR  |=  mask;     // PRIV pin marked secure
+    port->PRIVCFGR &= ~mask;     // PRIV = 0 → non-privileged access allowed
🤖 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/platform/STM32/sdio_n6xx.c` around lines 197 - 206, In sdioRifMarkPin,
the trailing comment on the PRIVCFGR write is misleading; instead of saying
“NPRIV = 0” update the comment for port->PRIVCFGR &= ~mask to correctly reflect
that clearing the PRIV bit allows non-privileged access (e.g. “PRIV = 0 →
non-privileged access allowed”); keep references to IO_GPIO, IO_Pin, SECCFGR and
PRIVCFGR so the change is localized to the comment in sdioRifMarkPin.
src/platform/STM32/system_stm32n6xx.c (3)

161-167: 💤 Low value

NVIC ICER/ICPR loop count consistency with startup_stm32n657xx.s.

The C loop here correctly uses sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]), but the corresponding assembly loop in startup_stm32n657xx.s (lines 67 and 74) hardcodes r3 = #16``. Cortex-M55 with up to 480 IRQs needs 15 registers; 16 is fine and harmless (writes to reserved tail are ignored), but the two loops are now visually inconsistent on a "what's the iteration count" question. Either drop the assembly down to a CMSIS-derived constant (via .equ from a generated header) or accept the duplication with a one-line comment in both spots cross-referencing each other.

🤖 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/platform/STM32/system_stm32n6xx.c` around lines 161 - 167, The NVIC
register-clear loops in system_stm32n6xx.c (the for loop using
sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0])) and the hardcoded loop count in
startup_stm32n657xx.s are inconsistent; fix by either (A) making the assembly
use a shared CMSIS-derived constant (add a .equ in the generated header and
reference it in startup_stm32n657xx.s) or (B) add a one-line comment in both
places cross-referencing each other and explaining why the assembly uses 16
iterations (writes to reserved tail are ignored) so the visual discrepancy is
documented; update the comment in system_stm32n6xx.c near the
NVIC->ICER/NVIC->ICPR loop or modify startup_stm32n657xx.s to reference the
shared constant accordingly.

308-432: 💤 Low value

Every HAL failure path is an unrecoverable while (1); — at least leave a fingerprint.

There are seven while (1); spins inside SystemClock_Config() (lines 321, 324, 337, 350, 368, 397, 421), each on a distinct HAL call. The PR design is "let the OBL/IWDG path recover" (and the comment in systemResetToBootloader at 452-462 confirms this is intentional), but on a cold boot before IWDG is armed there's nothing differentiating which spin you ended up in.

For bring-up reliability, write a marker into the .bf_obl_dbg debug buffer before each spin so the OBL @DBGRAM DFU alt can tell the host which HAL step failed:

♻️ Sketch (optional)
+#define BF_OBL_FAULT(code)  do { *(volatile uint32_t *)0x24100130UL = (code); __DSB(); } while (0)
     ...
-    if (HAL_PWREx_ConfigSupply(PWR_EXTERNAL_SOURCE_SUPPLY) != HAL_OK) {
-        while (1);
-    }
+    if (HAL_PWREx_ConfigSupply(PWR_EXTERNAL_SOURCE_SUPPLY) != HAL_OK) {
+        BF_OBL_FAULT(0xDEAD0001);
+        while (1);
+    }
🤖 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/platform/STM32/system_stm32n6xx.c` around lines 308 - 432,
SystemClock_Config contains seven unrecoverable spins; before each while (1);
write a distinct marker into the .bf_obl_dbg debug buffer so the OBL DFU alt can
identify which HAL call failed. Concretely: in SystemClock_Config, immediately
before the failure branches that follow HAL_PWREx_ConfigSupply,
HAL_PWREx_ControlVoltageScaling, HAL_RCC_OscConfig (first HSI block),
HAL_RCC_ClockConfig (HSI clock switch), HAL_RCC_OscConfig (HSE/PLL bring-up),
HAL_RCC_ClockConfig (PLL distribution), and HAL_RCCEx_PeriphCLKConfig, store a
unique byte/word identifier into the .bf_obl_dbg area (use distinct values per
location) and ensure the write is non-blocking/simple memory write so it will
persist through the spin; keep the existing while (1); spins unchanged
otherwise. Use the .bf_obl_dbg symbol and SystemClock_Config/HAL_* function
names to find the spots and pick unique markers for each failure path.

121-142: ⚡ Quick win

Magic addresses for secure-alias RCC / DBGMCU — promote to named defines.

The literals 0x56028A48, 0x56028248, 0x56028A44, 0x56028244, 0x54001004 (and bit positions (1<<0)..(1<<20) for DBGMCU.CR) are repeated verbatim at lines 134-142 and again at lines 295-301 after the clock-reconfig re-assert. The comments at lines 123-133 carry the only documentation for what each address/bit means. A single block of #defines at the top of the file (or in platform.h) eliminates the duplication, the typo risk, and makes the comment-as-documentation actually link to symbols that will outlive future re-reads.

♻️ Sketch
+// Secure-alias register accesses (RIFSC-gated; HAL/CMSIS macros emit NS aliases
+// that get silently dropped in the no-mcmse build). See OBL's system_stm32n6xx_obl.c
+// for the matching writes.
+#define RCC_S_MISCENSR      (*(volatile uint32_t *)0x56028A48UL)
+#define RCC_S_MISCENR       (*(volatile uint32_t *)0x56028248UL)
+#define RCC_S_BUSENSR       (*(volatile uint32_t *)0x56028A44UL)
+#define RCC_S_BUSENR        (*(volatile uint32_t *)0x56028244UL)
+#define DBGMCU_S_CR         (*(volatile uint32_t *)0x54001004UL)
+#define DBGMCU_CR_DBGCLKEN  (1UL << 20)
+#define DBGMCU_CR_DBG_SLEEP (1UL <<  0)
+#define DBGMCU_CR_DBG_STOP  (1UL <<  1)
+#define DBGMCU_CR_DBG_STBY  (1UL <<  2)
+#define RCC_BUSENSR_APB3ENS (1UL << 10)
🤖 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/platform/STM32/system_stm32n6xx.c` around lines 121 - 142, The magic
secure-alias addresses and DBGMCU.CR bit positions used in systemInit (and later
re-assert) are hard-coded and duplicated; create clear named constants (e.g.,
RCC_S_MISCENSR_SECURE_ALIAS, RCC_S_MISCENR_NS_ALIAS, RCC_S_BUSENSR_SECURE_ALIAS,
RCC_S_BUSENR_NS_ALIAS, DBGMCU_S_CR_SECURE_ALIAS and
DBGMCU_CR_DBGCLKEN/DBG_SLEEP/DBG_STOP/DBG_STANDBY bit masks) at the top of this
file or in the platform header, replace the literal constants and (1<<N)
expressions in systemInit and the re-assert block with those defines, and update
the existing comment to reference the new symbols so the meanings aren’t lost
and duplication/typo risk is removed (use the existing systemInit and re-assert
code locations to find all occurrences).
src/platform/STM32/link/STM32N657XX_RAM.ld (2)

223-233: ⚡ Quick win

.DMA_RW_D2 missing NOLOAD — inconsistent with XIP/FSBL variants.

STM32N657XX_XIP.ld (line 260) and STM32N657XX_FSBL_FULL.ld (line 228) both mark this section (NOLOAD); this one does not. Practically benign for an empty section, but gdb will zero-fill the D2_RAM region on load when this script is used, and any future input section landing in .DMA_RW would gain unexpected initializer bytes embedded in the .elf.

♻️ Proposed fix
-  .DMA_RW_D2 :
+  .DMA_RW_D2 (NOLOAD) :
   {
     . = ALIGN(32);
     PROVIDE(dmarw_start = .);
🤖 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/platform/STM32/link/STM32N657XX_RAM.ld` around lines 223 - 233, The
.DMA_RW_D2 output section should be marked NOLOAD to match the XIP/FSBL variants
and prevent gdb from zero-filling D2_RAM or embedding unexpected initializers;
update the linker script section definition for ".DMA_RW_D2" (the block that
sets PROVIDE(dmarw_start), _sdmarw, _dmarw_start__, KEEP(*(.DMA_RW)),
PROVIDE(dmarw_end), _edmarw, _dmarw_end__) to include the NOLOAD attribute while
keeping the existing alignment and >D2_RAM placement.

40-46: 💤 Low value

__config_start aliased to eepromData — couple the 4096 size to the array.

__config_end - __config_start = 4096 is hardcoded here while the actual eepromData[] size lives in C. If anyone tweaks EEPROM_SIZE without updating this script, isEEPROMStructureValid() walks past the array into adjacent .bss / .sram2. A _Static_assert(sizeof(eepromData) == EEPROM_SIZE, ...) near the array definition catches the drift at compile time, and there's a per-PR known caveat already noted in the PR objectives that CONFIG_IN_RAM saves aren't persistent across NVIC_SystemReset().

🤖 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/platform/STM32/link/STM32N657XX_RAM.ld` around lines 40 - 46, The linker
hardcodes 4096 for __config_end which can drift from the actual eepromData[]
size; instead expose the array size from C and use that symbol in the linker.
Add a C definition like `const unsigned long __eeprom_size = EEPROM_SIZE;` (or
use sizeof(eepromData) with a _Static_assert) and then change the linker
expressions for __config_start / __config_end to use ABSOLUTE(eepromData) and
ABSOLUTE(eepromData) + __eeprom_size (referencing symbols __config_start,
__config_end, eepromData, and __eeprom_size) so the linker-bound range always
matches the real array size.
src/platform/STM32/link/STM32N657XX_FSBL_FULL.ld (1)

26-58: 💤 Low value

LOAD_RAM length is a magic 521728 — explain or derive.

Line 33: LENGTH = 521728. This is 512K - 0x400 (the boot-ROM header offset), but a reader has to do the arithmetic. Using 512K - 0x400 or a small _FSBL_HEADER_SIZE = 0x400; symbol makes the intent self-documenting and protects against silent breakage if anyone changes ORIGIN.

♻️ Proposed fix
-    /* Loaded image — boot ROM lands the .stm32 payload here. AXISRAM2
-     * runs from 0x34100000-0x341FFFFF; the FSBL slot starts at the 0x80400
-     * boot-ROM offset, leaving 521 728 bytes (509 KiB) of usable space. */
-    LOAD_RAM (rwx)    : ORIGIN = 0x34180400, LENGTH = 521728
+    /* Loaded image — boot ROM lands the .stm32 payload here. AXISRAM2 runs
+     * from 0x34100000-0x341FFFFF; the FSBL slot starts at the 0x80400
+     * boot-ROM header offset, leaving 509 KiB of usable space. */
+    LOAD_RAM (rwx)    : ORIGIN = 0x34180400, LENGTH = (512K - 0x400)
🤖 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/platform/STM32/link/STM32N657XX_FSBL_FULL.ld` around lines 26 - 58, The
LENGTH value for LOAD_RAM is a magic number (521728) representing 512K minus the
FSBL header offset; replace the hardcoded 521728 with a self-documenting
expression or symbol (e.g., define _FSBL_HEADER_SIZE = 0x400 or an equivalent
_FSBL_SLOT_OFFSET) and compute LENGTH as 512K - _FSBL_HEADER_SIZE (or
ORIGIN-dependent calculation) in the MEMORY entry for LOAD_RAM so the intent is
clear and changes to offsets won’t silently break; update the comment near
LOAD_RAM/ORIGIN (and any related symbols like LOAD_RAM, ORIGIN, or the new
_FSBL_HEADER_SIZE) to reflect the derivation.
src/platform/STM32/startup/startup_stm32n657xx.s (3)

131-141: 💤 Low value

AXISRAM-clock-enable bitmask 0x18F — same job is done again in systemInit(); the duplication is justified but worth a one-line note.

The startup pre-enables AXISRAM1..6 here at the NS alias (0x46028A4C), then system_stm32n6xx.c:275-280 re-enables each one individually through CMSIS macros. Both writes are needed (this one so the .bss zero loop doesn't fault before any C runs; the C-side one to also touch the secure alias path for HAL bookkeeping), but a future reader will rightly ask "why twice?". Add a cross-reference:

 /* Enable AXISRAM1..6 clocks before any AXISRAM1+ store. The boot ROM only
  * clocks AXISRAM2 when it hands off to a signed FSBL; .bss, the stack and
  * the DMA-capable regions live in AXISRAM1 / AXISRAM3+ and the next loop
  * (.bss zero) faults if the RAM is unclocked. Idempotent for the FSBL-stub
  * handoff and SWD-load paths where AXISRAM1 is already clocked. Writes
  * RCC->MEMENSR via the NS alias (0x4602_8A4C); the register itself is
- * shared regardless of alias. */
+ * shared regardless of alias.
+ *
+ * NOTE: systemInit() in system_stm32n6xx.c re-enables these individually
+ * through CMSIS macros — this pre-enable is mandatory only here because
+ * the .bss zero loop below executes before any C code runs. */
   ldr   r2, =0x46028A4C
   ldr   r3, =0x0000018F   /* AXISRAM3..6 (b3:0) | AXISRAM1 (b7) | AXISRAM2 (b8) */
🤖 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/platform/STM32/startup/startup_stm32n657xx.s` around lines 131 - 141, Add
a one-line comment immediately above the "post-AXISRAM-clock-enable" write (the
ldr/str sequence to 0x46028A4C setting 0x0000018F) explaining that this
non-secure-side bulk AXISRAM1..6 enable is required so the .bss zeroing in early
startup won't fault, and that systemInit() later re-enables each AXISRAM via
CMSIS macros (see systemInit) to also touch the secure alias for HAL
bookkeeping—this cross-reference clarifies why the same 0x18F mask is written
twice.

58-87: 💤 Low value

NVIC clear loop hardcodes r3 = #16`` — Cortex-M55 has 8 implemented ICER/ICPR registers; tail writes are wasted but harmless.

Cortex-M55 with up to 480 IRQs uses up to 15 ICER registers; the N6 CMSIS header (stm32n657xx.h) declares ICER[16] (CMSIS standard array size), so 16 iterations is what the C-side sizeof(NVIC->ICER)/sizeof(...) in system_stm32n6xx.c:163 resolves to. The two are consistent in count but inconsistent in how the count is expressed. For a small DX win, define it once:

♻️ Sketch
+  /* Match CMSIS ICER[]/ICPR[] array size — see core_cm55.h */
+  .equ NVIC_REGS, 16
   cpsid i
   movs  r0, `#0`
   ldr   r1, =0xFFFFFFFF
   ldr   r2, =0xE000E180   /* NVIC_ICER0 */
-  movs  r3, `#16`
+  movs  r3, `#NVIC_REGS`
   ...
   ldr   r2, =0xE000E280   /* NVIC_ICPR0 */
-  movs  r3, `#16`
+  movs  r3, `#NVIC_REGS`

Also, the comments "clear ISER[i]" (line 69) and "clear ISPR[i]" (line 76) describe the effect (Set-Enable / Set-Pending gets cleared), but the registers being written are ICER / ICPR. Worth a one-word tweak for the next reader:

-  str   r1, [r2]          /* clear ISER[i] */
+  str   r1, [r2]          /* write ICER[i] → clears ISER[i] enable bits */
🤖 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/platform/STM32/startup/startup_stm32n657xx.s` around lines 58 - 87,
Introduce a single named constant for the NVIC register count and use it instead
of hardcoding "movs r3, `#16`", and tweak the two comments to refer to ICER/ICPR
(the registers being written) rather than ISER/ISPR: add an assembly constant
(e.g. NVIC_REG_COUNT = 16) and load it into r3 before the two loops that write
0xE000E180 (NVIC_ICER0) and 0xE000E280 (NVIC_ICPR0), replace the two "clear
ISER[i]" / "clear ISPR[i]" comments with "clear ICER[i]" / "clear ICPR[i]" so
the code uses one authoritative count symbol (used by both loops) and the
comments accurately describe the registers being written.

93-122: ⚡ Quick win

Debug-marker base address 0x24100000 is duplicated between this file and the linker scripts — wire it through a single symbol.

STM32N657XX_XIP.ld:52 declares DBG_RAM at 0x24100000 (and STM32N657XX_FSBL_FULL.ld / _RAM.ld need to match the OBL @DBGRAM DFU alt). This startup file hardcodes the same base (and the offsets 0x80, 0x100, 0x104, 0x108, 0x10C, 0x110, 0x120) again. If DBG_RAM's ORIGIN ever moves (e.g. another secure/non-secure alias re-org), the linker, OBL, and this startup all drift independently.

Export the base from the linker script and load via PC-relative literal:

♻️ Sketch

In STM32N657XX_XIP.ld (and FSBL/RAM):

     DBG_RAM (rw)      : ORIGIN = 0x24100000, LENGTH = 512
+    /* exported so startup/OBL share the base */
+    __dbg_ram_base = 0x24100000;

In startup:

-  ldr   r3, =0xCABA0001
-  ldr   r2, =0x24100000
+  ldr   r2, =__dbg_ram_base
+  ldr   r3, =0xCABA0001
   str   r3, [r2]
-  ldr   r2, =0x24100080
+  add   r2, r2, `#0x80`
   str   r3, [r2]
   ...
🤖 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/platform/STM32/startup/startup_stm32n657xx.s` around lines 93 - 122,
Replace the hardcoded debug RAM base and offsets with the linker-exported
DBG_RAM symbol so the startup stays in sync with the linker scripts; in
startup_stm32n657xx.s (the block that writes CABA0001 and the self-PC), load the
base via a PC-relative literal referencing DBG_RAM (e.g., use LDR r2, =DBG_RAM)
and then write to DBG_RAM+0x80, DBG_RAM+0x100, +0x104, +0x108, +0x10C, +0x110
and store the PC to DBG_RAM+0x120 instead of using 0x24100000 and the hardcoded
offsets, ensuring the symbol name DBG_RAM is used for all addresses so any
ORIGIN change in STM32N657XX_XIP.ld (and matching FSBL/RAM/OBL) stays
consistent.
src/platform/STM32/link/STM32N657XX_XIP.ld (1)

145-196: ⚡ Quick win

Contiguity requirement is implicit — add an ASSERT to fail builds early.

The copy at Reset_Handler walks _stext.._etext and reads from _sitext (= LOADADDR(.ram_code)), assuming .ram_code → .data → .fastram_data → .dmaram_data are perfectly contiguous in both VMA and LMA with no gaps. Any future re-ordering or inserted section (NOLOAD or XIP-only) silently breaks the copy and produces a corrupt image — the comment at lines 135-141 is the only safeguard.

A linker ASSERT makes this enforcement compile-time:

♻️ Proposed safety net
   .dmaram_data :
   {
     . = ALIGN(32);
     PROVIDE(dmaram_start = .);
     _sdmaram = .;
     _dmaram_start__ = _sdmaram;
     _sdmaram_data = .;
     *(.dmaram_data)
     *(.dmaram_data*)
     . = ALIGN(32);
     _edmaram_data = .;
     _etext = .;
   } >RAM AT >FLASH
+
+  ASSERT((_etext - _stext) == (LOADADDR(.dmaram_data) + SIZEOF(.dmaram_data) - LOADADDR(.ram_code)),
+         "XIP: VMA span (_stext.._etext) must equal LMA span; non-contiguous copy region detected")
🤖 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/platform/STM32/link/STM32N657XX_XIP.ld` around lines 145 - 196, Add a
linker-time ASSERT to ensure .ram_code, .data, .fastram_data and .dmaram_data
are contiguous in memory so the Reset_Handler copy from _stext.._etext using
ram_code (LOADADDR(.ram_code) / _sitext) cannot be broken by future reordering;
specifically assert that ram_code_end == LOADADDR(.data) (or that ram_code_start
+ SIZEOF(.ram_code) == LOADADDR(.data)) and that the end of .data equals
LOADADDR(.fastram_data) (or _efastram_data == LOADADDR(.fastram_data)) and that
the end of .fastram_data equals LOADADDR(.dmaram_data) (or _edmaram_data ==
LOADADDR(.dmaram_data)), using the existing symbols ram_code_start,
ram_code_end, ram_code, _stext, _etext, _sfastram_data/_efastram_data and
_sdmaram/_edmaram_data so the build fails if any gap exists.
🤖 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 `@Makefile`:
- Line 199: The conditional uses a directory-only wildcard `$(wildcard .git/)`
which fails for git worktrees where `.git` is a file; change the wildcard to
`$(wildcard .git)` in the Makefile condition (the `ifneq ($(wildcard .git/),)`
check) so the branch that computes `REVISION` runs for both regular repos and
worktrees, ensuring `REVISION` is populated instead of staying `norevision`.

In `@src/config`:
- Line 1: The PR currently pins the submodule src/config to commit
17b73b32e7d07ddf7c9b73c14c3cda7c4c879a1a while companion PR
betaflight/config#1096 is still open; do not merge this change until that
companion PR is merged to the config repo default branch, then update the
submodule pointer in src/config to the merged commit (or re-run git submodule
update --init && git add src/config after the upstream merge) so downstream
consumers and CI do not encounter a dangling submodule reference.

In `@src/main/cli/cli.c`:
- Around line 6289-6294: The code reads addr and count using strtoul without
checking if parsing consumed any digits, so invalid tokens become 0 and later
touch memory; update the parsing logic around the strtoul calls that produce
addr and count (the lines using uint32_t addr = (uint32_t)strtoul(p, &p, 16);
and the subsequent count parsing) to validate the endptr: ensure p advanced past
the input digit(s) (and optionally check errno for overflow) before using addr,
and similarly validate the second strtoul call (or the branch that sets count)
so non-numeric inputs are rejected or produce a controlled error; apply the same
validation to the analogous block around the other occurrence you noted (the
block at 6317-6324) and avoid proceeding to memory reads/writes if parsing
fails.

In `@src/platform/common/stm32/fault_handlers.c`:
- Around line 117-125: The handler assumes an 8-word exception frame; before
loading stacked PC/LR (the LDRs using [r0,`#24`] and [r0,`#20`]), detect whether the
EXC_RETURN indicates an extended FP frame (test LR bit 4) and if so add the
FP-stack size (32 bytes) to r0 so the subsequent loads point to the correct
LR/PC. Concretely, after selecting MSP/PSP into r0 (the existing "mrseq r0, msp
/ mrsne r0, psp"), insert a "tst lr, `#16`" and conditionally add 32 to r0 (e.g.
"addeq"/"addne" variant or conditional add) before the "ldr r3, [r0, `#24`]" and
"ldr r3, [r0, `#20`]" instructions so the OBL snapshot records the correct
faulting PC/LR for FP-using frames.

In `@src/platform/STM32/adc_stm32n6xx.c`:
- Around line 418-423: When skipping a device because dmaSpec is NULL, you must
also undo the earlier reservation of dmaBufferIndex slots and clear any stale
mapping in adcOperatingConfig[]. Specifically, when you hit the branch in
adc_stm32n6xx.c that checks if (!dmaSpec), in addition to setting
adc->channelBits = 0, decrement the global/local dmaBufferIndex by the number of
channels previously consumed for this dev (the same count used to advance
dmaBufferIndex at Line 398) and set adcOperatingConfig[dev].dmaIndex to an
invalid sentinel (e.g., -1 or 0xFF) so the start loop and adcGetChannelValues()
won't rely on a stale mapping.

In `@src/platform/STM32/link/STM32N657XX_FSBL_FULL.ld`:
- Around line 168-218: The .fastram_data and .dmaram_data sections are marked
(NOLOAD) so their initializer bytes are omitted and initialiseMemorySections()
(which uses LOADADDR(.fastram_data)) ends up copying garbage; change those
section declarations (referencing .fastram_data and .dmaram_data in the linker
script) to be loadable like .data by removing (NOLOAD) and giving them an LMA
via AT/LOAD region (e.g. keep VMA >FASTRAM or >RAM but set LMA to the load
region using AT >LOAD_RAM or AT (_sfastram_idata) ), preserving the ALIGN() and
existing symbols (_sfastram_idata, _sdmaram_idata, _sfastram_data,
_sdmaram_data, _efastram_data, _edmaram_data) so initialiseMemorySections() and
FAST_DATA/DMA_DATA-initialized variables get their initializer bytes copied
correctly.

In `@src/platform/STM32/link/STM32N657XX_XIP.ld`:
- Around line 94-109: The linker script currently excludes only .text from
clock-switch objects but not their read-only data, so .rodata from
system_stm32n6xx.o / stm32n6xx_hal_rcc.o / stm32n6xx_hal_rcc_ex.o can be placed
in FLASH and consumed before RAM code is set up; update the EXCLUDE_FILE(...)
usage in the .text output section (the pattern with EXCLUDE_FILE and symbols
system_stm32n6xx.o, stm32n6xx_hal_rcc.o, stm32n6xx_hal_rcc_ex.o) to also exclude
.rodata and .rodata* (or otherwise ensure those object files’ .rodata* sections
are consumed by the .ram_code pattern) so their const data is placed in RAM-safe
sections rather than FLASH/XSPI.

In `@src/platform/STM32/sdio_n6xx.c`:
- Around line 195-234: sdioRifConfigure writes SDMMC2 attributes to the wrong
RIMC_ATTRx index and clobbers the register; for SDMMC2 use RIFSC->RIMC_ATTRx[4]
(not [3]) and perform a masked read-modify-write (e.g., via MODIFY_REG or
explicit mask/OR/AND) instead of plain assignment to RIFSC->RIMC_ATTRx so you
don't overwrite unrelated fields; keep the existing bit sets to
RIFSC->RISC_SECCFGRx[1] and RIFSC->RISC_PRIVCFGRx[1] but confirm the bit
positions (21 for SDMMC1, 22 for SDMMC2) against the reference manual.

In `@src/platform/STM32/system_stm32n6xx.c`:
- Around line 169-184: The pending NVIC IRQ for USB1_OTG_HS can be latched
between the initial NVIC->ICPR clear and the AHB5 OTG1 reset; to fix, after
pulsing the OTG1 reset (the SET_BIT calls on RCC->AHB5RSTSR/AHB5RSTCR and their
readbacks) add a second NVIC pending-clear so the latched pending bit is dropped
before interrupts are re-enabled: clear NVIC->ICPR (write 0xFFFFFFFF to the
appropriate NVIC->ICPR[i] words or call NVIC_ClearPendingIRQ(OTG1_IRQn) /
NVIC_ClearPendingIRQ(USB1_OTG_HS_IRQn)) immediately after the OTG1 reset
sequence and before the __enable_irq() call.

In `@src/platform/STM32/vcp_hal/usbd_conf_stm32n6xx.c`:
- Around line 122-125: HAL_PCD_MspInit currently does an unbounded wait on the
USB 3.3V ready flag (calling HAL_PWREx_EnableVddUSBVMEN() and looping on
__HAL_PWR_GET_FLAG(PWR_FLAG_USB33RDY)), which can hang boot; change this to a
bounded wait using a timeout (e.g., capture start = HAL_GetTick(), loop until
flag set or timeout expires) and on timeout cleanly abort USB init: disable the
VDD USB enable, clear any partial PCD state, and return an error/skip further
USB setup from HAL_PCD_MspInit (so callers of HAL_PCD_Init/PCD start will see
init failure and USB is effectively disabled). Use/introduce a descriptive
timeout constant (USB33RDY_WAIT_MS) and reference HAL_PWREx_EnableVddUSBVMEN,
__HAL_PWR_GET_FLAG, PWR_FLAG_USB33RDY, and HAL_PCD_MspInit in your changes.

---

Outside diff comments:
In `@src/platform/STM32/link/STM32N657XX_LRUN.ld`:
- Around line 286-296: The .DMA_RW_D2 section is currently loadable but lacks a
flash LMA so initialized data placed into .DMA_RW will be garbage at boot;
either mark the section NOLOAD to leave it uninitialized or give it an LMA and
startup copy like other initialized RAM sections by changing the .DMA_RW_D2
linker stanza to include "AT > FLASH" (or mirror the existing pattern used for
other initialized RAM regions) and ensure the startup code performs a copy from
the flash LMA to the RAM VMA using the provided symbols
(dmarw_start/dmarw_end/_sdmarw/_edmarw) so LRUN images get a valid XSPI-backed
load image and initializers are preserved.

In `@src/platform/STM32/startup/stm32n6xx_hal_conf.h`:
- Around line 23-37: Remove the duplicate HAL_LTDC_MODULE_ENABLED definition and
its first corresponding include block to avoid redundant preprocessor
definitions and duplicate includes; specifically, delete the earlier occurrence
of the HAL_LTDC_MODULE_ENABLED macro and the first LTDC-related include section
so only the later LTDC definition and its single include block (the LTDC + RIF
block) remain, ensuring references to HAL_RIF_MODULE_ENABLED and the intended
LTDC include are preserved.

---

Nitpick comments:
In `@src/main/fc/init.c`:
- Around line 1073-1078: The current `#if` !defined(STM32N6) skips
unusedPinsInit() on N6 which risks floating writable pins; create a tracked
issue for implementing a RIFSC-aware filter (so IOTraversePins/IOConfigGPIO
consult RISC_SECCFGR/PRIVCFGR and skip restricted banks) and add a concise TODO
comment in init.c referencing that issue ID and explaining why the guard is
temporary; include the symbols unusedPinsInit, IOTraversePins, IOConfigGPIO,
RISC_SECCFGR, and PRIVCFGR in the comment so future work knows exactly what's
required.

In `@src/platform/STM32/link/STM32N657XX_FSBL_FULL.ld`:
- Around line 26-58: The LENGTH value for LOAD_RAM is a magic number (521728)
representing 512K minus the FSBL header offset; replace the hardcoded 521728
with a self-documenting expression or symbol (e.g., define _FSBL_HEADER_SIZE =
0x400 or an equivalent _FSBL_SLOT_OFFSET) and compute LENGTH as 512K -
_FSBL_HEADER_SIZE (or ORIGIN-dependent calculation) in the MEMORY entry for
LOAD_RAM so the intent is clear and changes to offsets won’t silently break;
update the comment near LOAD_RAM/ORIGIN (and any related symbols like LOAD_RAM,
ORIGIN, or the new _FSBL_HEADER_SIZE) to reflect the derivation.

In `@src/platform/STM32/link/STM32N657XX_RAM.ld`:
- Around line 223-233: The .DMA_RW_D2 output section should be marked NOLOAD to
match the XIP/FSBL variants and prevent gdb from zero-filling D2_RAM or
embedding unexpected initializers; update the linker script section definition
for ".DMA_RW_D2" (the block that sets PROVIDE(dmarw_start), _sdmarw,
_dmarw_start__, KEEP(*(.DMA_RW)), PROVIDE(dmarw_end), _edmarw, _dmarw_end__) to
include the NOLOAD attribute while keeping the existing alignment and >D2_RAM
placement.
- Around line 40-46: The linker hardcodes 4096 for __config_end which can drift
from the actual eepromData[] size; instead expose the array size from C and use
that symbol in the linker. Add a C definition like `const unsigned long
__eeprom_size = EEPROM_SIZE;` (or use sizeof(eepromData) with a _Static_assert)
and then change the linker expressions for __config_start / __config_end to use
ABSOLUTE(eepromData) and ABSOLUTE(eepromData) + __eeprom_size (referencing
symbols __config_start, __config_end, eepromData, and __eeprom_size) so the
linker-bound range always matches the real array size.

In `@src/platform/STM32/link/STM32N657XX_XIP.ld`:
- Around line 145-196: Add a linker-time ASSERT to ensure .ram_code, .data,
.fastram_data and .dmaram_data are contiguous in memory so the Reset_Handler
copy from _stext.._etext using ram_code (LOADADDR(.ram_code) / _sitext) cannot
be broken by future reordering; specifically assert that ram_code_end ==
LOADADDR(.data) (or that ram_code_start + SIZEOF(.ram_code) == LOADADDR(.data))
and that the end of .data equals LOADADDR(.fastram_data) (or _efastram_data ==
LOADADDR(.fastram_data)) and that the end of .fastram_data equals
LOADADDR(.dmaram_data) (or _edmaram_data == LOADADDR(.dmaram_data)), using the
existing symbols ram_code_start, ram_code_end, ram_code, _stext, _etext,
_sfastram_data/_efastram_data and _sdmaram/_edmaram_data so the build fails if
any gap exists.

In `@src/platform/STM32/sdio_n6xx.c`:
- Around line 747-750: Both SDMMC1/SDMMC2 IRQ handlers currently call
HAL_SD_IRQHandler(&hsd1), which works now but is fragile if both controllers are
enabled; add a brief explanatory comment in SDMMC2_IRQHandler (and
SDMMC1_IRQHandler if present) noting that only one controller is wired at a time
and sdioHardware->instance selects the active controller in sdioPinConfigure(),
so the handler intentionally uses hsd1; if future work enables both controllers,
replace the hardcoded hsd1 usage with instance-aware dispatching (e.g., choose
the correct SD handle based on sdioHardware->instance) rather than copying the
same handle.
- Around line 197-206: In sdioRifMarkPin, the trailing comment on the PRIVCFGR
write is misleading; instead of saying “NPRIV = 0” update the comment for
port->PRIVCFGR &= ~mask to correctly reflect that clearing the PRIV bit allows
non-privileged access (e.g. “PRIV = 0 → non-privileged access allowed”); keep
references to IO_GPIO, IO_Pin, SECCFGR and PRIVCFGR so the change is localized
to the comment in sdioRifMarkPin.

In `@src/platform/STM32/startup/startup_stm32n657xx.s`:
- Around line 131-141: Add a one-line comment immediately above the
"post-AXISRAM-clock-enable" write (the ldr/str sequence to 0x46028A4C setting
0x0000018F) explaining that this non-secure-side bulk AXISRAM1..6 enable is
required so the .bss zeroing in early startup won't fault, and that systemInit()
later re-enables each AXISRAM via CMSIS macros (see systemInit) to also touch
the secure alias for HAL bookkeeping—this cross-reference clarifies why the same
0x18F mask is written twice.
- Around line 58-87: Introduce a single named constant for the NVIC register
count and use it instead of hardcoding "movs r3, `#16`", and tweak the two
comments to refer to ICER/ICPR (the registers being written) rather than
ISER/ISPR: add an assembly constant (e.g. NVIC_REG_COUNT = 16) and load it into
r3 before the two loops that write 0xE000E180 (NVIC_ICER0) and 0xE000E280
(NVIC_ICPR0), replace the two "clear ISER[i]" / "clear ISPR[i]" comments with
"clear ICER[i]" / "clear ICPR[i]" so the code uses one authoritative count
symbol (used by both loops) and the comments accurately describe the registers
being written.
- Around line 93-122: Replace the hardcoded debug RAM base and offsets with the
linker-exported DBG_RAM symbol so the startup stays in sync with the linker
scripts; in startup_stm32n657xx.s (the block that writes CABA0001 and the
self-PC), load the base via a PC-relative literal referencing DBG_RAM (e.g., use
LDR r2, =DBG_RAM) and then write to DBG_RAM+0x80, DBG_RAM+0x100, +0x104, +0x108,
+0x10C, +0x110 and store the PC to DBG_RAM+0x120 instead of using 0x24100000 and
the hardcoded offsets, ensuring the symbol name DBG_RAM is used for all
addresses so any ORIGIN change in STM32N657XX_XIP.ld (and matching FSBL/RAM/OBL)
stays consistent.

In `@src/platform/STM32/system_stm32n6xx.c`:
- Around line 161-167: The NVIC register-clear loops in system_stm32n6xx.c (the
for loop using sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0])) and the hardcoded
loop count in startup_stm32n657xx.s are inconsistent; fix by either (A) making
the assembly use a shared CMSIS-derived constant (add a .equ in the generated
header and reference it in startup_stm32n657xx.s) or (B) add a one-line comment
in both places cross-referencing each other and explaining why the assembly uses
16 iterations (writes to reserved tail are ignored) so the visual discrepancy is
documented; update the comment in system_stm32n6xx.c near the
NVIC->ICER/NVIC->ICPR loop or modify startup_stm32n657xx.s to reference the
shared constant accordingly.
- Around line 308-432: SystemClock_Config contains seven unrecoverable spins;
before each while (1); write a distinct marker into the .bf_obl_dbg debug buffer
so the OBL DFU alt can identify which HAL call failed. Concretely: in
SystemClock_Config, immediately before the failure branches that follow
HAL_PWREx_ConfigSupply, HAL_PWREx_ControlVoltageScaling, HAL_RCC_OscConfig
(first HSI block), HAL_RCC_ClockConfig (HSI clock switch), HAL_RCC_OscConfig
(HSE/PLL bring-up), HAL_RCC_ClockConfig (PLL distribution), and
HAL_RCCEx_PeriphCLKConfig, store a unique byte/word identifier into the
.bf_obl_dbg area (use distinct values per location) and ensure the write is
non-blocking/simple memory write so it will persist through the spin; keep the
existing while (1); spins unchanged otherwise. Use the .bf_obl_dbg symbol and
SystemClock_Config/HAL_* function names to find the spots and pick unique
markers for each failure path.
- Around line 121-142: The magic secure-alias addresses and DBGMCU.CR bit
positions used in systemInit (and later re-assert) are hard-coded and
duplicated; create clear named constants (e.g., RCC_S_MISCENSR_SECURE_ALIAS,
RCC_S_MISCENR_NS_ALIAS, RCC_S_BUSENSR_SECURE_ALIAS, RCC_S_BUSENR_NS_ALIAS,
DBGMCU_S_CR_SECURE_ALIAS and DBGMCU_CR_DBGCLKEN/DBG_SLEEP/DBG_STOP/DBG_STANDBY
bit masks) at the top of this file or in the platform header, replace the
literal constants and (1<<N) expressions in systemInit and the re-assert block
with those defines, and update the existing comment to reference the new symbols
so the meanings aren’t lost and duplication/typo risk is removed (use the
existing systemInit and re-assert code locations to find all occurrences).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1dc479d-eab3-4e9a-9aa0-3b72efb3ef9d

📥 Commits

Reviewing files that changed from the base of the PR and between 119d740 and 1625ee1.

⛔ Files ignored due to path filters (38)
  • lib/main/STM32/n6_fsbl/.gitignore is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/FlashLayout.tsv is excluded by !**/*.tsv, !lib/**
  • lib/main/STM32/n6_fsbl/FlashLayout_BF_AS_FSBL.tsv is excluded by !**/*.tsv, !lib/**
  • lib/main/STM32/n6_fsbl/Makefile is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/STM32N657XX_FSBL.ld is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/main.c is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/main.h is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/prebuilt/bf_as_fsbl.stm32 is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/prebuilt/n6_fsbl_signed.stm32 is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/startup_stm32n657xx_fsbl.s is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/stm32n6xx_hal_conf.h is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/stm32n6xx_hal_msp.c is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/stm32n6xx_it.c is excluded by !lib/**
  • lib/main/STM32/n6_fsbl/system_stm32n6xx_fsbl.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/.gitignore is excluded by !lib/**
  • lib/main/STM32/n6_obl/Makefile is excluded by !lib/**
  • lib/main/STM32/n6_obl/STM32N657XX_OBL.ld is excluded by !lib/**
  • lib/main/STM32/n6_obl/Unbricking.md is excluded by !lib/**
  • lib/main/STM32/n6_obl/app_openbootloader.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/external_memory_interface.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/flash/flash_iface.h is excluded by !lib/**
  • lib/main/STM32/n6_obl/flash/mx66uw1g45g.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/main.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/main.h is excluded by !lib/**
  • lib/main/STM32/n6_obl/obl_app.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/openbootloader_conf.h is excluded by !lib/**
  • lib/main/STM32/n6_obl/otp_interface.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/prebuilt/obl_mx66uw1g45g_signed.stm32 is excluded by !lib/**
  • lib/main/STM32/n6_obl/startup_stm32n657xx_obl.s is excluded by !lib/**
  • lib/main/STM32/n6_obl/stm32n6xx_hal_conf.h is excluded by !lib/**
  • lib/main/STM32/n6_obl/stm32n6xx_hal_msp.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/stm32n6xx_it.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/system_stm32n6xx_obl.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/usbd_conf.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/usbd_conf.h is excluded by !lib/**
  • lib/main/STM32/n6_obl/usbd_desc.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/usbd_dfu.c is excluded by !lib/**
  • lib/main/STM32/n6_obl/usbd_dfu_if.c is excluded by !lib/**
📒 Files selected for processing (27)
  • Makefile
  • src/config
  • src/main/cli/cli.c
  • src/main/drivers/bf_obl_contract.h
  • src/main/fc/faults.c
  • src/main/fc/init.c
  • src/main/fc/tasks.c
  • src/main/main.c
  • src/main/target/common_post.h
  • src/platform/STM32/adc_stm32n6xx.c
  • src/platform/STM32/bus_octospi_stm32n6xx.c
  • src/platform/STM32/io_stm32.c
  • src/platform/STM32/link/STM32N657XX_FSBL_FULL.ld
  • src/platform/STM32/link/STM32N657XX_LRUN.ld
  • src/platform/STM32/link/STM32N657XX_RAM.ld
  • src/platform/STM32/link/STM32N657XX_XIP.ld
  • src/platform/STM32/mk/STM32N6.mk
  • src/platform/STM32/sdio_n6xx.c
  • src/platform/STM32/serial_uart_stm32n6xx.c
  • src/platform/STM32/startup/startup_stm32n657xx.s
  • src/platform/STM32/startup/stm32n6xx_hal_conf.h
  • src/platform/STM32/system_stm32n6xx.c
  • src/platform/STM32/target/STM32N657/target.h
  • src/platform/STM32/vcp_hal/usbd_conf_stm32n6xx.c
  • src/platform/common/stm32/fault_handlers.c
  • src/platform/common/stm32/io_def_generated.h
  • src/utils/def_generated.pl

Comment thread Makefile
Comment thread src/config Outdated
Comment thread src/main/cli/cli.c Outdated
Comment thread src/platform/common/stm32/fault_handlers.c
Comment thread src/platform/STM32/adc_stm32n6xx.c Outdated
Comment thread src/platform/STM32/link/STM32N657XX_FSBL_FULL.ld
Comment thread src/platform/STM32/link/STM32N657XX_XIP.ld
Comment thread src/platform/STM32/sdio_n6xx.c
Comment thread src/platform/STM32/system_stm32n6xx.c
Comment thread src/platform/STM32/vcp_hal/usbd_conf_stm32n6xx.c
@blckmn

blckmn commented May 11, 2026

Copy link
Copy Markdown
Member Author

PR review pass — push at 7dcde47.

Addressed (11):

  • main.c — LED1 heartbeat now gated behind STM32N6 so other targets are unaffected (Copilot).
  • cli.c — mr/mw validate strtoul endptr; bad input rejected instead of becoming 0x00000000 (CodeRabbit).
  • adc_stm32n6xx.c — resolve dmaSpec before consuming dmaBufferIndex / writing dmaIndex (CodeRabbit).
  • system_stm32n6xx.c — second NVIC->ICPR clear after AHB5 OTG1 reset (CodeRabbit).
  • vcp_hal/usbd_conf_stm32n6xx.c — bounded 100 ms wait on PWR_FLAG_USB33RDY (CodeRabbit).
  • link/STM32N657XX_LRUN.ld + RAM.ld — .DMA_RW_D2 marked NOLOAD to match XIP/FSBL_FULL (CodeRabbit).
  • link/STM32N657XX_XIP.ld — EXCLUDE_FILE extended to .rodata* for clock-switch objects (CodeRabbit).
  • startup/stm32n6xx_hal_conf.h — duplicate HAL_LTDC_MODULE_ENABLED / include removed (CodeRabbit outside-diff).
  • n6_obl/openbootloader_conf.h — INTERFACES_SUPPORTED corrected to 2 + comment update (Copilot).
  • n6_obl/usbd_conf.h + usbd_dfu_if.c — comment fixes (@DBGRAM / AXISRAM2) (Copilot).
  • sdio_n6xx.c — PRIVCFGR comment polarity (CodeRabbit nit).

Declined (4):

  • Makefile worktree wildcard — already fixed in master via fix(build): detect git worktrees when setting REVISION #15209.
  • fault_handlers.c hardcoded 0x24100010 — gated by ENABLE_BF_OBL, N6-only (Copilot).
  • fault_handlers.c extended FP frame offset — false positive (ARMv8-M ARM B11.1.2: standard-frame offsets are preserved when FP context is stacked) (CodeRabbit).
  • sdio_n6xx.c RIMC_ATTRx[3]/[4] — current indices are bring-up-verified on N6570-DK, no RM citation for [4] (CodeRabbit).

src/config submodule bump stays as-is, pending companion betaflight/config#1096.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/platform/STM32/link/STM32N657XX_LRUN.ld (1)

179-189: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale FLASH1 references in comments — sections now load from FLASH.

After moving all AT > directives from FLASH1 to FLASH, two comments in .ram_code still reference the old region:

  • Line 179: /* LMA in FLASH1 for initialiseMemorySections() */ — the LMA is now in FLASH (0x70100000).
  • Line 188: /* end of all FLASH1-backed sections — startup copies _stext.._etext */ — those sections are now FLASH-backed.

Misleading for anyone tracing the boot-time copy path or debugging _sitext/_etext.

📝 Suggested wording fix
-  ram_code = LOADADDR(.ram_code);  /* LMA in FLASH1 for initialiseMemorySections() */
+  ram_code = LOADADDR(.ram_code);  /* LMA in FLASH for initialiseMemorySections() */
   .ram_code :
   {
     . = ALIGN(4);
     ram_code_start = .;
     *(.ram_code)
     *(.ram_code*)
     . = ALIGN(4);
     ram_code_end = .;
-    _etext = .;        /* end of all FLASH1-backed sections — startup copies _stext.._etext */
+    _etext = .;        /* end of all FLASH-backed sections — startup copies _stext.._etext */
   } >RAM AT >FLASH
🤖 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/platform/STM32/link/STM32N657XX_LRUN.ld` around lines 179 - 189, Update
the two stale comments in the .ram_code linker block to reflect that the load
memory address (LOADADDR(.ram_code)) and the FLASH-backed sections
(_stext.._etext) now reside in FLASH (e.g., 0x70100000) instead of FLASH1;
specifically edit the comment on the ram_code LMA line and the comment after
ram_code_end/_etext so they mention FLASH (and optionally the concrete address)
and the startup copy path used by initialiseMemorySections() and the
_stext.._etext copy.
🤖 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.

Outside diff comments:
In `@src/platform/STM32/link/STM32N657XX_LRUN.ld`:
- Around line 179-189: Update the two stale comments in the .ram_code linker
block to reflect that the load memory address (LOADADDR(.ram_code)) and the
FLASH-backed sections (_stext.._etext) now reside in FLASH (e.g., 0x70100000)
instead of FLASH1; specifically edit the comment on the ram_code LMA line and
the comment after ram_code_end/_etext so they mention FLASH (and optionally the
concrete address) and the startup copy path used by initialiseMemorySections()
and the _stext.._etext copy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6208e9b4-53b3-460c-a0b9-ed2594def43f

📥 Commits

Reviewing files that changed from the base of the PR and between 1e8ae36 and 7dcde47.

⛔ Files ignored due to path filters (3)
  • lib/main/STM32/n6_obl/openbootloader_conf.h is excluded by !lib/**
  • lib/main/STM32/n6_obl/usbd_conf.h is excluded by !lib/**
  • lib/main/STM32/n6_obl/usbd_dfu_if.c is excluded by !lib/**
📒 Files selected for processing (9)
  • src/main/cli/cli.c
  • src/main/main.c
  • src/platform/STM32/adc_stm32n6xx.c
  • src/platform/STM32/link/STM32N657XX_LRUN.ld
  • src/platform/STM32/link/STM32N657XX_RAM.ld
  • src/platform/STM32/link/STM32N657XX_XIP.ld
  • src/platform/STM32/sdio_n6xx.c
  • src/platform/STM32/system_stm32n6xx.c
  • src/platform/STM32/vcp_hal/usbd_conf_stm32n6xx.c
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/main/cli/cli.c
  • src/platform/STM32/vcp_hal/usbd_conf_stm32n6xx.c
  • src/main/main.c
  • src/platform/STM32/adc_stm32n6xx.c
  • src/platform/STM32/link/STM32N657XX_XIP.ld
  • src/platform/STM32/link/STM32N657XX_RAM.ld
  • src/platform/STM32/system_stm32n6xx.c
  • src/platform/STM32/sdio_n6xx.c

@blckmn
blckmn force-pushed the feat/n6570dk branch 2 times, most recently from 25c6450 to 50d58b2 Compare May 12, 2026 08:20
Adds STM32N6 platform support, with STM32N657DK_DSK320 as the first
working target. End-to-end on the dev kit: boot ROM → Betaflight
OpenBootloader (lib/main/STM32/n6_obl) → BF running from XSPI at
600 MHz, USB VCP enumerates as 0483:5740, CLI accessible. Companion
target-config PR: betaflight/config#1096.

Vendor SDK + CMSIS:
- lib/main/STM32N6: CubeN6 v1.3.0 (HAL, CMSIS, USBD library,
  OpenBootloader middleware).
- lib/main/CMSIS/Core/Include: ARMv8.1-M / Cortex-M55 core headers
  (core_cm55.h, cachel1_armv7.h, pmu_armv8.h).

Platform (src/platform/STM32):
- N6 family startup, system init, vector table, XIP/LRUN linker
  scripts (with ALIGN(4) on .pg_registry / .pg_resetdata / .ARM
  sections so .data LMA stays in step with VMA).
- USB OTG_HS PHY bring-up: VddUSB / USB33RDY wait, OTG/PHY reset
  pulse, HSE/2 reference, USBPHYC_CR.FSEL = 0b010, RIF master + slave
  attributes wired so SETUP packets reach the controller's RX FIFO.
- Secure-alias DBGMCU/RCC writes (NS-tagged transactions through the
  CMSIS macros silently drop on RIFSC; SWD attach to a running BF
  fails AP1 examination without these).
- SDIO/SDMMC2 RIF master + per-pin secure attributes (HAL_SD_Init
  itself still wedges on STAR; pin/RIF infrastructure is in place).
- ADC: skip the device when no DMA spec is configured rather than
  dereferencing a NULL dmaSpec.
- IO subsystem extended through GPIO port O via the
  io_def_generated.pl perl helper.
- systemResetToBootloader spins with IRQs masked so OBL's IWDG fires
  and OBL routes the next boot to DFU via RCC->RSR.IWDGRSTF (a clean
  SYSRESETREQ would set SFTRSTF and OBL would just relaunch BF).
- LTDC LCD console backend for the N6570-DK RK050HR18-CT panel (off
  by default; bring-up still wedges the chip when enabled — see
  feedback notes in source).

Betaflight OpenBootloader (lib/main/STM32/n6_obl):
- Custom signed FSBL loaded from XSPI nor0 0x0; sets up clocks,
  memory-maps XSPI, validates BF vector table at 0x70100000.
- Decides DFU vs jump from RCC->RSR (IWDGRSTF / LCKRSTF / WWDGRSTF).
- Arms a 10 s IWDG before jumping to BF.
- DFU mode re-flashes BF over USB without touching BOOT0.
- Optional OBL_FORCE_DFU build flag short-circuits the boot decision
  for SWD-load development iteration.
- Unbricking.md covers the full TSV recovery path via the boot ROM
  system DFU when OBL itself is broken.

BF ↔ OBL contract:
- drivers/bf_obl_contract.h: BF_OBL_IWDG_REFRESH macro. ENABLE_BF_OBL
  defaulted off in common_post.h; N6 target.h opts in.
- BF refreshes OBL's IWDG from TASK_SERIAL (the scheduler exempts it
  from the time-budget check, so it is the most reliable carrier).
- bl rom from CLI / MSP DFU spins with IRQs masked so IWDG fires
  and OBL routes the next boot to DFU.
- 1 Hz LED1 toggle in run() — visible 'BF is alive' heartbeat
  independent of any task running.

Misc fixes:
- fc/faults.c: __attribute__((used)) on systemFaultAction so LTO
  doesn't drop the symbol the inline-asm tail-calls in HardFault
  reach.
- Skip unusedPinsInit() on N6 — IOTraversePins wedges on at least one
  RIFSC-restricted port; TODO to teach the IO layer about restricted
  ports.
- Startup vector table rewritten to match the CMSIS IRQn ordering for
  N6.

Status:
- Working: boot path, USB VCP + CLI, virtual gyro/acc/baro/mag stack,
  ADC1 + VBAT sense.
- Pending: real LSM6DSK320X on SPI1, LIS2MDL + LPS22DF on I2C1, SDMMC2
  init wedge on STAR, LTDC LCD bring-up wedge.

PR marked draft until the real-sensor / SDIO / LCD TODOs are closed.
@nerdCopter

Copy link
Copy Markdown
Member

AI Generated comment

This PR's Makefile diff silently reverted #15209. #15209 changed the REVISION guard in Makefile from $(wildcard .git/) to $(wildcard .git), so worktree checkouts (where .git is a gitdir-pointer file, not a directory) would still resolve a commit SHA instead of falling back to norevision. This PR's diff on the same lines changed $(wildcard .git) back to $(wildcard .git/), reintroducing the norevision bug in git worktrees.

Confirmed via git blame on current master: c925d4f682 (this PR, single-parent commit) is what changed the line back, 5 days after #15209 merged.

Fix re-applying #15209: #15446

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants