Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions src/main/drivers/pinio.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ static bool pinioInitTimerPWM(int slot, IO_t io, const timerHardware_t *timHw, b
return true;
}

// True if another channel on the same physical timer is dedicated to a fixed-period
// output (WS2811 LED strip or BEEPER tone). pinioInitTimerPWM() has no per-timer
// exclusivity check of its own -- timerGetTCH() will happily hand out a TCH for any
// channel regardless of what else is running on that timer -- so without this guard,
// configuring such a pad as timer-PWM would call timerConfigBase() and reprogram the
// period/prescaler shared by every channel on the timer, corrupting the fixed-period
// output it belongs to.
static bool timerHasFixedPeriodSibling(const timerHardware_t *timHw)
{
for (int i = 0; i < timerHardwareCount; i++) {
if (timerHardware[i].tim == timHw->tim && (timerHardware[i].usageFlags & (TIM_USE_LED | TIM_USE_BEEPER))) {
return true;
}
}
return false;
}

void pinioInit(void)
{
int runtimeCount = 0;
Expand All @@ -126,7 +143,7 @@ void pinioInit(void)

bool inverted = (pinioHardware[i].flags & PINIO_FLAGS_INVERTED) != 0;
const timerHardware_t *timHw = timerGetByTag(pinioHardware[i].ioTag, TIM_USE_ANY);
if (timHw && IOGetOwner(io) == OWNER_FREE && pinioInitTimerPWM(runtimeCount, io, timHw, inverted)) {
if (timHw && IOGetOwner(io) == OWNER_FREE && !timerHasFixedPeriodSibling(timHw) && pinioInitTimerPWM(runtimeCount, io, timHw, inverted)) {
runtimeCount++;
continue;
}
Expand All @@ -152,13 +169,13 @@ void pinioInit(void)
if (!io || IOGetOwner(io) != OWNER_FREE) {
continue;
}
if (pinioInitTimerPWM(runtimeCount, io, timHw, false)) {
if (!timerHasFixedPeriodSibling(timHw) && pinioInitTimerPWM(runtimeCount, io, timHw, false)) {
runtimeCount++;
continue;
}

// GPIO fallback: pad's timer is already running at a fixed frequency
// for another purpose (e.g. BEEPER), so PWM duty control isn't available.
// for another purpose (e.g. BEEPER or LED), so PWM duty control isn't available.
IOInit(io, OWNER_PINIO, RESOURCE_OUTPUT, RESOURCE_INDEX(runtimeCount));
IOConfigGPIO(io, IOCFG_OUT_PP);
pinioRuntime[runtimeCount].inverted = false;
Expand Down
31 changes: 26 additions & 5 deletions src/main/drivers/pwm_mapping.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ static bool checkPwmTimerConflicts(const timerHardware_t *timHw)
return false;
}

static void timerHardwareOverride(timerHardware_t * timer, bool isCanonicalBeeperPad) {
static void timerHardwareOverride(timerHardware_t * timer, bool isCanonicalBeeperPad, bool isCanonicalLedPad) {
switch (timerOverrides(timer2id(timer->tim))->outputMode) {
case OUTPUT_MODE_MOTORS:
timer->usageFlags &= ~(TIM_USE_SERVO|TIM_USE_LED|TIM_USE_BEEPER);
Expand All @@ -222,8 +222,17 @@ static void timerHardwareOverride(timerHardware_t * timer, bool isCanonicalBeepe
timer->usageFlags |= TIM_USE_SERVO;
break;
case OUTPUT_MODE_LED:
timer->usageFlags &= ~(TIM_USE_MOTOR|TIM_USE_SERVO|TIM_USE_BEEPER);
timer->usageFlags |= TIM_USE_LED;
// Other channels of this timer share its period register, so they can't
// be repurposed as motor/servo/BEEPER either. Only the first (canonical)
// pad is the one ws2811LedStripInit() actually wires up; siblings can still
// drive a GPIO-only PINIO (binary on/off, no PWM), since that doesn't need
// the timer's fixed WS2811-bitrate period.
timer->usageFlags &= ~(TIM_USE_MOTOR|TIM_USE_SERVO|TIM_USE_BEEPER|TIM_USE_LED);
timer->usageFlags |= TIM_USE_PINIO;
if (isCanonicalLedPad) {
timer->usageFlags &= ~TIM_USE_PINIO;
timer->usageFlags |= TIM_USE_LED;
}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
break;
case OUTPUT_MODE_PINIO:
timer->usageFlags &= ~(TIM_USE_MOTOR|TIM_USE_SERVO|TIM_USE_LED|TIM_USE_BEEPER);
Expand Down Expand Up @@ -292,8 +301,14 @@ static void pwmAssignOutput(timMotorServoHardware_t *timOutputs, timerHardware_t
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
case MAP_TO_LED_OUTPUT:
// Unlike motor/servo, do NOT pwmClaimTimer() here: that propagates
// this pad's flags to every sibling pad sharing the physical timer,
// which is correct for motor/servo (a multi-channel timer should
// offer multiple motor/servo outputs) but wrong for LED — only this
// one canonical pad is ever driven, and propagating TIM_USE_LED would
// overwrite siblings' correct TIM_USE_PINIO/BEEPER (from the earlier
// canonical-pad pass in timerHardwareOverride()) back to a false LED flag.
timHw->usageFlags &= TIM_USE_LED;
pwmClaimTimer(timHw->tim, timHw->usageFlags);
break;
default:
break;
Expand Down Expand Up @@ -327,6 +342,7 @@ void pwmBuildTimerOutputList(timMotorServoHardware_t *timOutputs)

// Apply all timerOverrides upfront so flag state is stable for both passes
bool beeperClaimed[HARDWARE_TIMER_DEFINITION_COUNT] = { false };
bool ledClaimed[HARDWARE_TIMER_DEFINITION_COUNT] = { false };
for (int idx = 0; idx < timerHardwareCount; idx++) {
timerHardware_t *timHw = &timerHardware[idx];
uint8_t timId = timer2id(timHw->tim);
Expand All @@ -335,7 +351,12 @@ void pwmBuildTimerOutputList(timMotorServoHardware_t *timOutputs)
beeperClaimed[timId] = true;
isCanonicalBeeperPad = true;
}
timerHardwareOverride(timHw, isCanonicalBeeperPad);
bool isCanonicalLedPad = false;
if (timerOverrides(timId)->outputMode == OUTPUT_MODE_LED && !ledClaimed[timId]) {
ledClaimed[timId] = true;
isCanonicalLedPad = true;
}
timerHardwareOverride(timHw, isCanonicalBeeperPad, isCanonicalLedPad);
}

// Assign outputs in priority order: dedicated first, then auto.
Expand Down
Loading
Loading