Commit Graph
50899 Commits
Author SHA1 Message Date
Jacob Dahl cbc6a6f7ab fix(ark/fmu-v6xrt): size the IOB pool for CAN receive (#28462)
Build all targets / Scan for Board Targets (push) Canceled after 0s
Build all targets / Seed [${{ matrix.chip_family }}] (push) Canceled after 0s
Build all targets / Build [${{ matrix.runner }}][${{ matrix.group }}] (push) Canceled after 0s
Build all targets / Upload Artifacts (push) Canceled after 0s
Checks / Gate Checks [check_format] (push) Canceled after 0s
Checks / Gate Checks [check_newlines] (push) Canceled after 0s
Checks / Gate Checks [module_documentation] (push) Canceled after 0s
Checks / Gate Checks [shellcheck_all] (push) Canceled after 0s
Checks / Gate Checks [validate_module_configs] (push) Canceled after 0s
Checks / Unit Tests (push) Canceled after 0s
MacOS build / build (macos-15) (push) Canceled after 0s
MacOS build / build (macos-latest) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:22.04) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:24.04) (push) Canceled after 0s
Container build / Set Tags and Variables (push) Canceled after 0s
Container build / Build Container (amd64) (push) Canceled after 0s
Container build / Build Container (arm64) (push) Canceled after 0s
Container build / Deploy To Registry (push) Canceled after 0s
Docs - Orchestrator / T1: Detect Changes (push) Canceled after 0s
Docs - Orchestrator / T2: PR Metadata (push) Canceled after 0s
Docs - Orchestrator / T2: Metadata Sync (push) Canceled after 0s
Docs - Orchestrator / T2: Link Check (push) Canceled after 0s
Docs - Orchestrator / T3: Build Site (push) Canceled after 0s
Docs - Orchestrator / T4: Deploy (push) Canceled after 0s
Failsafe Simulator Build / build (failsafe_web) (push) Canceled after 0s
ITCM check / Checking nxp_mr-tropic (push) Canceled after 0s
ITCM check / Checking nxp_tropic-community (push) Canceled after 0s
ITCM check / Checking px4_fmu-v5x (push) Canceled after 0s
ITCM check / Checking px4_fmu-v6xrt (push) Canceled after 0s
ROS Integration Tests / build (push) Canceled after 0s
ROS Translation Node Tests / Build and test [humble] (push) Canceled after 0s
ROS Translation Node Tests / Build and test [jazzy] (push) Canceled after 0s
SITL Tests / Testing PX4 quadx (push) Canceled after 0s
SITL Tests / Testing PX4 standard_vtol (push) Canceled after 0s
SITL Tests / Testing PX4 xvert (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v5x (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v6x (push) Canceled after 0s
Python CI Checks / build (push) Canceled after 0s
FLASH usage analysis / Publish Results (push) Canceled after 0s
Static Analysis / Clang-Tidy (push) Canceled after 0s
NuttX queues every received CAN frame in one I/O buffer from the pool
shared with the rest of the network stack. At 24 buffers the two FlexCAN
interfaces together have 7 ms of headroom at 3260 frames/s, and the
uavcan thread is preempted for longer than that: the socket layer
dropped 445 frames per 300 s on the bench, which shows up as transfer
errors and failed DroneCAN parameter reads. 192 buffers (40 KB of the
1.1 MB free) give 59 ms at that rate; IOB_NCHAINS follows by default.

Assisted-by: Claude:claude-fable-5

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-29 21:16:30 -06:00
Saibernard 436cc71c89 fix(ekf2): avoid failover to an instance with a failing test ratio (#28418)
* fix(ekf2): keep evaluating instance selection while the primary is unhealthy

The selection block only ran when UpdateErrorScores() reported a change:
a new instance appearing or a health transition. A primary that stops
publishing produces exactly one such transition, on the cycle its
timeout is detected. If no switch happens on that cycle, nothing sets
updated again: the stable alternatives do not count as primary updates
and the timed out instance is skipped thereafter, so the fallback logic
is never evaluated again even though the module keeps being scheduled.

Today that single evaluation always resolves the situation, because the
fallback switches unconditionally to the best healthy candidate on that
same cycle. But any selection policy that can decline to switch on the
transition cycle, for example one that waits out a transient fault,
needs the decision re-evaluated while the primary remains unhealthy.
Re-enter the selection block whenever the selected instance is
unhealthy. The decisions inside are unchanged and switching is
idempotent, so behaviour today is identical.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* fix(ekf2): do not fail over to an instance with a sustained test ratio failure

When the primary EKF goes unhealthy the selector falls back to the best
instance that is healthy, and healthy only requires zero filter fault
flags and a positive combined test ratio. An instance whose test ratio
has been failing for a long time therefore remains a first class switch
target even though its state can be far from the truth.

b7efd4f947 introduced this on purpose for the switch-away direction: a
test ratio at or above one became a warning rather than ill health, so a
transient ratio spike cannot hard fail an instance, and a warned primary
is left through the lower relative error path once the warning has been
sustained for one second. What that commit did not do is apply the same
reasoning to the switch-to direction. The candidate loop only filters on
healthy, so a brief hard fault on the primary, for example transient
accelerometer clipping, sends the selector straight to a diverged
instance.

That is the mechanism behind the repeated altitude jumps in issue 27013:
one instance had stopped fusing baro, its vertical state up to 155.6 m
from the other instance while its combined test ratio sat pegged at 2,
and each of the seven short clipping faults on the good instance bounced
the selector back to it (15 instance switches in total counting the
returns), the worst switch stepping the published altitude by 128.5 m
and provoking a hard TECS reaction.

Classify fallback candidates with the same sustained warning test the
switch-away trigger already uses. When the primary goes unhealthy, fail
over immediately to the best candidate without a sustained warning; the
different IMU preference is kept within each tier, and a candidate
without a sustained warning is preferred even over a warned candidate on
a different IMU, since a warned instance is the one known to be
diverging. A sustained warned candidate is accepted in two cases only:
the primary has timed out entirely, where frozen attitude and position
outputs are worse than any live alternative, or the primary has been
continuously unhealthy for kWarnedFallbackDelay (five seconds), so a
brief fault rides out on the current state while a persistently faulted
primary still gets the least bad alternative rather than none. The
re-evaluation of this decision while the primary stays unhealthy is
provided by the previous commit.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* test(ekf2): add a functional test for the instance selector

The selector had no test at any level. This drives EKF2Selector through
published multi instance estimator_status messages on the real work
queue and observes estimator_selector_status, so the selection policy is
exercised without a simulator.

The scenarios encode the failure pattern from issue 27013 and the
no-whipsaw property discussed there: a clean fallback on a hard primary
fault stays immediate, the switch away from a degraded primary through
the sustained warning path still works, three separate brief hard faults
on the primary no longer bounce the selector to an instance whose test
ratio has been failing for seconds, a primary that stops publishing
falls back to the degraded instance without delay, and a primary that
stays hard faulted for longer than the ride-out window still falls back
rather than being kept forever.

The scenario setup helpers run until the selector reaches the intended
starting state rather than assuming fixed timings, since the health
hysteresis and warning windows run on wall clock time. The fault-clear
windows exceed the selector's one second healthy hysteresis so the
faults are genuinely separate and the unhealthy-since tracking restarts
between them. The harness waits until the work queue manager actually
serves queues before constructing the selector: a fixed delay races the
manager startup on a loaded runner.

to run: make tests TESTFILTER=EKF2Selector

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-08-29 15:23:11 -06:00
Saibernard 3b1517ccd8 fix(battery_status): keep analog filter state across parameter updates (#28452)
* fix(battery_status): seed the analog filters from the first sample

Nothing seeded the voltage and current filters, so after boot the
filtered values converged from zero over the filter time constant,
several seconds of falsely low battery readings. Seed each filter with
its first sample.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* fix(battery_status): reseed the filters after they are enabled again

A filter that was disabled and later enabled kept its seeded flag, so
it resumed from the stale state it held before being disabled. Clear
the flag when a filter is disabled so enabling it again restarts from
the live measurement.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* fix(battery_status): keep the analog filter state across parameter updates

updateParams replaced the voltage and current filters with freshly
constructed ones, which zeroes their state. It runs on any parameter
change anywhere in the system, so with filtering enabled every change
restarted the filtered voltage and current from zero. The next
published voltage then sits below the 2.1 V battery recognition
threshold, which reports the battery as disconnected, and while armed
the battery warning only ever escalates, so a single in flight
param set could latch a critical battery failsafe and command RTL or
land per COM_LOW_BAT_ACT. Recovery of the filtered value takes several
time constants, tens of seconds at the BAT_V_FILT maximum of 5 s.

Carry the previous state across the reconstruction once the filter has
been seeded.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-08-29 13:49:27 -06:00
Saibernard 71f8b37183 fix(mc_pos_control): bypass the velocity low pass when the cutoff is rejected (#28451)
setCutoffFreq refuses a cutoff at or above half the sample rate and
leaves the filter untouched, but all four velocity filter calls ignored
the return. With the filters at their initial alpha of zero this froze
the velocity feedback at zero, and the condition is reachable from a
permitted parameter value, MPC_VEL_LP allows up to 50 Hz while the
position loop commonly runs at 100 Hz or less. In SIH a hover with
MPC_VEL_LP=50 oscillates half a metre in altitude with vertical speed
peaks near 1 m/s, and holds 2.5 m within centimetres with this change.

Follow the pattern VehicleAngularVelocity already uses, check the
return and bypass the low pass stage when the requested cutoff is not
achievable. One deliberate behaviour change comes with that, a runtime
parameter change from a valid to an unachievable cutoff now bypasses
the stage instead of keeping the stale previous configuration.

Assisted-by: Claude:claude-fable-5

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-08-29 13:46:04 -06:00
Jacob Dahl 3f6b26f90b fix(ark/fmu-v6xrt): start the BMP390 as internal on Shared I2C2 (#28453)
Build all targets / Scan for Board Targets (push) Canceled after 0s
Build all targets / Seed [${{ matrix.chip_family }}] (push) Canceled after 0s
Build all targets / Build [${{ matrix.runner }}][${{ matrix.group }}] (push) Canceled after 0s
Build all targets / Upload Artifacts (push) Canceled after 0s
Checks / Gate Checks [check_format] (push) Canceled after 0s
Checks / Gate Checks [check_newlines] (push) Canceled after 0s
Checks / Gate Checks [module_documentation] (push) Canceled after 0s
Checks / Gate Checks [shellcheck_all] (push) Canceled after 0s
Checks / Gate Checks [validate_module_configs] (push) Canceled after 0s
Checks / Unit Tests (push) Canceled after 0s
MacOS build / build (macos-15) (push) Canceled after 0s
MacOS build / build (macos-latest) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:22.04) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:24.04) (push) Canceled after 0s
Container build / Set Tags and Variables (push) Canceled after 0s
Container build / Build Container (amd64) (push) Canceled after 0s
Container build / Build Container (arm64) (push) Canceled after 0s
Container build / Deploy To Registry (push) Canceled after 0s
Docs - Orchestrator / T1: Detect Changes (push) Canceled after 0s
Docs - Orchestrator / T2: PR Metadata (push) Canceled after 0s
Docs - Orchestrator / T2: Metadata Sync (push) Canceled after 0s
Docs - Orchestrator / T2: Link Check (push) Canceled after 0s
Docs - Orchestrator / T3: Build Site (push) Canceled after 0s
Docs - Orchestrator / T4: Deploy (push) Canceled after 0s
Failsafe Simulator Build / build (failsafe_web) (push) Canceled after 0s
ITCM check / Checking nxp_mr-tropic (push) Canceled after 0s
ITCM check / Checking nxp_tropic-community (push) Canceled after 0s
ITCM check / Checking px4_fmu-v5x (push) Canceled after 0s
ITCM check / Checking px4_fmu-v6xrt (push) Canceled after 0s
ROS Integration Tests / build (push) Canceled after 0s
ROS Translation Node Tests / Build and test [humble] (push) Canceled after 0s
ROS Translation Node Tests / Build and test [jazzy] (push) Canceled after 0s
SITL Tests / Testing PX4 quadx (push) Canceled after 0s
SITL Tests / Testing PX4 standard_vtol (push) Canceled after 0s
SITL Tests / Testing PX4 xvert (push) Canceled after 0s
Docs - Crowdin - Upload Guide sources (en) / upload-to-crowdin (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v5x (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v6x (push) Canceled after 0s
Python CI Checks / build (push) Canceled after 0s
FLASH usage analysis / Publish Results (push) Canceled after 0s
Static Analysis / Clang-Tidy (push) Canceled after 0s
I2C2 carries the onboard BMP390 and the PM2 connector. Labelling it External forced -X, so the baro was classified external.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-29 12:20:57 -06:00
TIM ANDERSON 6d6412e3cb fix(rc): reject invalid ST24 packet lengths (#28450)
Require every accepted length to include the packet type and CRC fields. Handle the minimum valid zero-payload frame by transitioning directly to CRC processing, and add a regression test for malformed lengths and parser recovery.

Fixes #28425

Assisted-by: OpenAI Codex:GPT-5

Signed-off-by: Tim Anderson <timothyanderson096@gmail.com>
2026-08-29 12:15:16 -06:00
Andrew Wilkins 8f43cf7268 Update mc_10_optical_flow_gps_mixed.md (#28434) 2026-08-29 11:35:34 -04:00
Jacob Dahl 0a441d9443 fix(commander): capture home on the ground before motors spin (#27734)
Build all targets / Scan for Board Targets (push) Canceled after 0s
Build all targets / Seed [${{ matrix.chip_family }}] (push) Canceled after 0s
Build all targets / Build [${{ matrix.runner }}][${{ matrix.group }}] (push) Canceled after 0s
Build all targets / Upload Artifacts (push) Canceled after 0s
Checks / Gate Checks [check_format] (push) Canceled after 0s
Checks / Gate Checks [check_newlines] (push) Canceled after 0s
Checks / Gate Checks [module_documentation] (push) Canceled after 0s
Checks / Gate Checks [shellcheck_all] (push) Canceled after 0s
Checks / Gate Checks [validate_module_configs] (push) Canceled after 0s
Checks / Unit Tests (push) Canceled after 0s
MacOS build / build (macos-15) (push) Canceled after 0s
MacOS build / build (macos-latest) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:22.04) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:24.04) (push) Canceled after 0s
Container build / Set Tags and Variables (push) Canceled after 0s
Container build / Build Container (amd64) (push) Canceled after 0s
Container build / Build Container (arm64) (push) Canceled after 0s
Container build / Deploy To Registry (push) Canceled after 0s
Docs - Orchestrator / T1: Detect Changes (push) Canceled after 0s
Docs - Orchestrator / T2: PR Metadata (push) Canceled after 0s
Docs - Orchestrator / T2: Metadata Sync (push) Canceled after 0s
Docs - Orchestrator / T2: Link Check (push) Canceled after 0s
Docs - Orchestrator / T3: Build Site (push) Canceled after 0s
Docs - Orchestrator / T4: Deploy (push) Canceled after 0s
Failsafe Simulator Build / build (failsafe_web) (push) Canceled after 0s
ITCM check / Checking nxp_mr-tropic (push) Canceled after 0s
ITCM check / Checking nxp_tropic-community (push) Canceled after 0s
ITCM check / Checking px4_fmu-v5x (push) Canceled after 0s
ITCM check / Checking px4_fmu-v6xrt (push) Canceled after 0s
ROS Integration Tests / build (push) Canceled after 0s
ROS Translation Node Tests / Build and test [humble] (push) Canceled after 0s
ROS Translation Node Tests / Build and test [jazzy] (push) Canceled after 0s
SITL Tests / Testing PX4 quadx (push) Canceled after 0s
SITL Tests / Testing PX4 standard_vtol (push) Canceled after 0s
SITL Tests / Testing PX4 xvert (push) Canceled after 0s
Docs - Crowdin - Upload Guide sources (en) / upload-to-crowdin (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v5x (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v6x (push) Canceled after 0s
Python CI Checks / build (push) Canceled after 0s
Sync ROS 2 messages to px4_msgs / sync_to_px4_msgs (push) Canceled after 0s
FLASH usage analysis / Publish Results (push) Canceled after 0s
Static Analysis / Clang-Tidy (push) Canceled after 0s
Move the arm-time home capture to before ARMING_STATE_ARMED so it runs before the ESCs are enabled and prop wash perturbs the baro, and gate it on landed && (!_mission_in_progress || seq_current == 0). The previous !_mission_in_progress gate skipped the capture entirely when arming straight into a mission, leaving home referenced to a stale (cold-baro, fewer-sats) on-ground fix. seq_current distinguishes a genuine mission start from a mid-mission re-arm (land/disarm/continue), which must not move home.

Part of #27730.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 23:12:28 -06:00
Alex KlimajandJacob Dahl ed93e5c87c feat(boards): add the ARK FMU-v6XRT (#27161)
* ARKV6X-RT Initial Commit

* icm45686 yaw 270

* iis2mdc yaw 180

* fix(ark/v6x-rt): wrap FLASH_END macro body in parentheses

Fixes clang-tidy bugprone-macro-parentheses.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* update(ark/v6x-rt): modify bootloader binary for enhancements

* feat(ark/v6x-rt): enable LSM6DSV80X IMU on SPI3

Enable the lsm6dsv driver, register the SPI3 chip select with the
LSM6DSV devtype, and start the driver with -T 80 to select the
LSM6DSV80X high-g variant (shared WHO_AM_I 0x73).

Signed-off-by: alexklimaj <alex@arkelectron.com>

* feat(ark/v6x-rt): start IIM-20670 IMU on SPI2

Rotation verified on bench (yaw 90). The driver comes with PX4 PR

Signed-off-by: alexklimaj <alex@arkelectron.com>
#27624; until it merges the start call fails harmlessly at boot.

* fix(ark/v6x-rt): correct SPI3 DRDY2 pin, clang-tidy parens, cleanup

- GPIO_SPI3_DRDY2_SENSOR3 pointed at GPIO_EMC_B2_09 (the buzzer pin); corrected to GPIO_EMC_B2_18 / GPIO2_IO28 per schematic
- parenthesize BOOT_DEVICES_SELECTION / BOOT_DEVICES_FILTER_ONUSB macro bodies (clang-tidy bugprone-macro-parentheses)
- set board_id / BOARD_TYPE to 62
- remove unused ENET INT/RST GPIO macros copied from fmu-v6xrt
- remove duplicate GPIO_VDD_3V3_SENSORS4_EN init-list entry and Configuration banner
- rename fmuv6xrt_* board functions, file headers, include guards, and Kconfig symbols to ark/v6x-rt

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* rename v6x-rt to fmu-v6xrt

* feat(ark/fmu-v6xrt): build the IIM-20670 driver for the SPI2 IMU

rc.board_sensors already starts iim20670, but the Kconfig symbol was left
as a TODO placeholder because the driver was not in tree yet, so the SPI2
IMU never came up. The driver exists now.

Depends on #27624 - the symbol is unknown to Kconfig until that merges.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): declare the IMXRT chip in the board config

Without it Kconfig falls back to ARCH_CHIP_UNSET and every configure warns
"ARCH_CHIP_UNSET was assigned the value 'y' but got the value 'n'".

Cosmetic only - the real chip selection comes from the NuttX defconfig via
CONFIG_ARCH_CHIP_MIMXRT1176DVMAA, and the image is byte-identical either
way. px4/fmu-v6xrt and nxp/tropic-community already declare it.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* feat(ark/fmu-v6xrt): calibrate FlexSPI DLL read strobe at boot

Port of PX4/PX4-Autopilot#28141 (9f4bc80006), which landed on px4/fmu-v6xrt.
The two init.c files were otherwise identical apart from board-specific
names, so this takes the change unmodified.

Corrects the ROM-provided DLL delay by finding the valid DQS sampling range
and selecting its midpoint, for reliable octal-DDR flash reads.

* fix(ark/fmu-v6xrt): declare the SPI2 IMU as an IIM-20670

The LPSPI2 entry still carried the ICM45686 devtype placeholder from before
the driver existed. SPIBusIterator matches a driver to a bus device on
devtype_driver, so iim20670 found no instance and the SPI2 IMU never came up
even with the driver built in.

Depends on #27624 for DRV_IMU_DEVTYPE_IIM20670, same as the Kconfig symbol
already enabled here.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): enable the INA226 by default

rc.board_sensors only starts a power monitor explicitly when one of the
SENS_EN_INA* params is set, and otherwise falls back to i2c_launcher
autostart. With SENS_EN_INA226 left at its 0 default the board always
took the fallback, which never produced a working instance, so the
board had no battery monitor at all.

ark/fmu-v6x already sets this. Verified on hardware: started this way
the INA226 on I2C1 0x41 reads 15.998 V against a 16.007 V bench supply.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): correct the zero-padded DRDY pin macros

GPIO_PIN05 and GPIO_PIN07 do not exist - the NuttX imxrt pin macros are
not zero padded (GPIO_PIN5, GPIO_PIN7). Three DRDY definitions named
them, so any translation unit that expanded GPIO_SPI2_DRDY1_SENSOR2,
GPIO_SPI6_DRDY1_EXTERNAL1 or GPIO_SPI6_DRDY2_EXTERNAL1 would fail to
compile.

Nothing expands them today: the SPI bus description in spi.cpp carries
its own port/pin pairs, and the only other reference is
GPIO_DRDY_OFF_SPI6_DRDY2_EXTERNAL1, which is itself unused. That is why
this has gone unnoticed. Found by writing a bench command that did use
them.

boards/px4/fmu-v6xrt/src/board_config.h has the same three typos and
needs the same fix separately.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): document the board's architecture and SWD flashing

The RT1176 has no internal flash and boots XIP from an external octal NOR
via a boot ROM that reads an FCB and IVT out of the image, which is enough
unlike an STM32 that the flashing procedure looks arbitrary without it.
Covers the differences, the dependencies, and the verified pyocd sequence
for the bootloader and the application.

* feat(ark/fmu-v6xrt): build the heater driver and regulate on the LSM6DSV80X

The heater (R34/Q1 on GPIO2_IO27) is populated and plumbed in
board_config.h but the driver was never built, so the IMUs ran at
ambient. The LSM6DSV80X publishes its high-g channel, whose zero-g
level moves 2 mg/degC (DS14764 Table 3) - an 0.8 m/s^2 walk over a
40 degC swing, enough to trip the accel consistency check - so bind
the heater to that sensor. HEATER1_TEMP stays at the driver default.

* fix(ark/fmu-v6xrt): place SubscriptionIntervalBase in ITCM

The ITCM include list still named uORB::SubscriptionInterval::{updated,copy},
which stopped existing when those methods moved onto SubscriptionIntervalBase<Lb0/Lb1>.
The wildcards fail open, so the two hottest uORB copies ran XIP instead of ITCM.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): float SPIX_SYNC so it cannot fight LSM6DSV INT2

The MCU pin was driven push-pull high into LSM6DSV80X INT2, which idles
low and is active-high. Same net as ark/fmu-v6x, which leaves the pad
as an input with pulldown.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): complain when the IMU hwtype is unknown

The three internal IMUs only start on ARKV6XRT000. A future FMUM id
would previously boot with mag and baro only and no indication why.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): default RC to CRSF on the RC port

COMMON_RC already builds the per-protocol UART drivers, but none of
them were bound to a port, so there was no RC until a user set
RC_*_PRT_CFG. Bind CRSF to the RC serial (300) and leave the
auto-detect rc_input driver out — PPM/DSM-bind is not wanted here.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* feat(ark/fmu-v6xrt): enable SPARTN framing and PPS capture

Parity with ark/fmu-v6x: build the GPS SPARTN framer so PointPerfect
corrections can be injected, and include pps_capture so a mixer
PPS_Input pin can timestamp GNSS TIMEPULSE.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): drive panic LEDs from the real phy_set_led

autoleds.c defined board_autoled_on/off against a local empty
phy_set_led stub because the real one in led.c was static, so
NuttX PANIC/ASSERT never lit an LED. Fold the autoled hooks into
led.c, matching ark/fmu-v6x.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* chore(ark/fmu-v6xrt): drop leftover Kinetis and unused board symbols

automount.c is still the FMUK66 SD automounter and is compiled out.
GPIO_PWM_IN names a pinmux that does not exist, PX4_I2C_BUS_MTD
contradicts mtd.cpp, the Boot Flash Kconfig choice is unread, and
the RUNFROMISRAM copy in imxrt_ocram_initialize.c does not compile
on this XIP board. Also point HW_REV/VER_SENSE at the ADC channels
they actually sample and fix the USDHC pin table.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* perf(ark/fmu-v6xrt): grow the logger RAM buffer to 128 KiB

The imxrt arch default is 64 KiB and the logger already sat at 96 % of
that at rest. The RT1176 has megabytes of OCRAM; another 64 KiB of
ring buffer is cheap insurance against SD write dropouts in flight.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* feat(ark/fmu-v6xrt): publish LPUART11 as the EXT2 serial port

LPUART11 is built as /dev/ttyS7 and brought out on the PAB UART4 pins.
Without CONFIG_BOARD_SERIAL_EXT2 the device node exists but nothing can
bind a protocol to it from the serial-port params.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): enable 64-bit off_t so SD files can exceed 2 GiB

Without CONFIG_FS_LARGEFILE, off_t is 32-bit: a single ULog cannot
cross 2 GiB and df/ls wrap on cards larger than 4 GiB. px4/fmu-v6xrt
gained this in 05be273a35; the ARK defconfig was forked before that.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): sync the FRAM MTD driver and actually erase it

The local copy never advanced the bwrite source, so a multi-page
param save repeated the first 128 bytes, and lacked the word-aligned
bounce buffer from e8a4304e1c. Pull those in from px4/fmu-v6xrt.

Erase was still wrong in both trees: `uint8_t buf[128] = {0xff}` only
sets byte 0, and BULKERASE issued one WREN for 256 page programs.
WEL clears after each write, so only page 0 was programmed. memset
0xff and WREN per page so `mtd erase` blanks the store.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): match ark/fmu-v6x UART buffer sizes

TELEM2 (LPUART8) and EXT2 (LPUART11) were on the NuttX 256 B default.
Copy the v6x sizes by port: console 180/1500, GPS1 TX 1500, TELEM2 RX
800, EXT2 600/1500.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): keep UAVCAN off CAN3, ESC on CAN2 only

PAB pinouts CAN3 but no ARK carrier breaks it out. FlexCAN3 stays built so a different carrier can bring can2 up.

Assisted-by: Grok:grok-4.6
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): drop i2c_launcher

SENS_EN_INA226 is defaulted on, so the auto-detect fallback is unused. Start INA from the param only, like ark/fmu-v6x.

Assisted-by: Grok:grok-4.6
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): keep SENSORS4 up and float BMP INT

SENSORS4 is the BMP390 analog rail, not the SE051. Cycling it off
while VDDIO (FMU_3V3) stays up skips POR. Leave it on after the
first enable. Configure I2C2_DRDY1 as a floating input; INT idles
push-pull low and is unused by the driver.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): start the IIM-20670 last

Sensor instances follow start order, and at equal priority the voter
keeps the lowest instance, so whichever IMU starts first is the primary
by default. The IIM-20670's gyro filter cannot be opened past 60 Hz,
which is several milliseconds of delay ahead of the rate loop; it is a
fallback, not a primary. Start the ICM-45686 first and the LSM6DSV80X
ahead of it.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(rt117x): define PX4_MAKE_GPIO_EXTI

pps_capture builds its GPIO with PX4_MAKE_GPIO_EXTI, which only the
STM32 micro_hal defines. On i.MX RT an EXTI pin is just an input that
imxrt_gpiosetevent() attaches to, so it is the plain input pinset. Needed
for CONFIG_DRIVERS_PPS_CAPTURE on ark/fmu-v6xrt.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* build(ark/fmu-v6xrt): state CONFIG_IMXRT_USDHC_DMA in the defconfig

It is the Kconfig default, so it was already on, but nothing in the
board tree said so and the question of whether SD ran PIO came up twice.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* build(ark/fmu-v6xrt): give the logger a 5000-byte stack

High-water 3092 of 3608 with the logger at 368 KiB/s, the tightest task
on the board. The RT1176 has 1.7 MB of SRAM to spend.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): add an IMU section to the README

Bus, start line, ODR, publish rate and full-scale per IMU, why the
publish rates differ, why the instances are ordered as they are, and the
two caveats that lived only in driver comments and PR text: the
LSM6DSV80X publishing its high-g channel, and the IIM-20670's 60 Hz
gyro filter.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): call out the LSM6DSV80X offset temperature coefficient

The heater exists for this die: its high-g zero-g offset moves ~2 mg/degC, so an accel calibration only holds at the temperature it was done at. Say so, and that calibration and flight both happen with the heater at its setpoint.

* docs(ark/fmu-v6xrt): add the ARKV6X-RT flight controller page

The target shipped with no user-facing page, so it was also absent from
the supported-hardware, PAB-compatible and Ethernet board lists.

Photo, store URL and the mechanical/electrical specs still need to come
from ARK.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): restructure the page to the current board-doc shape

The docs maintainer has been asking every recent board PR for the same
things: a Specifications block, a flow-control column on the serial
table, and Power, PWM Outputs, Radio Control, GPS & Compass and Debug
Port sections with anchors. Match that rather than the older ARKV6X
page, which predates it.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): mark the unpublished specs with TODO

Photo, product page, input voltage, current draw, dimensions and weight
are not public yet. Leave a marker where each belongs rather than a
silent gap, so a reviewer can see what is outstanding and the PR
checklist has something to point at.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): the heater is not specific to the LSM6DSV80X

Every IMU on every ARK board gets a heater; this die is only the most temperature-sensitive one, which is why the heater targets it.

* fix(ark/fmu-v6xrt): regulate the heater on the ICM-45686

HEATER1_SENS_ID pointed at the LSM6DSV80X because its high-g channel drifted 2 mg/°C. The driver now publishes that part's low-g channel (0.07 mg/°C), so the die that flies is the one to hold at temperature. The pad warms the whole board either way; this only picks the feedback sensor.

* fix(ark/fmu-v6xrt): single-IMU selection like ark/fmu-v6x

The imxrt arch defaults turn on multi-EKF (EKF2_MULTI_IMU 3, SENS_IMU_MODE 0), so the EKF2 selector picked the primary by test ratio and at rest it could land on any of the three IMUs. Override to the voter with one primary, as ark/fmu-v6x does.

* fix(ark/fmu-v6xrt): drop the Skynode TELEM2 mavlink autostart

rc.board_mavlink came along with the px4/fmu-v6xrt copy: on base ids 009-011 it starts mavlink at 3 Mbaud on TELEM2 and locks the port. Those ids are Skynode carriers; on an ARK carrier that ever reports one, TELEM2 would be silently taken. ark/fmu-v6x has no such file.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* feat(ark/fmu-v6xrt): whitelist the generic airframes like ark/fmu-v6x

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* chore(ark/fmu-v6xrt): align the driver and module set with ark/fmu-v6x

Add what v6x carriers can use: batt_smbus, payload_deliverer, pca9685_pwm_out (started on PCA9685_EN_BUS like v6x), and the ADIS16507 and SCH16T external IMUs; add the mavlink-dev variant and the rover HIWONDER_EMM. Drop what this board cannot use or does not want: battery_status (no analog battery channels, BOARD_BATT_V_LIST is {-1,-1}), local_position_estimator, septentrio.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* refactor(ark/fmu-v6xrt): name the eighth UART TEL4 like ark/fmu-v6x

The port was EXT2 after the px4/fmu-v6xrt copy, so the same carrier connector is SER_TEL4_* on ark/fmu-v6x and SER_EXT2_* here. One name across the product line.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): correct the PFD frequency comments

SYS_PLL2 PFD3 is 432 MHz, not 216 — the value FlexIO1 divides by 4 to
reach the 108 MHz that BOARD_FLEXIO_PREQ assumes for DShot timing.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* build(ark/fmu-v6xrt): default eth0 to static 192.168.0.4 like ark/fmu-v6x

The two ARK products should present the same ethernet identity. netman writes
the compiled-in default into the carrier EEPROM on first boot, so the v6XRT
left a fresh carrier on DHCP with a ~70 s fallback to 10.41.10.2, an address
nothing at ARK uses, while the v6X has been 192.168.0.4 since #24281.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* perf(ark/fmu-v6xrt): run the DShot cycle from ITCM

Same list as px4/fmu-v6xrt: up_dshot_trigger ran over XIP and its critical section measured 5.6 µs worst case on cache misses at 800 Hz.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): start the onboard baro unconditionally

SENS_INT_BARO_EN gated the BMP390 even though it is started as an external
sensor, so the parameter never meant what it says here, and it is being
replaced by CAL_BAROn_PRIO.

* fix(ark/fmu-v6xrt): receive RC on LPUART6 RX

The NXP v6XRT template single-wires on TX because its RC net is the TX pad. This board wires PAB X1-70 (carrier SBUS/RC pin 2) to LPUART6 RX.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/fmu-v6xrt): pin ICM-45686 as the primary IMU

The three IMUs run at equal voter priority, so the sensor voter selects
the primary by whichever validates first at boot and keeps it — a
non-deterministic race that on a test flight landed the primary on the
LSM6DSV80X instead of the ICM-45686. Seed the ICM's calibration slot with
a higher priority so selection is deterministic; the voter still fails
over to the other IMUs if it degrades.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(ark/fmu-v6xrt): add board photos and correct the user page

The remaining TODOs were the photos, store URL, and the v6x electrical/mechanical numbers. The IMU section and serial table still described the LSM6DSV80X high-g channel, a heater on that die, and EXT2.

Assisted-by: Grok:grok-4.6
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: alexklimaj <alex@arkelectron.com>
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 22:54:00 -06:00
Jacob Dahl 3969a1b8ff fix(ci): publish flashable CAN node firmware (#28445)
* fix(ci): publish flashable CAN node firmware

Release packaging only collected .px4 files, which the DroneCAN bootloader cannot flash.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ci): attach canbootloader .bin as the SWD image

The cannode bootloader is a raw .bin for st-flash, not a .px4 envelope.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ci): drop cannode.zip from release assets

The target-named .uavcan.bin and _canbootloader.bin files are the flashable images; the zip only duplicated them.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 22:17:34 -06:00
PX4BuildBot b5aa22812d docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-08-29 03:20:20 +00:00
Jacob Dahl c9f5442402 feat(sensors): classify sensors independently of bus topology (#28352)
* feat(sensors): decouple sensor internal/external classification from the bus

A sensor's internal/external classification was derived from its bus
(px4_i2c_bus_external / px4_spi_bus_external), but a bus is a wire: one bus
routinely serves both chips soldered on the FC and a pinned-out connector, and
any onboard sensor on such a shared bus was classified external. That hands an
onboard mag an operator-settable rotation instead of the board rotation, feeds
an onboard baro's self-heated die temperature into air density as if it were
ambient, and inverts the 75/50 default priority. FMU-v6C and AirBrainH743
worked around it with hand-rolled device_id whitelists behind
BOARD_OVERRIDE_I2C_DEVICE_EXTERNAL; FMU-v6XRT's second onboard baro was simply
misclassified.

Classify the device instead of the bus: drivers publish is_external in
sensor_accel/gyro/mag/baro, derived from the device id by default and
overridden by the new -O start flag ("onboard") for onboard sensors that share
a bus with an external connector. -I/-X stay pure bus probe filters.

The question "is this sensor external" had four answers. It now has one, with
a single override point:

  -O -> I2CSPIDriverConfig::external -> Device::set_external() and the
        PX4* wrappers -> is_external in the sensor topic

px4_i2c_device_external() was px4_i2c_bus_external() with a device id decode in
front, and calibration::DeviceExternal() forwarded to device_is_external();
both are gone. Device::external() stops being a bus query: it is non-virtual,
defaults to device_is_external(), and takes the declared value through
set_external(), so the five I2C/SPI overrides that used to answer it from the
bus are deleted and cannot diverge again. device::I2C and device::SPI set it
from the config, so every driver built on the bus framework follows -O without
doing anything. px4_i2c_bus_external() and px4_spi_bus_external() survive as
what they honestly are - bus predicates - reachable only through the fallback.

-O is opt-in per driver (BusCLIArguments::support_onboard), the same way -k is:
a flag that every driver advertised but only a handful honoured would be a
silent no-op on the rest. This also drops the special case for the mcp23009 and
mcp23017 GPIO expanders, which use -O for their output state and simply do not
opt in.

sensor_gyro_fifo carries is_external too. VehicleAngularVelocity prefers the
FIFO topic for any IMU that publishes one, so without it the rate controller
would take its rotation from the bus while VehicleIMU took it from the topic -
the same chip, two classifications.

The BOARD_OVERRIDE_I2C_DEVICE_EXTERNAL hook is removed along with both board
implementations, replaced by -O on the affected rc.board_sensors lines.
FMU-v5x, MR-CANHUBK3, KakuteF7, NXT-Dual and MicoAir H743-Lite each start an
onboard barometer on an external bus and get the flag as well.

Assisted-by: Claude:claude-fable-5, Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* feat(sensors): treat shared buses as first-class topology

A boolean internal/external flag cannot describe a bus that carries both
hard-mounted chips and a connector. -O was an opt-in override for that
case, so most drivers silently ignored it and classification still
followed the bus.

Declare Internal / External / Shared on the bus, probe External and
Shared, and classify each sensor from -I/-s vs -X/-S.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ci): drop stale ITCM symbols and clang-tidy errors

ITCM lists still named calibration::DeviceExternal and
px4_spi_bus_external after both were removed. The host test stubs
forwarded varargs in a way the analyzer rejected, and stripped I2C
headers kept extra trailing newlines.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(sensors): inline device_is_external into the header

Reaching it through drivers__device pulled the kernel-only library into
the userspace image of a protected build; giving it a library of its own
put an archive referencing px4_i2c_buses/px4_spi_buses after the board
library that defines them. Inlining sidesteps both.

* fix(sensors): compile tcbp001ta and probe canhubk3 GPS mag

tcbp001ta is not an I2CSPIDriver, so config.external does not exist; it
only ever starts on internal SPI. -X on canhubk3 I2C2 never ran because
that bus is Internal.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 21:14:13 -06:00
Jacob Dahl b4f9388cd9 fix(drivers/imu): clip FIFO samples against the declared range, not the int16 rail (#28310)
* fix(drivers/imu): clip FIFO samples against the declared range, not the int16 rail

updateFIFO() counted a sample as clipped only within 1 LSB of INT16_MIN
or INT16_MAX, while update() compares against _clip_limit (range/scale).
The two agree only for sensors whose sensitivity is exactly range/32768.
ST parts leave headroom in the word: the LSM6DSV80X high-g channel at
+/-80 g and 3.904 mg/LSB saturates at 20492 counts, its gyro at
+/-4000 dps and 140 mdps/LSB at 28571, the LSM9DS1 and ADIS16607 are
similar, and the BMI055's 12-bit accel word never reaches the rail at
all. None of them could report a clip on the FIFO path, so the EKF's
delta-velocity clipping handling never engaged on those sensors.

Compare against _clip_limit on the FIFO path as well. For rail-scaled
sensors the threshold moves from 32766 to 32735 counts (the existing
0.999 margin), which is what the non-FIFO path already used.

* docs(msg): SensorGyroFifo/SensorAccelFifo counts are raw, not SI

x/y/z are int16 counts; SI is count * scale. The comments called them
rad/s and m/s^2, which is what sensor_gyro/sensor_accel carry.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 21:13:44 -06:00
Jacob Dahl 243936abe2 fix(drivers/invensense): drop invalid gyro FIFO samples (#28308)
With FIFO_HOLD_LAST_DATA_EN clear the sensor inserts -32768 for accel and gyro samples that carry no data (power-on until the first ODR sample, a disabled sensor, mismatched ODRs) and limits valid samples to -32766..+32767. The accel path already drops these; the gyro path published them as negative full scale.
2026-08-28 21:13:24 -06:00
PX4BuildBot 12fb7f7528 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-08-29 02:33:59 +00:00
Jacob Dahl 55bb62c2c7 refactor(sensors)!: replace SENS_INT_BARO_EN with CAL_BAROn_PRIO (#28351)
* refactor(sensors)!: replace SENS_INT_BARO_EN with CAL_BAROn_PRIO

SENS_INT_BARO_EN gated driver startup, so disabling the internal barometer
also stopped it being logged, and every board had to reimplement the check in
its rc.board_sensors - most never did. CAL_BAROn_PRIO already selects
barometers per device_id, so use it instead: 0 now means the driver runs and
the data is logged, corrected and voter-tracked, but the sensor is never
selected, never used as a failover, and never blocks arming.

Disabling a barometer that way was previously only partly effective. The voter
could still pick a priority-0 sensor when it was the only one with data, fault
demotion could raise a disabled sensor's priority back to 1, and the clamp in
ParametersUpdate() could not reach 0 at all once a sensor had been demoted.

Excluding a sensor from selection must not stop it being evaluated:
DataValidator::confidence() is the only thing that maintains the error state
every sensor reports, so a priority-0 sensor is still scored, just never
chosen. Handing over from a sensor the operator disabled is likewise not a
failover, and is no longer counted as one.

The arming check follows the magnetometer's shape: the barometer actually in
use has to be healthy, as does any the estimator is fusing, but the spares do
not. Having no enabled barometer, or none the voter can select, each reports
its own failure.

A disabled barometer now also receives the relative and GNSS offsets, which is
what makes its logged data directly comparable to the primary.

Users who had SENS_INT_BARO_EN=0 lose the setting on upgrade: the calibration
slot depends on enumeration order and is not knowable at import time. The
internal barometer returns as a failover only, since external barometers
default to priority 75 against 50.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* test(sensors): run the data validator tests in CI

The DataValidator tests still included <validation/data_validator.h>, a path
that stopped existing when the library moved out of ecl, and their CMakeLists
was never added to any build - so nothing had compiled them, let alone run
them, for years.

Port them to gtest and register them with px4_add_unit_gtest so they run with
the rest of the unit tests. Coverage is unchanged apart from replacing rand()
with a fixed sequence, so a failure reproduces, and adding the priority-0
cases: a disabled sensor is never selected even when it is the only one with
data, it still reports its own error state, and handing over from one is not
a failover.

DataValidator is otherwise a leaf library, but print() reaches for the logging
macros and the high-resolution timer, which would drag uORB, the work queues
and the POSIX daemon into a host test. A small stub translation unit supplies
those three symbols instead.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 20:27:18 -06:00
Jacob Dahl 3826a2a706 fix(ark): pin the primary IMU on fmu-v6x and pi6x (#28444)
* fix(ark/fmu-v6x): pin the bus-1 IMU as the primary

The three IMUs run at equal voter priority, so the sensor voter selects
the primary by whichever validates first at boot and keeps it — a
non-deterministic race. Seed the intended IMU's calibration slot (per
hwtype: IIM-42652 on ARKV6X000, IIM-42653 on ARKV6X001, both on SPI1,
already the heater's regulated sensor) with a higher priority so
selection is deterministic; the voter still fails over to the other
IMUs if it degrades.

param set-default is shadowed by a stored calibration, so this takes
effect on a fresh flash calibrated afterwards; an already-calibrated
board keeps its stored priority until the params are reset.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(ark/pi6x): pin the SPI1 IMU as the primary

Same equal-priority voter race as fmu-v6x: two ICM-42688-P run at equal
priority, so the primary is decided by a boot race. Seed the SPI1 IMU's
calibration slot (already the heater's regulated sensor) with a higher
priority so selection is deterministic, with fallback to the SPI2 IMU on
degradation. Replaces the placeholder TODO on HEATER1_SENS_ID, whose id
is the running SPI1 device.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 19:10:19 -06:00
PX4BuildBot b64bfbefb3 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-08-29 00:55:31 +00:00
Alex KlimajandJacob Dahl e4c10fad7f fix(drivers/imu/invensense/icm45686): constant scale and DRDY timestamps (#27663)
* fix(drivers/icm45686): correct accel 20-bit extension nibble extraction

'x & 0xF0 >> 4' evaluates as 'x & (0xF0 >> 4)' since shift binds tighter
than bitwise AND, so the accel hires path read the gyro's extension
nibble (low) instead of its own (high), injecting gyro-correlated noise
into the accel LSBs.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* refactor(drivers/icm45686): always publish full-scale data

Publish from the 16-bit FIFO registers (data[19:4]) which always cover
the full +/-32 g and +/-4000 dps ranges, instead of switching the
published scale per batch between the 20-bit hires representation and
the 16-bit fallback whenever any sample crossed ~1.4 g / ~170 dps. The
scale is now constant for the life of the driver, set once alongside
the range, and the dual-pass decode and 20-bit reassembly are removed.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* fix(drivers/icm45686): detect FIFO overflow

The overflow perf counter existed but was never incremented. A FIFO at
capacity in stop-on-full mode means newer samples were dropped; count
it and reset the FIFO instead of draining the stale backlog (same
handling as the ICM42688P driver).

Signed-off-by: alexklimaj <alex@arkelectron.com>

* feat(drivers/icm45686): use the data ready interrupt when available

Route the FIFO watermark to INT1 (push-pull, pulsed, active low) and
timestamp batches in the interrupt callback instead of polling, with
the same polling fallback and watchdog backup schedule as the
ICM42688P driver. Removes work-queue scheduling jitter from
timestamp_sample on boards that wire the DRDY line.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* fix(drivers/icm45686): program the FIFO watermark in frames

ConfigureFIFOWatermark() scans the register table for FIFO_CONFIG1_0 and
FIFO_CONFIG1_1, but neither register was in it, so FIFO_WM was never
written and kept its power-on value. The FIFO threshold interrupt now
driving the schedule therefore had no relation to the configured sample
rate. Add the two entries.

The threshold is also a FIFO frame count on this part, not a byte count
as on the ICM42688P this was copied from, so it was 20x too large.

Select the >= watermark condition as well, otherwise the interrupt is
missed whenever the FIFO overshoots the threshold between reads.

* fix(drivers/icm45686): reset the FIFO when the backlog exceeds one transfer

The overflow check only fired once the FIFO was completely full (409
frames), but the transfer buffer holds FIFO_MAX_SAMPLES. Anything in
between was drained 32 frames at a time, stamping stale samples with the
current timestamp and reporting nothing.

Reset above FIFO_MAX_SAMPLES instead, which also covers the saturated
case.

* fix(drivers/icm45686): re-read FIFO_COUNT before using it

Errata AN-000364 (2.2) states the first FIFO_COUNT read can return a
stale value. The count now decides whether the FIFO is reset, so acting
on a stale one costs a batch of samples.

* fix(drivers/icm45686): use exact full-scale conversion factors

The gyro scale was built from a rounded 131 LSB/dps, 0.055% off the exact
4000 dps / 2^15. Express both scales directly as FSR / 2^15 so they match
the ranges set just above.

* fix(drivers/icm45686): correct temperature sensitivity

132.48 LSB/C is the ICM42688P value, carried over with the driver. The
ICM45686 20 byte FIFO frame reports temperature at 128 LSB/C with a 25 C
offset, so readings were about 1.2 C low at 60 C.

* refactor(drivers/icm45686): drop the periodic register check

Re-reading configuration registers in the run loop and resetting the sensor
when one disagrees is nondeterministic behaviour guarding against something
that does not happen: nothing in the FIFO read path writes to a register
address, so a configured register does not spontaneously change. It only adds
register traffic to the cycle and gives a transient SPI error a path to reset
a healthy sensor.

Configure() still verifies every register once after writing it, which is
what makes the CONFIGURE retry work.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* refactor(drivers/icm45686): hold the FIFO transfer buffer as a member

The 641 byte transfer buffer was a stack local, zero-initialised on every
FIFO read in wq:SPIx. Nothing reads past transfer_size, so the clear was
never protecting against stale data; only the command byte has to be
restored, because transfer() overwrites it with the byte clocked in
alongside.

Also drop the self-assignments in the frame correction loops and the unused
_temperature_update_timestamp.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/icm45686): latch the FIFO watermark so INT1 can fire

The watermark threshold takes effect only when FIFO_WM[15:8] is written
(DS-000577 17.30), and a threshold of 0 means the watermark is disabled.
_fifo_gyro_samples is capped at 32, so the MSByte is always 0x00, which
equals the register's reset value - and the read-modify-write that
applies the register table skips any write whose value is unchanged. So
the MSByte write never reached the wire, the LSByte never latched, the
threshold stayed 0, and FIFO_THS never asserted.

The driver did not notice: RegisterCheck passes trivially on a zero
value, so Configure() succeeded, the data-ready interrupt attached, and
every FIFO read then came from the ScheduleDelayed(interval * 2)
watchdog at exactly half the configured rate.

Write the MSByte unconditionally, after the LSByte. Measured on an ARK
FMU-v6XRT: sensor_accel/sensor_gyro go from 398.7 Hz to 803 Hz against
the configured 800 Hz, and the "DRDY missed" counter stops at 0 where it
had been climbing at ~533/s.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/icm45686): drop invalid gyro samples and leave HIRES off

Gyro FIFO -32768 was published as negative full scale. Accel already
dropped it. FIFO_HIRES_EN was still set after publishing 16-bit data,
so the chip emitted 20-byte packets for a discarded nibble. Use the
16-byte packet and read temperature from TEMP_DATA.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(nxp/mr-tropic): drop the stale ICM45686 ITCM entry

ProcessTemperature() is gone — temperature now comes from TEMP_DATA
outside the FIFO path — so the linker script named a section the ELF no
longer contains and itcm_check failed. Its replacement,
UpdateTemperature(), runs at 1 Hz behind a blocking SPI transfer and has
nothing to gain from ITCM.

Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/icm45686): enable the FIR anti-alias filter and interpolator

GYRO_SRC_CTRL and ACCEL_SRC_CTRL reset to 0, which leaves the FIR AAF
and the interpolator off, so the 6.4 kHz UI output was a bare decimation
of the ADC with nothing ahead of it. Every other InvenSense driver here
runs with its AAF on, and ArduPilot sets SRC_CTRL=2 on this part. The
interpolator is also the block that re-times the ODR to CLKIN, so the
-c path was not doing what it claimed.

Both fields live in IPREG_SYS1/SYS2, so add the IREG indirect access
(address + data in one burst, 4 us gap between operations) and check
them alongside the bank 0 registers. The UI LPF stays bypassed.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: alexklimaj <alex@arkelectron.com>
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 18:47:41 -06:00
Jacob Dahl 7eede5e670 fix(drivers/invensense): always publish full-scale FIFO data (#28134)
Publish the 16 bit FIFO registers directly and set the scale once in
Configure(), removing the dual decode pass and the per-batch scale
switch. The 20 bit extension nibble is no longer used.

Also corrects the IIM42653 range, which reported +/-16 g and +/-2000 dps
while ACCEL_CONFIG0/GYRO_CONFIG0 program +/-32 g and +/-4000 dps.
2026-08-28 18:44:13 -06:00
Alex KlimajandJacob Dahl 29dea325db feat(drivers/imu/st/lsm6dsv): LSM6DSV 80X and 320X (#27625)
* drivers: LSM6DSV 80X and 320X

* fix(drivers/lsm6dsv): flip y/z axes to match PX4 driver convention

The ST sensor frame is right handed with z up. Flip y and z in the
driver so it publishes x forward, y right, z down like the InvenSense
drivers, and board rotations only describe physical mounting.

Update FMU-v6c accordingly: -R 26 (PITCH_180_YAW_90) becomes -R 6
(YAW_270), now matching the ICM42688P sharing the same footprint.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* fix(drivers/lsm6dsv): don't flush FIFO on high-g full-scale change

Flushing discarded batched gyro and low-g samples on every range step,
causing a data gap at exactly the moments the high-g channel matters.
Only the high-g channel's scale changes, so instead skip publishing and
clip-evaluating high-g samples for one read cycle while the words
captured at the previous full-scale drain naturally.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* fix(drivers/lsm6dsv): publish high-g samples captured before a full-scale change

Instead of skipping the high-g channel for a cycle after a range step,
normalize samples captured at the previous full-scale to the current
one (counts scaled by the exact sensitivity ratio) and publish them.
Samples are classified by reconstructed capture time since the batch
drained after a change mixes old- and new-scale words. This keeps the
high-g data flowing through escalations, which happen mid-impact when
that data matters most.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* fix(drivers/lsm6dsv): retry WHO_AM_I read in probe

The first SPI transaction after power-up can return garbage (reads
0xFF) while the device's shared I2C/I3C/SPI interface latches onto SPI
mode on the first CS falling edge. The single-shot probe read made
startup fail until bus traffic preceded it; retry up to 3 times like
the InvenSense drivers do.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* fix(drivers/lsm6dsv): set full-scale before enabling the ODRs

Both datasheets footnote CTRL6 that FS_G must be set while the gyro is in
power-down, and the reset default FS_G=000 is reserved on the 80X, 320X and
DSK320X. Configure() writes _register_cfg in array order, and that order put
CTRL1/CTRL2 ahead of CTRL6/CTRL8 — so the gyro powered up at full ODR on a
reserved full-scale, and FS_G was then changed while it was running.

Move every full-scale, filter and FIFO register ahead of CTRL1/CTRL2, which
set the ODRs and are what actually power the sensors up.

* fix(drivers/lsm6dsv): drain the FIFO in a single burst read

FIFORead() issued one 8-byte SPI transaction per 7-byte FIFO word. At the
7.68 kHz the high-g variants run, that is ~23k transactions/s, and each one
pays a bus lock, a CS toggle and a full SPI reconfigure — roughly 0.5-1 ms of
bus time per 2.5 ms drain.

AN5763 / AN6119 section 9.8 document reading DIFF_FIFO words as a single
(N * 7)-byte operation: with IF_INC set the address rounds from
FIFO_DATA_OUT_Z_H back to FIFO_DATA_OUT_TAG at every word boundary. Read the
whole drain into one buffer and dispatch by tag out of it, which is also the
shape every InvenSense driver in the tree already uses.

Also correct the FIFO::DEPTH comment: 512 is the ceiling of the DIFF_FIFO
counter, not the buffer size (1.5 KB, ~219 uncompressed words).

* fix(drivers/lsm6dsv): pin the high-g channel at its top full-scale

The high-g full-scale escalated 32 -> 64 -> 80 g on high-g clipping and
de-escalated after 2 s quiet, which in turn needed a rescale path to normalize
samples still batched at the previous sensitivity. None of it earns its keep.

The high-g channel is only ever published while the low-g channel clips, i.e.
above 16 g. All the ladder buys there is resolution — 0.976 vs 3.904 mg/LSB,
0.0096 vs 0.038 m/s² — while the fallback engages with the high-g channel's
±1.5 g typ zero-g offset uncalibrated, a 15 m/s² bias step. And it climbs one
step per FIFO drain (2.5 ms at the default IMU_GYRO_RATEMAX), so it needs
5-7.5 ms to reach ±80 g and lands after an impact's peak has passed — precisely
the sample it exists to capture.

Pin each variant at its top range instead: ±80 g on the 80X, ±320 g on the
320X. The first peak is caught unclipped, and ManageHighGFullScale(),
ApplyHighGFullScale(), RescaleCount(), the stale-sample normalization and the
high-g clip evaluation all go with it.

Two latent bugs go away rather than needing fixes. SampleClips()'s int16
threshold (~23070 counts) could never fire at ±80 g, which saturates around
20492 (3.904 mg/LSB), so sustained >64 g would have oscillated 64<->80 g; it
now only sees the low-g channel, whose ±16 g / 0.488 mg/LSB scaling does reach
the rail. And escalation could drive an 80X started with -T 320 into FS codes
011/100, which are reserved on that part — nothing writes them now.

* fix(drivers/lsm6dsv): drop unused ACCEL_ODR_HIGHG

The high-g variants' accel ODR is never referenced by name — CTRL1, CTRL2 and
the FIFO BDR all take the register code, and the sample timing is derived from
GYRO_ODR_HIGHG.

* fix(drivers/lsm6dsv): publish the high-g channel directly on the 80X / 320X

The accelerometer channel was chosen per FIFO batch: publish low-g normally,
switch to high-g for any batch where a low-g sample passed a clip threshold.
A sensor_accel_fifo message carries a single scale factor, so that meant
restating the scale of samples that had already been taken, and the decision
had no hysteresis: sustained vibration near the threshold flips the scale
batch to batch at the full publish rate, stepping the high-g channel's
uncalibrated +/-1.5 g typ zero-g offset in and out of the estimator each time.

On a part picked for its high-g range the low-g channel's finer resolution is
not worth any of that, so publish the high-g channel and nothing else, with
the scale fixed in Configure() for the lifetime of the driver — the same
fixed-scale approach the ICM45686 moved to.

The low-g channel stays enabled and batched; its FIFO words are simply not
consumed. Dropping BDR_XL would save a third of the FIFO traffic but is only
safe once XL_HG_BATCH_EN is confirmed to batch independently of it.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/lsm6dsv): drain whole sample periods only

The transfer buffer was sized to hold a partial trailing period on top of
FIFO_MAX_SAMPLES full ones, so a maximal drain yielded 33 gyro and 33 accel
words into 32-slot messages. The per-channel bound checks then dropped the
newest sample of each without a trace, leaving timestamp_sample one dt
optimistic for that batch.

Hand FIFORead whole periods instead and leave any period still being batched
in the FIFO for the next cycle. The message capacity is now a static_assert
rather than a runtime check, and the remaining bound is a real error path: it
can only trip if the tag stream stops matching the configured batching, which
is corruption, not a backlog.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* refactor(drivers/lsm6dsv): drop the periodic register check

Re-reading configuration registers in the run loop and resetting the sensor
when one disagrees is nondeterministic behaviour guarding against something
that does not happen: nothing in the FIFO read path writes to a register
address, so a configured register does not spontaneously change. It only adds
register traffic to the cycle and gives a transient SPI error a path to reset
a healthy sensor.

The 1 Hz temperature update was in the else arm of that check, so it only ran
on cycles the check skipped; it now runs on its own timer. Configure() still
verifies every register once after writing it, which is what makes the
CONFIGURE retry work.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* refactor(drivers/lsm6dsv): hold the FIFO transfer buffer as a member

The transfer buffer was a stack local, zero-initialised on every FIFO read in
wq:SPIx. Nothing reads past transfer_size, so the clear was never protecting
against stale data; only the command byte has to be restored, because
transfer() overwrites it with the byte clocked in alongside.

Also drop the FIFO temperature tag handling. FIFO_CTRL4 leaves ODR_T_BATCH at
0, so no temperature word is ever batched and the branch was unreachable;
UpdateTemperature() is what actually reports temperature.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* perf(drivers/lsm6dsv): batch only the high-g channel on the 80X / 320X

The low-g channel was still batched into the FIFO alongside the high-g
one and its words simply discarded, so every sample period cost three
FIFO words and three words of SPI traffic for two that were used. The
high-g channel batches at its own ODR under XL_HG_BATCH_EN, independent
of BDR_XL, so leave the low-g channel unbatched on those parts. One
third less FIFO fill and SPI3 traffic at the same publish rate.

* docs(drivers/lsm6dsv): correct the high-g clip detection comment

The comment claimed PX4Accelerometer detects this channel's saturation
from range/scale via UpdateClipLimit(). That limit only serves the
non-FIFO update() path; updateFIFO() flags clipping off the int16 rail,
which the ±80 g channel never reaches. State that the saturation goes
unreported rather than the opposite.

* fix(drivers/lsm6dsv): publish the low-g ±16 g channel on the 80X / 320X

The high-g accelerometer is a sports-impact element: ±1.5 g typical zero-g offset, ±2 mg/°C tempco and 1000 µg/√Hz, against ±12 mg, ±0.07 mg/°C and 60 µg/√Hz for the low-g one in the same package. Pinning it at a narrower full-scale does not change the tempco. As the published accel it reads 0.4-0.6 m/s² low uncalibrated and drifts through a calibration with die temperature, which is the wrong property for a backup flight IMU.

Batch the low-g channel at the same 7.68 kHz and leave the high-g accelerometer powered down: XL_HG_BATCH_EN clear, CTRL1_XL_HG at reset, no ACCEL_HG words. Scale is 0.488 mg/LSB for every variant; the gyro stays ±4000 dps.

---------

Signed-off-by: alexklimaj <alex@arkelectron.com>
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 18:41:25 -06:00
Alex KlimajandJacob Dahl 7135e1561f feat(drivers/imu/invensense/iim20670): add the IIM-20670 driver (#27624)
* drivers: iim-20670

* fix(drivers/iim20670): discard sensor output while settling after configure

The first samples after reset/configuration can read full-scale while
the signal path settles, publishing garbage and falsely escalating the
one-way accel range ladder (observed stepping 16->32->64 g at boot on
a stationary board). Discard output for the first 100 ms of READ.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* feat(drivers/iim20670): default to +/-32 g and recover range after quiet period

Start at the +/-32 g full-scale (accel_fs_sel = 011, fixed at runtime)
and escalate to the +/-64 g low resolution output registers only while
clipping, stepping back down after 2 s without clipping instead of
latching the coarse range until reboot. Both register sets are
maintained continuously by the sensor, so switching between them never
publishes a stale-scale sample.

Signed-off-by: alexklimaj <alex@arkelectron.com>

* fix(drivers/iim20670): program the +/-32 g full-scale that is published

Configure() overwrote the bank 6 register config with accel_fs_sel = 001
before writing it, a leftover from the 16/32/64 g ladder that was replaced by
a fixed +/-32 g full-scale. The sensor was therefore left at +/-16.384 g
(2000 LSB/g) while PX4Accelerometer was told 1000 LSB/g, so every published
acceleration was twice the real value.

It was silent because the periodic register check compares against the same
mutated struct, and because the +/-64 g escalation reads the low resolution
registers, which span +/-65.536 g at either full-scale setting.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* perf(drivers/iim20670): read and publish at 1 kHz

The driver ran the whole pipeline at the sensor's 8 kHz internal rate: eight
out-of-frame SPI transactions per sample is 64k transactions/s, and because
this part has no FIFO every sample was an individual sensor_accel and
sensor_gyro publication, waking VehicleIMU 8000 times a second.

None of it bought anything. The configured FLT cut-offs are 60 Hz for the
gyro and 400 Hz for the accel, so there is no content above the Nyquist limit
of 1 kHz to capture. Decimate the ODR pin in the interrupt instead of
dropping it, which keeps the sample timestamps tied to the sensor's own
sampling instants.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* refactor(drivers/iim20670): drop the periodic register check

Re-reading configuration registers in the run loop and resetting the sensor
when one disagrees is nondeterministic behaviour guarding against something
that does not happen: nothing in the read path writes to a register address,
so a configured register does not spontaneously change. What it does do is
add register traffic and bank switching to every 100 ms cycle and give any
transient SPI error a path to reset a healthy sensor.

Configure() still verifies every register once after writing it, which is
what makes the CONFIGURE retry work.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/iim20670): validate CRC and status on register reads

RegisterRead() returned (response >> 8) & 0xFFFF whatever came back, so a
failed CRC or a non-success RS field went straight into the caller.

That matters most in RegisterSetAndClearBits(). The datasheet requires a
read-modify-write for these registers because "all the other bits that are
contained into the same registers have to be considered reserved and changing
them might cause unwanted effects" (DS-000183 section 6.17), so a corrupted
read is written back into the reserved bits of a full-scale register. It now
skips the write and lets the Configure() verification pass fail into the
CONFIGURE retry.

CheckResponse() gained the status counting so every rejected response is
counted exactly once, CRC into "bad CRC" and a bad RS into "bad transfer";
the ReadData() caller no longer double counts.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/iim20670): select bank 0 before the unlock sequence

The six UNLOCK_SEQUENCE frames all write tcode_status, which is bank 0 offset
0x19, and bits [30:26] of an SPI command are an offset into whichever bank is
currently selected. Configure() sent them without selecting a bank.

On the first attempt that is harmless - reset leaves bank 0 selected - but
Configure() returns early on a WHOAMI mismatch, which happens after the read
that selects bank 1. The retry then writes the sequence into bank 1 offset
0x19 and nothing gets unlocked. The datasheet's own self-test procedure opens
with "Send SPI command to set Bank0" for the same reason.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/iim20670): report transfer errors via set_error_count

The driver never called set_error_count(), so sensor_accel.error_count and
sensor_gyro.error_count stayed at zero no matter how many CRC or status
failures the SPI link produced.

That field is not just for logging: VehicleIMU forwards it into
vehicle_imu_status, voted_sensors_update feeds that to the accel and gyro
voters, and DataValidator turns it into an error density that can drop a
sensor's confidence to zero. A degrading link on this IMU was therefore
invisible to sensor voting and showed up only in perf output. Both other IMUs
on the board already report it.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/iim20670): apply the ODR pin routing by writing 0x16 without a read

Bank-3 0x16 answers every read with an error status, so the read-modify-write
helper never wrote it - and its bit 0 is what applies the staged routing. Pin 12
stayed undriven, the interrupt never fired, and the driver ran on its watchdog
at exactly half rate. Write the bit directly (it self-clears). 0x14 reads back
the routing in effect rather than the staged write, so its second step carries
bit 9 forward instead of re-reading it; the read after the apply is the check
that the routing took, and without it the driver polls at the sample rate rather
than arming an interrupt that cannot fire.

Assisted-by: Claude:claude-fable-5

* fix(drivers/iim20670): scale the clip threshold to the active register set

ACCEL_CLIP_THRESHOLD was 32100 raw counts on both the HR (1000 LSB/g)
and LR (500 LSB/g) output registers, so de-escalation armed at 32.1 g
in HR and 64.2 g in LR. Under sustained 33–64 g the driver recovered
to HR, immediately re-clipped, and flapped. Use 16050 counts in LR so
the gate stays ~32.1 g.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(drivers/iim20670): always publish the ±64 g accel registers

The driver switched between the 1000 LSB/g and 500 LSB/g output
registers from signal amplitude, so PX4Accelerometer's scale changed at
runtime. Same class of cost as the ICM42688P 20-bit/16-bit switch
(#28134): consumers that key off scale (GyroFFT's window among them)
reset, and the extra bit is not worth it. Read ACCEL_*_DATA_LR at a
fixed 500 LSB/g and drop the escalate/recover path.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: alexklimaj <alex@arkelectron.com>
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 18:40:52 -06:00
Jacob DahlandBalduin c092f80f38 refactor(drv_hrt): out-line hrt_elapsed_time to save flash (#28443)
hrt_elapsed_time() was a static inline expanding to ~15 bytes at each
of its ~450 call sites (call to hrt_absolute_time plus 64-bit compare
and subtract). Move the definition into px4_platform so each call site
is a single branch. px4iofirmware does not link px4_platform, so it
compiles the new source directly.

Saves 3592 bytes of flash on px4_fmu-v6x_default.

Co-authored-by: Balduin <balduin@auterion.com>
2026-08-28 18:40:25 -06:00
Jacob DahlandBalduin 26c5ef2076 refactor(px4_work_queue): out-line ScheduledWorkItem ctor and ScheduleNow (#28440)
* refactor(px4_work_queue): out-line ScheduledWorkItem constructor to save flash

The header-inline constructor zero-initialises the hrt_call member at
every derived work-item constructor across ~150 classes. Move the
definition (verbatim) into ScheduledWorkItem.cpp next to the
destructor; the derived constructors already pay a call into the base
constructor chain, so this only removes the duplicated inline stores.

Saves 1376 B of .text on px4_fmu-v6x_default.

Signed-off-by: Balduin <balduin@auterion.com>

* refactor(px4_work_queue): out-line WorkItem::ScheduleNow to save flash

The body was inlined at ~240 call sites; a plain call is smaller at
every one of them.

Saves 672 B FLASH on px4_fmu-v6x_default.

Signed-off-by: Balduin <balduin@auterion.com>

---------

Signed-off-by: Balduin <balduin@auterion.com>
Co-authored-by: Balduin <balduin@auterion.com>
2026-08-28 18:31:57 -06:00
Jacob DahlandBalduin 28c5230c2b refactor(mavlink): de-templatize handle_message_command_both to save flash (#28442)
The two instantiations (mavlink_command_long_t / mavlink_command_int_t)
compiled to byte-identical 760 B functions: the body only reads command,
target_system/component and param1-4, which both decode handlers copy
1:1 into the vehicle_command_s they pass alongside. Take only the
normalized vehicle command instead.

Saves 760 B FLASH on px4_fmu-v6x_default.

Signed-off-by: Balduin <balduin@auterion.com>
Co-authored-by: Balduin <balduin@auterion.com>
2026-08-28 18:26:21 -06:00
Jacob DahlandBalduin 255d9c602b refactor(platform): out-line BusCLIArguments constructor to save flash (#28441)
The constructor is inlined into every I2C/SPI driver's command-line
entry point, and with it the ~15 default member initializers and the
32-byte _options zero-fill, costing ~50-70 B per driver. Move the
definition (verbatim) into i2c_spi_buses.cpp, which already holds the
rest of the CLI parsing code. Purely cold-path (driver start/CLI).

Saves 2456 B of .text on px4_fmu-v6x_default.

Signed-off-by: Balduin <balduin@auterion.com>
Co-authored-by: Balduin <balduin@auterion.com>
2026-08-28 18:26:09 -06:00
Jacob DahlandBalduin d3ca991b8e refactor(mavlink): make streams_list constexpr to save flash and RAM (#28439)
The StreamListItem constructor was not constexpr, so the ~100-entry
streams_list was built at boot by 2.3 KB of static-init code into
.bss. Constant-initialise it into .rodata instead.

Saves 1272 bytes of flash and 1088 bytes of RAM on px4_fmu-v6x_default.

Signed-off-by: Balduin <balduin@auterion.com>
Co-authored-by: Balduin <balduin@auterion.com>
2026-08-28 18:25:41 -06:00
Jacob Dahl 55aa0ea75e feat(dshot): EDT and BDShot HAL contract on i.MX RT (#28412)
* feat(dshot): EDT and BDShot HAL contract on i.MX RT

The FlexIO driver ignored edt_enable and only marked a channel ready after a CRC-good post-training frame, so one missing ESC blocked telemetry for every motor. Match the STM32 consumer contract: ready every cycle, consecutive CRC hysteresis, train on any valid GCR, and gate FlexIO output from up_dshot_arm.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(dshot): keep the trained BDShot offset across an i.MX RT dropout

Going offline restarted baud training from BDSHOT_TCMP_MIN_OFFSET. The
offset tracks the ESC oscillator, not the link, so a transient dropout
threw away a still-valid result and pinned the channel offline for the
whole re-sweep, starving the ESC RPM notch. Train once on first connect
and let the success hysteresis handle recovery.

Also close three smaller gaps: the sweep stopped one round early and
never evaluated BDSHOT_TCMP_MAX_OFFSET, up_bdshot_get_erpm carried a
bound check the next line subsumes, and re-arming left a stale channel
state that latched a garbage SHIFTBUFBIS read as a response.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(dshot): harvest i.MX RT BDShot RX before the next TX

dshot_motor_data_set flipped state to DSHOT_START and cleared SHIFTSTAT before the frame was consumed, so a delayed FlexIO IRQ transmitted irq_data instead of latching telemetry. Harvest under a critical section and skip the burst while the receive window is still open.

Zero driver state in up_dshot_init so a module restart cannot keep a stale online bit through retraining. After a second offline period, restart the TCMP sweep so a wrong baud can recover.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(dshot): guard the whole i.MX RT DShot cycle, not just the receive

The busy window started at the IRQ's receive timestamp, so a trigger landing during the frame itself reconfigured the shifter mid-transmit, and up_dshot_arm enabled the shifter interrupt with nothing queued. The first trigger after arming also counted a missing response, and the trained TCMP was updated outside the critical section the IRQ reads it in.

Stamp the cycle at transmit and size the window for frame, ESC turnaround and response. Latch, decode and reconfigure in one critical section, mask the IRQ by the enable registers instead of a channel mask, and leave the timer interrupt off while receiving. Warn for outputs the FlexIO cannot serve instead of dropping them silently.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(dshot): keep the i.MX RT DShot trigger out of a critical section

up_dshot_trigger runs at the output rate, so masking every interrupt on the MCU for the whole latch, decode and reconfigure pass was far too long. The IRQ only acts on a channel whose interrupt is enabled and the trigger only touches a channel whose cycle is over, so the two contexts never own the same channel at once and none of that needed a lock.

What does: the SHIFTBUF write and the interrupt enable must not be separated by preemption, since the IRQ has to queue the second word within 20 us at DShot1200, and SHIFTSIEN/TIMIEN have no set/clear aliases, so the thread's read-modify-write must be atomic against the IRQ's. That is a dozen register accesses.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(dshot): do not clear SSF after re-enabling the i.MX RT transmit shifter

In transmit mode SSF sets on enable and is the timer's active-low trigger. Clearing it before SHIFTBUF is written asserts the trigger, so the timer shifts an empty shifter for one compare and the real word lands late: 14 garbage sub-bits, a truncated frame, no ESC response. Only the SHIFTBUF write may clear it.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(dshot): resynchronise the i.MX RT BDShot receive timer on the channel pin

The receive timer was set to reset on its timer pin's rising edge, but the timer pin was left at FXIO_D0, so the baud counter never resynchronised; and with the timer output starting high the first shift came a full period after the start edge, so every sample sat on a bit boundary. Only a baud 2-3 % faster than the ESC's pulled the samples inside the bits, which is why training found a three-count window and why channels fell off it per run.

Point the timer pin at the channel pin so a baud-mode reset reloads the divider on every falling edge of the response, and start the output low so the shift lands mid-bit. On an ARK 4in1 at DShot300 every offset from -10 to +15 now decodes 198/200, all four channels train on the first sweep, and the CRC error rate matches the previous driver. The status output shows the training mask.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* perf(dshot): run the i.MX RT DShot cycle from ITCM

up_dshot_trigger executed from the NOR over XIP, so its ~1 µs critical section measured 5.6 µs worst case on instruction cache misses at 800 Hz. Map the per-cycle path — the trigger, its FlexIO callees, the HAL getters and the DShot module's Run/updateOutputs/telemetry — into ITCM on fmu-v6xrt, about 3 KB. decode_gcr_payload is inlined so the list needs no compiler-named partial section.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* fix(dshot): keep the i.MX RT BDShot offset across a dropout

A missing response counted toward the re-sweep, so any dropout over 0.5 s — a wire, an ESC power cycle — cost a full seven-second sweep after the ESC came back although its oscillator had not changed. Only frames that arrive and fail to decode restart training now; a dropout just takes the channel offline and it is back 200 good frames after the ESC returns.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* perf(dshot): sweep the i.MX RT BDShot baud in 25-frame rounds

With the receive timer resynchronising, every offset is either clean or fails outright, so 200 frames per offset only stretched the sweep to eight seconds at 800 Hz once the whole window started passing. 25 frames with one allowed miss give the same mask in a second, well inside the five seconds DShot.cpp ignores telemetry after boot.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* docs(dshot): restore the i.MX RT FlexIO register comments

The rewrite dropped the comments naming what each shifter and timer register write configures. They are the only prose map of the FlexIO setup, so keep them wherever the code they describe survives.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 17:27:27 -06:00
Jacob Dahl 595395e1e4 fix(dshot): retry EDT enable until the ESC confirms it (#28438)
EDT enable was sent once, a second after bidirectional telemetry came online, and never checked. AM32 only executes commands once armed, which takes a second of zero throttle plus its arming tune, so that single request can be dropped. Bluejay clears EDT whenever the motor stops, so it was off after the first flight. Treat any EDT frame after a request as confirmation, retry once a second up to five times, and start over on reconnect and on disarm.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 17:26:16 -06:00
Jacob Dahl bd048b6e3c fix(dshot): set telemetry bit on DShot commands
Bluejay and BLHeli_S ignore commands unless the tlm bit is set, so EDT enable never latched. ESC_INFO keeps it clear: AM32 answers the bit with a KISS frame on the same UART as the EEPROM dump and aborts that frame when the dump starts, so the response would begin with a truncated frame.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 17:26:16 -06:00
JCandjc-works 73a56a6a9b logger: fix mission log lifecycle for continuous modes (#28307)
Co-authored-by: jc-works <jc-works@users.noreply.github.com>
2026-08-28 16:34:20 -06:00
SaibernardandJacob Dahl 2809806c5b refactor(lib): take AlphaFilter time parameters in microseconds (#28421)
Build all targets / Scan for Board Targets (push) Canceled after 0s
Build all targets / Seed [${{ matrix.chip_family }}] (push) Canceled after 0s
Build all targets / Build [${{ matrix.runner }}][${{ matrix.group }}] (push) Canceled after 0s
Build all targets / Upload Artifacts (push) Canceled after 0s
Checks / Gate Checks [check_format] (push) Canceled after 0s
Checks / Gate Checks [check_newlines] (push) Canceled after 0s
Checks / Gate Checks [module_documentation] (push) Canceled after 0s
Checks / Gate Checks [shellcheck_all] (push) Canceled after 0s
Checks / Gate Checks [validate_module_configs] (push) Canceled after 0s
Checks / Unit Tests (push) Canceled after 0s
MacOS build / build (macos-15) (push) Canceled after 0s
MacOS build / build (macos-latest) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:22.04) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:24.04) (push) Canceled after 0s
Container build / Set Tags and Variables (push) Canceled after 0s
Container build / Build Container (amd64) (push) Canceled after 0s
Container build / Build Container (arm64) (push) Canceled after 0s
Container build / Deploy To Registry (push) Canceled after 0s
Docs - Orchestrator / T1: Detect Changes (push) Canceled after 0s
Docs - Orchestrator / T2: PR Metadata (push) Canceled after 0s
Docs - Orchestrator / T2: Metadata Sync (push) Canceled after 0s
Docs - Orchestrator / T2: Link Check (push) Canceled after 0s
Docs - Orchestrator / T3: Build Site (push) Canceled after 0s
Docs - Orchestrator / T4: Deploy (push) Canceled after 0s
Failsafe Simulator Build / build (failsafe_web) (push) Canceled after 0s
ITCM check / Checking nxp_mr-tropic (push) Canceled after 0s
ITCM check / Checking nxp_tropic-community (push) Canceled after 0s
ITCM check / Checking px4_fmu-v5x (push) Canceled after 0s
ITCM check / Checking px4_fmu-v6xrt (push) Canceled after 0s
ROS Integration Tests / build (push) Canceled after 0s
ROS Translation Node Tests / Build and test [humble] (push) Canceled after 0s
ROS Translation Node Tests / Build and test [jazzy] (push) Canceled after 0s
SITL Tests / Testing PX4 quadx (push) Canceled after 0s
SITL Tests / Testing PX4 standard_vtol (push) Canceled after 0s
SITL Tests / Testing PX4 xvert (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v5x (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v6x (push) Canceled after 0s
Python CI Checks / build (push) Canceled after 0s
FLASH usage analysis / Publish Results (push) Canceled after 0s
Static Analysis / Clang-Tidy (push) Canceled after 0s
* fix(mathlib): swap the misnamed AlphaFilter alpha tests

AlphaOneTest configures an alpha of almost zero and asserts the state
does not move, while AlphaZeroTest configures an alpha of one and
asserts pass through. The assertions are correct but each carries the
other's name. Swap the names.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* refactor(lib): take AlphaFilter time parameters in microseconds

The AlphaFilter interfaces took float seconds while most PX4 time
sources are integer microseconds, and the #28415 bug came from exactly
that mismatch: a microseconds sample interval passed into the seconds
interface. Take the time parameters of the constructor, setParameters
and update as uint64_t microseconds and convert once inside the filter,
as suggested in the #28415 review. The float and mixed-type overloads
are deleted, so a caller passing float seconds now fails to compile
instead of silently producing a wrong alpha. FilteredDerivative wraps
the same interface and moves with it.

Call sites that already hold a microseconds timestamp delta pass it
directly and drop their 1e-6 conversion. Call sites that only have a
float seconds value convert explicitly at the call, clamped to zero
first where the value is a user settable parameter, since a negative
float to unsigned conversion is undefined. Constants that also serve
non-filter uses stay in seconds and convert at the call. Behaviour is
equivalent at every site, to within one microsecond of truncation and
one float ulp on reconstructed constants.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* test(ekf2): update change indication baselines for the microsecond filter interface

The microsecond conversion truncates each sample interval to a whole
microsecond before reconstructing the float alpha, which shifts the EKF
outputs by float rounding amounts. 23 values change in each baseline,
all at the least significant digits.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* refactor(lib): use hrt_abstime for microsecond filter times at call sites

The AlphaFilter interface stays uint64_t so mathlib does not depend on
drv_hrt.h, but modules and drivers that already hold hrt_abstime
timestamps declared their filter intervals and time constants as raw
uint64_t next to them.

* style(lib): use time literals for AlphaFilter constants

Files that already include drv_hrt.h spelled microsecond constants as
raw integers with a comment giving the unit.

* refactor(vision_target_estimator): store the bias LPF time constant in microseconds

Every other AlphaFilter constant was converted to microseconds; this
one stayed float seconds and was cast at both use sites.

* style(lib): drop redundant hrt_abstime casts on time literals

The _s and _ms literals already return hrt_abstime.

* style(ekf2): move the time_literals using-directive below the includes

* fix(microstrain): pass the geoid height update timestamp as hrt_abstime

The float parameter received a microsecond timestamp and loses the
microsecond resolution after about 17 seconds of uptime.

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 11:52:33 -06:00
Saibernard 2de7ab05fa fix(gz_bridge): support GPS failure injection (#28398)
* fix(gz_bridge): support GPS failure injection

Modern Gazebo publishes sensor_gps directly from GZBridge, bypassing the shared failure-injection processing. Route each NavSat sample through process_gnss using the actual uORB publication instance so off, stuck, wrong, and recovery work for the addressed receiver.

Add functional regression coverage for the real GZBridge NavSat callback and sensor_gps publication path, plus recovery coverage for the shared GNSS processor.

Fixes #22296

Assisted-by: Codex:gpt-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

* docs: gps failure injection is available on Gazebo

The gz_bridge now applies the shared GNSS failure state to the
simulator's NavSat data, so the table entry and the SIM_GZ_EN_GPS
workaround note are out of date.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>

---------

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-08-28 11:28:57 -06:00
Nir Mor 730742b6e1 build(uxrce_dds_client): fix Micro-XRCE-DDS client build with GCC 14
GCC 14 promotes implicit function declarations to hard errors. The
client's POSIX UDP transport is compiled (though never linked) for
NuttX targets and trips this via getaddrinfo/freeaddrinfo. Downgrade
the diagnostic for the external project build only, restoring the
pre-GCC-14 behavior.

Signed-off-by: Nir Mor <nir.mor@gmail.com>
2026-08-28 09:24:00 -07:00
PX4BuildBot 5950f142d8 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-08-28 15:51:56 +00:00
Mahima YogaandSilvan Fuhrer c74272f6ac chore: remove arbitrary parameter max
Co-authored-by: Silvan Fuhrer <silvan@auterion.com>
2026-08-28 17:43:33 +02:00
mahima-yoga 3f87fc6cb1 fix(navigator): do not descend to the loiter altitude after the climbout
The loiter established after a takeoff was always commanded at the takeoff
altitude. With a time-based climbout the vehicle can be above
that already. Take the higher of the two to prevent diving down right after climb.

Signed-off-by: mahima-yoga <mahima@auterion.com>
2026-08-28 17:43:33 +02:00
mahima-yoga 1d14de2b12 feat(fw_mode_manager): add a time based takeoff climbout
Add FW_TKO_CLMB_T, which ends the climbout that many seconds after the
vehicle started climbing. It replaces the altitude.

Defaults to 0, which keeps the climbout ending at the takeoff altitude.

Signed-off-by: mahima-yoga <mahima@auterion.com>
2026-08-28 17:43:33 +02:00
mahima-yoga 22da2cb891 refactor(navigator): end the fixed-wing climbout on the reported takeoff status
Both the Navigator and the mode manager decided when the climbout was over,
each by comparing an altitude of its own. They only agreed because they read
the same number.

Report the end of the climbout from the mode manager and act on it in
the Navigator, so that it is decided in one place. No behaviour change with
the default parameters.

Signed-off-by: mahima-yoga <mahima@auterion.com>
2026-08-28 17:43:33 +02:00
Jacob Dahl a3189c5379 fix(motion_planning): stop auto setpoint drifting past an unreached waypoint (#27733)
* fix(motion_planning): aim at the target once the trajectory passes it

The L1 look-ahead point was projected forward along the prev->target line even after the smoothed trajectory passed the target. When a multicopter overshoots a mission waypoint the navigator has not marked reached, the setpoint triplet stays fixed on that waypoint, so the look-ahead kept marching down the extended leg and the vehicle drifted away from it indefinitely (no failsafe) instead of braking onto it.

Once the trajectory is at or past the target along the leg, return the target as the crossing point so the smoother decelerates and holds on the waypoint. Normal cornering is unaffected: while approaching the target the look-ahead is unchanged, and the navigator advances the triplet before the trajectory passes the waypoint.

Part of #27730.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

* test(motion_planning): cover drift past an unreached waypoint

Add a PositionSmoothing regression test that flies a fixed waypoint
triplet through the target without the navigator advancing, as happens
when a multicopter overshoots a waypoint it cannot accept. It asserts
the smoother brakes and turns back to the target instead of marching the
look-ahead point down the extended leg, which otherwise drives the
setpoint away from the waypoint unbounded with no failsafe.

While here, compute the prev->target vector and its length once in
_getL1Point instead of deriving the unit vector and the leg length
separately. Behavior-neutral.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>

---------

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
2026-08-28 16:27:51 +02:00
Stefano Colli 648319b620 fix(mavlinkftp): increase session timeout to 30s 2026-08-28 16:17:03 +02:00
bresch a3213a41df fix(ekf2): prevent mag resets after manual heading
Manual heading overrides the current heading. The mag shouldn't be able
to reset back to it again, as long as the manual heading is valid. The
manual heading flag can be reset when a yaw aiding source is fused for a
long period of time.
2026-08-28 15:42:17 +02:00
bresch 25f1d44d33 fix(ekf2): move mag decl start-stop flag
Group with mag_hdg and mag_3d
2026-08-28 15:42:17 +02:00
PX4BuildBot 7c4bf078f4 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-08-28 12:31:59 +00:00
Federico Magri d4ad655f4b feat(CAN):Add option to center the UAVCAN servo (#28423) 2026-08-28 14:25:42 +02:00
Saibernard 0bdf8c2fb0 fix(commander): convert the baro timestamp delta to seconds for the home altitude filter (#28415)
Build all targets / Scan for Board Targets (push) Canceled after 0s
Build all targets / Seed [${{ matrix.chip_family }}] (push) Canceled after 0s
Build all targets / Build [${{ matrix.runner }}][${{ matrix.group }}] (push) Canceled after 0s
Build all targets / Upload Artifacts (push) Canceled after 0s
Checks / Gate Checks [check_format] (push) Canceled after 0s
Checks / Gate Checks [check_newlines] (push) Canceled after 0s
Checks / Gate Checks [module_documentation] (push) Canceled after 0s
Checks / Gate Checks [shellcheck_all] (push) Canceled after 0s
Checks / Gate Checks [validate_module_configs] (push) Canceled after 0s
Checks / Unit Tests (push) Canceled after 0s
MacOS build / build (macos-15) (push) Canceled after 0s
MacOS build / build (macos-latest) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:22.04) (push) Canceled after 0s
Ubuntu environment build / Build and Test (ubuntu:24.04) (push) Canceled after 0s
Container build / Set Tags and Variables (push) Canceled after 0s
Container build / Build Container (amd64) (push) Canceled after 0s
Container build / Build Container (arm64) (push) Canceled after 0s
Container build / Deploy To Registry (push) Canceled after 0s
Docs - Orchestrator / T1: Detect Changes (push) Canceled after 0s
Docs - Orchestrator / T2: PR Metadata (push) Canceled after 0s
Docs - Orchestrator / T2: Metadata Sync (push) Canceled after 0s
Docs - Orchestrator / T2: Link Check (push) Canceled after 0s
Docs - Orchestrator / T3: Build Site (push) Canceled after 0s
Docs - Orchestrator / T4: Deploy (push) Canceled after 0s
Failsafe Simulator Build / build (failsafe_web) (push) Canceled after 0s
ITCM check / Checking nxp_mr-tropic (push) Canceled after 0s
ITCM check / Checking nxp_tropic-community (push) Canceled after 0s
ITCM check / Checking px4_fmu-v5x (push) Canceled after 0s
ITCM check / Checking px4_fmu-v6xrt (push) Canceled after 0s
ROS Integration Tests / build (push) Canceled after 0s
ROS Translation Node Tests / Build and test [humble] (push) Canceled after 0s
ROS Translation Node Tests / Build and test [jazzy] (push) Canceled after 0s
SITL Tests / Testing PX4 quadx (push) Canceled after 0s
SITL Tests / Testing PX4 standard_vtol (push) Canceled after 0s
SITL Tests / Testing PX4 xvert (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v5x (push) Canceled after 0s
FLASH usage analysis / Analyzing px4_fmu-v6x (push) Canceled after 0s
Python CI Checks / build (push) Canceled after 0s
FLASH usage analysis / Publish Results (push) Canceled after 0s
Static Analysis / Clang-Tidy (push) Canceled after 0s
The low pass filter smoothing barometric altitude for the in-air home
position correction was fed the raw timestamp difference. uORB timestamps
are microseconds, but AlphaFilter::setParameters() documents both of its
arguments as seconds and the filter is constructed with a 5 second time
constant, so the sample interval arrived a million times too large:

  alpha = dt / (tau + dt)

  rate     dt        alpha (before)   alpha (intended)
  50 Hz    0.0200 s        0.999750           0.003984
  100 Hz   0.0100 s        0.999500           0.001996
  200 Hz   0.0050 s        0.999001           0.000999

At an alpha of 0.9997 the filter passes essentially every raw sample
through, giving an effective time constant of 5 us instead of 5 s, so
_lpf_baro.getState() has been effectively unfiltered barometric altitude.

That state feeds the in-air home altitude correction: it is offset by
_baro_gps_static_offset and then compared against the GNSS altitude, and
home.alt is shifted when the two differ by more than
kAltitudeDifferenceThreshold. A GNSS velocity integral gates that
comparison for consistency.

The same conversion is already done correctly for the GNSS integral a few
lines below in this file, and for the geoid height filter in EKF2.

Note that this does change behaviour: the filter now actually applies its
5 s time constant, so _lpf_baro.getState() lags during a climb by roughly
the time constant times the climb rate. _baro_gps_static_offset is
captured once when the correction window opens, so that lag does not
cancel and it biases baro_alt_corrected while climbing. Reviewers who
know this feature should say whether the 5 s constant and the 1 m
threshold, both tuned while the filter was effectively a pass-through,
still want the same values now that it filters.

Assisted-by: Claude:claude-fable-5

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-08-27 18:40:21 -06:00
Saibernard e50ef1ee09 test(motion_planning): register the two unbuilt gtest files (#28416)
TrajectoryConstraintsTest.cpp and ManualVelocitySmoothingXYTest.cpp have
been in this directory since the files were moved into the library, but
neither was ever added to CMakeLists.txt, so `make tests` has never built
or run them. Twelve test cases were sitting dead in the tree.

Both compile and pass unmodified against the current library:
TrajectoryConstraints 10/10, ManualVelocitySmoothingXY 2/2.

Assisted-by: Claude:claude-fable-5

Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
2026-08-27 16:43:39 -06:00
Jaeyoung Lim 6011989fbc Fix imu time stamp to publication time for gz sim (#28414) 2026-08-27 16:30:43 -06:00
Matthias Grob b4bcbb22fb fix(FlightTaskAuto): passed waypoint check in 3D
This should not have real downsides but in priciple the driftaway could also happen vertically.
2026-08-27 19:32:43 +02:00
Roberto Rubinacci 779ec879e8 fix(flight_mode_manager): return to a waypoint that was not reached 2026-08-27 19:32:43 +02:00