From b321e8e17d843cbf85b9afae95e5c9590c875818 Mon Sep 17 00:00:00 2001 From: "Vitor Moreno B. Sales" Date: Thu, 21 May 2020 06:29:38 -0300 Subject: [PATCH 1/2] [PID] Increase parameters and output resolution (#377) Parameters are 0.03125% for kP and kI, 0.00981% for kD. Increased output 4 times to give enought room for kP. PID now ignore compute if input is zero to avoid overshoot, kI and kD part as well. Added idlePID.Initialize() to crank part to start PID from a working condition. Show idleLoad when cranking. Fix TS numbers, increase max value Co-authored-by: Josh Stewart --- reference/speeduino.ini | 14 ++--- speeduino/idle.ino | 75 +++++++++++++---------- speeduino/src/PID_v1/PID_v1.cpp | 102 ++++++++++++++++++-------------- speeduino/src/PID_v1/PID_v1.h | 1 + speeduino/updates.ino | 10 +++- 5 files changed, 117 insertions(+), 85 deletions(-) diff --git a/reference/speeduino.ini b/reference/speeduino.ini index 38e005eb..b84438f4 100644 --- a/reference/speeduino.ini +++ b/reference/speeduino.ini @@ -576,9 +576,9 @@ page = 6 lnchHardLim = scalar, U08, 51, "rpm", 100, 0.0, 100, 25500, 0 lnchFuelAdd = scalar, U08, 52, "%", 1.0, 0.0, 0.0, 80, 0 - idleKP = scalar, U08, 53, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) - idleKI = scalar, U08, 54, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) - idleKD = scalar, U08, 55, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) + idleKP = scalar, U08, 53, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte) + idleKI = scalar, U08, 54, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte) + idleKD = scalar, U08, 55, "%", 0.00781, 0.0, 0.0, 1.99, 3 ; * ( 1 byte) boostLimit = scalar, U08, 56, "kPa", 2.0, 0.0, 0.0, 511.0, 0 boostKP = scalar, U08, 57, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) boostKI = scalar, U08, 58, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) @@ -1019,9 +1019,9 @@ page = 10 ;All related to the closed loop VVT control vvtCLholdDuty = scalar, U08, 126, "%", 1.0, 0.0, 0.0, 100.0, 0 - vvtCLKP = scalar, U08, 127, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) - vvtCLKI = scalar, U08, 128, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) - vvtCLKD = scalar, U08, 129, "%", 1.0, 0.0, 0.0, 200.0, 0 ; * ( 1 byte) + vvtCLKP = scalar, U08, 127, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte) + vvtCLKI = scalar, U08, 128, "%", 0.03125, 0.0, 0.0, 7.96, 2 ; * ( 1 byte) + vvtCLKD = scalar, U08, 129, "%", 0.00781, 0.0, 0.0, 1.99, 3 ; * ( 1 byte) vvtCLMinAng = scalar, U16, 130, "deg", 1.0, 0.0, 0.0, 360.0, 0 ; * ( 1 bytes) vvtCLMaxAng = scalar, U16, 132, "deg", 1.0, 0.0, 0.0, 360.0, 0 ; * ( 1 bytes) @@ -1367,7 +1367,7 @@ menuDialog = main 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 } + subMenu = iacPwmCrank_curve, "Idle - PWM Cranking Duty Cycle", 7, { iacAlgorithm == 2 || iacAlgorithm == 3 } subMenu = iacStep_curve, "Idle - Stepper Motor", 7, { iacAlgorithm == 4 } subMenu = iacStepCrank_curve, "Idle - Stepper Motor Cranking", 7, { iacAlgorithm == 4 || iacAlgorithm == 5 } subMenu = std_separator diff --git a/speeduino/idle.ino b/speeduino/idle.ino index f29e0b68..a0ea2e22 100644 --- a/speeduino/idle.ino +++ b/speeduino/idle.ino @@ -87,7 +87,7 @@ void initialiseIdle() #elif defined(CORE_TEENSY) idle_pwm_max_count = 1000000L / (32 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 16uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz #endif - idlePID.SetOutputLimits(percentage(configPage2.iacCLminDuty, idle_pwm_max_count), percentage(configPage2.iacCLmaxDuty, idle_pwm_max_count)); + idlePID.SetOutputLimits(percentage(configPage2.iacCLminDuty, idle_pwm_max_count<<2), percentage(configPage2.iacCLmaxDuty, idle_pwm_max_count<<2)); idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD); idlePID.SetMode(AUTOMATIC); //Turn PID on @@ -159,7 +159,7 @@ void initialiseIdle() } idlePID.SetSampleTime(100); - idlePID.SetOutputLimits(0, (configPage9.iacMaxSteps * 3)); //Maximum number of steps; always less than home steps count. + idlePID.SetOutputLimits(0, (configPage9.iacMaxSteps * 3)<<2); //Maximum number of steps; always less than home steps count. idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD); idlePID.SetMode(AUTOMATIC); //Turn PID on break; @@ -247,6 +247,17 @@ void idleControl() case IAC_ALGORITHM_PWM_CL: //Case 3 is PWM 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 + { 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 second @@ -254,7 +265,7 @@ void idleControl() PID_computed = idlePID.Compute(true); if(PID_computed == true) { - idle_pwm_target_value = idle_pid_target_value; + idle_pwm_target_value = idle_pid_target_value>>2; //increased resolution if( idle_pwm_target_value == 0 ) { disableIdle(); @@ -267,6 +278,7 @@ void idleControl() } idleCounter++; + } break; case IAC_ALGORITHM_STEP_OL: //Case 4 is open loop stepper control @@ -291,9 +303,9 @@ void idleControl() else { //Standard running - //Only do a lookup of the required value around 4 times per second (Once every 255 mainloops). Any more than this can create too much jitter and require a hyster value that is too high + //Only do a lookup of the required value around 4 times per second. Any more than this can create too much jitter and require a hyster value that is too high //We must also have more than zero RPM for the running state - if (((mainLoopCount & 255) == 1) && (currentStatus.RPM > 0)) + if (BIT_CHECK(LOOP_TIMER, BIT_TIMER_4HZ) && (currentStatus.RPM > 0)) { idleStepper.targetIdleStep = table2D_getValue(&iacStepTable, (currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET)) * 3; //All temps are offset by 40 degrees. Step counts are divided by 3 in TS. Multiply back out here if(currentStatus.idleUpActive == true) { idleStepper.targetIdleStep += configPage2.idleUpAdder; } //Add Idle Up amount if active @@ -319,7 +331,7 @@ void idleControl() //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! { - if( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK )/* ||currentStatus.rpmDOT>=500||currentStatus.rpmDOT<=-500*/) + if( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK) ) { //Currently cranking. Use the cranking table idleStepper.targetIdleStep = table2D_getValue(&iacCrankStepsTable, (currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET)) * 3; //All temps are offset by 40 degrees. Step counts are divided by 3 in TS. Multiply back out here @@ -332,36 +344,35 @@ void idleControl() } doStep(); - + idle_pid_target_value = idleStepper.targetIdleStep<<2; //Resolution increased + idlePID.Initialize(); //Update output to smooth transition } - else { - if( (idleCounter & 31) == 1) - { - //This only needs to be run very infrequently, once every 32 calls to idleControl(). This is approx. once per second - idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD); - iacStepTime_uS = configPage6.iacStepTime * 1000; - iacCoolTime_uS = configPage9.iacCoolTime * 1000; + if( (idleCounter & 31) == 1) + { + //This only needs to be run very infrequently, once every 32 calls to idleControl(). This is approx. once per second + idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD); + iacStepTime_uS = configPage6.iacStepTime * 1000; + iacCoolTime_uS = configPage9.iacCoolTime * 1000; + } + + 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; //All temps are offset by 40 degrees + PID_computed = idlePID.Compute(true); + idleStepper.targetIdleStep = idle_pid_target_value>>2; //Increase resolution + if(currentStatus.idleUpActive == true) { idleStepper.targetIdleStep += configPage2.idleUpAdder; } //Add Idle Up amount if active + + //limit to the configured max steps. This must include any idle up adder, to prevent over-opening. + if (idleStepper.targetIdleStep > (configPage9.iacMaxSteps * 3) ) + { + idleStepper.targetIdleStep = configPage9.iacMaxSteps * 3; + } + + doStep(); + idleCounter++; } - - 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; //All temps are offset by 40 degrees - if(currentStatus.idleUpActive == true) { idle_pid_target_value += configPage2.idleUpAdder; } //Add Idle Up amount if active - PID_computed = idlePID.Compute(true); - idleStepper.targetIdleStep = idle_pid_target_value; - - //limit to the configured max steps. This must include any idle up adder, to prevent over-opening. - if (idleStepper.targetIdleStep > (configPage9.iacMaxSteps * 3) ) - { - idleStepper.targetIdleStep = configPage9.iacMaxSteps * 3; - } - - doStep(); - - idleCounter++; - } - currentStatus.idleLoad = idleStepper.curIdleStep / 2; //Current step count (Divided by 2 for byte) + currentStatus.idleLoad = idleStepper.curIdleStep / 2; //Current step count (Divided by 2 for byte) } //Set or clear the idle active flag if(idleStepper.targetIdleStep != idleStepper.curIdleStep) { BIT_SET(currentStatus.spark, BIT_SPARK_IDLE); } diff --git a/speeduino/src/PID_v1/PID_v1.cpp b/speeduino/src/PID_v1/PID_v1.cpp index 39f6d3bc..ea3ca579 100755 --- a/speeduino/src/PID_v1/PID_v1.cpp +++ b/speeduino/src/PID_v1/PID_v1.cpp @@ -213,13 +213,12 @@ integerPID::integerPID(long* Input, long* Output, long* Setpoint, byte Kp, byte Ki, byte Kd, byte ControllerDirection) { - myOutput = Output; + myOutput = Output; myInput = Input; - mySetpoint = Setpoint; + mySetpoint = Setpoint; inAuto = false; - integerPID::SetOutputLimits(0, 255); //default output limit corresponds to - //the arduino pwm limits + integerPID::SetOutputLimits(0, 255); //default output limit corresponds to the arduino pwm limits SampleTime = 250; //default Controller Sample Time is 0.25 seconds. This is the 4Hz control time for Idle and VVT @@ -240,45 +239,57 @@ bool integerPID::Compute(bool pOnE) { if(!inAuto) return false; unsigned long now = millis(); - //SampleTime = (now - lastTime); unsigned long timeChange = (now - lastTime); if(timeChange >= SampleTime) { /*Compute all the working error variables*/ - long input = *myInput; - long error = *mySetpoint - input; - long dInput = (input - lastInput); - - outputSum += (ki * error)/1024; //Note that ki is multiplied by 1024 so it must be divided by 1024 here - if(outputSum > outMax) { outputSum = outMax; } - else if(outputSum < outMin) { outputSum = outMin; } - - /*Compute PID Output*/ - long output; - - if(pOnE) + long input = *myInput; + if(input > 0) //Fail safe, should never be 0 { - output = (kp * error) + outputSum - ((kd * dInput)/128); + long error = *mySetpoint - input; + long dInput = (input - lastInput); + long outMinResized = outMin< outMaxResized) { outputSum = outMaxResized; } + else if(outputSum < outMinResized) { outputSum = outMinResized; } + } + + /*Compute PID Output*/ + long output; + + if(pOnE) + { + output = (kp * error); + if (ki != 0) { output += outputSum; } + if (kd != 0) { output -= (kd * dInput)>>2; } + output >>= PID_SHIFTS; + } + else + { + outputSum -= (kp * dInput); + if(outputSum > outMaxResized) { outputSum = outMaxResized; } + else if(outputSum < outMinResized) { outputSum = outMinResized; } + + output = outputSum; + if (kd != 0) { output -= (kd * dInput)>>2; } + output >>= PID_SHIFTS; + } + + + if(output > outMax) output = outMax; + else if(output < outMin) output = outMin; + *myOutput = output; + + /*Remember some variables for next time*/ + lastInput = input; + lastTime = now; + + return true; } - else - { - outputSum -= kp * dInput; - if(outputSum > outMax) { outputSum = outMax; } - else if(outputSum < outMin) { outputSum = outMin; } - - output = outputSum - ((kd * dInput)/128); - } - - - if(output > outMax) output = outMax; - else if(output < outMin) output = outMin; - *myOutput = output; - - /*Remember some variables for next time*/ - lastInput = input; - lastTime = now; - - return true; } else return false; } @@ -302,9 +313,10 @@ void integerPID::SetTunings(byte Kp, byte Ki, byte Kd) kd = Kd / SampleTimeInSec; */ long InverseSampleTimeInSec = 1000 / SampleTime; - kp = Kp; - ki = (long)((long)Ki * 1024) / InverseSampleTimeInSec; - kd = ((long)Kd * InverseSampleTimeInSec); + //New resolution, 2 shifts to improve ki here | kp 1.563% | ki 1.563% | kd 0.195% + kp = (uint16_t)Kp<<2; + ki = (long)(Ki<<2) / InverseSampleTimeInSec; + kd = (long)(Kd<<2) * InverseSampleTimeInSec; if(controllerDirection == REVERSE) { @@ -347,8 +359,8 @@ void integerPID::SetOutputLimits(long Min, long Max) if(*myOutput > outMax) *myOutput = outMax; else if(*myOutput < outMin) *myOutput = outMin; - if(outputSum > outMax) { outputSum = outMax; } - else if(outputSum < outMin) { outputSum = outMin; } + if((outputSum>>PID_SHIFTS) > outMax) { outputSum = outMax<>PID_SHIFTS) < outMin) { outputSum = outMin< outMax) { outputSum = outMax; } - else if(outputSum < outMin) { outputSum = outMin; } + if((outputSum>>PID_SHIFTS) > outMax) { outputSum = outMax<>PID_SHIFTS) < outMin) { outputSum = outMin< Date: Thu, 21 May 2020 22:20:37 +1000 Subject: [PATCH 2/2] Remove channel checks from the hardware test outputs --- speeduino/TS_CommandButtonHandler.ino | 112 ++++++++------------------ 1 file changed, 32 insertions(+), 80 deletions(-) diff --git a/speeduino/TS_CommandButtonHandler.ino b/speeduino/TS_CommandButtonHandler.ino index 191cfae6..48db7455 100644 --- a/speeduino/TS_CommandButtonHandler.ino +++ b/speeduino/TS_CommandButtonHandler.ino @@ -110,79 +110,55 @@ uint16_t TS_CommandButtonsHandler(int buttonCommand) break; case TS_CMD_INJ5_ON: // cmd group is for injector5 on actions - #if INJ_CHANNELS >= 5 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector5(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector5(); } break; case TS_CMD_INJ5_OFF: // cmd group is for injector5 off actions - #if INJ_CHANNELS >= 5 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector5(); BIT_CLEAR(HWTest_INJ_50pc, 5); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector5(); BIT_CLEAR(HWTest_INJ_50pc, 5); } break; case TS_CMD_INJ5_50PC: // cmd group is for injector5 50%dc actions - #if INJ_CHANNELS >= 5 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 5); } - if(!BIT_CHECK(HWTest_INJ_50pc, 5)) { closeInjector5(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 5); } + if(!BIT_CHECK(HWTest_INJ_50pc, 5)) { closeInjector5(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; case TS_CMD_INJ6_ON: // cmd group is for injector6 on actions - #if INJ_CHANNELS >= 6 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector6(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector6(); } break; case TS_CMD_INJ6_OFF: // cmd group is for injector6 off actions - #if INJ_CHANNELS >= 6 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector6(); BIT_CLEAR(HWTest_INJ_50pc, 6); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector6(); BIT_CLEAR(HWTest_INJ_50pc, 6); } break; case TS_CMD_INJ6_50PC: // cmd group is for injector6 50% dc actions - #if INJ_CHANNELS >= 6 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 6); } - if(!BIT_CHECK(HWTest_INJ_50pc, 6)) { closeInjector6(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 6); } + if(!BIT_CHECK(HWTest_INJ_50pc, 6)) { closeInjector6(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; case TS_CMD_INJ7_ON: // cmd group is for injector7 on actions - #if INJ_CHANNELS >= 7 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector7(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector7(); } break; case TS_CMD_INJ7_OFF: // cmd group is for injector7 off actions - #if INJ_CHANNELS >= 7 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector7(); BIT_CLEAR(HWTest_INJ_50pc, 7); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector7(); BIT_CLEAR(HWTest_INJ_50pc, 7); } break; case TS_CMD_INJ7_50PC: // cmd group is for injector7 50%dc actions - #if INJ_CHANNELS >= 7 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 7); } - if(!BIT_CHECK(HWTest_INJ_50pc, 7)) { closeInjector7(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 7); } + if(!BIT_CHECK(HWTest_INJ_50pc, 7)) { closeInjector7(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; case TS_CMD_INJ8_ON: // cmd group is for injector8 on actions - #if INJ_CHANNELS >= 8 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector8(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ openInjector8(); } break; case TS_CMD_INJ8_OFF: // cmd group is for injector8 off actions - #if INJ_CHANNELS >= 8 - if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector8(); BIT_CLEAR(HWTest_INJ_50pc, 8); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ){ closeInjector8(); BIT_CLEAR(HWTest_INJ_50pc, 8); } break; case TS_CMD_INJ8_50PC: // cmd group is for injector8 50% dc actions - #if INJ_CHANNELS >= 8 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 8); } - if(!BIT_CHECK(HWTest_INJ_50pc, 8)) { closeInjector8(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_INJ_50pc, 8); } + if(!BIT_CHECK(HWTest_INJ_50pc, 8)) { closeInjector8(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; case TS_CMD_IGN1_ON: // cmd group is for spark1 on actions @@ -238,79 +214,55 @@ uint16_t TS_CommandButtonsHandler(int buttonCommand) break; case TS_CMD_IGN5_ON: // cmd group is for spark5 on actions - #if IGN_CHANNELS >= 5 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil5Charge(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil5Charge(); } break; case TS_CMD_IGN5_OFF: // cmd group is for spark5 off actions - #if IGN_CHANNELS >= 5 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil5Charge(); BIT_CLEAR(HWTest_IGN_50pc, 5); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil5Charge(); BIT_CLEAR(HWTest_IGN_50pc, 5); } break; case TS_CMD_IGN5_50PC: // cmd group is for spark4 50%dc actions - #if IGN_CHANNELS >= 5 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 5); } - if(!BIT_CHECK(HWTest_IGN_50pc, 5)) { coil5Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 5); } + if(!BIT_CHECK(HWTest_IGN_50pc, 5)) { coil5Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; case TS_CMD_IGN6_ON: // cmd group is for spark6 on actions - #if IGN_CHANNELS >= 6 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil6Charge(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil6Charge(); } break; case TS_CMD_IGN6_OFF: // cmd group is for spark6 off actions - #if IGN_CHANNELS >= 6 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil6Charge(); BIT_CLEAR(HWTest_IGN_50pc, 6); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil6Charge(); BIT_CLEAR(HWTest_IGN_50pc, 6); } break; case TS_CMD_IGN6_50PC: // cmd group is for spark6 50%dc actions - #if IGN_CHANNELS >= 6 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 6); } - if(!BIT_CHECK(HWTest_IGN_50pc, 6)) { coil6Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 6); } + if(!BIT_CHECK(HWTest_IGN_50pc, 6)) { coil6Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; case TS_CMD_IGN7_ON: // cmd group is for spark7 on actions - #if IGN_CHANNELS >= 7 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil7Charge(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil7Charge(); } break; case TS_CMD_IGN7_OFF: // cmd group is for spark7 off actions - #if IGN_CHANNELS >= 7 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil7Charge(); BIT_CLEAR(HWTest_IGN_50pc, 7); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil7Charge(); BIT_CLEAR(HWTest_IGN_50pc, 7); } break; case TS_CMD_IGN7_50PC: // cmd group is for spark7 50%dc actions - #if IGN_CHANNELS >= 7 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 7); } - if(!BIT_CHECK(HWTest_IGN_50pc, 7)) { coil7Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 7); } + if(!BIT_CHECK(HWTest_IGN_50pc, 7)) { coil7Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; case TS_CMD_IGN8_ON: // cmd group is for spark8 on actions - #if IGN_CHANNELS >= 8 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil8Charge(); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { beginCoil8Charge(); } break; case TS_CMD_IGN8_OFF: // cmd group is for spark8 off actions - #if IGN_CHANNELS >= 8 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil8Charge(); BIT_CLEAR(HWTest_IGN_50pc, 8); } - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { endCoil8Charge(); BIT_CLEAR(HWTest_IGN_50pc, 8); } break; case TS_CMD_IGN8_50PC: // cmd group is for spark8 50%dc actions - #if IGN_CHANNELS >= 8 - if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 8); } - if(!BIT_CHECK(HWTest_IGN_50pc, 8)) { coil8Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) - #endif + if( BIT_CHECK(currentStatus.testOutputs, 1) ) { BIT_TOGGLE(HWTest_IGN_50pc, 8); } + if(!BIT_CHECK(HWTest_IGN_50pc, 8)) { coil8Low(); } //Ensure this output is turned off (Otherwise the output may stay on permanently) break; //VSS Calibration routines