Skip to content

perf(current-filter): make the averaging window a compile-time constant#10

Open
dakejahl wants to merge 1 commit into
mainfrom
perf/current-filter-const
Open

perf(current-filter): make the averaging window a compile-time constant#10
dakejahl wants to merge 1 commit into
mainfrom
perf/current-filter-const

Conversation

@dakejahl

@dakejahl dakejahl commented Jun 22, 2026

Copy link
Copy Markdown

Summary

Make the current-smoothing window a compile-time constant, and tidy up the moving-average state it touches.

Problem

The current smoothing averaged over const uint8_t numReadings = 50;. A const-qualified variable is not a constant expression, so total / numReadings in getSmoothedCurrent() divided by a value loaded from memory on every 1 kHz sample — a __aeabi_uidiv soft-division call on Cortex-M0 (F051 et al.), a hardware udiv on M3/M4+. The surrounding state was also a handful of bare, generically-named globals (total, readIndex, readings, smoothedcurrent), and smoothed_raw_current was a file-scope global used only as a throwaway intermediate at its single call site.

Solution

Define NUM_CURRENT_READINGS 50 and use it for both the array size and the divisor. With a compile-time divisor the compiler specializes the divide: on M3/M4+ it folds to a multiply/shift, and on M0 — which lacks the umull needed for the reciprocal multiply — it still calls __aeabi_uidiv but drops the runtime divisor load (verified with the bundled GCC 10.3.1 at -O3). The remaining changes are cleanup: namespace the moving-average state as current_total / current_read_index / current_readings, and drop smoothedcurrent (returned directly) and smoothed_raw_current (now a local, with its single getSmoothedCurrent() call hoisted out of the #ifdef NXP branches). Window size and behavior are unchanged; builds clean under -Werror on F051 (M0) and G431 (M4).

Possible follow-ups, out of scope here — happy to discuss in review:

  • A power-of-two window (e.g. 32 or 64) would let the divide become a single shift on M0 as well, at the cost of changing the averaging window length (it feeds the current-limit PID and telemetry).
  • Replacing the 50-tap boxcar with an EMA (as battery_voltage already does) would eliminate the 100-byte current_readings[] buffer and all the circular-buffer bookkeeping — a larger, behavior-changing simplification.

@dakejahl dakejahl added the risk:1 Trivial — functionally inert or near-zero risk label Jun 22, 2026
numReadings was a const-qualified variable, not a constant expression,
so total / numReadings divided by a value loaded from memory on every
1 kHz current sample. Making the window a #define lets the compiler
specialize the divide: on M3/M4+ it folds to a multiply/shift, and on
M0 (no umull for the reciprocal multiply) it still calls __aeabi_uidiv
but at least drops the runtime divisor load.

While here, namespace the moving-average state (current_total,
current_read_index, current_readings) and drop two globals that were
only ever throwaway intermediates: smoothedcurrent (the return value)
and smoothed_raw_current (now a local at its single call site). Window
size and behavior are unchanged.
@dakejahl dakejahl force-pushed the perf/current-filter-const branch from a94ed6d to d87276b Compare June 22, 2026 20:27
@dakejahl dakejahl changed the title perf(main): make the current filter window a compile time constant perf(current-filter): make the averaging window a compile-time constant Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reviewed risk:1 Trivial — functionally inert or near-zero risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant