From df1f5ccbf017e6adf85e41f914f9f4691a4e868f Mon Sep 17 00:00:00 2001 From: Tjeerd <33102280+Tjeerdie@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:37:58 +0100 Subject: [PATCH] Closed loop idle improve (#481) * Squashed commit of the following: commit c73f316fa5bf8c929a8fef5736f4a40045ea992f Author: Tjeerd Date: Wed Nov 25 21:53:45 2020 +0100 cleanup and fix merge issues commit 57f1e8c6c73bcfb699b18ac51a2ec646be822f55 Merge: 04478ca 131673d Author: Tjeerd Date: Wed Nov 25 18:59:58 2020 +0100 Merge branch 'Closedloop_idle_improvements' into ClosedLoopIdleImprovement commit 131673dc60417cbc773b0763d2f93e917c83325f Author: Tjeerd Date: Sun Nov 22 21:54:03 2020 +0100 more fiddeling with idle control commit e4406166cde91552027a0dfb7958d6894098f066 Author: Tjeerd Date: Sun Nov 22 20:36:24 2020 +0100 More fiddeling with idle control commit 45822003d41e021e3ed93e8e14ce05479ddfd17b Author: Tjeerd Date: Sat Nov 21 14:59:32 2020 +0100 fix initial value commit ff8fadae7844bd8e5934ee0f311fbf5e0842ec29 Author: Tjeerd Date: Thu Nov 19 23:14:14 2020 +0100 add TPS limit to prevent integeral windup. commit 7683b2e65569787b1c94eae6f4847cdadd394402 Author: Tjeerd Date: Thu Nov 19 22:40:53 2020 +0100 cleanup idle.h commit da55ee9dbd76d65608bfb5b950bc948a498b9599 Author: Tjeerd Date: Thu Nov 19 22:36:18 2020 +0100 Further improvement simplifying code for closedloop PID control with feedforward. make PWM output work Tinkering new Idle control working on closedloop idle Initial work on improving closed loop Idle control * reset platformio.ini Co-authored-by: Tjeerd --- reference/speeduino.ini | 49 ++++++++++++++++----------- speeduino/board_stm32_official.ino | 2 +- speeduino/globals.h | 7 ++-- speeduino/idle.h | 2 ++ speeduino/idle.ino | 54 +++++++++++++++++++++++++++++- speeduino/src/PID_v1/PID_v1.cpp | 12 ++++--- speeduino/src/PID_v1/PID_v1.h | 3 +- 7 files changed, 99 insertions(+), 30 deletions(-) diff --git a/reference/speeduino.ini b/reference/speeduino.ini index 0a5893d3..cda4fddb 100644 --- a/reference/speeduino.ini +++ b/reference/speeduino.ini @@ -390,10 +390,13 @@ page = 1 idleUpOutputInv = bits, U08, 118, [1:1], "No", "Yes" idleUpOutputPin = bits, U08, 118, [2:7], "Board Default", "INVALID", "INVALID", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "A8", "A9", "A10", "A11", "A12", "A13", "A14", "A15", "INVALID" - tachoSweepMaxRPM = scalar, U08, 119, "RPM", 100, 0.0, 100, 10000, 0 - primingDelay = scalar, U08, 120, "S", 0.1, 0.0, 0.0, 25.5, 1 + tachoSweepMaxRPM = scalar, U08, 119, "RPM", 100, 0.0, 100, 10000, 0 + primingDelay = scalar, U08, 120, "S", 0.1, 0.0, 0.0, 25.5, 1 - unused2-95 = array, U08, 121, [7], "%", 1.0, 0.0, 0.0, 255, 0 + iacTPSlimit = scalar, U08, 121, "%", 1, 0, 0, 100, 0 + iacRPMlimitHysteresis = scalar, U08, 122, "RPM" 10, 0 10 2500, 0 + + unused2-95 = array, U08, 121, [5], "%", 1.0, 0.0, 0.0, 255, 0 ;Page 2 is the fuel map and axis bins only page = 2 @@ -638,7 +641,7 @@ page = 6 iacCrankBins = array, U08, 112, [4], "F", 1.8, -22.23, -40, 215, 0 #endif - iacAlgorithm = bits , U08, 116, [0:2], "None", "On/Off", "PWM Open loop", "PWM Closed loop", "Stepper Open Loop", "Stepper Closed Loop", "INVALID", "INVALID" + iacAlgorithm = bits , U08, 116, [0:2], "None", "On/Off", "PWM Open loop", "PWM Closed loop", "Stepper Open Loop", "Stepper Closed Loop", "PWM Closed+Open loop", "INVALID" iacStepTime = bits , U08, 116, [3:5], "INVALID","1", "2", "3", "4", "5", "6","INVALID" iacChannels = bits, U08, 116, [6:6], "1", "2" iacPWMdir = bits , U08, 116, [7:7], "Normal", "Reverse" @@ -1348,6 +1351,8 @@ page = 14 defaultValue = fanPin, 0 defaultValue = iacCLminDuty,0 defaultValue = iacCLmaxDuty,100 + defaultValue = iacTPSlimit, 5 + defaultValue = iacRPMlimitHysteresis, 200 defaultValue = boostMinDuty,0 defaultValue = boostMaxDuty,100 defaultValue = boostSens, 2000 @@ -1575,13 +1580,13 @@ menuDialog = main subMenu = ASE, "Afterstart Enrichment (ASE)" subMenu = std_separator subMenu = idleSettings, "Idle Control" - subMenu = iacClosedLoop_curve, "Idle - RPM targets", 7, { iacAlgorithm == 3 || iacAlgorithm == 5 || idleAdvEnabled >= 1 } - subMenu = iacPwm_curve, "Idle - PWM Duty Cycle", 7, { iacAlgorithm == 2 } - subMenu = iacPwmCrank_curve, "Idle - PWM Cranking Duty Cycle", 7, { iacAlgorithm == 2 || iacAlgorithm == 3 } + subMenu = iacClosedLoop_curve, "Idle - RPM targets", 7, { iacAlgorithm == 3 || iacAlgorithm == 5 || iacAlgorithm == 6 || idleAdvEnabled >= 1 } + subMenu = iacPwm_curve, "Idle - PWM Duty Cycle", 7, { iacAlgorithm == 2 || iacAlgorithm == 6} + subMenu = iacPwmCrank_curve, "Idle - PWM Cranking Duty Cycle", 7, { iacAlgorithm == 2 || iacAlgorithm == 3 || iacAlgorithm == 6} subMenu = iacStep_curve, "Idle - Stepper Motor", 7, { iacAlgorithm == 4 } subMenu = iacStepCrank_curve, "Idle - Stepper Motor Cranking", 7, { iacAlgorithm == 4 || iacAlgorithm == 5 } subMenu = std_separator - subMenu = idleUpSettings, "Idle Up Settings", { iacAlgorithm == 2 || iacAlgorithm == 3 || iacAlgorithm == 4 || iacAlgorithm == 5 } + subMenu = idleUpSettings, "Idle Up Settings", { iacAlgorithm == 2 || iacAlgorithm == 3 || iacAlgorithm == 4 || iacAlgorithm == 5 || iacAlgorithm == 6 } subMenu = std_separator subMenu = idleAdvanceSettings, "Idle Advance Settings" @@ -1705,6 +1710,8 @@ menuDialog = main iacPWMdir = "Normal PWM valves increase RPM with higher duty. If RPM decreases with higher duty then select Reverse" iacCLminDuty = "When using closed loop idle control, this is the minimum duty cycle that the PID loop will allow. Combined with the maximum value, this specifies the working range of your idle valve" iacCLmaxDuty = "When using closed loop idle control, this is the maximum duty cycle that the PID loop will allow. Combined with the minimum value, this specifies the working range of your idle valve" + iacTPSlimit = "When using OL+CL idle control, if the TPS is higher than this value closed loop idle resets the integeral of the PID (To prevent RPM dips comming back to idle)" + iacRPMlimitHysteresis = "When using closed loop idle control, if the closed loop Target RPM + this value is higher than the actual RPM, closed loop idle resets the integeral of the PID (To prevent RPM dips comming back to idle)" iacFastTemp = "Below this temperature, the idle output will be high (On). Above this temperature, it will turn off." idleUpPolarity = "Normal polarity is a ground switch where an earthed signal activates the Idle Up. The internal pullup will be enabled with Normal polarity. \n Inverted may be used if a 5v signal is used to enable the Idle Up." CTPSPolarity = "Normal polarity is a ground switch where an earthed signal activates the closed throttle position. The internal pullup will be enabled with Normal polarity. \n Inverted may be used if a 5v signal is used to enable the closed throttle position." @@ -2371,16 +2378,18 @@ menuDialog = main field = "Stepper Inverted", iacStepperInv, { iacAlgorithm == 4 || iacAlgorithm == 5 } dialog = pwm_idle, "PWM Idle" - field = "Number of outputs", iacChannels, { iacAlgorithm == 2 || iacAlgorithm == 3 } - field = "Idle valve frequency", idleFreq, { iacAlgorithm == 2 || iacAlgorithm == 3 } - field = "Idle valve direction", iacPWMdir, { iacAlgorithm == 2 || iacAlgorithm == 3 } + field = "Number of outputs", iacChannels, { iacAlgorithm == 2 || iacAlgorithm == 3 || iacAlgorithm == 6} + field = "Idle valve frequency", idleFreq, { iacAlgorithm == 2 || iacAlgorithm == 3 || iacAlgorithm == 6} + field = "Idle valve direction", iacPWMdir, { iacAlgorithm == 2 || iacAlgorithm == 3 || iacAlgorithm == 6} dialog = closedloop_idle, "Closed loop Idle" - field = "P", idleKP, { iacAlgorithm == 3 || iacAlgorithm == 5 } - field = "I", idleKI, { iacAlgorithm == 3 || iacAlgorithm == 5 } - field = "D", idleKD, { iacAlgorithm == 3 || iacAlgorithm == 5 } - field = "Minimum valve duty", iacCLminDuty, { iacAlgorithm == 3 } - field = "Maximum valve duty", iacCLmaxDuty, { iacAlgorithm == 3 } + field = "P", idleKP, { iacAlgorithm == 3 || iacAlgorithm == 5 || iacAlgorithm == 6} + field = "I", idleKI, { iacAlgorithm == 3 || iacAlgorithm == 5 || iacAlgorithm == 6} + field = "D", idleKD, { iacAlgorithm == 3 || iacAlgorithm == 5 || iacAlgorithm == 6} + field = "Minimum valve duty", iacCLminDuty, { iacAlgorithm == 3 || iacAlgorithm == 6} + field = "Maximum valve duty", iacCLmaxDuty, { iacAlgorithm == 3 || iacAlgorithm == 6} + field = "Integeral reset above TPS", iacTPSlimit { iacAlgorithm == 6 } + field = "Integeral reset RPM Hysteresis", iacRPMlimitHysteresis { iacAlgorithm == 6 } dialog = idleSettings, "Idle Settings" topicHelp = "https://wiki.speeduino.com/en/configuration/Idle" @@ -2526,7 +2535,7 @@ menuDialog = main topicHelp = "https://wiki.speeduino.com/en/configuration/IdleAdvance" panel = idleAdvanceSettings_east panel = idle_advance_curve, { idleAdvEnabled >= 1 } - panel = iacClosedLoop_curve, { iacAlgorithm == 3 || iacAlgorithm == 5 || idleAdvEnabled >= 1 } + panel = iacClosedLoop_curve, { iacAlgorithm == 3 || iacAlgorithm == 5 || iacAlgorithm == 6 || idleAdvEnabled >= 1 } dialog = rotary_ignition, "Rotary Ignition", 4 @@ -4584,7 +4593,7 @@ cmdVSSratio6 = "E\x99\x06" flex = scalar, U08, 34, "%", 1.000, 0.000 flexFuelCor = scalar, U08, 35, "%", 1.000, 0.000 flexIgnCor = scalar, S08, 36, "deg", 1.000, 0.000 - idleLoad = scalar, U08, 37, { bitStringValue( idleUnits , iacAlgorithm ) }, { (iacAlgorithm == 2 || iacAlgorithm == 3 || iacMaxSteps <= 255) ? 1.000 : 2.000 }, 0.000 ; This is a combined variable covering both PWM and stepper IACs. The units and precision used depend on which idle algorithm is chosen + idleLoad = scalar, U08, 37, { bitStringValue( idleUnits , iacAlgorithm ) }, { (iacAlgorithm == 2 || iacAlgorithm == 3 || iacAlgorithm == 6 || iacMaxSteps <= 255) ? 1.000 : 2.000 }, 0.000 ; This is a combined variable covering both PWM and stepper IACs. The units and precision used depend on which idle algorithm is chosen testoutputs = scalar, U08, 38, "bits", 1.000, 0.000 testenabled = bits, U08, 38, [0:0] testactive = bits, U08, 38, [1:1] @@ -4790,8 +4799,8 @@ cmdVSSratio6 = "E\x99\x06" entry = hardLimitOn , "Hard Limiter", int, "%d" entry = idleControlOn, "Idle Control", int, "%d" entry = idleLoad, "IAC value", int, "%d" - entry = CLIdleTarget, "Idle Target RPM", int, "%d", { iacAlgorithm == 3 || iacAlgorithm == 5 || idleAdvEnabled >= 1 } ;Only show for closed loop idle modes and if idle advance is enabled - entry = CLIdleDelta, "Idle RPM Delta", int, "%d", { iacAlgorithm == 3 || iacAlgorithm == 5 || idleAdvEnabled >= 1 } ;Only show for closed loop idle modes and if idle advance is enabled + entry = CLIdleTarget, "Idle Target RPM", int, "%%d", { iacAlgorithm == 3 || iacAlgorithm == 5 || iacAlgorithm == 6 || idleAdvEnabled >= 1 } ;Only show for closed loop idle modes and if idle advance is enabled + entry = CLIdleDelta, "Idle RPM Delta", int, "%d", { iacAlgorithm == 3 || iacAlgorithm == 5 || iacAlgorithm == 6 || idleAdvEnabled >= 1 } ;Only show for closed loop idle modes and if idle advance is enabled entry = baro, "Baro Pressure", int, "%d" entry = nitrousOn, "Nitrous", int, "%d", { n2o_enable > 0 } entry = syncLossCounter, "Sync Loss #", int, "%d" diff --git a/speeduino/board_stm32_official.ino b/speeduino/board_stm32_official.ino index 15801985..382f9c82 100644 --- a/speeduino/board_stm32_official.ino +++ b/speeduino/board_stm32_official.ino @@ -20,7 +20,7 @@ *********************************************************************************************************** * Idle */ - if( (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_OL) || (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_CL) ) + if( (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_OL) || (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_CL) || (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_OLCL)) { idle_pwm_max_count = 1000000L / (TIMER_RESOLUTION * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 4uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 5KHz } diff --git a/speeduino/globals.h b/speeduino/globals.h index 2c112b11..df840331 100644 --- a/speeduino/globals.h +++ b/speeduino/globals.h @@ -787,8 +787,11 @@ struct config2 { byte tachoSweepMaxRPM; byte primingDelay; - - byte unused2_95[7]; + + byte iacTPSlimit; + byte iacRPMlimitHysteresis; + + byte unused2_95[5]; #if defined(CORE_AVR) }; diff --git a/speeduino/idle.h b/speeduino/idle.h index 878d12a6..ba126c82 100644 --- a/speeduino/idle.h +++ b/speeduino/idle.h @@ -11,6 +11,7 @@ #define IAC_ALGORITHM_PWM_CL 3 #define IAC_ALGORITHM_STEP_OL 4 #define IAC_ALGORITHM_STEP_CL 5 +#define IAC_ALGORITHM_PWM_OLCL 6 //Openloop plus closedloop IAC control #define STEPPER_FORWARD 0 #define STEPPER_BACKWARD 1 @@ -53,6 +54,7 @@ volatile bool idle_pwm_state; unsigned int idle_pwm_max_count; //Used for variable PWM frequency volatile unsigned int idle_pwm_cur_value; long idle_pid_target_value; +long FeedForwardTerm; unsigned long idle_pwm_target_value; long idle_cl_target_rpm; byte idleCounter; //Used for tracking the number of calls to the idle control function diff --git a/speeduino/idle.ino b/speeduino/idle.ino index 04dcf5ad..fc7503e4 100644 --- a/speeduino/idle.ino +++ b/speeduino/idle.ino @@ -70,6 +70,13 @@ void initialiseIdle() enableIdle(); break; + case IAC_ALGORITHM_PWM_OLCL: + iacPWMTable.xSize = 10; + iacPWMTable.valueSize = SIZE_BYTE; + iacPWMTable.axisSize = SIZE_BYTE; + iacPWMTable.values = configPage6.iacOLPWMVal; + iacPWMTable.axisX = configPage6.iacBins; + case IAC_ALGORITHM_PWM_CL: //Case 3 is PWM closed loop iacClosedLoopTable.xSize = 10; @@ -314,6 +321,51 @@ void idleControl() } break; + + case IAC_ALGORITHM_PWM_OLCL: //case 6 is PWM Open Loop table as feedforward term plus closed loop. + //No cranking specific value for closed loop (yet?) + if( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK) ) + { + //Currently cranking. Use the cranking table + currentStatus.idleDuty = table2D_getValue(&iacCrankDutyTable, currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //All temps are offset by 40 degrees + currentStatus.idleLoad = currentStatus.idleDuty; + idle_pwm_target_value = percentage(currentStatus.idleDuty, idle_pwm_max_count); + idle_pid_target_value = idle_pwm_target_value << 2; //Resolution increased + idlePID.Initialize(); //Update output to smooth transition + } + else + { + //Read the OL table as feedforward term + FeedForwardTerm = percentage(table2D_getValue(&iacPWMTable, currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET), idle_pwm_max_count<<2); //All temps are offset by 40 degrees + + currentStatus.CLIdleTarget = (byte)table2D_getValue(&iacClosedLoopTable, currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //All temps are offset by 40 degrees + idle_cl_target_rpm = (uint16_t)currentStatus.CLIdleTarget * 10; //Multiply the byte target value back out by 10 + if( (idleCounter & 31) == 1) { idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD); } //This only needs to be run very infrequently, once every 32 calls to idleControl(). This is approx. once per 9 seconds + if((currentStatus.RPM - idle_cl_target_rpm > configPage2.iacRPMlimitHysteresis*10) || (currentStatus.TPS > configPage2.iacTPSlimit)){ //reset integeral to zero when TPS is bigger than set value in TS (opening throttle so not idle anymore). OR when RPM higher than Idle Target + RPM Histeresis (comming back from high rpm with throttle closed) + idlePID.ResetIntegeral(); + } + PID_computed = idlePID.Compute(true, FeedForwardTerm); + + if(PID_computed == true) + { + idle_pwm_target_value = idle_pid_target_value>>2; //increased resolution + if( idle_pwm_target_value == 0 ) + { + disableIdle(); + BIT_CLEAR(currentStatus.spark, BIT_SPARK_IDLE); //Turn the idle control flag off + break; + } + BIT_SET(currentStatus.spark, BIT_SPARK_IDLE); //Turn the idle control flag on + currentStatus.idleLoad = ((unsigned long)(idle_pwm_target_value * 100UL) / idle_pwm_max_count); + if(currentStatus.idleUpActive == true) { currentStatus.idleDuty += configPage2.idleUpAdder; } //Add Idle Up amount if active + + } + idleCounter++; + } + + break; + + case IAC_ALGORITHM_STEP_OL: //Case 4 is open loop stepper control //First thing to check is whether there is currently a step going on and if so, whether it needs to be turned off if( (checkForStepping() == false) && (isStepperHomed() == true) ) //Check that homing is complete and that there's not currently a step already taking place. MUST BE IN THIS ORDER! @@ -577,7 +629,7 @@ static inline void disableIdle() //Typically this is enabling the PWM interrupt static inline void enableIdle() { - if( (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_CL) || (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_OL) ) + if( (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_CL) || (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_OL) || (configPage6.iacAlgorithm == IAC_ALGORITHM_PWM_OLCL) ) { IDLE_TIMER_ENABLE(); } diff --git a/speeduino/src/PID_v1/PID_v1.cpp b/speeduino/src/PID_v1/PID_v1.cpp index 0f91576c..b8b09d2b 100755 --- a/speeduino/src/PID_v1/PID_v1.cpp +++ b/speeduino/src/PID_v1/PID_v1.cpp @@ -235,7 +235,7 @@ integerPID::integerPID(long* Input, long* Output, long* Setpoint, * pid Output needs to be computed. returns true when the output is computed, * false when nothing has been done. **********************************************************************************/ -bool integerPID::Compute(bool pOnE) +bool integerPID::Compute(bool pOnE, long FeedForwardTerm) { if(!inAuto) return false; unsigned long now = millis(); @@ -250,12 +250,13 @@ bool integerPID::Compute(bool pOnE) long dInput = (input - lastInput); long outMinResized = outMin< outMaxResized) { outputSum = outMaxResized; } - else if(outputSum < outMinResized) { outputSum = outMinResized; } + if(outputSum > outMaxResized-FeedForwardTerm) { outputSum = outMaxResized-FeedForwardTerm; } + else if(outputSum < outMinResized-FeedForwardTerm) { outputSum = outMinResized-FeedForwardTerm; } } /*Compute PID Output*/ @@ -266,6 +267,7 @@ bool integerPID::Compute(bool pOnE) output = (kp * error); if (ki != 0) { output += outputSum; } if (kd != 0) { output -= (kd * dInput)>>2; } + output += FeedForwardTerm; output >>= PID_SHIFTS; } else @@ -276,9 +278,9 @@ bool integerPID::Compute(bool pOnE) output = outputSum; if (kd != 0) { output -= (kd * dInput)>>2; } + output += FeedForwardTerm; output >>= PID_SHIFTS; } - if(output > outMax) output = outMax; else if(output < outMin) output = outMin; @@ -522,7 +524,7 @@ void integerPID::SetControllerDirection(byte Direction) ******************************************************************************/ int integerPID::GetMode(){ return inAuto ? AUTOMATIC : MANUAL;} int integerPID::GetDirection(){ return controllerDirection;} - +void integerPID::ResetIntegeral() { outputSum=0;} //************************************************************************************************************************ #define limitMultiplier 100 //How much outMin and OutMax must be multiplied by to get them in the same scale as the output diff --git a/speeduino/src/PID_v1/PID_v1.h b/speeduino/src/PID_v1/PID_v1.h index 21d449f6..1356eb63 100755 --- a/speeduino/src/PID_v1/PID_v1.h +++ b/speeduino/src/PID_v1/PID_v1.h @@ -97,7 +97,7 @@ class integerPID void SetMode(int Mode); // * sets PID to either Manual (0) or Auto (non-0) - bool Compute(bool pOnE); // * performs the PID calculation. it should be + bool Compute(bool, long FeedForwardTerm = 0); // * performs the PID calculation. it should be // called every time loop() cycles. ON/OFF and // calculation frequency can be set using SetMode // SetSampleTime respectively @@ -127,6 +127,7 @@ class integerPID int GetMode(); // inside the PID. int GetDirection(); // void Initialize(); + void ResetIntegeral(); private: