From 9b7ded23438d92ac0b3951fe7781a56b7ed1f8f7 Mon Sep 17 00:00:00 2001 From: "Sven H." Date: Tue, 25 Nov 2025 11:51:07 +0100 Subject: [PATCH] Fix PCA9685 V2 driver: PWMTO fading logic and overflow (#24159) Fixed integer overflow causing loops when dimming down. Corrected step calculation for smooth fading with LEDs. NOT tested with servos! --- .../xdrv_15_pca9685_v2.ino | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685_v2.ino b/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685_v2.ino index 5ba364e30..2e24bc271 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685_v2.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685_v2.ino @@ -285,18 +285,33 @@ void PCA9685_RunMotor() if (m->step == 0 || (m->step % m->every == 0)) { - // AddLog(LOG_LEVEL_DEBUG, "PCA9685: MOTOR dev=%u pin=%u s=%u e=%u pwm=%lu target=%lu dir=%d", - // dev, - // pin, - // m->step, - // m->every, - // m->pwm, - // m->target, - // m->direction); + // --- SECURITY-FIX START --- + + // We temporarily use "int32_t" (signed and large enough) + // so that we don't run into overflows. + int32_t next_val = (int32_t)m->pwm + (int32_t)m->direction; - PCA9685_SetPWM(dev, pin, m->pwm + m->direction, pca9685.inverted[dev]); + // we dim UP (direction > 0) + if (m->direction > 0) { + // // If we overshoot the target -> clamp to the target + if (next_val > m->target) next_val = m->target; + // Absolute hardware upper limit (4096 is the “fully on” bit) + if (next_val > 4096) next_val = 4096; + } + + // we dim DOWN (direction < 0) + if (m->direction < 0) { + // If we were to fall below the target -> clamp to the target + if (next_val < m->target) next_val = m->target; + // Absolute lower limit + if (next_val < 0) next_val = 0; + } + + // Now write the safe, verified value + PCA9685_SetPWM(dev, pin, (uint16_t)next_val, pca9685.inverted[dev]); + + // --- SECURITY-FIX END --- } - m->step++; } } @@ -441,24 +456,28 @@ bool PCA9685_Command(void) // m->target, // m->direction); - m->every = 0; - while (m->every < 1) - { - uint16_t stepValue = abs((int16_t)m->pwm - (int16_t)m->target) / abs(m->direction); - if (stepValue < 1) - { - m->direction += m->target < m->pwm ? -1 : 1; - continue; - } + // calculate total difference (e.g. 4095 steps) + uint16_t totalSteps = abs((int16_t)m->pwm - (int16_t)m->target); + + // calculate how many 50ms-ticks we have at all + // tids is in 0.1s. so tids * 2 is the amount of 50ms ticks. + uint16_t totalTicks = tids * 2; - m->every = round((tids * 200) / stepValue); - if (m->every < 1) - { - m->direction += m->target < m->pwm ? -1 : 1; - continue; - } + if (totalTicks < 1) totalTicks = 1; // lowest possible + + // calculate steps per tick + uint16_t stepsPerTick = totalSteps / totalTicks; + + if (stepsPerTick < 1) { + // Slow fade (we have morew time than steps) + m->direction = m->target < m->pwm ? -1 : 1; + m->every = totalTicks / totalSteps; + } else { + // Fast Fade (LED Mode: We need to make big steps) + m->direction = (m->target < m->pwm ? -1 : 1) * stepsPerTick; + m->every = 1; // Fire every tick! } - + m->running = true; } } @@ -622,4 +641,4 @@ bool Xdrv15(uint32_t function) } #endif // USE_PCA9685_V2 -#endif // USE_IC2 \ No newline at end of file +#endif // USE_IC2