fix(dronecan): count stall-rail trips in esc.Status error_count#54
Draft
AlexKlimaj wants to merge 1 commit into
Draft
fix(dronecan): count stall-rail trips in esc.Status error_count#54AlexKlimaj wants to merge 1 commit into
AlexKlimaj wants to merge 1 commit into
Conversation
error_count was desync_happened alone, which is incremented in exactly one place - the jump-desync check in runtimeProcessDesyncCheck. Every other way an established run dies left it untouched: - the INTERVAL_TIMER stall rail (faultHandleBemfIntervalStall) bumps bemf_timeout_happened and restarts the loop - the blind-grind rail kicks INTERVAL_TIMER to 46000 so that stall rail is guaranteed to trip on the next main-loop pass - the blind-step / miss-bucket limit in bemf_zc.c hands off the same way So the continuous partial desync documented at the top of the grind rail (423 blind steps/s at a 19.5% miss rate, duty cut engaging and releasing in a limit cycle to 102 A) reported error_count 0 to the flight controller for the entire event. Add fault_stall_trips, a monotonic since-boot counter incremented at the stall rail under the existing zero_crosses > 100 gate, and sum it with desync_happened in the new faultErrorCount(). The gate matters: a start attempt that never got going is the stuck-rotor rail's job, with its throttle-scaled tolerance, and heavy props legitimately kick many times at low throttle - counting those would make error_count climb on every normal start. The stall rail is the single aggregation point for the grind and blind/miss-limit paths, so counting it once is what keeps one physical failure from being reported as two or three events. The episode-rail latch is likewise not a separate addend: it is a consequence of accumulating the above and is a state (ESC_FAULT_STUCK), not an event. Attribution between the sub-causes belongs in the AM32-reserved FlexDebug payload and is left to a follow-up. The counter is volatile: it is read from the main loop for telemetry while the grind rail that forces the trip runs in tenKhzRoutine, i.e. ISR context. Extend the existing SITL esc.Status test to assert a healthy spool-up to a steady 4-6k rpm reports error_count 0 - the regression guard for the zero_crosses gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoD65bRT7unXhETnZ13WEq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
uavcan.equipment.esc.Status.error_countwasdesync_happenedalone, and that counter is incremented in exactly one place — the jump-desync check inruntimeProcessDesyncCheck(Src/runtime_loop.c:125). Every other way an established run dies left it untouched:faultHandleBemfIntervalStall) bumpsbemf_timeout_happenedand restarts the loopINTERVAL_TIMERto 46000 so that stall rail is guaranteed to trip on the next main-loop passbemf_zc.c:84hands off the same waySo the continuous partial desync documented in the block comment above the grind rail — 423 blind steps/s at a 19.5% miss rate, duty cut engaging and releasing in a limit cycle to 102 A — reported
error_count = 0to the flight controller for the entire event.Change
Add
fault_stall_trips, a monotonic since-boot counter incremented at the stall rail under the existingzero_crosses > 100gate, and sum it withdesync_happenedin a newfaultErrorCount().Src/DroneCAN/DroneCAN.cnow calls that instead of readingdesync_happeneddirectly.Why the gate matters
A start attempt that never got going is the stuck-rotor rail's job, with its throttle-scaled tolerance (
bemf_timeout100 below input 150). Heavy props legitimately kick many times at low throttle — counting those would makeerror_countclimb on every normal start. Reusing the same gate the episode charge already uses keeps that out.Why only two addends
The stall rail is the single aggregation point for the grind and blind/miss-limit paths, so counting it once is what keeps one physical failure from being reported as two or three events. Deliberately not separate addends:
ESC_FAULT_STUCK), not an eventAttribution between those sub-causes belongs in the AM32-reserved FlexDebug payload — see follow-up below.
Concurrency
fault_stall_tripsisvolatile: it is read from the main loop for telemetry while the grind rail that forces the trip runs infaultDesyncEpisodeTick1kHz→tenKhzRoutine, i.e. ISR context (Mcu/f051/Src/stm32f0xx_it.c:210). The stall rail itself is main-loop (main.c:515).Note
desync_happenedremains a plainuint32_t— pre-existing, and its only writer and reader are both main-loop, so it is left alone here.Cost
Measured on
ARK_4IN1_F051withHWCI_PERF=1(the binding target —make size-check-ark):faultErrorCount()is garbage-collected on non-CAN targets since nothing calls it; the 4 B counter itself still lands in.bssthere. That is deliberate — gating it would mean#ifdef DRONECAN_SUPPORTinside the fault path for 4 bytes.Verification
make AM32_SITL_CANandmake ARK_4IN1_F051build clean under-Wall -Wextra -Werror.make size-check-arkPASS (94.63% flash / 86.00% RAM, both under gate).make check_formatpasses with clang-format 22.1.5, the version CI pins. (Heads up for anyone reproducing locally: with clang-format 18 the check reports 4 pre-existing failures inMcu/f415,Mcu/g431, andMcu/v203— that is version skew, not drift, and those files are untouched here.)make cppchecknot run — cppcheck isn't installed in this environment.Test added
The existing
test_dronecan_throttle_and_esc_statusnow asserts an honest spool-up to a steady 4-6k rpm reportserror_count == 0— the regression guard for thezero_crossesgate. Run 3× independently to confirm it isn't flaky.There is no positive test that the counter increments: inducing a genuine stall-rail trip in SITL is not something the current harness does, so that side is unverified in CI and wants a bench check.
Follow-up, deliberately not here
Per-cause attribution in FlexDebug. Note PR #37 already brings in an upstream FlexDebug v2 (duty/input/ADC/flags), so the breakdown should land on top of #37 as v3 rather than independently claiming v2 — and it needs the matching
hwcihost-side decoder change.🤖 Generated with Claude Code
https://claude.ai/code/session_01HoD65bRT7unXhETnZ13WEq
Generated by Claude Code