244 lines
9.7 KiB
C++
244 lines
9.7 KiB
C++
/*
|
|
Speeduino - Simple engine management for the Arduino Mega 2560 platform
|
|
Copyright (C) Josh Stewart
|
|
A full copy of the license may be found in the projects root directory
|
|
*/
|
|
//Old PID method. Retained incase the new one has issues
|
|
//integerPID boostPID(&MAPx100, &boost_pwm_target_value, &boostTargetx100, configPage6.boostKP, configPage6.boostKI, configPage6.boostKD, DIRECT);
|
|
integerPID_ideal boostPID(¤tStatus.MAP, ¤tStatus.boostDuty , ¤tStatus.boostTarget, &configPage10.boostSens, &configPage10.boostIntv, configPage6.boostKP, configPage6.boostKI, configPage6.boostKD, DIRECT); //This is the PID object if that algorithm is used. Needs to be global as it maintains state outside of each function call
|
|
|
|
/*
|
|
Fan control
|
|
*/
|
|
void initialiseFan()
|
|
{
|
|
if( configPage6.fanInv == 1 ) { fanHIGH = LOW; fanLOW = HIGH; }
|
|
else { fanHIGH = HIGH; fanLOW = LOW; }
|
|
digitalWrite(pinFan, fanLOW); //Initiallise program with the fan in the off state
|
|
currentStatus.fanOn = false;
|
|
}
|
|
|
|
void fanControl()
|
|
{
|
|
if( configPage6.fanEnable == 1 )
|
|
{
|
|
int onTemp = (int)configPage6.fanSP - CALIBRATION_TEMPERATURE_OFFSET;
|
|
int offTemp = onTemp - configPage6.fanHyster;
|
|
|
|
if ( (!currentStatus.fanOn) && (currentStatus.coolant >= onTemp) ) { digitalWrite(pinFan,fanHIGH); currentStatus.fanOn = true; }
|
|
if ( (currentStatus.fanOn) && (currentStatus.coolant <= offTemp) ) { digitalWrite(pinFan, fanLOW); currentStatus.fanOn = false; }
|
|
}
|
|
}
|
|
|
|
void initialiseAuxPWM()
|
|
{
|
|
#if defined(CORE_AVR)
|
|
TCCR1B = 0x00; //Disbale Timer1 while we set it up
|
|
TCNT1 = 0; //Reset Timer Count
|
|
TIFR1 = 0x00; //Timer1 INT Flag Reg: Clear Timer Overflow Flag
|
|
TCCR1A = 0x00; //Timer1 Control Reg A: Wave Gen Mode normal (Simply counts up from 0 to 65535 (16-bit int)
|
|
TCCR1B = (1 << CS12); //Timer1 Control Reg B: Timer Prescaler set to 256. 1 tick = 16uS. Refer to http://www.instructables.com/files/orig/F3T/TIKL/H3WSA4V7/F3TTIKLH3WSA4V7.jpg
|
|
#elif defined(CORE_TEENSY)
|
|
//FlexTimer 1 is used for boost and VVT. There are 8 channels on this module
|
|
FTM1_MODE |= FTM_MODE_WPDIS; // Write Protection Disable
|
|
FTM1_MODE |= FTM_MODE_FTMEN; //Flex Timer module enable
|
|
FTM1_MODE |= FTM_MODE_INIT;
|
|
FTM1_SC |= FTM_SC_CLKS(0b1); // Set internal clocked
|
|
FTM1_SC |= FTM_SC_PS(0b111); //Set prescaler to 128 (2.1333uS tick time)
|
|
|
|
//Enable each compare channel individually
|
|
FTM1_C0SC &= ~FTM_CSC_MSB; //According to Pg 965 of the K64 datasheet, this should not be needed as MSB is reset to 0 upon reset, but the channel interrupt fails to fire without it
|
|
FTM1_C0SC |= FTM_CSC_MSA; //Enable Compare mode
|
|
FTM1_C0SC |= FTM_CSC_CHIE; //Enable channel compare interrupt
|
|
FTM1_C1SC &= ~FTM_CSC_MSB; //According to Pg 965 of the K64 datasheet, this should not be needed as MSB is reset to 0 upon reset, but the channel interrupt fails to fire without it
|
|
FTM1_C1SC |= FTM_CSC_MSA; //Enable Compare mode
|
|
FTM1_C1SC |= FTM_CSC_CHIE; //Enable channel compare interrupt
|
|
|
|
#endif
|
|
|
|
boost_pin_port = portOutputRegister(digitalPinToPort(pinBoost));
|
|
boost_pin_mask = digitalPinToBitMask(pinBoost);
|
|
vvt_pin_port = portOutputRegister(digitalPinToPort(pinVVT_1));
|
|
vvt_pin_mask = digitalPinToBitMask(pinVVT_1);
|
|
|
|
#if defined(CORE_STM32) || defined(CORE_TEENSY) //2uS resolution Min 8Hz, Max 5KHz
|
|
boost_pwm_max_count = 1000000L / (2 * configPage6.boostFreq * 2); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle. The x2 is there because the frequency is stored at half value (in a byte) to allow freqneucies up to 511Hz
|
|
vvt_pwm_max_count = 1000000L / (2 * configPage6.vvtFreq * 2); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle
|
|
#else
|
|
boost_pwm_max_count = 1000000L / (16 * configPage6.boostFreq * 2); //Converts the frequency in Hz to the number of ticks (at 16uS) it takes to complete 1 cycle. The x2 is there because the frequency is stored at half value (in a byte) to allow freqneucies up to 511Hz
|
|
vvt_pwm_max_count = 1000000L / (16 * configPage6.vvtFreq * 2); //Converts the frequency in Hz to the number of ticks (at 16uS) it takes to complete 1 cycle
|
|
#endif
|
|
ENABLE_VVT_TIMER(); //Turn on the B compare unit (ie turn on the interrupt)
|
|
|
|
boostPID.SetOutputLimits(configPage2.boostMinDuty, configPage2.boostMaxDuty);
|
|
if(configPage6.boostMode == BOOST_MODE_SIMPLE) { boostPID.SetTunings(100, 100, 100); }
|
|
else { boostPID.SetTunings(configPage6.boostKP, configPage6.boostKI, configPage6.boostKD); }
|
|
|
|
currentStatus.boostDuty = 0;
|
|
boostCounter = 0;
|
|
#if defined(CORE_STM32) //Need to be initialised last due to instant interrupt
|
|
Timer1.setMode(2, TIMER_OUTPUT_COMPARE);
|
|
Timer1.setMode(3, TIMER_OUTPUT_COMPARE);
|
|
if(boost_pwm_max_count > 0) { Timer1.attachInterrupt(2, boostInterrupt);}
|
|
if(vvt_pwm_max_count > 0) { Timer1.attachInterrupt(3, vvtInterrupt);}
|
|
Timer1.resume();
|
|
#endif
|
|
}
|
|
|
|
#define BOOST_HYSTER 40
|
|
void boostControl()
|
|
{
|
|
if( configPage6.boostEnabled==1 )
|
|
{
|
|
if( (boostCounter & 7) == 1) { currentStatus.boostTarget = get3DTableValue(&boostTable, currentStatus.TPS, currentStatus.RPM) * 2; } //Boost target table is in kpa and divided by 2
|
|
if(currentStatus.MAP >= (currentStatus.boostTarget - BOOST_HYSTER) )
|
|
{
|
|
//If flex fuel is enabled, there can be an adder to the boost target based on ethanol content
|
|
if( configPage2.flexEnabled == 1 )
|
|
{
|
|
currentStatus.boostTarget += table2D_getValue(&flexBoostTable, currentStatus.ethanolPct);;
|
|
}
|
|
else
|
|
{
|
|
currentStatus.flexBoostCorrection = 0;
|
|
}
|
|
|
|
if(currentStatus.boostTarget > 0)
|
|
{
|
|
//This only needs to be run very infrequently, once every 16 calls to boostControl(). This is approx. once per second
|
|
if( (boostCounter & 15) == 1)
|
|
{
|
|
boostPID.SetOutputLimits(configPage2.boostMinDuty, configPage2.boostMaxDuty);
|
|
|
|
if(configPage6.boostMode == BOOST_MODE_SIMPLE) { boostPID.SetTunings(100, 100, 100); }
|
|
else { boostPID.SetTunings(configPage6.boostKP, configPage6.boostKI, configPage6.boostKD); }
|
|
}
|
|
|
|
bool PIDcomputed = boostPID.Compute(); //Compute() returns false if the required interval has not yet passed.
|
|
if(currentStatus.boostDuty == 0) { DISABLE_BOOST_TIMER(); BOOST_PIN_LOW(); } //If boost duty is 0, shut everything down
|
|
else
|
|
{
|
|
if(PIDcomputed == true)
|
|
{
|
|
boost_pwm_target_value = ((unsigned long)(currentStatus.boostDuty) * boost_pwm_max_count) / 10000; //Convert boost duty (Which is a % multipled by 100) to a pwm count
|
|
ENABLE_BOOST_TIMER(); //Turn on the compare unit (ie turn on the interrupt) if boost duty >0
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
//If boost target is 0, turn everything off
|
|
boostDisable();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Boost control does nothing if kPa below the hyster point
|
|
boostDisable();
|
|
}
|
|
}
|
|
else { // Disable timer channel and zero the flex boost correction status
|
|
DISABLE_BOOST_TIMER();
|
|
currentStatus.flexBoostCorrection = 0;
|
|
}
|
|
|
|
boostCounter++;
|
|
}
|
|
|
|
void vvtControl()
|
|
{
|
|
if( configPage6.vvtEnabled == 1 )
|
|
{
|
|
byte vvtDuty = get3DTableValue(&vvtTable, currentStatus.TPS, currentStatus.RPM);
|
|
|
|
//VVT table can be used for controlling on/off switching. If this is turned on, then disregard any interpolation or non-binary values
|
|
if( (configPage6.VVTasOnOff == true) && (vvtDuty < 100) ) { vvtDuty = 0; }
|
|
|
|
if(vvtDuty == 0)
|
|
{
|
|
//Make sure solenoid is off (0% duty)
|
|
VVT_PIN_LOW();
|
|
DISABLE_VVT_TIMER();
|
|
}
|
|
else if (vvtDuty >= 100)
|
|
{
|
|
//Make sure solenoid is on (100% duty)
|
|
VVT_PIN_HIGH();
|
|
DISABLE_VVT_TIMER();
|
|
}
|
|
else
|
|
{
|
|
vvt_pwm_target_value = percentage(vvtDuty, vvt_pwm_max_count);
|
|
ENABLE_VVT_TIMER();
|
|
}
|
|
}
|
|
else { DISABLE_VVT_TIMER(); } // Disable timer channel
|
|
}
|
|
|
|
void boostDisable()
|
|
{
|
|
boostPID.Initialize(); //This resets the ITerm value to prevent rubber banding
|
|
currentStatus.boostDuty = 0;
|
|
DISABLE_BOOST_TIMER(); //Turn off timer
|
|
BOOST_PIN_LOW(); //Make sure solenoid is off (0% duty)
|
|
}
|
|
|
|
//The interrupt to control the Boost PWM
|
|
#if defined(CORE_AVR)
|
|
ISR(TIMER1_COMPA_vect)
|
|
#elif defined (CORE_TEENSY) || defined(CORE_STM32)
|
|
static inline void boostInterrupt() //Most ARM chips can simply call a function
|
|
#endif
|
|
{
|
|
if (boost_pwm_state == true)
|
|
{
|
|
BOOST_PIN_LOW(); // Switch pin to low
|
|
BOOST_TIMER_COMPARE = BOOST_TIMER_COUNTER + (boost_pwm_max_count - boost_pwm_cur_value);
|
|
boost_pwm_state = false;
|
|
}
|
|
else
|
|
{
|
|
BOOST_PIN_HIGH(); // Switch pin high
|
|
BOOST_TIMER_COMPARE = BOOST_TIMER_COUNTER + boost_pwm_target_value;
|
|
boost_pwm_cur_value = boost_pwm_target_value;
|
|
boost_pwm_state = true;
|
|
}
|
|
}
|
|
|
|
//The interrupt to control the VVT PWM
|
|
#if defined(CORE_AVR)
|
|
ISR(TIMER1_COMPB_vect)
|
|
#elif defined (CORE_TEENSY) || defined(CORE_STM32)
|
|
static inline void vvtInterrupt() //Most ARM chips can simply call a function
|
|
#endif
|
|
{
|
|
if (vvt_pwm_state == true)
|
|
{
|
|
VVT_PIN_LOW(); // Switch pin to low
|
|
VVT_TIMER_COMPARE = VVT_TIMER_COUNTER + (vvt_pwm_max_count - vvt_pwm_cur_value);
|
|
vvt_pwm_state = false;
|
|
}
|
|
else
|
|
{
|
|
VVT_PIN_HIGH(); // Switch pin high
|
|
VVT_TIMER_COMPARE = VVT_TIMER_COUNTER + vvt_pwm_target_value;
|
|
vvt_pwm_cur_value = vvt_pwm_target_value;
|
|
vvt_pwm_state = true;
|
|
}
|
|
}
|
|
|
|
#if defined(CORE_TEENSY)
|
|
void ftm1_isr(void)
|
|
{
|
|
//FTM1 only has 2 compare channels
|
|
//Use separate variables for each test to ensure conversion to bool
|
|
bool interrupt1 = (FTM1_C0SC & FTM_CSC_CHF);
|
|
bool interrupt2 = (FTM1_C1SC & FTM_CSC_CHF);
|
|
|
|
if(interrupt1) { FTM1_C0SC &= ~FTM_CSC_CHF; boostInterrupt(); }
|
|
else if(interrupt2) { FTM1_C1SC &= ~FTM_CSC_CHF; vvtInterrupt(); }
|
|
|
|
}
|
|
#endif
|