Fix PWM motor-role double-counting in pwmEnsureEnoughtMotors() - #11787
Fix PWM motor-role double-counting in pwmEnsureEnoughtMotors()#11787sensei-hacker wants to merge 2 commits into
Conversation
Pass 1 counted a shared-timer motor-only group of n outputs as 2n-1 instead of n: pwmClaimTimer() force-syncs every sibling on the same physical timer as soon as the first one is visited, and the loop had no guard against re-counting a sibling that was already promoted by that broadcast when it reached its own turn later in the same pass. The inflated count made pass 2 more conservative than it should be, silently demoting a later AUTO output from motor to servo with no warning. Triggerable both by target.c declaring 2+ TIM_USE_MOTOR channels on one timer (unconditional at boot) and by the ordinary runtime timer_output_mode MOTORS override exposed in Configurator's Mixer tab. Adds a per-physical-timer dedup guard, matching the de-duplication pass 2 already has via its !TIM_IS_MOTOR_ONLY(...) check.
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
PR Summary by QodoFix PWM motor-role double-counting in pwmEnsureEnoughtMotors()
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1. Motor outputs undercounted
|
| if (TIM_IS_MOTOR_ONLY(timHw->usageFlags) && !timerCounted[timer2id(timHw->tim)]) { | ||
| timerCounted[timer2id(timHw->tim)] = true; | ||
| motorOnlyOutputs++; | ||
| motorOnlyOutputs += pwmClaimTimer(timHw->tim, timHw->usageFlags); |
There was a problem hiding this comment.
1. Motor outputs undercounted 🐞 Bug ≡ Correctness
Pass 1 now de-duplicates motor-only counting per physical timer, but it only adds 1 + pwmClaimTimer(changed), so a timer whose channels are already TIM_USE_MOTOR contributes just 1 regardless of how many motor pads it has. This undercount can make pass 2 promote TIM_USE_OUTPUT_AUTO pads to motors and claim whole timers as motor-only, silently removing servo-capable outputs.
Agent Prompt
## Issue description
`pwmEnsureEnoughtMotors()` pass 1 now uses `timerCounted[]` to avoid double-counting a physical timer, but it still relies on `motorOnlyOutputs++` plus `pwmClaimTimer()`'s **changed** count to represent the number of motor outputs on that timer. If all channels on a timer are already motor-only, `pwmClaimTimer()` returns 0, so the timer contributes only 1 to `motorOnlyOutputs` even if it has 2–4 motor pads.
This can leave `motorOnlyOutputs < motorCount` and cause pass 2 to promote `TIM_USE_OUTPUT_AUTO` pads to motors unnecessarily; because promotion calls `pwmClaimTimer()`, it can claim an entire unrelated timer as motor-only and those siblings will not be demoted later (they no longer match the pass-2 `!TIM_IS_MOTOR_ONLY(...)` guard).
## Issue Context
Example target with 4 motor pads on a single timer plus AUTO pads:
- `IFLIGHT_BLITZ_F7_AIO` has 4x `TIM_USE_MOTOR` on `TIM3` and additional `TIM_USE_OUTPUT_AUTO` on `TIM1`/`TIM4`, so after this change pass 1 counts the 4 TIM3 motors as **1**, potentially triggering pass-2 promotion of AUTO outputs.
## Fix Focus Areas
- src/main/drivers/pwm_mapping.c[260-317]
- src/main/target/IFLIGHT_BLITZ_F7_AIO/target.c[32-45]
## Suggested fix approach
When encountering the first motor-only pad for a physical timer (i.e., when `timerCounted[timerId]` is false), compute the number of usable pads for that timer and add that count once, instead of adding `1 + pwmClaimTimer(changed)`.
Concretely:
1. Compute `timerId = timer2id(timHw->tim)` once.
2. If motor-only and not yet counted:
- Count pads that share `timHw->tim` (optionally excluding conflicts via `checkPwmTimerConflicts()` if the intent is “usable motor outputs”).
- Add that pad-count to `motorOnlyOutputs`.
- Call `pwmClaimTimer()` to sync flags, but do **not** use its return value for the motor output count.
This preserves de-duplication while keeping the counter accurate for timers where channels were already motor-only.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
…oading actions/download-artifact)
Summary
Pass 1 of
pwmEnsureEnoughtMotors()insrc/main/drivers/pwm_mapping.covercounts motor-only outputs that share a physical timer: a group ofnshared-timer outputs that are already motor-only at the start of pass 1 inflates themotorOnlyOutputscounter by2n - 1instead ofn.pwmClaimTimer()force-syncs every output sharing a physical timer as soon as the first one is visited. Each sibling then independently satisfiesTIM_IS_MOTOR_ONLYwhen the loop reaches its own index later in the same pass, and gets counted again — pass 1 has no guard against this, unlike pass 2's!TIM_IS_MOTOR_ONLY(...)check for the same class of re-visit.The inflated count makes pass 2 more conservative than it should be when deciding whether to promote remaining
AUTOoutputs to motors. This can silently demote an output the user configured as a motor down to servo, with no error, log message, or other indication.Trigger paths
Both hit an ordinary user, not just unusual
target.cauthoring:target.cdeclares 2+ channels sharing one physical timer as plainTIM_USE_MOTOR(notTIM_USE_OUTPUT_AUTO) — hit at boot regardless of user action. A survey of the target tree found 34 targets with this pattern (e.g.KROOZX,SPRACINGF7DUAL,IFLIGHT_BLITZ_F7_AIO).timer_output_mode <timer> MOTORSoverride, exposed in Configurator's Mixer tab, applied to a physical timer serving 2+ outputs.timerHardwareOverride()applies the override before theTIM_IS_MOTOR_ONLYcheck, so an override-forced group hits the same inflation as a compile-time-declared one.Fix
Adds a per-physical-timer
timerCounted[]dedup guard to pass 1, so each physical timer's motor-only group is counted exactly once regardless of how many of its channels get individually re-visited later in the same pass — mirroring the de-duplication pass 2 already has.Branch scope
Confirmed present on
release/9.1. Confirmed not present onmaintenance-10.x— that branch'spwmEnsureEnoughtMotors()was already replaced by a unifiedpwmBuildTimerOutputList()(direct per-pad assignment, no separate inflatable counter) as an apparent unintentional side effect of unrelated refactor work, so nomaintenance-10.xfix is needed.Testing
cmake -DSITL=ON,make SITL.elf) compiles cleanly with no new warnings.simulate_pwm_roles.pyin the INAV harness tooling, not part of this PR) — before the fix, a synthetic 2-channel shared-timer group forced toMOTORSinflated the count to 3 and silently demoted a third output atmotorCount=3; after the fix, the group counts correctly as 2 and the third output promotes as expected.Related Issues
None filed yet — found while debugging PWM/DSHOT output setup on a custom target.