From 77d7cb8137003fdc66ec25f3569c9e0534cb9fe1 Mon Sep 17 00:00:00 2001 From: Roberto <54690067+robrozo@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:44:14 -0300 Subject: [PATCH] Update HeaterService.h For heating control, using fast PWM via analogWrite() is not ideal. In thermal systems, the most effective approach is Time Proportional Control, where the heater is driven based on an on/off time window. The code has been updated to use a 10-second time window, allowing the heater to be controlled by the proportion of ON/OFF time within that window. This reduces unnecessary switching and improves overall temperature control stability. --- src/HeaterService.h | 150 +++++++++++++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 52 deletions(-) diff --git a/src/HeaterService.h b/src/HeaterService.h index c318543a1..764c7bfc1 100644 --- a/src/HeaterService.h +++ b/src/HeaterService.h @@ -27,74 +27,117 @@ class HeaterService { } - HeaterServiceStatus Compute(double input, double target, double heaterPercentage) + + void WriteTimeProportionalPWM(uint8_t pin, int pwm) { - HeaterServiceStatus status; - uint8_t _heaterBus = GetBus(); - SetUP(); - if (StopCompute()) + unsigned long now = millis(); + + if (now - _windowStartTime >= _windowSize) { - status.PIDActing = false; - status.PWM = 0; - status.PWMPercentage = 0; - TurnOff(); - return status; + _windowStartTime += _windowSize; } - SetPidParameters(input, target); + unsigned long onTime = (pwm * _windowSize) / 1023; - if (_activeStatus->PIDSettingsUpdated) - { - _activeStatus->PIDSettingsUpdated = false; - StartPID(_brewSettingsService->KP, _brewSettingsService->KI, _brewSettingsService->KD); - Serial.println("BrewSettings Updated: " + String(_brewSettingsService->KP) + "/" + String(_brewSettingsService->KI) + "/" + String(_brewSettingsService->KD)); - return status; - } + if ((now - _windowStartTime) < onTime) + digitalWrite(pin, InvertedPWM() ? LOW : HIGH); + else + digitalWrite(pin, InvertedPWM() ? HIGH : LOW); + } - if (_activeStatus->ActiveStep == boil) - { - status.PIDActing = false; - status.PWM = ((1023 * _brewSettingsService->BoilPowerPercentage) / 100); - status.PWMPercentage = (status.PWM * 100) / 1023; - analogWrite(_heaterBus, InvertedPWM() ? abs(status.PWM - 1023) : status.PWM); - return status; - } +HeaterServiceStatus Compute(double input, double target, double heaterPercentage) +{ + HeaterServiceStatus status = {0, 0, false}; - if (_activeStatus->FullPower) - heaterPercentage = 100; + uint8_t heaterBus = GetBus(); - if (GetPidSetPoint() - GetPidInput() > _brewSettingsService->PIDStart) - { - status.PIDActing = false; - status.PWM = ((1023 * heaterPercentage) / 100); - status.PWMPercentage = (status.PWM * 100) / 1023; - analogWrite(_heaterBus, status.PWM); - return status; - } + SetUP(); - // to prevent pid overshoot - if (GetPidInput() > GetPidSetPoint() + 0.1) - { - status.PWM = 0; - status.PWMPercentage = 0; - status.PIDActing = false; - analogWrite(_heaterBus, _activeStatus->PWM); - StartPID(_brewSettingsService->KP, _brewSettingsService->KI, _brewSettingsService->KD); - return status; - } + if (StopCompute()) + { + TurnOff(); + return status; + } + + SetPidParameters(input, target); + + if (_activeStatus->PIDSettingsUpdated) + { + _activeStatus->PIDSettingsUpdated = false; - PidCompute(); + StartPID( + _brewSettingsService->KP, + _brewSettingsService->KI, + _brewSettingsService->KD + ); - int maxPWM = ((1023 * heaterPercentage) / 100); - status.PWM = GetPidOutput() > maxPWM ? maxPWM : GetPidOutput(); + Serial.println( + "BrewSettings Updated: " + + String(_brewSettingsService->KP) + "/" + + String(_brewSettingsService->KI) + "/" + + String(_brewSettingsService->KD) + ); + + return status; + } + + if (_activeStatus->ActiveStep == boil) + { + status.PWM = (1023 * _brewSettingsService->BoilPowerPercentage) / 100; status.PWMPercentage = (status.PWM * 100) / 1023; - analogWrite(_heaterBus, status.PWM); + WriteTimeProportionalPWM(heaterBus, status.PWM); - status.PIDActing = status.PWM > 0; return status; } + if (_activeStatus->FullPower) + heaterPercentage = 100; + + if (GetPidSetPoint() - GetPidInput() > _brewSettingsService->PIDStart) + { + status.PWM = (1023 * heaterPercentage) / 100; + status.PWMPercentage = (status.PWM * 100) / 1023; + status.PIDActing = false; + + WriteTimeProportionalPWM(heaterBus, status.PWM); + + return status; + } + + // proteção contra overshoot + if (GetPidInput() > GetPidSetPoint() + 0.1) + { + status.PWM = 0; + status.PWMPercentage = 0; + status.PIDActing = false; + + WriteTimeProportionalPWM(heaterBus, 0); + + StartPID( + _brewSettingsService->KP, + _brewSettingsService->KI, + _brewSettingsService->KD + ); + + return status; + } + + // cálculo PID + PidCompute(); + + int maxPWM = (1023 * heaterPercentage) / 100; + + status.PWM = GetPidOutput() > maxPWM ? maxPWM : GetPidOutput(); + status.PWMPercentage = (status.PWM * 100) / 1023; + + WriteTimeProportionalPWM(heaterBus, status.PWM); + + status.PIDActing = status.PWM > 0; + + return status; +} + protected: virtual void SetUP(); virtual boolean StopCompute(); @@ -112,5 +155,8 @@ class HeaterService ActiveStatus *_activeStatus; BrewSettingsService *_brewSettingsService; PID *_kettlePID; + unsigned long _windowStartTime = 0; + const unsigned long _windowSize = 10000; // 10 segundos + }; -#endif \ No newline at end of file +#endif