feat: humanization injection — adaptive feed-rate, 912 MHz overclock, measured-rate synth cadence#8
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make injection cadence track the real measured mouse poll interval instead of the nominal bInterval, add a per-axis normalized-speed envelope for noise shaping, and fix two clock-source bugs found while grounding against the i.MXRT1060 RM / NXP SDK / Teensy core. Clock fixes (pre-existing bugs): - PIT was loaded using F_CPU/4 (204 MHz) but is clocked by PERCLK = 24 MHz (core/startup.c). Every injection period was ~8.5x too long. Add PIT_CLK_HZ and pit_ldval_from_us() with 64-bit-safe conversion. - GPT2 1 MHz tick was hard-coded /204 (only correct at F_CPU=816). Derive the prescaler from F_CPU so the microsecond tick survives a clock change. Adaptive feed rate: - Timestamp real mouse-report arrival from the free-running GPT2 counter at the precise poll-detect point (mouse interface only). - EWMA the measured delivery interval with dropout/burst outlier rejection; pre-seed from nominal bInterval so the first (possibly huge, movement-gated) inter-report gap can't poison the estimate. - Slew pit_base_ldval toward the measured target (~3%/tick, min +/-1 step so it fully converges) instead of jumping. RM-correct: applied at next reload via the existing precompute-in-ISR pattern. No FPU in the ISR. Per-axis normalized-speed envelope (peak-with-decay, time-based decay): - Track peak_x/peak_y in counts/sec (DPI-independent via measured interval). - Gated behind HZ_ADAPTIVE_NOISE (default off): the default build is byte-for- byte the prior raw-speed behavior and pays no expf/divide per frame. Determinism hardening: - Explicit .fastrun (ITCM) on the humanize per-tick hot path (no-op on host tests) to lock in placement against LTO/flag changes. - Linker ASSERT guarding the 64 KB non-cacheable DMA window; comment in usb_host.c documenting why the dsb-only pattern is correct. LED feedback (no UART): heartbeat idle slot reports adaptive convergence -- 0.5 Hz acquiring, 1 Hz once the PIT base locks on the measured rate. UART ERROR/ACTIVE status keeps priority. Tests: humanize host suite extended to 13 checks (interval convergence, target-ldval math, confidence gate, dropout rejection, seed-poison guard, conservation-with-feed). Passes in both default and -DHZ_ADAPTIVE_NOISE=1 builds; firmware cross-compiles and links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the branch to one complete, verified state across the in-flight work: KMBox Net endpoint features, hot-path determinism hardening, and a guarded core overclock. Both protocol variants build warning-free and host tests pass. Features (KMBox Net spec, src/actions.*, hurra.*, kmbox.*, proto.h): - Motion programs: timed linear + cubic Bézier moves, fixed-point with residue carry so endpoints land exact; stepped from the poll loop. - Physical-input masking (mouse buttons/axes/wheel, keyboard keycodes) enforced in the merge path before injection. - Physical-only telemetry (pre-merge, pre-mask) for true-input monitoring. Deterministic hot path: - Remove the last per-tick __aeabi_uldivmod: humanize_target_ldval and pit_ldval_from_us now use 32-bit us*(clk/1e6) (PERCLK is a whole MHz). Binary has zero soft-divide calls. - DSB after W1C in all four ISRs (pit0, dma_rx, uart_idle, usb2) to stop spurious re-entry / double ticks. - Build main.c at -O2 with the other hot sources (poll loop lives there). Clocks: - gpt_profile/led scale derived from F_CPU; static-assert F_CPU > 528 MHz (set_arm_clock fallback doesn't program dividers) and IPG whole-MHz. - Fix LED heartbeat scale: was hardcoded for 150 MHz IPG, now F_CPU/4. Overclock to 912 MHz (Makefile F_CPU ?=, overridable): - Replace the under-volting two-point DCDC table with the interpolating voltage curve (1250 mV + 25 mV/28 MHz, capped at 1575 mV silicon max). Old table gave 912 MHz ~1275 mV vs required ~1525 mV (brownout); also corrects 816 MHz from 1275 -> 1425 mV. - Thermal guard: tempmon wired into the 100 ms status tick; >=85 C trips a distinct ~12 Hz heartbeat (priority over UART status), clears at 73 C. Detection only — we never re-clock live (would corrupt F_CPU timebases). Repo hygiene: ignore __pycache__/*.pyc; add motion_test, load-test tool, and the KMBox Net requirements doc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive the standalone synth mouse-report path from the firmware's measured real poll interval instead of a hardcoded ~1 ms gate. On an 8 kHz mouse the silent-injection cadence now matches the active (merge-path) rate instead of dropping to 1 kHz at liftoff. - New pure, host-testable synth_cadence module (wrap-safe unsigned timing, measured-interval period with 1 kHz fallback and an 8 kHz floor). - kmbox synth gate switched to GPT2 microseconds + measured interval; retires the millis()/SYNTH_SILENCE_MS 1 kHz assumption. - Host unit tests wired into `make test`.
There was a problem hiding this comment.
Pull request overview
This PR merges the accumulated fix/humanization-injection work into main, updating the firmware’s injection cadence to track the measured real mouse poll interval, adding KMBox Net-oriented motion/mask/monitor features, and adjusting platform clocking/overclock + timing infrastructure. It also adds host tools and host-native unit tests to validate key behaviors without hardware.
Changes:
- Add adaptive feed-rate timing driven by measured report delivery intervals (GPT2 µs), and use it to drive PIT reload selection and synth cadence decisions.
- Implement KMBox Net features: motion programs (duration + Bézier), physical-input masking, and physical-only telemetry (Hurra; Ferrum builds link with no-op stubs).
- Add tooling/docs/tests: Ferrum load/aim harness improvements, new host-native unit tests, and documentation updates.
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/ferrum_load_test.py | New command-channel load test (latency/throughput/integrity) for Ferrum over bridge PTY. |
| tools/ferrum_aim_test.py | Reworked aim test with paced sender thread, higher-rate emission, and updated setup guidance. |
| test/synth_cadence_test.c | New host-native unit tests for wrap-safe synth cadence decisions. |
| test/motion_test.c | New host-native unit tests for motion-program endpoint/conservation/cancellation. |
| test/humanize_test.c | Expanded tests for measured-interval EWMA and PIT target derivation behaviors. |
| src/usb_host.c | Add cache/DMA rationale and DSB barrier in USB2 ISR for posted-write flush. |
| src/synth_cadence.h | New pure, host-testable cadence policy helpers (period clamp/silence/due). |
| src/proto.h | Add Hurra phys-telemetry aliases + Ferrum no-op stubs to keep merge path unified. |
| src/main.c | PIT clock fix (PERCLK 24 MHz), adaptive PIT slew, GPT2 arrival timestamping, overtemp LED mode. |
| src/led.c | Make LED heartbeat scale derived from F_CPU/IPG instead of a fixed constant. |
| src/kmbox.c | Add motion tick, physical telemetry + masking pre-merge, and measured-rate synth cadence. |
| src/hurra.h | Declare Hurra physical-only telemetry hooks. |
| src/hurra.c | Add new Hurra frame types for motion programs, phys mask, phys telemetry enable, and phys TLM emitters. |
| src/humanize.h | Expose adaptive feed-rate API: record arrivals, measured interval, target LDVAL. |
| src/humanize.c | Implement measured-interval EWMA + target LDVAL; optional normalized-speed envelope (compile-time gated). |
| src/gpt_profile.h | Make GPT2 prescaler derive from F_CPU for an exact 1 µs tick; add static asserts. |
| src/actions.h | Add APIs for physical masking and motion programs. |
| src/actions.c | Implement physical mask state + motion program generator (linear + Bézier) ticked from poll loop. |
| README.md | Refresh project overview, testing instructions, and repo layout; reference new tools. |
| Makefile | Introduce configurable F_CPU default (912 MHz), expand tests, and include main.c in hot-path -O2. |
| KMBOX_NET_FIRMWARE_REQUIREMENTS.md | New detailed firmware requirements/spec document for KMBox Net endpoint support. |
| docs/adaptive-feed-rate-plan.md | New design/plan doc capturing timing fixes and adaptive feed-rate approach. |
| core/startup.c | Improve overclock voltage selection logic for >528 MHz; guard against undervolting. |
| core/imxrt1062_mm.ld | Add linker ASSERT to keep .dmabuffers within the non-cacheable MPU window. |
| .gitignore | Ignore Python bytecode artifacts for tools/. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ```sh | ||
| pip install pyserial pynput | ||
| tools/ferrum_aim_test.py /dev/tty.usbserial-XXXX | ||
| ``` |
There was a problem hiding this comment.
Fixed in 5af01a0 — README now points the aim test at the hurra-bridge PTY (~/.hurra-bridge.tty).
| src/humanize.c/.h sub-pixel jitter + dwell | ||
| tools/ferrum_test.py protocol smoke harness | ||
| tools/ferrum_aim_test.py closed-loop aim test against on-screen dots | ||
| Makefile ARM GCC build, 816 MHz, -O2 hot path |
There was a problem hiding this comment.
Fixed in 5af01a0 — layout table now reads 912 MHz default (F_CPU-overridable).
| # lifetime for clock. Drop to 816000000 to back off; the tempmon thermal | ||
| # throttle (main.c) guards against runaway heat at any of these. |
There was a problem hiding this comment.
Fixed in 5af01a0 — reworded; tempmon flags overtemp via LED/telemetry only and does not downclock. Dropped the 'throttle' wording.
| #define OC_VOLT_STEP_HZ 28000000u | ||
| #define OC_VOLT_STEP_MV 25u | ||
| #define OC_MAX_VOLT_MV 1575u | ||
| uint32_t voltage = 1250u; // >528 MHz base | ||
| if (frequency > 600000000u) { | ||
| voltage += ((frequency - 600000000u) / OC_VOLT_STEP_HZ) * OC_VOLT_STEP_MV; | ||
| if (voltage > OC_MAX_VOLT_MV) voltage = OC_MAX_VOLT_MV; | ||
| } |
There was a problem hiding this comment.
Fixed in 5af01a0 — the OC voltage constants are now local consts instead of function-scoped #defines.
| except serial.SerialTimeoutException: | ||
| pass # firmware backpressure — drop this tick, next one will resend | ||
| return False # firmware/bridge backpressure — drop, next tick resends | ||
| except serial.SerialException: |
There was a problem hiding this comment.
Fixed in 3e0c54c — comment now states the step is dropped (not resent); the next tick integrates velocity and computes a fresh delta.
- README: aim-test points at the hurra-bridge PTY (not a raw serial dev); layout table shows 912 MHz default (F_CPU-overridable), not 816. - Makefile: tempmon flags overtemp via LED/telemetry only — it does not downclock. Drop the "thermal throttle" wording that implied protection. - startup.c: the per-block OC voltage constants are local consts now, not #defines that leak identifiers into the rest of the file.
Integrates the work accumulated on
fix/humanization-injectionintomain.What's included (4 commits)
cb64497) — drives the standalone synth mouse-report path from the firmware's measured real poll interval instead of a hardcoded ~1 ms gate. On an 8 kHz mouse the silent-injection cadence now matches the active (merge-path) rate instead of dropping to 1 kHz at liftoff. New pure, host-testablesynth_cadencemodule (wrap-safe timing, measured-interval period with 1 kHz fallback + 8 kHz floor); retires themillis()/SYNTH_SILENCE_MS1 kHz assumption.287311e) — voltage-scaled 912 MHz default with thermal guard; motion-program path (automove/Bézier/phys-masking/input-monitoring); 64-bit-divide-determinism fixes in the hot path.551dc69).768b18c).Testing
makeandmake PROTOCOL=ferrumbuild clean (zero warnings).make testpasses: humanize, motion, and synth_cadence host suites.Notes
wip/pit-driven-synth-emission(has an open stale-frame edge case + needs on-target logic-analyzer validation). Not part of this PR.