Performance: optimize division (#1082)

* Add udiv_32_16

* Apply udiv_32_16() where possible

* Convert udiv_32_16 to assembler
It's worth 20 loop/s

* Remove unused functions

* Remove degreesPeruSx2048 - unused

* Remove angleToTime - replace with direct calls
1. Drop angleToTime()
It's slow, only partially implemented and adds zero value
(and has MISRA violations)
2. Consistent function naming
3. Doxygen

* triggerPri_Nissan360 shouldn't set timePerDegree.
It will be overwritten every loop by doCrankSpeedCalcs()

* Use angleToTimeMicroSecPerDegree() instead of timePerDegree
No loss in performance
Increased injection open/close time accuracy (so unit test values must change)
Can remove timePerDegree global.

* Hide (encapsulate) crank math globals.

* Base all angle to time conversions on decoder computed variables.
This is within 2us of the revolution based method
and is much faster - which is essentially zero percent change.

* Performance: move calculation of degreesPeruSx32768
into decoders.
Remove doCrankSpeedCalcs() - it's doing nothing
at the moment.

* Apply libdivide to triggerSetEndTeeth functions.
Since triggerToothAngle is set once at initialization
time, we can generate the libdivide struct
once and reuse it many times.

* Remove lastToothCalcAdvance - unused

* Replace 16-bit division with shift

* Replace 32-bit divison with 16-bit division

* Avoid 32-bit division; use div100()

* inline percentage()

* Optimize div100()

* MISRA fixes

* Replace magic numbers with #defs

* Replace libdivide structs with inline constants
No perf or memory changes

* Use fixed types for PWM max count variables

* Accurate rounded integer division
* Formalise rounding behavior (DIV_ROUND_CORRECT)
* Apply DIV_ROUND_CORRECT to DIV_ROUND_CLOSEST(),
UDIV_ROUND_CLOSEST(), div100(), div360(),
percentage() & halfPercentage()
* Add, fix & improve unit tests

* Add udiv_32_16_closest()

* Perf: Limit percentage calculations to 16-bits

* MISRA fixes

* Add compare_executiontime() to encapsulate common perf testing code

* Signed to unsigned division

* Convert ignitionLimits() to an inline function.
Slight speed up, probably due to removing
multiple evaluations of macro arguments.

* Split unit tests up.

* udiv_32_16 - check for valid parameters
This commit is contained in:
tx_haggis 2023-11-05 16:10:08 -06:00 committed by GitHub
parent 5ad8784976
commit 9bbd16c81b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1782 additions and 1474 deletions

View File

@ -14,7 +14,9 @@ board=megaatmega2560
framework=arduino
build_unflags = -Os
build_flags = -DUSE_LIBDIVIDE -O3 -ffast-math -fshort-enums -funroll-loops -Wall -Wextra -std=c99
lib_deps = EEPROM, Time
; Note that fp64lib is only used by unit tests. It isn't referenced by the firmware & will be
; ignored.
lib_deps = EEPROM, Time, fp64lib
;test_build_project_src = true
test_build_src = yes
debug_tool = simavr

View File

@ -305,7 +305,7 @@ bool TS_CommandButtonsHandler(uint16_t buttonCommand)
uint32_t calibrationGap = vssGetPulseGap(0);
if( calibrationGap > 0 )
{
configPage2.vssPulsesPerKm = 60000000UL / calibrationGap;
configPage2.vssPulsesPerKm = MICROS_PER_MIN / calibrationGap;
writeConfig(1); // Need to manually save the new config value as it will not trigger a burn in tunerStudio due to use of ControllerPriority
BIT_SET(currentStatus.status3, BIT_STATUS3_VSS_REFRESH); //Set the flag to trigger the UI reset
}

View File

@ -49,7 +49,7 @@ volatile PINMASK_TYPE fan_pin_mask;
#if defined(PWM_FAN_AVAILABLE)//PWM fan not available on Arduino MEGA
volatile bool fan_pwm_state;
unsigned int fan_pwm_max_count; //Used for variable PWM frequency
uint16_t fan_pwm_max_count; //Used for variable PWM frequency
volatile unsigned int fan_pwm_cur_value;
long fan_pwm_value;
#endif
@ -69,8 +69,8 @@ volatile unsigned int boost_pwm_cur_value = 0;
uint32_t vvtWarmTime;
bool vvtIsHot;
bool vvtTimeHold;
unsigned int vvt_pwm_max_count; //Used for variable PWM frequency
unsigned int boost_pwm_max_count; //Used for variable PWM frequency
uint16_t vvt_pwm_max_count; //Used for variable PWM frequency
uint16_t boost_pwm_max_count; //Used for variable PWM frequency
//Old PID method. Retained in case the new one has issues
//integerPID boostPID(&MAPx100, &boost_pwm_target_value, &boostTargetx100, configPage6.boostKP, configPage6.boostKI, configPage6.boostKD, DIRECT);
@ -325,7 +325,7 @@ void initialiseFan(void)
if ( configPage2.fanEnable == 2 ) // PWM Fan control
{
#if defined(CORE_TEENSY)
fan_pwm_max_count = 1000000L / (32 * configPage6.fanFreq * 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
fan_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.fanFreq * 2U)); //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
fan_pwm_value = 0;
}
@ -481,11 +481,11 @@ void initialiseAuxPWM(void)
currentStatus.vvt2Angle = 0;
#if defined(CORE_AVR)
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. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (16U * configPage6.vvtFreq * 2U)); //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
#elif defined(CORE_TEENSY35)
vvt_pwm_max_count = 1000000L / (32 * configPage6.vvtFreq * 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
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.vvtFreq * 2U)); //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
#elif defined(CORE_TEENSY41)
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. Note that the frequency is divided by 2 coming fro TS to allow for up to 512hz
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.vvtFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming fro TS to allow for up to 512hz
#endif
if(configPage6.vvtMode == VVT_MODE_CLOSED_LOOP)
@ -515,11 +515,11 @@ void initialiseAuxPWM(void)
{
// config wmi pwm output to use vvt output
#if defined(CORE_AVR)
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. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (16U * configPage6.vvtFreq * 2U)); //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
#elif defined(CORE_TEENSY35)
vvt_pwm_max_count = 1000000L / (32 * configPage6.vvtFreq * 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
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.vvtFreq * 2U)); //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
#elif defined(CORE_TEENSY41)
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. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.vvtFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
#endif
BIT_CLEAR(currentStatus.status4, BIT_STATUS4_WMI_EMPTY);
currentStatus.wmiPW = 0;

View File

@ -101,13 +101,12 @@ extern volatile PORT_TYPE *fan_pin_port;
extern volatile PINMASK_TYPE fan_pin_mask;
#if defined(PWM_FAN_AVAILABLE)//PWM fan not available on Arduino MEGA
extern unsigned int fan_pwm_max_count; //Used for variable PWM frequency
extern uint16_t fan_pwm_max_count; //Used for variable PWM frequency
void fanInterrupt(void);
#endif
extern unsigned int vvt_pwm_max_count; //Used for variable PWM frequency
extern unsigned int boost_pwm_max_count; //Used for variable PWM frequency
extern uint16_t vvt_pwm_max_count; //Used for variable PWM frequency
extern uint16_t boost_pwm_max_count; //Used for variable PWM frequency
void boostInterrupt(void);
void vvtInterrupt(void);

View File

@ -34,8 +34,8 @@ void initBoard(void)
TCCR1B = TIMER_PRESCALER_256; //Timer1 Control Reg B: Timer Prescaler set to 256. 1 tick = 16uS.
TIFR1 = (1 << OCF1A) | (1<<OCF1B) | (1<<OCF1C) | (1<<TOV1) | (1<<ICF1); //Clear the compare flags, overflow flag and external input flag bits
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 frequencies 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
boost_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (16U * configPage6.boostFreq * 2U)); //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 frequencies up to 511Hz
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (16U * configPage6.vvtFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 16uS) it takes to complete 1 cycle
// put idle_pwm_max_count calculation here?
/*

View File

@ -108,7 +108,7 @@ STM32RTC& rtc = STM32RTC::getInstance();
*/
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
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (TIMER_RESOLUTION * configPage6.idleFreq * 2U)); //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
}
//This must happen at the end of the idle init
@ -150,10 +150,9 @@ STM32RTC& rtc = STM32RTC::getInstance();
* Auxiliaries
*/
//2uS resolution Min 8Hz, Max 5KHz
boost_pwm_max_count = 1000000L / (TIMER_RESOLUTION * configPage6.boostFreq * 2); //Converts the frequency in Hz to the number of ticks (at 4uS) it takes to complete 1 cycle. The x2 is there because the frequency is stored at half value (in a byte) to allow frequencies up to 511Hz
vvt_pwm_max_count = 1000000L / (TIMER_RESOLUTION * configPage6.vvtFreq * 2); //Converts the frequency in Hz to the number of ticks (at 4uS) it takes to complete 1 cycle
fan_pwm_max_count = 1000000L / (TIMER_RESOLUTION * configPage6.fanFreq * 2); //Converts the frequency in Hz to the number of ticks (at 4uS) it takes to complete 1 cycle
boost_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (TIMER_RESOLUTION * configPage6.boostFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 4uS) it takes to complete 1 cycle. The x2 is there because the frequency is stored at half value (in a byte) to allow frequencies up to 511Hz
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (TIMER_RESOLUTION * configPage6.vvtFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 4uS) it takes to complete 1 cycle
fan_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (TIMER_RESOLUTION * configPage6.fanFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 4uS) it takes to complete 1 cycle
//Need to be initialised last due to instant interrupt
#if ( STM32_CORE_VERSION_MAJOR < 2 )

View File

@ -158,11 +158,9 @@ void initBoard()
//Enable IRQ Interrupt
NVIC_ENABLE_IRQ(IRQ_FTM1);
boost_pwm_max_count = 1000000L / (32 * configPage6.boostFreq * 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
vvt_pwm_max_count = 1000000L / (32 * configPage6.vvtFreq * 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
fan_pwm_max_count = 1000000L / (32 * configPage6.fanFreq * 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
boost_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.boostFreq * 2U)); //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
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.vvtFreq * 2U)); //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
fan_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.fanFreq * 2U)); //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
}
/*

View File

@ -83,10 +83,10 @@ void initBoard()
}
//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 frequencies 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
boost_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.boostFreq * 2U)); //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 frequencies up to 511Hz
vvt_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.vvtFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle
#if defined(PWM_FAN_AVAILABLE)
fan_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
fan_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.vvtFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle
#endif
//TODO: Configure timers here

View File

@ -295,14 +295,14 @@ uint16_t correctionAccel(void)
{
//Get the MAP rate change
MAP_change = (currentStatus.MAP - MAPlast);
currentStatus.mapDOT = ldiv(1000000, (MAP_time - MAPlast_time)).quot * MAP_change; //This is the % per second that the MAP has moved
currentStatus.mapDOT = (MICROS_PER_SEC / (MAP_time - MAPlast_time)) * MAP_change; //This is the % per second that the MAP has moved
//currentStatus.mapDOT = 15 * MAP_change; //This is the kpa per second that the MAP has moved
}
else if(configPage2.aeMode == AE_MODE_TPS)
{
//Get the TPS rate change
TPS_change = (currentStatus.TPS - currentStatus.TPSlast);
//currentStatus.tpsDOT = ldiv(1000000, (TPS_time - TPSlast_time)).quot * TPS_change; //This is the % per second that the TPS has moved
//currentStatus.tpsDOT = ldiv(MICROS_PER_SEC, (TPS_time - TPSlast_time)).quot * TPS_change; //This is the % per second that the TPS has moved
currentStatus.tpsDOT = (TPS_READ_FREQUENCY * TPS_change) / 2; //This is the % per second that the TPS has moved, adjusted for the 0.5% resolution of the TPS
}
@ -1021,8 +1021,8 @@ uint16_t correctionsDwell(uint16_t dwell)
if(dwellPerRevolution > revolutionTime)
{
//Possibly need some method of reducing spark duration here as well, but this is a start
uint16_t adjustedSparkDur = (sparkDur_uS * revolutionTime) / dwellPerRevolution;
tempDwell = (revolutionTime / pulsesPerRevolution) - adjustedSparkDur;
uint16_t adjustedSparkDur = udiv_32_16(sparkDur_uS * revolutionTime, dwellPerRevolution);
tempDwell = udiv_32_16(revolutionTime, (uint16_t)pulsesPerRevolution) - adjustedSparkDur;
}
return tempDwell;

View File

@ -1,104 +1,63 @@
#include "globals.h"
#include "crankMaths.h"
#include "decoders.h"
#include "timers.h"
#include "maths.h"
volatile uint16_t timePerDegree;
volatile uint16_t timePerDegreex16;
volatile uint16_t degreesPeruSx2048;
volatile unsigned long degreesPeruSx32768;
#define SECOND_DERIV_ENABLED 0
//These are only part of the experimental 2nd deriv calcs
#if SECOND_DERIV_ENABLED!=0
byte deltaToothCount = 0; //The last tooth that was used with the deltaV calc
int rpmDelta;
#endif
/*
* Converts a crank angle into a time from or since that angle occurred.
* Positive angles are assumed to be in the future, negative angles in the past:
* * Future angle calculations will use a predicted speed/acceleration
* * Past angle calculations will use the known speed
*
* Currently 4 methods are planned and/or available:
* 1) Last interval based on a full revolution
* 2) Last interval based on the time between the last 2 teeth (Crank Pattern dependent)
* 3) Closed loop error correction (Alpha-beta filter)
* 4) 2nd derivative prediction (Speed + acceleration)
*/
unsigned long angleToTime(uint16_t angle, byte method)
{
unsigned long returnTime = 0;
if( (method == CRANKMATH_METHOD_INTERVAL_REV) || (method == CRANKMATH_METHOD_INTERVAL_DEFAULT) )
{
returnTime = angleToTimeIntervalRev(angle);
//returnTime = angle * (unsigned long)timePerDegree;
}
else if (method == CRANKMATH_METHOD_INTERVAL_TOOTH)
{
//Still uses a last interval method (ie retrospective), but bases the interval on the gap between the 2 most recent teeth rather than the last full revolution
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
noInterrupts();
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
returnTime = ( (toothTime * angle) / tempTriggerToothAngle );
}
else { returnTime = angleToTime(angle, CRANKMATH_METHOD_INTERVAL_REV); } //Safety check. This can occur if the last tooth seen was outside the normal pattern etc
}
return returnTime;
uint32_t angleToTimeMicroSecPerDegree(uint16_t angle) {
UQ24X8_t micros = (uint32_t)angle * (uint32_t)microsPerDegree;
return RSHIFT_ROUND(micros, microsPerDegree_Shift);
}
/*
* Convert a time (uS) into an angle at current speed
* Currently 4 methods are planned and/or available:
* 1) Last interval based on a full revolution
* 2) Last interval based on the time between the last 2 teeth (Crank Pattern dependent)
* 3) Closed loop error correction (Alpha-beta filter)
* 4) 2nd derivative prediction (Speed + acceleration)
*/
uint16_t timeToAngle(unsigned long time, byte method)
{
uint16_t returnAngle = 0;
if( (method == CRANKMATH_METHOD_INTERVAL_REV) || (method == CRANKMATH_METHOD_INTERVAL_DEFAULT) )
{
//A last interval method of calculating angle that does not take into account any acceleration. The interval used is the time taken to complete the last full revolution
//degreesPeruSx2048 is the number of degrees the crank moves per uS. This value is almost always <1uS, so it is multiplied by 2048. This allows an angle calculation with only a multiply and a bitshift without any appreciable drop in accuracy
returnAngle = fastTimeToAngle(time);
}
else if (method == CRANKMATH_METHOD_INTERVAL_TOOTH)
{
//Still uses a last interval method (ie retrospective), but bases the interval on the gap between the 2 most recent teeth rather than the last full revolution
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
noInterrupts();
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
returnAngle = ( (unsigned long)(time * tempTriggerToothAngle) / toothTime );
}
else { returnAngle = timeToAngle(time, CRANKMATH_METHOD_INTERVAL_REV); } //Safety check. This can occur if the last tooth seen was outside the normal pattern etc
}
else if (method == CRANKMATH_METHOD_ALPHA_BETA)
{
//Not yet implemented. Default to Rev
returnAngle = timeToAngle(time, CRANKMATH_METHOD_INTERVAL_REV);
}
else if (method == CRANKMATH_METHOD_2ND_DERIVATIVE)
{
//Not yet implemented. Default to Rev
returnAngle = timeToAngle(time, CRANKMATH_METHOD_INTERVAL_REV);
}
return returnAngle;
uint32_t angleToTimeIntervalTooth(uint16_t angle) {
noInterrupts();
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
return (toothTime * (uint32_t)angle) / tempTriggerToothAngle;
}
//Safety check. This can occur if the last tooth seen was outside the normal pattern etc
else {
interrupts();
return angleToTimeMicroSecPerDegree(angle);
}
}
uint16_t timeToAngleDegPerMicroSec(uint32_t time) {
uint32_t degFixed = time * (uint32_t)degreesPerMicro;
return RSHIFT_ROUND(degFixed, degreesPerMicro_Shift);
}
uint16_t timeToAngleIntervalTooth(uint32_t time)
{
noInterrupts();
//Still uses a last interval method (ie retrospective), but bases the interval on the gap between the 2 most recent teeth rather than the last full revolution
if(BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT))
{
unsigned long toothTime = (toothLastToothTime - toothLastMinusOneToothTime);
uint16_t tempTriggerToothAngle = triggerToothAngle; // triggerToothAngle is set by interrupts
interrupts();
return (unsigned long)(time * (uint32_t)tempTriggerToothAngle) / toothTime;
}
else {
interrupts();
//Safety check. This can occur if the last tooth seen was outside the normal pattern etc
return timeToAngleDegPerMicroSec(time);
}
}
#if SECOND_DERIV_ENABLED!=0
void doCrankSpeedCalcs(void)
{
//********************************************************
@ -106,7 +65,7 @@ void doCrankSpeedCalcs(void)
//We use a 1st Deriv acceleration prediction, but only when there is an even spacing between primary sensor teeth
//Any decoder that has uneven spacing has its triggerToothAngle set to 0
//THIS IS CURRENTLY DISABLED FOR ALL DECODERS! It needs more work.
if( SECOND_DERIV_ENABLED && (BIT_CHECK(decoderState, BIT_DECODER_2ND_DERIV)) && (toothHistoryIndex >= 3) && (currentStatus.RPM < 2000) ) //toothHistoryIndex must be greater than or equal to 3 as we need the last 3 entries. Currently this mode only runs below 3000 rpm
if( (BIT_CHECK(decoderState, BIT_DECODER_2ND_DERIV)) && (toothHistoryIndex >= 3) && (currentStatus.RPM < 2000) ) //toothHistoryIndex must be greater than or equal to 3 as we need the last 3 entries. Currently this mode only runs below 3000 rpm
{
//Only recalculate deltaV if the tooth has changed since last time (DeltaV stays the same until the next tooth)
//if (deltaToothCount != toothCurrentCount)
@ -129,7 +88,7 @@ void doCrankSpeedCalcs(void)
}
else { angle1 = triggerToothAngle; angle2 = triggerToothAngle; }
uint32_t toothDeltaV = (1000000L * angle2 / toothHistory[toothHistoryIndex]) - (1000000L * angle1 / toothHistory[toothHistoryIndex-1]);
uint32_t toothDeltaV = (MICROS_PER_SEC * angle2 / toothHistory[toothHistoryIndex]) - (MICROS_PER_SEC * angle1 / toothHistory[toothHistoryIndex-1]);
uint32_t toothDeltaT = toothHistory[toothHistoryIndex];
//long timeToLastTooth = micros() - toothLastToothTime;
@ -137,33 +96,6 @@ void doCrankSpeedCalcs(void)
}
timePerDegreex16 = ldiv( 2666656L, currentStatus.RPM + rpmDelta).quot; //This gives accuracy down to 0.1 of a degree and can provide noticeably better timing results on low resolution triggers
timePerDegree = timePerDegreex16 / 16;
}
else
{
//If we can, attempt to get the timePerDegree by comparing the times of the last two teeth seen. This is only possible for evenly spaced teeth
noInterrupts();
if( (BIT_CHECK(decoderState, BIT_DECODER_TOOTH_ANG_CORRECT)) && (toothLastToothTime > toothLastMinusOneToothTime) && (abs(currentStatus.rpmDOT) > 30) )
{
//noInterrupts();
unsigned long tempToothLastToothTime = toothLastToothTime;
unsigned long tempToothLastMinusOneToothTime = toothLastMinusOneToothTime;
uint16_t tempTriggerToothAngle = triggerToothAngle;
interrupts();
timePerDegreex16 = (unsigned long)( (tempToothLastToothTime - tempToothLastMinusOneToothTime)*16) / tempTriggerToothAngle;
timePerDegree = timePerDegreex16 / 16;
}
else
{
//long timeThisRevolution = (micros_safe() - toothOneTime);
interrupts();
//Take into account any likely acceleration that has occurred since the last full revolution completed:
//long rpm_adjust = (timeThisRevolution * (long)currentStatus.rpmDOT) / 1000000;
long rpm_adjust = 0;
timePerDegreex16 = ldiv( 2666656L, currentStatus.RPM + rpm_adjust).quot; //The use of a x16 value gives accuracy down to 0.1 of a degree and can provide noticeably better timing results on low resolution triggers
timePerDegree = timePerDegreex16 / 16;
}
}
degreesPeruSx2048 = 2048 / timePerDegree;
degreesPeruSx32768 = 524288 / timePerDegreex16;
}
#endif

View File

@ -4,31 +4,76 @@
#include "maths.h"
#include "globals.h"
#define CRANKMATH_METHOD_INTERVAL_DEFAULT 0
#define CRANKMATH_METHOD_INTERVAL_REV 1
#define CRANKMATH_METHOD_INTERVAL_TOOTH 2
#define CRANKMATH_METHOD_ALPHA_BETA 3
#define CRANKMATH_METHOD_2ND_DERIVATIVE 4
#define SECOND_DERIV_ENABLED 0
//#define fastDegreesToUS(targetDegrees) ((targetDegrees) * (unsigned long)timePerDegree)
#define fastDegreesToUS(targetDegrees) (((targetDegrees) * (unsigned long)timePerDegreex16) >> 4)
/*#define fastTimeToAngle(time) (((unsigned long)time * degreesPeruSx2048) / 2048) */ //Divide by 2048 will be converted at compile time to bitshift
#define fastTimeToAngle(time) (((unsigned long)(time) * degreesPeruSx32768) / 32768) //Divide by 32768 will be converted at compile time to bitshift
#define ignitionLimits(angle) ( (((int16_t)(angle)) >= CRANK_ANGLE_MAX_IGN) ? ((angle) - CRANK_ANGLE_MAX_IGN) : ( ((int16_t)(angle) < 0) ? ((angle) + CRANK_ANGLE_MAX_IGN) : (angle)) )
unsigned long angleToTime(uint16_t angle, byte method);
inline unsigned long angleToTimeIntervalRev(uint16_t angle) {
return div360(angle * revolutionTime);
/**
* @brief Makes one pass at nudging the angle to within [0,CRANK_ANGLE_MAX_IGN]
*
* @param angle A crank angle in degrees
* @return int16_t
*/
static inline int16_t ignitionLimits(int16_t angle) {
return nudge(0, CRANK_ANGLE_MAX_IGN, angle, CRANK_ANGLE_MAX_IGN);
}
uint16_t timeToAngle(unsigned long time, byte method);
void doCrankSpeedCalcs(void);
extern volatile uint16_t timePerDegree;
extern volatile uint16_t timePerDegreex16;
extern volatile unsigned long degreesPeruSx32768;
/** @brief At 1 RPM, each degree of angular rotation takes this many microseconds */
#define MICROS_PER_DEG_1_RPM INT32_C(166667)
/** @brief The maximum rpm that the ECU will attempt to run at.
*
* It is NOT related to the rev limiter, but is instead dictates how fast certain operations will be
* allowed to run. Lower number gives better performance
**/
#define MAX_RPM INT16_C(18000)
/** @brief Absolute minimum RPM that the crank math (& therefore all of Speeduino) can be used with
*
* This is dictated by the use of uint16_t as the base type for storing
* angle<->time conversion factor (degreesPerMicro)
*/
#define MIN_RPM ((MICROS_PER_DEG_1_RPM/(UINT16_MAX/16UL))+1UL)
/**
* @name Converts angular degrees to the time interval that amount of rotation
* will take at current RPM.
*
* Based on angle of [0,720] and min/max RPM, result ranges from
* 9 (MAX_RPM, 1 deg) to 2926828 (MIN_RPM, 720 deg)
*
* @param angle Angle in degrees
* @return Time interval in uS
*/
///@{
/** @brief Converts based on the time one degree of rotation takes
*
* Inverse of timeToAngleDegPerMicroSec
*/
uint32_t angleToTimeMicroSecPerDegree(uint16_t angle);
/** @brief Converts based on the time interval between the 2 most recently detected decoder teeth
*
* Inverse of timeToAngleIntervalTooth
*/
uint32_t angleToTimeIntervalTooth(uint16_t angle);
///@}
/**
* @name Converts a time interval in microsecods to the equivalent degrees of angular (crank)
* rotation at current RPM.
*
* @param time Time interval in uS
* @return Angle in degrees
*/
///@{
/** @brief Converts based on the the interval on time one degree of rotation takes
*
* Inverse of angleToTimeMicroSecPerDegree
*/
uint16_t timeToAngleDegPerMicroSec(uint32_t time);
/** @brief Converts based on the time interval between the 2 most recently detected decoder teeth
*
* Inverse of angleToTimeIntervalTooth
*/
uint16_t timeToAngleIntervalTooth(uint32_t time);
///@}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,7 @@ extern bool decoderHasFixedCrankingTiming;
*/
//This isn't to to filter out wrong pulses on triggers, but just to smooth out the cam angle reading for better closed loop VVT control.
#define ANGLE_FILTER(input, alpha, prior) (((long)input * (256 - alpha) + ((long)prior * alpha))) >> 8
#define ANGLE_FILTER(input, alpha, prior) (((long)(input) * (256 - (alpha)) + ((long)(prior) * (alpha)))) >> 8
void loggerPrimaryISR(void);
void loggerSecondaryISR(void);
@ -282,9 +282,25 @@ extern volatile uint16_t triggerToothAngle; //The number of crank degrees that e
extern byte checkSyncToothCount; //How many teeth must've been seen on this revolution before we try to confirm sync (Useful for missing tooth type decoders)
extern unsigned long elapsedTime;
extern unsigned long lastCrankAngleCalc;
extern int16_t lastToothCalcAdvance; //Invalid value here forces calculation of this on first main loop
extern unsigned long lastVVTtime; //The time between the vvt reference pulse and the last crank pulse
typedef uint32_t UQ24X8_t;
constexpr uint8_t UQ24X8_Shift = 8U;
/** @brief uS per degree at current RPM in UQ24.8 fixed point */
extern UQ24X8_t microsPerDegree;
constexpr uint8_t microsPerDegree_Shift = UQ24X8_Shift;
typedef uint16_t UQ1X15_t;
constexpr uint8_t UQ1X15_Shift = 15U;
/** @brief Degrees per uS in UQ1.15 fixed point.
*
* Ranges from 8 (0.000246) at MIN_RPM to 3542 (0.108) at MAX_RPM
*/
extern UQ1X15_t degreesPerMicro;
constexpr uint8_t degreesPerMicro_Shift = UQ1X15_Shift;
extern uint16_t ignition1EndTooth;
extern uint16_t ignition2EndTooth;
extern uint16_t ignition3EndTooth;
@ -296,12 +312,8 @@ extern uint16_t ignition8EndTooth;
extern int16_t toothAngles[24]; //An array for storing fixed tooth angles. Currently sized at 24 for the GM 24X decoder, but may grow later if there are other decoders that use this style
//Used for identifying long and short pulses on the 4G63 (And possibly other) trigger patterns
#define LONG 0;
#define SHORT 1;
#define CRANK_SPEED 0
#define CAM_SPEED 1
#define CRANK_SPEED 0U
#define CAM_SPEED 1U
#define TOOTH_CRANK 0
#define TOOTH_CAM_SECONDARY 1

View File

@ -1,6 +1,7 @@
#include "globals.h"
#include "engineProtection.h"
#include "maths.h"
byte oilProtStartTime = 0;
@ -105,10 +106,10 @@ byte checkOilPressureLimit(void)
if(currentStatus.oilPressure < oilLimit)
{
//Check if this is the first time we've been below the limit
if(oilProtStartTime == 0) { oilProtStartTime = (millis() / 100); }
if(oilProtStartTime == 0) { oilProtStartTime = div100(millis()); }
/* Check if countdown has reached its target, if so then instruct to cut */
if( (uint8_t(millis()/100) >= (uint16_t(oilProtStartTime + configPage10.oilPressureProtTime)) ) || (alreadyActive > 0) )
if( (uint8_t(div100(millis())) >= (uint16_t(oilProtStartTime + configPage10.oilPressureProtTime)) ) || (alreadyActive > 0) )
{
BIT_SET(currentStatus.engineProtectStatus, ENGINE_PROTECT_BIT_OIL);
oilProtectActive = 1;

View File

@ -120,8 +120,9 @@
#define interruptSafe(c) (noInterrupts(); {c} interrupts();) //Wraps any code between nointerrupt and interrupt calls
#define MS_IN_MINUTE 60000
#define US_IN_MINUTE 60000000
#define MICROS_PER_SEC INT32_C(1000000)
#define MICROS_PER_MIN INT32_C(MICROS_PER_SEC*60U)
#define MICROS_PER_HOUR INT32_C(MICROS_PER_MIN*60U)
#define SERIAL_PORT_PRIMARY 0
#define SERIAL_PORT_SECONDARY 3
@ -243,11 +244,11 @@
#define OUTPUT_CONTROL_DIRECT 0
#define OUTPUT_CONTROL_MC33810 10
#define IGN_MODE_WASTED 0
#define IGN_MODE_SINGLE 1
#define IGN_MODE_WASTEDCOP 2
#define IGN_MODE_SEQUENTIAL 3
#define IGN_MODE_ROTARY 4
#define IGN_MODE_WASTED 0U
#define IGN_MODE_SINGLE 1U
#define IGN_MODE_WASTEDCOP 2U
#define IGN_MODE_SEQUENTIAL 3U
#define IGN_MODE_ROTARY 4U
#define SEC_TRIGGER_SINGLE 0
#define SEC_TRIGGER_4_1 1
@ -345,14 +346,12 @@
#define MULTIPLY_MAP_MODE_BARO 1
#define MULTIPLY_MAP_MODE_100 2
#define FOUR_STROKE 0
#define TWO_STROKE 1
#define FOUR_STROKE 0U
#define TWO_STROKE 1U
#define GOING_LOW 0
#define GOING_HIGH 1
#define MAX_RPM 18000 /**< The maximum rpm that the ECU will attempt to run at. It is NOT related to the rev limiter, but is instead dictates how fast certain operations will be allowed to run. Lower number gives better performance */
#define BATTV_COR_MODE_WHOLE 0
#define BATTV_COR_MODE_OPENTIME 1
@ -384,7 +383,7 @@
#define CALIBRATION_TABLE_SIZE 512 ///< Calibration table size for CLT, IAT, O2
#define CALIBRATION_TEMPERATURE_OFFSET 40 /**< All temperature measurements are stored offset by 40 degrees.
This is so we can use an unsigned byte (0-255) to represent temperature ranges from -40 to 215 */
#define OFFSET_FUELTRIM 127 ///< The fuel trim tables are offset by 128 to allow for -128 to +128 values
#define OFFSET_FUELTRIM 127U ///< The fuel trim tables are offset by 128 to allow for -128 to +128 values
#define OFFSET_IGNITION 40 ///< Ignition values from the main spark table are offset 40 degrees downwards to allow for negative spark timing
#define SERIAL_BUFFER_THRESHOLD 32 ///< When the serial buffer is filled to greater than this threshold value, the serial processing operations will be performed more urgently in order to avoid it overflowing. Serial buffer is 64 bytes long, so the threshold is set at half this as a reasonable figure
@ -581,11 +580,11 @@ struct statuses {
uint16_t RPM; ///< RPM - Current Revs per minute
byte RPMdiv100; ///< RPM value scaled (divided by 100) to fit a byte (0-255, e.g. 12000 => 120)
long longRPM; ///< RPM as long int (gets assigned to / maintained in statuses.RPM as well)
int mapADC;
uint16_t mapADC;
int baroADC;
long MAP; ///< Manifold absolute pressure. Has to be a long for PID calcs (Boost control)
int16_t EMAP; ///< EMAP ... (See @ref config6.useEMAP for EMAP enablement)
int16_t EMAPADC;
uint16_t EMAPADC;
byte baro; ///< Barometric pressure is simply the initial MAP reading, taken before the engine is running. Alternatively, can be taken from an external sensor
byte TPS; /**< The current TPS reading (0% - 100%). Is the tpsADC value after the calibration is applied */
byte tpsADC; /**< byte (valued: 0-255) representation of the TPS. Downsampled from the original 10-bit (0-1023) reading, but before any calibration is applied */
@ -696,6 +695,10 @@ struct statuses {
byte airConStatus;
};
static inline bool HasAnySync(const statuses &status) {
return status.hasSync || BIT_CHECK(status.status3, BIT_STATUS3_HALFSYNC);
}
/** Page 2 of the config - mostly variables that are required for fuel.
* These are "non-live" EFI setting, engine and "system" variables that remain fixed once sent
* (and stored to e.g. EEPROM) from configuration/tuning SW (from outside by USBserial/bluetooth).
@ -929,13 +932,13 @@ struct config4 {
byte ignBypassPin : 6; //Pin the ignition bypass is activated on
byte ignBypassHiLo : 1; //Whether this should be active high or low.
byte ADCFILTER_TPS;
byte ADCFILTER_CLT;
byte ADCFILTER_IAT;
byte ADCFILTER_O2;
byte ADCFILTER_BAT;
byte ADCFILTER_MAP; //This is only used on Instantaneous MAP readings and is intentionally very weak to allow for faster response
byte ADCFILTER_BARO;
uint8_t ADCFILTER_TPS;
uint8_t ADCFILTER_CLT;
uint8_t ADCFILTER_IAT;
uint8_t ADCFILTER_O2;
uint8_t ADCFILTER_BAT;
uint8_t ADCFILTER_MAP; //This is only used on Instantaneous MAP readings and is intentionally very weak to allow for faster response
uint8_t ADCFILTER_BARO;
byte cltAdvBins[6]; /**< Coolant Temp timing advance curve bins */
byte cltAdvValues[6]; /**< Coolant timing advance curve values. These are translated by 15 to allow for negative values */

View File

@ -22,7 +22,7 @@ unsigned int completedHomeSteps;
volatile bool idle_pwm_state;
bool lastDFCOValue;
unsigned int idle_pwm_max_count; //Used for variable PWM frequency
uint16_t idle_pwm_max_count; //Used for variable PWM frequency
volatile unsigned int idle_pwm_cur_value;
long idle_pid_target_value;
long FeedForwardTerm;
@ -109,11 +109,11 @@ void initialiseIdle(bool forcehoming)
iacCrankDutyTable.axisX = configPage6.iacCrankBins;
#if defined(CORE_AVR)
idle_pwm_max_count = 1000000L / (16 * 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
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (16U * configPage6.idleFreq * 2U)); //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
#elif defined(CORE_TEENSY35)
idle_pwm_max_count = 1000000L / (32 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 32uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.idleFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 32uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
#elif defined(CORE_TEENSY41)
idle_pwm_max_count = 1000000L / (2 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.idleFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
#endif
enableIdle();
break;
@ -133,11 +133,11 @@ void initialiseIdle(bool forcehoming)
iacCrankDutyTable.axisX = configPage6.iacCrankBins;
#if defined(CORE_AVR)
idle_pwm_max_count = 1000000L / (16 * 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
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (16U * configPage6.idleFreq * 2U)); //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
#elif defined(CORE_TEENSY)
idle_pwm_max_count = 1000000L / (32 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 32uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.idleFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 32uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
#elif defined(CORE_TEENSY41)
idle_pwm_max_count = 1000000L / (2 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.idleFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 2uS) 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.iacCLminValue, idle_pwm_max_count<<2), percentage(configPage2.iacCLmaxValue, idle_pwm_max_count<<2));
idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD);
@ -157,11 +157,11 @@ void initialiseIdle(bool forcehoming)
iacCrankDutyTable.axisX = configPage6.iacCrankBins;
#if defined(CORE_AVR)
idle_pwm_max_count = 1000000L / (16 * 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
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (16U * configPage6.idleFreq * 2U)); //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
#elif defined(CORE_TEENSY)
idle_pwm_max_count = 1000000L / (32 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 32uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (32U * configPage6.idleFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 32uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
#elif defined(CORE_TEENSY41)
idle_pwm_max_count = 1000000L / (2 * configPage6.idleFreq * 2); //Converts the frequency in Hz to the number of ticks (at 2uS) it takes to complete 1 cycle. Note that the frequency is divided by 2 coming from TS to allow for up to 512hz
idle_pwm_max_count = (uint16_t)(MICROS_PER_SEC / (2U * configPage6.idleFreq * 2U)); //Converts the frequency in Hz to the number of ticks (at 2uS) 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.iacCLminValue, idle_pwm_max_count<<2), percentage(configPage2.iacCLmaxValue, idle_pwm_max_count<<2));
idlePID.SetTunings(configPage6.idleKP, configPage6.idleKI, configPage6.idleKD);
@ -573,7 +573,7 @@ void idleControl(void)
// Now assign the real PWM value
idle_pwm_target_value = TEMP_idle_pwm_target_value>>2; //increased resolution
currentStatus.idleLoad = ((unsigned long)(idle_pwm_target_value * 100UL) / idle_pwm_max_count);
currentStatus.idleLoad = udiv_32_16(idle_pwm_target_value * 100UL, idle_pwm_max_count);
}
idleCounter++;
}

View File

@ -36,7 +36,7 @@ struct StepperIdle
byte moreAirDirection;
};
extern unsigned int idle_pwm_max_count; //Used for variable PWM frequency
extern uint16_t idle_pwm_max_count; //Used for variable PWM frequency
extern long FeedForwardTerm;
void initialiseIdle(bool forcehoming);

View File

@ -1,72 +1,18 @@
#include <Arduino.h>
#include "maths.h"
#include <stdlib.h>
#ifdef USE_LIBDIVIDE
#include "src/libdivide/constant_fast_div.h"
// Constants used for libdivide. Using predefined constants saves flash and RAM (.bss)
// versus calling the libdivide generator functions (E.g. libdivide_s32_gen)
const libdivide::libdivide_s16_t libdiv_s16_100 = { .magic = S16_MAGIC(100), .more = S16_MORE(100) };
const libdivide::libdivide_u16_t libdiv_u16_100 = { .magic = U16_MAGIC(100), .more = U16_MORE(100) };
// 32-bit constants generated here: https://godbolt.org/z/vP8Kfejo9
const libdivide::libdivide_u32_t libdiv_u32_100 = { .magic = 2748779070, .more = 6 };
const libdivide::libdivide_s32_t libdiv_s32_100 = { .magic = 1374389535, .more = 5 };
const libdivide::libdivide_u32_t libdiv_u32_200 = { .magic = 2748779070, .more = 7 };
const libdivide::libdivide_u32_t libdiv_u32_360 = { .magic = 1813430637, .more = 72 };
#endif
//Replace the standard arduino map() function to use the div function instead
int fastMap(unsigned long x, int in_min, int in_max, int out_min, int out_max)
{
unsigned long a = (x - (unsigned long)in_min);
int b = (out_max - out_min);
int c = (in_max - in_min);
int d = (ldiv( (a * (long)b) , (long)c ).quot);
return d + out_min;
//return ldiv( ((x - in_min) * (out_max - out_min)) , (in_max - in_min) ).quot + out_min;
//return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
//Return x percent of y
//This is a relatively fast approximation of a percentage value.
unsigned long percentage(uint8_t x, unsigned long y)
{
return div100(y * x);
}
//Same as above, but 0.5% accuracy
unsigned long halfPercentage(uint8_t x, unsigned long y)
{
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_u32_do(y * x, &libdiv_u32_200);
#else
return (y * x) / 200;
#endif
}
/*
* Calculates integer power values. Same as pow() but with ints
*/
inline long powint(int factor, unsigned int exponent)
{
long product = 1;
unsigned int counter = exponent;
while ( (counter--) > 0) { product *= factor; }
return product;
}
//Generates a random number from 1 to 100 (inclusive).
//The initial seed used is always based on micros(), though this is unlikely to cause an issue as the first run is nearly random itself
//Function requires 4 bytes to store state and seed, but operates very quickly (around 4uS per call)
uint8_t a, x, y, z;
static uint8_t a, x, y, z;
uint8_t random1to100()
{
//Check if this is the first time being run. If so, seed the random number generator with micros()
if( (a == 0) && (x == 0) && (y == 0) && (z == 0) )
if( (a == 0U) && (x == 0U) && (y == 0U) && (z == 0U) )
{
x = micros() >> 24;
y = micros() >> 16;
z = micros() >> 8;
x = micros() >> 24U;
y = micros() >> 16U;
z = micros() >> 8U;
a = micros();
}
@ -78,6 +24,6 @@ uint8_t random1to100()
z=a;
a = z ^ t ^ ( z >> 1) ^ (t << 1);
}
while(a >= 100);
return (a+1);
while(a >= 100U);
return (a+1U);
}

View File

@ -1,82 +1,307 @@
#ifndef MATH_H
#define MATH_H
#include <stdint.h>
#include "globals.h"
unsigned long percentage(uint8_t x, unsigned long y);
unsigned long halfPercentage(uint8_t x, unsigned long y);
inline long powint(int factor, unsigned int exponent);
uint8_t random1to100();
#ifdef USE_LIBDIVIDE
// We use pre-computed constant parameters with libdivide where possible.
// Using predefined constants saves flash and RAM (.bss) versus calling the
// libdivide generator functions (E.g. libdivide_s32_gen)
// 32-bit constants generated here: https://godbolt.org/z/vP8Kfejo9
#include "src/libdivide/libdivide.h"
extern const struct libdivide::libdivide_u16_t libdiv_u16_100;
extern const struct libdivide::libdivide_s16_t libdiv_s16_100;
extern const struct libdivide::libdivide_u32_t libdiv_u32_100;
extern const struct libdivide::libdivide_s32_t libdiv_s32_100;
extern const struct libdivide::libdivide_u32_t libdiv_u32_360;
#include "src/libdivide/constant_fast_div.h"
#endif
inline uint8_t div100(uint8_t n) {
return n / (uint8_t)100U;
}
inline int8_t div100(int8_t n) {
return n / (int8_t)100U;
}
inline uint16_t div100(uint16_t n) {
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_u16_do(n, &libdiv_u16_100);
#else
return n / (uint16_t)100U;
#endif
}
inline int16_t div100(int16_t n) {
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_s16_do(n, &libdiv_s16_100);
#else
return n / (int16_t)100;
#endif
}
inline uint32_t div100(uint32_t n) {
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_u32_do(n, &libdiv_u32_100);
#else
return n / (uint32_t)100U;
#endif
}
#if defined(__arm__)
inline int div100(int n) {
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_s32_do(n, &libdiv_s32_100);
#else
return n / (int)100;
#endif
}
#else
inline int32_t div100(int32_t n) {
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_s32_do(n, &libdiv_s32_100);
#else
return n / (int32_t)100;
#endif
}
#endif
extern uint8_t random1to100(void);
inline uint32_t div360(uint32_t n) {
/**
* @defgroup group-rounded-div Rounding integer division
*
* @brief Integer division returns the quotient. I.e. rounds to zero. This
* code will round the result to nearest integer. Rounding behavior is
* controlled by #DIV_ROUND_BEHAVIOR
*
* @{
*/
/**
* @defgroup group-rounded-div-behavior Rounding behavior
* @{
*/
/** @brief Rounding behavior: always round down */
#define DIV_ROUND_DOWN -1
/** @brief Rounding behavior: always round up */
#define DIV_ROUND_UP 1
/** @brief Rounding behavior: round to nearest
*
* This rounds 0.5 away from zero. This is the same behavior
* as the standard library round() function.
*/
#define DIV_ROUND_NEAREST 0
/** @brief Integer division rounding behavior. */
#define DIV_ROUND_BEHAVIOR DIV_ROUND_NEAREST
// (Unit tests expect DIV_ROUND_NEAREST behavior)
/**
* @brief Computes the denominator correction for rounding division
* based on our rounding behavior.
*
* @param d The divisor (an integer)
* @param t The type of the result. E.g. uint16_t
*/
#define DIV_ROUND_CORRECT(d, t) ((t)(((d)>>1U)+(t)DIV_ROUND_BEHAVIOR))
///@}
/**
* @brief Rounded integer division
*
* Integer division returns the quotient. I.e. rounds to zero. This
* macro will round the result to nearest integer. Rounding behavior
* is controlled by #DIV_ROUND_BEHAVIOR
*
* @warning For performance reasons, this macro does not promote integers.
* So it will overflow if n>MAX(t)-(d/2).
*
* @param n The numerator (dividee) (an integer)
* @param d The denominator (divider) (an integer)
* @param t The type of the result. E.g. uint16_t
*/
#define DIV_ROUND_CLOSEST(n, d, t) ( \
(((n) < (t)(0)) ^ ((d) < (t)(0))) ? \
((t)((n) - DIV_ROUND_CORRECT(d, t))/(t)(d)) : \
((t)((n) + DIV_ROUND_CORRECT(d, t))/(t)(d)))
/**
* @brief Rounded \em unsigned integer division
*
* This is slighty faster than the signed version (DIV_ROUND_CLOSEST(n, d, t))
*
* @warning For performance reasons, this macro does not promote integers.
* So it will overflow if n>MAX(t)-(d/2).
*
* @param n The numerator (dividee) (an \em unsigned integer)
* @param d The denominator (divider) (an \em unsigned integer)
* @param t The type of the result. E.g. uint16_t
*/
#define UDIV_ROUND_CLOSEST(n, d, t) ((t)((n) + DIV_ROUND_CORRECT(d, t))/(t)(d))
/**
* @brief Rounded arithmetic right shift
*
* Right shifting throws away bits. When use for fixed point division, this
* effecitvely rounds down (towards zero). To round-to-the-nearest-integer
* when right-shifting by S, just add in 2 power S1 (which is the
* fixed-point equivalent of 0.5) first
*
* @param n The value to shift
* @param s The shift distance in bits
*/
#define RSHIFT_ROUND(n, s) (((n)+(1UL<<(s-1UL)))>>(s))
///@}
/** @brief Test whether the parameter is an integer or not. */
#define IS_INTEGER(d) ((d) == (int32_t)(d))
/**
* @defgroup group-div100 Optimised integer divison by 100
* @{
*/
static inline uint16_t div100(uint16_t n) {
// As of avr-gcc 5.4.0, the compiler will optimize this to a multiply/shift
// (unlike the signed integer overload, where __divmodhi4 is still called
// see https://godbolt.org/z/c5bs5noT1)
return UDIV_ROUND_CLOSEST(n, UINT16_C(100), uint16_t);
}
static inline int16_t div100(int16_t n) {
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_u32_do(n, &libdiv_u32_360);
// Try faster unsigned path first
if (n>0) {
return div100((uint16_t)n);
}
// Negative values here, so adjust pre-division to get same
// behavior as roundf(float)
return libdivide::libdivide_s16_do_raw(n - DIV_ROUND_CORRECT(UINT16_C(100), uint16_t), S16_MAGIC(100), S16_MORE(100));
#else
return n / 360U;
return DIV_ROUND_CLOSEST(n, UINT16_C(100), int16_t);
#endif
}
#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))
#define IS_INTEGER(d) (d == (int32_t)d)
static inline uint32_t div100(uint32_t n) {
#ifdef USE_LIBDIVIDE
if (n<=(uint32_t)UINT16_MAX) {
return div100((uint16_t)n);
}
return libdivide::libdivide_u32_do_raw(n + DIV_ROUND_CORRECT(UINT32_C(100), uint32_t), 2748779070L, 6);
#else
return UDIV_ROUND_CLOSEST(n, UINT32_C(100), uint32_t);
#endif
}
#if defined(__arm__)
static inline int div100(int n) {
return DIV_ROUND_CLOSEST(n, 100U, int);
}
#else
static inline int32_t div100(int32_t n) {
#ifdef USE_LIBDIVIDE
if (n<=INT16_MAX && n>=INT16_MIN) {
return div100((int16_t)n);
}
return libdivide::libdivide_s32_do_raw(n + (DIV_ROUND_CORRECT(UINT16_C(100), uint32_t) * (n<0 ? -1 : 1)), 1374389535L, 5);
#else
return DIV_ROUND_CLOSEST(n, UINT32_C(100), int32_t);
#endif
}
#endif
///@}
/**
* @brief Optimised integer division by 360
*
* @param n The numerator (dividee) (an integer)
* @return uint32_t
*/
static inline uint32_t div360(uint32_t n) {
#ifdef USE_LIBDIVIDE
return libdivide::libdivide_u32_do_raw(n + DIV_ROUND_CORRECT(UINT32_C(360), uint32_t), 1813430637L, 72);
#else
return (uint32_t)UDIV_ROUND_CLOSEST(n, UINT32_C(360), uint32_t);
#endif
}
/**
* @brief Integer based percentage calculation.
*
* @param percent The percent to calculate ([0, 100])
* @param value The value to operate on
* @return uint32_t
*/
static inline uint16_t percentage(uint8_t percent, uint16_t value) {
return (uint16_t)div100((uint32_t)value * (uint32_t)percent);
}
/**
* @brief Integer based half-percentage calculation.
*
* @param percent The percent to calculate ([0, 100])
* @param value The value to operate on
* @return uint16_t
*/
static inline uint16_t halfPercentage(uint8_t percent, uint16_t value) {
uint32_t x200 = (uint32_t)percent * (uint32_t)value;
#ifdef USE_LIBDIVIDE
return (uint16_t)libdivide::libdivide_u32_do_raw(x200 + DIV_ROUND_CORRECT(UINT32_C(200), uint32_t), 2748779070L, 7);
#else
return (uint16_t)UDIV_ROUND_CLOSEST(x200, UINT16_C(200), uint32_t);
#endif
}
/**
* @brief Make one pass at correcting the value into the range [min, max)
*
* @param min Minimum value (inclusive)
* @param max Maximum value (exclusive)
* @param value Value to nudge
* @param nudgeAmount Amount to change value by
* @return int16_t
*/
static inline int16_t nudge(int16_t min, int16_t max, int16_t value, int16_t nudgeAmount)
{
if (value<min) { return value + nudgeAmount; }
if (value>max) { return value - nudgeAmount; }
return value;
}
//This is a dedicated function that specifically handles the case of mapping 0-1023 values into a 0 to X range
//This is a common case because it means converting from a standard 10-bit analog input to a byte or 10-bit analog into 0-511 (Eg the temperature readings)
#define fastMap1023toX(x, out_max) ( ((unsigned long)x * out_max) >> 10)
#define fastMap1023toX(x, out_max) ( ((uint32_t)(x) * (out_max)) >> 10)
//This is a new version that allows for out_min
#define fastMap10Bit(x, out_min, out_max) ( ( ((unsigned long)x * (out_max-out_min)) >> 10 ) + out_min)
#define fastMap10Bit(x, out_min, out_max) ( ( ((uint32_t)(x) * ((out_max)-(out_min))) >> 10 ) + (out_min))
#if defined(CORE_AVR) || defined(ARDUINO_ARCH_AVR)
static inline bool udiv_is16bit_result(uint32_t dividend, uint16_t divisor) {
return divisor>(uint16_t)(dividend>>16U);
}
#endif
/**
* @brief Optimised division: uint32_t/uint16_t => uint16_t
*
* Optimised division of unsigned 32-bit by unsigned 16-bit when it is known
* that the result fits into unsigned 16-bit.
*
* ~60% quicker than raw 32/32 => 32 division on ATMega
*
* @note Bad things will likely happen if the result doesn't fit into 16-bits.
* @note Copied from https://stackoverflow.com/a/66593564
*
* @param dividend The dividend (numerator)
* @param divisor The divisor (denominator)
* @return uint16_t
*/
static inline uint16_t udiv_32_16 (uint32_t dividend, uint16_t divisor)
{
#if defined(CORE_AVR) || defined(ARDUINO_ARCH_AVR)
if (divisor==0U || !udiv_is16bit_result(dividend, divisor)) { return UINT16_MAX; }
#define INDEX_REG "r16"
asm(
" ldi " INDEX_REG ", 16 ; bits = 16\n\t"
"0:\n\t"
" lsl %A0 ; shift\n\t"
" rol %B0 ; rem:quot\n\t"
" rol %C0 ; left\n\t"
" rol %D0 ; by 1\n\t"
" brcs 1f ; if carry out, rem > divisor\n\t"
" cp %C0, %A1 ; is rem less\n\t"
" cpc %D0, %B1 ; than divisor ?\n\t"
" brcs 2f ; yes, when carry out\n\t"
"1:\n\t"
" sub %C0, %A1 ; compute\n\t"
" sbc %D0, %B1 ; rem -= divisor\n\t"
" ori %A0, 1 ; record quotient bit as 1\n\t"
"2:\n\t"
" dec " INDEX_REG " ; bits--\n\t"
" brne 0b ; until bits == 0"
: "=d" (dividend)
: "d" (divisor) , "0" (dividend)
: INDEX_REG
);
// Lower word contains the quotient, upper word contains the remainder.
return dividend & 0xFFFFU;
#else
// The non-AVR platforms are all fast enough (or have built in hardware dividers)
// so just fall back to regular 32-bit division.
return dividend / divisor;
#endif
}
/**
* @brief Same as udiv_32_16(), except this will round to nearest integer
* instead of truncating.
*
* Minor performance drop compared to non-rounding version.
**/
static inline uint16_t udiv_32_16_closest(uint32_t dividend, uint16_t divisor)
{
#if defined(CORE_AVR) || defined(ARDUINO_ARCH_AVR)
dividend = dividend + (uint32_t)(DIV_ROUND_CORRECT(divisor, uint16_t));
return udiv_32_16(dividend, divisor);
#else
return (uint16_t)UDIV_ROUND_CLOSEST(dividend, (uint32_t)divisor, uint32_t);
#endif
}
#endif

View File

@ -57,15 +57,15 @@ extern int channel7InjDegrees; /**< The number of crank degrees until cylinder 7
extern int channel8InjDegrees; /**< The number of crank degrees until cylinder 8 is at TDC */
#endif
inline uint16_t __attribute__((always_inline)) calculateInjectorStartAngle(uint16_t PWdivTimerPerDegree, int16_t injChannelDegrees, uint16_t injAngle);
static inline uint16_t __attribute__((always_inline)) calculateInjectorStartAngle(uint16_t PWdivTimerPerDegree, int16_t injChannelDegrees, uint16_t injAngle);
inline uint32_t __attribute__((always_inline)) calculateInjectorTimeout(const FuelSchedule &schedule, int channelInjDegrees, int injectorStartAngle, int crankAngle);
static inline uint32_t __attribute__((always_inline)) calculateInjectorTimeout(const FuelSchedule &schedule, int channelInjDegrees, int injectorStartAngle, int crankAngle);
inline void __attribute__((always_inline)) calculateIgnitionAngle(const int dwellAngle, const uint16_t channelIgnDegrees, int8_t advance, int *pEndAngle, int *pStartAngle);
static inline void __attribute__((always_inline)) calculateIgnitionAngle(const int dwellAngle, const uint16_t channelIgnDegrees, int8_t advance, int *pEndAngle, int *pStartAngle);
// Ignition for rotary.
inline void __attribute__((always_inline)) calculateIgnitionTrailingRotary(int dwellAngle, int rotarySplitDegrees, int leadIgnitionAngle, int *pEndAngle, int *pStartAngle);
static inline void __attribute__((always_inline)) calculateIgnitionTrailingRotary(int dwellAngle, int rotarySplitDegrees, int leadIgnitionAngle, int *pEndAngle, int *pStartAngle);
inline uint32_t __attribute__((always_inline)) calculateIgnitionTimeout(const IgnitionSchedule &schedule, int startAngle, int channelIgnDegrees, int crankAngle);
static inline uint32_t __attribute__((always_inline)) calculateIgnitionTimeout(const IgnitionSchedule &schedule, int startAngle, int channelIgnDegrees, int crankAngle);
#include "schedule_calcs.hpp"

View File

@ -4,7 +4,7 @@
#include "scheduler.h"
#include "crankMaths.h"
inline uint16_t calculateInjectorStartAngle(uint16_t pwDegrees, int16_t injChannelDegrees, uint16_t injAngle)
static inline uint16_t calculateInjectorStartAngle(uint16_t pwDegrees, int16_t injChannelDegrees, uint16_t injAngle)
{
// 0<=injAngle<=720°
// 0<=injChannelDegrees<=720°
@ -12,18 +12,18 @@ inline uint16_t calculateInjectorStartAngle(uint16_t pwDegrees, int16_t injChann
// 45<=CRANK_ANGLE_MAX_INJ<=720
// (CRANK_ANGLE_MAX_INJ can be as small as 360/nCylinders. E.g. 45° for 8 cylinder)
uint16_t startAngle = injAngle + injChannelDegrees;
uint16_t startAngle = (uint16_t)injAngle + (uint16_t)injChannelDegrees;
// Avoid underflow
while (startAngle<pwDegrees) { startAngle = startAngle + CRANK_ANGLE_MAX_INJ; }
while (startAngle<pwDegrees) { startAngle = startAngle + (uint16_t)CRANK_ANGLE_MAX_INJ; }
// Guarenteed to be >=0.
startAngle = startAngle - pwDegrees;
// Clamp to 0<=startAngle<=CRANK_ANGLE_MAX_INJ
while (startAngle>(uint16_t)CRANK_ANGLE_MAX_INJ) { startAngle = startAngle - CRANK_ANGLE_MAX_INJ; }
while (startAngle>(uint16_t)CRANK_ANGLE_MAX_INJ) { startAngle = startAngle - (uint16_t)CRANK_ANGLE_MAX_INJ; }
return startAngle;
}
inline uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, uint16_t openAngle, uint16_t crankAngle) {
static inline uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, uint16_t openAngle, uint16_t crankAngle) {
int16_t delta = openAngle - crankAngle;
if (delta<0)
{
@ -38,7 +38,7 @@ inline uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, uint16_t
}
}
return ((uint32_t)(delta) * (uint32_t)timePerDegree);
return angleToTimeMicroSecPerDegree((uint16_t)delta);
}
static inline int _adjustToInjChannel(int angle, int channelInjDegrees) {
@ -47,7 +47,7 @@ static inline int _adjustToInjChannel(int angle, int channelInjDegrees) {
return angle;
}
inline uint32_t calculateInjectorTimeout(const FuelSchedule &schedule, int channelInjDegrees, int openAngle, int crankAngle)
static inline uint32_t calculateInjectorTimeout(const FuelSchedule &schedule, int channelInjDegrees, int openAngle, int crankAngle)
{
if (channelInjDegrees==0) {
return _calculateInjectorTimeout(schedule, openAngle, crankAngle);
@ -55,15 +55,15 @@ inline uint32_t calculateInjectorTimeout(const FuelSchedule &schedule, int chann
return _calculateInjectorTimeout(schedule, _adjustToInjChannel(openAngle, channelInjDegrees), _adjustToInjChannel(crankAngle, channelInjDegrees));
}
inline void calculateIgnitionAngle(const int dwellAngle, const uint16_t channelIgnDegrees, int8_t advance, int *pEndAngle, int *pStartAngle)
static inline void calculateIgnitionAngle(const int dwellAngle, const uint16_t channelIgnDegrees, int8_t advance, int *pEndAngle, int *pStartAngle)
{
*pEndAngle = (channelIgnDegrees==0 ? CRANK_ANGLE_MAX_IGN : channelIgnDegrees) - advance;
*pEndAngle = (int16_t)(channelIgnDegrees==0U ? (uint16_t)CRANK_ANGLE_MAX_IGN : channelIgnDegrees) - (int16_t)advance;
if(*pEndAngle > CRANK_ANGLE_MAX_IGN) {*pEndAngle -= CRANK_ANGLE_MAX_IGN;}
*pStartAngle = *pEndAngle - dwellAngle;
if(*pStartAngle < 0) {*pStartAngle += CRANK_ANGLE_MAX_IGN;}
}
inline void calculateIgnitionTrailingRotary(int dwellAngle, int rotarySplitDegrees, int leadIgnitionAngle, int *pEndAngle, int *pStartAngle)
static inline void calculateIgnitionTrailingRotary(int dwellAngle, int rotarySplitDegrees, int leadIgnitionAngle, int *pEndAngle, int *pStartAngle)
{
*pEndAngle = leadIgnitionAngle + rotarySplitDegrees;
*pStartAngle = *pEndAngle - dwellAngle;
@ -71,7 +71,7 @@ inline void calculateIgnitionTrailingRotary(int dwellAngle, int rotarySplitDegre
if(*pStartAngle < 0) {*pStartAngle += CRANK_ANGLE_MAX_IGN;}
}
inline uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedule, int16_t startAngle, int16_t crankAngle) {
static inline uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedule, int16_t startAngle, int16_t crankAngle) {
int16_t delta = startAngle - crankAngle;
if (delta<0)
{
@ -85,7 +85,7 @@ inline uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedule, int1
return 0;
}
}
return angleToTimeIntervalRev(delta);
return angleToTimeMicroSecPerDegree(delta);
}
static inline uint16_t _adjustToIgnChannel(int angle, int channelInjDegrees) {
@ -94,7 +94,7 @@ static inline uint16_t _adjustToIgnChannel(int angle, int channelInjDegrees) {
return angle;
}
inline uint32_t calculateIgnitionTimeout(const IgnitionSchedule &schedule, int startAngle, int channelIgnDegrees, int crankAngle)
static inline uint32_t calculateIgnitionTimeout(const IgnitionSchedule &schedule, int startAngle, int channelIgnDegrees, int crankAngle)
{
if (channelIgnDegrees==0) {
return _calculateIgnitionTimeout(schedule, startAngle, crankAngle);

View File

@ -181,7 +181,7 @@ byte getVE2(void)
else if (configPage10.fuel2Algorithm == LOAD_SOURCE_IMAPEMAP)
{
//IMAP / EMAP
currentStatus.fuelLoad2 = (currentStatus.MAP * 100) / currentStatus.EMAP;
currentStatus.fuelLoad2 = ((int16_t)currentStatus.MAP * 100U) / currentStatus.EMAP;
}
else { currentStatus.fuelLoad2 = currentStatus.MAP; } //Fallback position
tempVE = get3DTableValue(&fuelTable2, currentStatus.fuelLoad2, currentStatus.RPM); //Perform lookup into fuel map for RPM vs MAP value

View File

@ -288,14 +288,14 @@ void readMAP(void)
MAPlast_time = MAP_time;
MAP_time = micros();
currentStatus.mapADC = ldiv(MAPrunningValue, MAPcount).quot;
currentStatus.mapADC = udiv_32_16(MAPrunningValue, MAPcount);
currentStatus.MAP = fastMap10Bit(currentStatus.mapADC, configPage2.mapMin, configPage2.mapMax); //Get the current MAP value
validateMAP();
//If EMAP is enabled, the process is identical to the above
if(configPage6.useEMAP == true)
{
currentStatus.EMAPADC = ldiv(EMAPrunningValue, MAPcount).quot; //Note that the MAP count can be reused here as it will always be the same count.
currentStatus.EMAPADC = udiv_32_16(EMAPrunningValue, MAPcount); //Note that the MAP count can be reused here as it will always be the same count.
currentStatus.EMAP = fastMap10Bit(currentStatus.EMAPADC, configPage2.EMAPMin, configPage2.EMAPMax);
if(currentStatus.EMAP < 0) { currentStatus.EMAP = 0; } //Sanity check
}
@ -396,7 +396,7 @@ void readMAP(void)
MAPlast_time = MAP_time;
MAP_time = micros();
currentStatus.mapADC = ldiv(MAPrunningValue, MAPcount).quot;
currentStatus.mapADC = udiv_32_16(MAPrunningValue, MAPcount);
currentStatus.MAP = fastMap10Bit(currentStatus.mapADC, configPage2.mapMin, configPage2.mapMax); //Get the current MAP value
validateMAP();
}
@ -532,7 +532,7 @@ void readBaro(void)
//Verify the engine isn't running by confirming RPM is 0 and it has been at least 1 second since the last tooth was detected
unsigned long timeToLastTooth = (micros() - toothLastToothTime);
if((currentStatus.RPM == 0) && (timeToLastTooth > 1000000UL))
if((currentStatus.RPM == 0) && (timeToLastTooth > MICROS_PER_SEC))
{
instanteneousMAPReading(); //Get the current MAP value
/*
@ -680,10 +680,10 @@ uint16_t getSpeed(void)
}
pulseTime = vssTotalTime / (VSS_SAMPLES - 1);
if ( (micros() - vssTimes[vssIndex]) > 1000000UL ) { tempSpeed = 0; } // Check that the car hasn't come to a stop. Is true if last pulse was more than 1 second ago
if ( (micros() - vssTimes[vssIndex]) > MICROS_PER_SEC ) { tempSpeed = 0; } // Check that the car hasn't come to a stop. Is true if last pulse was more than 1 second ago
else
{
tempSpeed = 3600000000UL / (pulseTime * configPage2.vssPulsesPerKm); //Convert the pulse gap into km/h
tempSpeed = MICROS_PER_HOUR / (pulseTime * configPage2.vssPulsesPerKm); //Convert the pulse gap into km/h
tempSpeed = ADC_FILTER(tempSpeed, configPage2.vssSmoothing, currentStatus.vss); //Apply speed smoothing factor
}
if(tempSpeed > 1000) { tempSpeed = currentStatus.vss; } //Safety check. This usually occurs when there is a hardware issue
@ -700,7 +700,7 @@ byte getGear(void)
//If the speed is non-zero, default to the last calculated gear
tempGear = currentStatus.gear;
uint16_t pulsesPer1000rpm = (currentStatus.vss * 10000UL) / currentStatus.RPM; //Gives the current pulses per 1000RPM, multiplied by 10 (10x is the multiplication factor for the ratios in TS)
uint16_t pulsesPer1000rpm = udiv_32_16(currentStatus.vss * 10000UL, currentStatus.RPM); //Gives the current pulses per 1000RPM, multiplied by 10 (10x is the multiplication factor for the ratios in TS)
//Begin gear detection
if( (pulsesPer1000rpm > (configPage2.vssRatio1 - VSS_GEAR_HYSTERESIS)) && (pulsesPer1000rpm < (configPage2.vssRatio1 + VSS_GEAR_HYSTERESIS)) ) { tempGear = 1; }
else if( (pulsesPer1000rpm > (configPage2.vssRatio2 - VSS_GEAR_HYSTERESIS)) && (pulsesPer1000rpm < (configPage2.vssRatio2 + VSS_GEAR_HYSTERESIS)) ) { tempGear = 2; }
@ -784,7 +784,7 @@ void knockPulse(void)
//Check if this the start of a knock.
if(knockCounter == 0)
{
//knockAngle = crankAngle + fastTimeToAngle( (micros() - lastCrankAngleCalc) );
//knockAngle = crankAngle + timeToAngleDegPerMicroSec( (micros() - lastCrankAngleCalc) );
knockStartTime = micros();
knockCounter = 1;
}

View File

@ -68,9 +68,8 @@ void setup(void)
inline uint16_t applyFuelTrimToPW(trimTable3d *pTrimTable, int16_t fuelLoad, int16_t RPM, uint16_t currentPW)
{
unsigned long pw1percent = 100 + get3DTableValue(pTrimTable, fuelLoad, RPM) - OFFSET_FUELTRIM;
if (pw1percent != 100) { return div100(uint32_t(pw1percent * currentPW)); }
return currentPW;
uint8_t pw1percent = 100U + get3DTableValue(pTrimTable, fuelLoad, RPM) - OFFSET_FUELTRIM;
return percentage(pw1percent, currentPW);
}
/** Speeduino main loop.
@ -138,7 +137,7 @@ void loop(void)
}
currentLoopTime = micros_safe();
unsigned long timeToLastTooth = (currentLoopTime - toothLastToothTime);
uint32_t timeToLastTooth = (currentLoopTime - toothLastToothTime);
if ( (timeToLastTooth < MAX_STALL_TIME) || (toothLastToothTime > currentLoopTime) ) //Check how long ago the last tooth was seen compared to now. If it was more than half a second ago then the engine is probably stopped. toothLastToothTime can be greater than currentLoopTime if a pulse occurs between getting the latest time and doing the comparison
{
currentStatus.longRPM = getRPM(); //Long RPM is included here
@ -460,13 +459,15 @@ void loop(void)
uint16_t injector8StartAngle = 0;
#endif
doCrankSpeedCalcs(); //In crankMaths.ino
//Check that the duty cycle of the chosen pulsewidth isn't too high.
uint32_t pwLimit = percentage(configPage2.dutyLim, revolutionTime); //The pulsewidth limit is determined to be the duty cycle limit (Eg 85%) by the total time it takes to perform 1 revolution
//Handle multiple squirts per rev
if (configPage2.strokes == FOUR_STROKE) { pwLimit = pwLimit * 2 / currentStatus.nSquirts; }
else { pwLimit = pwLimit / currentStatus.nSquirts; }
if (configPage2.strokes == FOUR_STROKE) { pwLimit = pwLimit * 2; }
// This requires 32-bit division, which is very slow on Mega 2560.
// So only divide if necessary - nSquirts is often only 1.
if (currentStatus.nSquirts!=1) {
pwLimit = pwLimit / currentStatus.nSquirts;
}
//Apply the pwLimit if staging is disabled and engine is not cranking
if( (!BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK)) && (configPage10.stagingEnabled == false) ) { if (currentStatus.PW1 > pwLimit) { currentStatus.PW1 = pwLimit; } }
@ -475,7 +476,7 @@ void loop(void)
//***********************************************************************************************
//BEGIN INJECTION TIMING
currentStatus.injAngle = table2D_getValue(&injectorAngleTable, currentStatus.RPMdiv100);
unsigned int PWdivTimerPerDegree = div(currentStatus.PW1, timePerDegree).quot; //How many crank degrees the calculated PW will take at the current speed
unsigned int PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW1); //How many crank degrees the calculated PW will take at the current speed
injector1StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
@ -487,7 +488,7 @@ void loop(void)
//The only thing that needs to be done for single cylinder is to check for staging.
if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW2, timePerDegree).quot; //Need to redo this for PW2 as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW2); //Need to redo this for PW2 as it will be dramatically different to PW1 when staging
//injector3StartAngle = calculateInjector3StartAngle(PWdivTimerPerDegree);
injector2StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
}
@ -504,7 +505,7 @@ void loop(void)
}
else if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW3, timePerDegree).quot; //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW3); //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
injector3StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
injector4StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
@ -528,7 +529,7 @@ void loop(void)
#if INJ_CHANNELS >= 6
if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW4, timePerDegree).quot; //Need to redo this for PW4 as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW4); //Need to redo this for PW4 as it will be dramatically different to PW1 when staging
injector4StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
injector5StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
injector6StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel3InjDegrees, currentStatus.injAngle);
@ -537,7 +538,7 @@ void loop(void)
}
else if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW4, timePerDegree).quot; //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW4); //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
injector4StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
#if INJ_CHANNELS >= 6
injector5StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
@ -559,7 +560,7 @@ void loop(void)
#if INJ_CHANNELS >= 8
if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW5, timePerDegree).quot; //Need to redo this for PW5 as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW5); //Need to redo this for PW5 as it will be dramatically different to PW1 when staging
injector5StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
injector6StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
injector7StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel3InjDegrees, currentStatus.injAngle);
@ -577,7 +578,7 @@ void loop(void)
}
else if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW3, timePerDegree).quot; //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW3); //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
injector3StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
injector4StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
}
@ -599,7 +600,7 @@ void loop(void)
#if INJ_CHANNELS >= 6
if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW6, timePerDegree).quot;
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW6);
injector6StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel6InjDegrees, currentStatus.injAngle);
}
#endif
@ -633,7 +634,7 @@ void loop(void)
#if INJ_CHANNELS >= 8
if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW4, timePerDegree).quot; //Need to redo this for staging PW as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW4); //Need to redo this for staging PW as it will be dramatically different to PW1 when staging
injector4StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
injector5StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
injector6StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel3InjDegrees, currentStatus.injAngle);
@ -646,7 +647,7 @@ void loop(void)
if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW4, timePerDegree).quot; //Need to redo this for staging PW as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW4); //Need to redo this for staging PW as it will be dramatically different to PW1 when staging
injector4StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
injector5StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
injector6StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel3InjDegrees, currentStatus.injAngle);
@ -688,7 +689,7 @@ void loop(void)
if( (configPage10.stagingEnabled == true) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE) == true) )
{
PWdivTimerPerDegree = div(currentStatus.PW5, timePerDegree).quot; //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
PWdivTimerPerDegree = timeToAngleDegPerMicroSec(currentStatus.PW5); //Need to redo this for PW3 as it will be dramatically different to PW1 when staging
injector5StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel1InjDegrees, currentStatus.injAngle);
injector6StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel2InjDegrees, currentStatus.injAngle);
injector7StartAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, channel3InjDegrees, currentStatus.injAngle);
@ -725,7 +726,7 @@ void loop(void)
}
currentStatus.dwell = correctionsDwell(currentStatus.dwell);
int dwellAngle = timeToAngle(currentStatus.dwell, CRANKMATH_METHOD_INTERVAL_REV); //Convert the dwell time to dwell angle based on the current engine speed
int dwellAngle = timeToAngleDegPerMicroSec(currentStatus.dwell); //Convert the dwell time to dwell angle based on the current engine speed
calculateIgnitionAngles(dwellAngle);
@ -1061,8 +1062,8 @@ void loop(void)
//ONLY ONE OF THE BELOW SHOULD BE USED (PROBABLY THE FIRST):
//*********
if(ignition1EndAngle > crankAngle) { uSToEnd = fastDegreesToUS( (ignition1EndAngle - crankAngle) ); }
else { uSToEnd = fastDegreesToUS( (360 + ignition1EndAngle - crankAngle) ); }
if(ignition1EndAngle > crankAngle) { uSToEnd = angleToTimeMicroSecPerDegree( (ignition1EndAngle - crankAngle) ); }
else { uSToEnd = angleToTimeMicroSecPerDegree( (360 + ignition1EndAngle - crankAngle) ); }
//*********
//uSToEnd = ((ignition1EndAngle - crankAngle) * (toothLastToothTime - toothLastMinusOneToothTime)) / triggerToothAngle;
//*********
@ -1222,18 +1223,18 @@ uint16_t PW(int REQ_FUEL, byte VE, long MAP, uint16_t corrections, int injOpen)
//iCorrections = divu100((corrections << bitShift));
unsigned long intermediate = ((uint32_t)REQ_FUEL * (uint32_t)iVE) >> 7; //Need to use an intermediate value to avoid overflowing the long
if ( configPage2.multiplyMAP > 0 ) { intermediate = (intermediate * (unsigned long)iMAP) >> 7; }
uint32_t intermediate = ((uint32_t)REQ_FUEL * (uint32_t)iVE) >> 7; //Need to use an intermediate value to avoid overflowing the long
if ( configPage2.multiplyMAP > 0 ) { intermediate = (intermediate * (uint32_t)iMAP) >> 7; }
if ( (configPage2.includeAFR == true) && (configPage6.egoType == EGO_TYPE_WIDE) && (currentStatus.runSecs > configPage6.ego_sdelay) ) {
//EGO type must be set to wideband and the AFR warmup time must've elapsed for this to be used
intermediate = (intermediate * (unsigned long)iAFR) >> 7;
intermediate = (intermediate * (uint32_t)iAFR) >> 7;
}
if ( (configPage2.incorporateAFR == true) && (configPage2.includeAFR == false) ) {
intermediate = (intermediate * (unsigned long)iAFR) >> 7;
intermediate = (intermediate * (uint32_t)iAFR) >> 7;
}
intermediate = (intermediate * (unsigned long)iCorrections) >> bitShift;
intermediate = (intermediate * (uint32_t)iCorrections) >> bitShift;
if (intermediate != 0)
{
//If intermediate is not 0, we need to add the opening time (0 typically indicates that one of the full fuel cuts is active)
@ -1244,7 +1245,7 @@ uint16_t PW(int REQ_FUEL, byte VE, long MAP, uint16_t corrections, int injOpen)
//AE Adds % of req_fuel
if ( configPage2.aeApplyMode == AE_MODE_ADDER )
{
intermediate += ( ((unsigned long)REQ_FUEL) * (currentStatus.AEamount - 100) ) / 100;
intermediate += div100(((uint32_t)REQ_FUEL) * (currentStatus.AEamount - 100U));
}
}
@ -1277,7 +1278,7 @@ byte getVE1(void)
else if (configPage2.fuelAlgorithm == LOAD_SOURCE_IMAPEMAP)
{
//IMAP / EMAP
currentStatus.fuelLoad = (currentStatus.MAP * 100) / currentStatus.EMAP;
currentStatus.fuelLoad = ((int16_t)currentStatus.MAP * 100U) / currentStatus.EMAP;
}
else { currentStatus.fuelLoad = currentStatus.MAP; } //Fallback position
tempVE = get3DTableValue(&fuelTable, currentStatus.fuelLoad, currentStatus.RPM); //Perform lookup into fuel map for RPM vs MAP value
@ -1307,7 +1308,7 @@ byte getAdvance1(void)
else if (configPage2.fuelAlgorithm == LOAD_SOURCE_IMAPEMAP)
{
//IMAP / EMAP
currentStatus.ignLoad = (currentStatus.MAP * 100) / currentStatus.EMAP;
currentStatus.ignLoad = ((int16_t)currentStatus.MAP * 100U) / currentStatus.EMAP;
}
tempAdvance = get3DTableValue(&ignitionTable, currentStatus.ignLoad, currentStatus.RPM) - OFFSET_IGNITION; //As above, but for ignition advance
tempAdvance = correctionsIgn(tempAdvance);
@ -1438,21 +1439,21 @@ void calculateStaging(uint32_t pwLimit)
{
//Scale the 'full' pulsewidth by each of the injector capacities
currentStatus.PW1 -= inj_opentime_uS; //Subtract the opening time from PW1 as it needs to be multiplied out again by the pri/sec req_fuel values below. It is added on again after that calculation.
uint32_t tempPW1 = (((unsigned long)currentStatus.PW1 * staged_req_fuel_mult_pri) / 100);
uint32_t tempPW1 = div100((uint32_t)currentStatus.PW1 * staged_req_fuel_mult_pri);
if(configPage10.stagingMode == STAGING_MODE_TABLE)
{
uint32_t tempPW3 = (((unsigned long)currentStatus.PW1 * staged_req_fuel_mult_sec) / 100); //This is ONLY needed in in table mode. Auto mode only calculates the difference.
uint32_t tempPW3 = div100((uint32_t)currentStatus.PW1 * staged_req_fuel_mult_sec); //This is ONLY needed in in table mode. Auto mode only calculates the difference.
byte stagingSplit = get3DTableValue(&stagingTable, currentStatus.fuelLoad, currentStatus.RPM);
currentStatus.PW1 = ((100 - stagingSplit) * tempPW1) / 100;
uint8_t stagingSplit = get3DTableValue(&stagingTable, currentStatus.fuelLoad, currentStatus.RPM);
currentStatus.PW1 = div100((100U - stagingSplit) * tempPW1);
currentStatus.PW1 += inj_opentime_uS;
//PW2 is used temporarily to hold the secondary injector pulsewidth. It will be assigned to the correct channel below
if(stagingSplit > 0)
{
BIT_SET(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE); //Set the staging active flag
currentStatus.PW2 = (stagingSplit * tempPW3) / 100;
currentStatus.PW2 = div100(stagingSplit * tempPW3);
currentStatus.PW2 += inj_opentime_uS;
}
else
@ -1471,7 +1472,7 @@ void calculateStaging(uint32_t pwLimit)
BIT_SET(currentStatus.status4, BIT_STATUS4_STAGING_ACTIVE); //Set the staging active flag
uint32_t extraPW = tempPW1 - pwLimit + inj_opentime_uS; //The open time must be added here AND below because tempPW1 does not include an open time. The addition of it here takes into account the fact that pwLlimit does not contain an allowance for an open time.
currentStatus.PW1 = pwLimit;
currentStatus.PW2 = ((extraPW * staged_req_fuel_mult_sec) / staged_req_fuel_mult_pri); //Convert the 'left over' fuel amount from primary injector scaling to secondary
currentStatus.PW2 = udiv_32_16(extraPW * staged_req_fuel_mult_sec, staged_req_fuel_mult_pri); //Convert the 'left over' fuel amount from primary injector scaling to secondary
currentStatus.PW2 += inj_opentime_uS;
}
else

View File

@ -243,8 +243,12 @@ static LIBDIVIDE_INLINE uint16_t libdivide_u16_do_raw(
uint16_t numer, uint16_t magic, uint8_t more);
static LIBDIVIDE_INLINE uint16_t libdivide_u16_do(
uint16_t numer, const struct libdivide_u16_t* denom);
static LIBDIVIDE_INLINE uint32_t libdivide_s32_do_raw(
int32_t numer, int32_t magic, uint8_t more);
static LIBDIVIDE_INLINE int32_t libdivide_s32_do(
int32_t numer, const struct libdivide_s32_t *denom);
static LIBDIVIDE_INLINE uint32_t libdivide_u32_do_raw(
uint32_t numer, uint32_t magic, uint8_t more);
static LIBDIVIDE_INLINE uint32_t libdivide_u32_do(
uint32_t numer, const struct libdivide_u32_t *denom);
static LIBDIVIDE_INLINE int64_t libdivide_s64_do(
@ -912,12 +916,11 @@ struct libdivide_u32_branchfree_t libdivide_u32_branchfree_gen(uint32_t d) {
return ret;
}
uint32_t libdivide_u32_do(uint32_t numer, const struct libdivide_u32_t *denom) {
uint8_t more = denom->more;
if (!denom->magic) {
uint32_t libdivide_u32_do_raw(uint32_t numer, uint32_t magic, uint8_t more) {
if (!magic) {
return numer >> more;
} else {
uint32_t q = libdivide_mullhi_u32(denom->magic, numer);
uint32_t q = libdivide_mullhi_u32(magic, numer);
if (more & LIBDIVIDE_ADD_MARKER) {
uint32_t t = ((numer - q) >> 1) + q;
return t >> (more & LIBDIVIDE_32_SHIFT_MASK);
@ -929,6 +932,10 @@ uint32_t libdivide_u32_do(uint32_t numer, const struct libdivide_u32_t *denom) {
}
}
uint32_t libdivide_u32_do(uint32_t numer, const struct libdivide_u32_t *denom) {
return libdivide_u32_do_raw(numer, denom->magic, denom->more);
}
uint32_t libdivide_u32_branchfree_do(
uint32_t numer, const struct libdivide_u32_branchfree_t *denom) {
uint32_t q = libdivide_mullhi_u32(denom->magic, numer);
@ -1428,11 +1435,10 @@ struct libdivide_s32_branchfree_t libdivide_s32_branchfree_gen(int32_t d) {
return result;
}
int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) {
uint8_t more = denom->more;
uint32_t libdivide_s32_do_raw(int32_t numer, int32_t magic, uint8_t more) {
uint8_t shift = more & LIBDIVIDE_32_SHIFT_MASK;
if (!denom->magic) {
if (!magic) {
uint32_t sign = (int8_t)more >> 7;
uint32_t mask = ((uint32_t)1 << shift) - 1;
uint32_t uq = numer + ((numer >> 31) & mask);
@ -1441,7 +1447,7 @@ int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) {
q = (q ^ sign) - sign;
return q;
} else {
uint32_t uq = (uint32_t)libdivide_mullhi_s32(denom->magic, numer);
uint32_t uq = (uint32_t)libdivide_mullhi_s32(magic, numer);
if (more & LIBDIVIDE_ADD_MARKER) {
// must be arithmetic shift and then sign extend
int32_t sign = (int8_t)more >> 7;
@ -1456,6 +1462,10 @@ int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) {
}
}
int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) {
return libdivide_s32_do_raw(numer, denom->magic, denom->more);
}
int32_t libdivide_s32_branchfree_do(int32_t numer, const struct libdivide_s32_branchfree_t *denom) {
uint8_t more = denom->more;
uint8_t shift = more & LIBDIVIDE_32_SHIFT_MASK;

View File

@ -196,6 +196,6 @@ extern uint32_t deferEEPROMWritesUntil;
#define EEPROM_CALIBRATION_IAT_OLD 3071
#define EEPROM_CALIBRATION_CLT_OLD 3583
#define EEPROM_DEFER_DELAY 1000000UL //1.0 second pause after large comms before writing to EEPROM
#define EEPROM_DEFER_DELAY MICROS_PER_SEC //1.0 second pause after large comms before writing to EEPROM
#endif // STORAGE_H

View File

@ -1,4 +1,5 @@
#include "table3d_interpolate.h"
#include "maths.h"
// ============================= Axis Bin Searching =========================
@ -140,7 +141,7 @@ static inline QU1X8_t compute_bin_position(table3d_axis_t value, const table3d_d
// But since we are computing the ratio (0 to 1), p is guaranteed to be
// less than binWidth and thus the division below will result in a value
// <=1. So we can reduce the data type from 24.8 (uint32_t) to 1.8 (uint16_t)
return p / (uint32_t)binWidth;
return udiv_32_16(p, (uint16_t)binWidth);
}

View File

@ -1,8 +1,9 @@
#include <Arduino.h>
#include <unity.h>
#include "tests_crankmaths.h"
#include "tests_maths.h"
extern void testPercent(void);
extern void testDivision(void);
#define UNITY_EXCLUDE_DETAILS
@ -17,7 +18,8 @@ void setup()
UNITY_BEGIN(); // IMPORTANT LINE!
testCrankMaths();
testMaths();
testPercent();
testDivision();
UNITY_END(); // stop unit testing
}

View File

@ -0,0 +1,222 @@
#include <unity.h>
#include "test_fp_support.h"
#include "maths.h"
#include "crankMaths.h"
#include "..\timer.hpp"
template <typename T>
static void test_div100(T testValue) {
assert_rounded_div(testValue, 100, div100(testValue));
}
template <typename T>
static void test_div100_Seed(T seedValue) {
test_div100<T>((T)seedValue-(T)100);
test_div100<T>((T)seedValue-(T)51);
test_div100<T>((T)seedValue-(T)50);
test_div100<T>((T)seedValue);
test_div100<T>((T)seedValue+(T)50);
test_div100<T>((T)seedValue+(T)51);
test_div100<T>((T)seedValue+(T)100);
}
void test_maths_div100_U16(void)
{
test_div100_Seed<uint16_t>(100U);
test_div100_Seed<uint16_t>(10000U);
test_div100<uint16_t>(0);
test_div100<uint16_t>(99);
test_div100<uint16_t>(UINT16_MAX-100);
// We expect this to fail - the rounding doesn't do integer promotion
TEST_ASSERT_EQUAL_UINT16(0, div100((uint16_t)UINT16_MAX));
}
void test_maths_div100_U32(void)
{
test_div100<uint32_t>(0);
test_div100_Seed<uint32_t>(100U);
test_div100_Seed<uint32_t>(10000U);
test_div100_Seed<uint32_t>(100000000UL);
}
void test_maths_div100_S16(void)
{
//Check both the signed and unsigned results
test_div100_Seed<uint16_t>(100U);
test_div100_Seed<uint16_t>(10000U);
test_div100<uint16_t>(0);
test_div100<uint16_t>(UINT16_MAX-100);
test_div100_Seed<int16_t>(-100);
test_div100_Seed<int16_t>(-10000);
test_div100<int16_t>(INT16_MIN+100);
// We expect this to fail - the rounding doesn't do integer promotion
TEST_ASSERT_EQUAL_UINT16(327U, div100((int16_t)INT16_MIN));
}
void test_maths_div100_S32(void)
{
//Check both the signed and unsigned results
#if defined(__arm__)
test_div100_Seed<int>(100U);
test_div100_Seed<int>(10000U);
test_div100_Seed<int>(100000000UL);
test_div100_Seed<int>(-100);
test_div100_Seed<int>(-10000);
test_div100_Seed<int>(-100000000);
#else
test_div100_Seed<int32_t>(100U);
test_div100_Seed<int32_t>(10000U);
test_div100_Seed<int32_t>(100000000UL);
test_div100_Seed<int32_t>(-100);
test_div100_Seed<int32_t>(-10000);
test_div100_Seed<int32_t>(-100000000);
#endif
}
static void test_div360(uint32_t testValue) {
assert_rounded_div(testValue, 360, div360(testValue));
}
void test_maths_div360(void)
{
test_div360(10000000UL-360UL);
test_div360((10000000UL-180UL)-1UL);
test_div360(10000000UL-180UL);
test_div360((10000000UL-180UL)+1UL);
test_div360(10000000UL);
test_div360((10000000UL+180UL)-1UL);
test_div360(10000000UL+180UL);
test_div360((10000000UL+180UL)+1UL);
test_div360(10000000UL+360UL);
test_div360((360UL*MICROS_PER_DEG_1_RPM)/MAX_RPM); // Min revolution time
test_div360((360UL*MICROS_PER_DEG_1_RPM)/MIN_RPM); // Max revolution time
}
void assert_udiv_32_16(uint32_t dividend, uint16_t divisor) {
TEST_ASSERT_EQUAL_UINT16(dividend/(uint32_t)divisor, udiv_32_16(dividend, divisor));
}
void test_maths_udiv_32_16(void)
{
// Divide by zero
TEST_ASSERT_EQUAL_UINT16(UINT16_MAX, udiv_32_16(0, 0));
// Result doesn't fit into 16-bits
TEST_ASSERT_EQUAL_UINT16(UINT16_MAX, udiv_32_16(UINT32_MAX, UINT16_MAX));
assert_udiv_32_16(1, 1);
assert_udiv_32_16(UINT16_MAX+1, UINT16_MAX);
assert_udiv_32_16(UINT16_MAX-1, UINT16_MAX);
assert_udiv_32_16(MICROS_PER_MIN, 60000); // 1000 RPM
assert_udiv_32_16(MICROS_PER_MIN, 54005); // 1111 RPM
assert_udiv_32_16(MICROS_PER_MIN, 7590); // 7905 RPM
assert_udiv_32_16(MICROS_PER_MIN, 7715); // 7777 RPM
assert_udiv_32_16(MICROS_PER_MIN, 3333); // 18000 RPM
}
void assert_udiv_32_16_closest(uint32_t dividend, uint16_t divisor) {
assert_rounded_div(dividend, divisor, udiv_32_16_closest(dividend, divisor));
}
void test_maths_udiv_32_16_closest(void)
{
// Divide by zero
TEST_ASSERT_EQUAL_UINT16(UINT16_MAX, udiv_32_16_closest(0, 0));
// Result doesn't fit into 16-bits
TEST_ASSERT_EQUAL_UINT16(UINT16_MAX, udiv_32_16_closest(UINT32_MAX-(UINT16_MAX/2)-1, UINT16_MAX));
assert_udiv_32_16(1, 1);
assert_udiv_32_16(2, 3);
assert_udiv_32_16(UINT16_MAX+1, UINT16_MAX);
assert_udiv_32_16(UINT16_MAX-1, UINT16_MAX);
assert_udiv_32_16(MICROS_PER_MIN, 60000); // 1000 RPM
assert_udiv_32_16(MICROS_PER_MIN, 54005); // 1111 RPM
assert_udiv_32_16(MICROS_PER_MIN, 7590); // 7905 RPM
assert_udiv_32_16(MICROS_PER_MIN, 7715); // 7777 RPM
assert_udiv_32_16(MICROS_PER_MIN, 3333); // 18000 RPM
}
static uint32_t indexToDividend(int16_t index) {
return (uint32_t)index + (UINT16_MAX*index);
}
void test_maths_udiv_32_16_perf(void)
{
uint16_t iters = 32;
uint16_t start_index = UINT16_MAX/3;
uint16_t end_index = UINT16_MAX/3*2;
uint16_t step = 111;
auto nativeTest = [] (uint16_t index, uint32_t &checkSum) { checkSum += (uint32_t)indexToDividend(index) / (uint32_t)index; };
auto optimizedTest = [] (uint16_t index, uint32_t &checkSum) { checkSum += udiv_32_16(indexToDividend(index), index); };
TEST_MESSAGE("udiv_32_16");
auto comparison = compare_executiontime<uint16_t, uint32_t>(iters, start_index, end_index, step, nativeTest, optimizedTest);
// The checksums will be different due to rounding. This is only
// here to force the compiler to run the loops above
TEST_ASSERT_INT32_WITHIN(UINT32_MAX/2, comparison.timeA.result, comparison.timeB.result);
TEST_ASSERT_LESS_THAN(comparison.timeA.durationMicros, comparison.timeB.durationMicros);
}
void test_maths_div100_s16_perf(void)
{
constexpr int16_t iters = 1;
constexpr int16_t start_index = -10000;
constexpr int16_t end_index = -1;
constexpr int16_t step = 11;
auto nativeTest = [] (int16_t index, int32_t &checkSum) { checkSum += (int16_t)index / (int16_t)100; };
auto optimizedTest = [] (int16_t index, int32_t &checkSum) { checkSum += div100(index); };
TEST_MESSAGE("div100_s16");
auto comparison = compare_executiontime<int16_t, int32_t>(iters, start_index, end_index, step, nativeTest, optimizedTest);
// The checksums will be different due to rounding. This is only
// here to force the compiler to run the loops above
TEST_ASSERT_INT32_WITHIN(UINT32_MAX/2, comparison.timeA.result, comparison.timeB.result);
TEST_ASSERT_LESS_THAN(comparison.timeA.durationMicros, comparison.timeB.durationMicros);
}
void test_maths_div100_s32_perf(void)
{
constexpr int32_t iters = 1;
constexpr int32_t start_index = -1439190;
constexpr int32_t end_index = -1;
constexpr int32_t step = 3715;
auto nativeTest = [] (int32_t index, int32_t &checkSum) { checkSum += (int32_t)index / (int32_t)100; };
auto optimizedTest = [] (int32_t index, int32_t &checkSum) { checkSum += div100(index); };
TEST_MESSAGE("div100_s32");
auto comparison = compare_executiontime<int32_t, int32_t>(iters, start_index, end_index, step, nativeTest, optimizedTest);
// The checksums will be different due to rounding. This is only
// here to force the compiler to run the loops above
TEST_ASSERT_INT32_WITHIN(UINT32_MAX/2, comparison.timeA.result, comparison.timeB.result);
TEST_ASSERT_LESS_THAN(comparison.timeA.durationMicros, comparison.timeB.durationMicros);
}
void testDivision(void) {
RUN_TEST(test_maths_div100_U16);
RUN_TEST(test_maths_div100_U32);
RUN_TEST(test_maths_div100_S16);
RUN_TEST(test_maths_div100_S32);
RUN_TEST(test_maths_udiv_32_16);
RUN_TEST(test_maths_udiv_32_16_closest);
RUN_TEST(test_maths_udiv_32_16_perf);
RUN_TEST(test_maths_div360);
RUN_TEST(test_maths_div100_s16_perf);
RUN_TEST(test_maths_div100_s32_perf);
}

View File

@ -0,0 +1,14 @@
#include "test_fp_support.h"
float64_t floatDivision(int32_t a, int32_t b) {
return fp64_div(fp64_int32_to_float64(a), fp64_int32_to_float64(b));
}
void assert_rounded_div(int32_t a, int32_t b, int32_t actual) {
float64_t fExpected = floatDivision(a, b);
int32_t expected = fp64_lround(fExpected);
char msg[64];
sprintf(msg, "a: %" PRIi32 ", b: %" PRIi32 " fExpected: %s", a, b, fp64_to_string(fExpected, 17, 15));
TEST_ASSERT_EQUAL_MESSAGE(expected, actual, msg);
}

View File

@ -0,0 +1,8 @@
#pragma once
#include <stdio.h>
#include <unity.h>
#include <fp64lib.h>
float64_t floatDivision(int32_t a, int32_t b);
void assert_rounded_div(int32_t a, int32_t b, int32_t actual);

View File

@ -1,8 +1,10 @@
#include "globals.h"
#include <unity.h>
// #include "globals.h"
#include "crankMaths.h"
#include "unity.h"
#include "decoders.h"
extern void SetRevolutionTime(uint32_t revTime);
struct crankmaths_rev_testdata {
uint16_t rpm;
unsigned long revolutionTime;
@ -10,6 +12,12 @@ struct crankmaths_rev_testdata {
unsigned long expected;
} *crankmaths_rev_testdata_current;
void test_crankmaths_angletotime_revolution_execute() {
crankmaths_rev_testdata *testdata = crankmaths_rev_testdata_current;
SetRevolutionTime(testdata->revolutionTime);
TEST_ASSERT_INT32_WITHIN(1, testdata->expected, angleToTimeMicroSecPerDegree(testdata->angle));
}
struct crankmaths_tooth_testdata {
uint16_t rpm;
uint16_t triggerToothAngle;
@ -18,17 +26,11 @@ struct crankmaths_tooth_testdata {
unsigned long expected;
} *crankmaths_tooth_testdata_current;
void test_crankmaths_angletotime_revolution_execute() {
crankmaths_rev_testdata *testdata = crankmaths_rev_testdata_current;
revolutionTime = testdata->revolutionTime;
TEST_ASSERT_EQUAL(testdata->expected, angleToTime(testdata->angle, CRANKMATH_METHOD_INTERVAL_REV));
}
void test_crankmaths_angletotime_tooth_execute() {
crankmaths_tooth_testdata *testdata = crankmaths_tooth_testdata_current;
triggerToothAngle = testdata->triggerToothAngle;
toothLastToothTime = toothLastMinusOneToothTime + testdata->toothTime;
TEST_ASSERT_EQUAL(testdata->expected, angleToTime(testdata->angle, CRANKMATH_METHOD_INTERVAL_TOOTH));
TEST_ASSERT_EQUAL(testdata->expected, angleToTimeIntervalTooth(testdata->angle));
}
void testCrankMaths()
@ -41,7 +43,7 @@ void testCrankMaths()
{ .rpm = 50, .revolutionTime = 1200000, .angle = 25, .expected = 83333 }, // 83333,3333
{ .rpm = 50, .revolutionTime = 1200000, .angle = 720, .expected = 2400000 },
{ .rpm = 2500, .revolutionTime = 24000, .angle = 0, .expected = 0 },
{ .rpm = 2500, .revolutionTime = 24000, .angle = 25, .expected = 1666 }, // 1666,6666
{ .rpm = 2500, .revolutionTime = 24000, .angle = 25, .expected = 1667 }, // 1666,6666
{ .rpm = 2500, .revolutionTime = 24000, .angle = 720, .expected = 48000 },
{ .rpm = 20000, .revolutionTime = 3000, .angle = 0, .expected = 0 },
{ .rpm = 20000, .revolutionTime = 3000, .angle = 25, .expected = 208 }, // 208,3333

View File

@ -1,151 +0,0 @@
//#include <Arduino.h>
#include <string.h> // memcpy
#include <unity.h>
#include <stdio.h>
#include "tests_maths.h"
#include "maths.h"
void testMaths()
{
RUN_TEST(test_maths_percent_U8);
RUN_TEST(test_maths_percent_U16);
RUN_TEST(test_maths_percent_U32);
RUN_TEST(test_maths_halfpercent_U8);
RUN_TEST(test_maths_halfpercent_U16);
RUN_TEST(test_maths_halfpercent_U32);
RUN_TEST(test_maths_div100_U8);
RUN_TEST(test_maths_div100_U16);
RUN_TEST(test_maths_div100_U32);
RUN_TEST(test_maths_div100_S8);
RUN_TEST(test_maths_div100_S16);
RUN_TEST(test_maths_div100_S32);
}
void test_maths_percent_U8(void)
{
uint8_t percentOf = 200;
TEST_ASSERT_EQUAL(100, percentage(50, percentOf));
TEST_ASSERT_EQUAL(150, percentage(75, percentOf));
TEST_ASSERT_EQUAL(0, percentage(0, percentOf));
TEST_ASSERT_EQUAL(200, percentage(100, percentOf));
TEST_ASSERT_EQUAL(250, percentage(125, percentOf));
}
void test_maths_percent_U16(void)
{
uint16_t percentOf = 20000;
TEST_ASSERT_EQUAL(10000, percentage(50, percentOf));
TEST_ASSERT_EQUAL(15000, percentage(75, percentOf));
TEST_ASSERT_EQUAL(0, percentage(0, percentOf));
TEST_ASSERT_EQUAL(20000, percentage(100, percentOf));
TEST_ASSERT_EQUAL(25000, percentage(125, percentOf));
}
void test_maths_percent_U32(void)
{
uint32_t percentOf = 20000000UL;
TEST_ASSERT_EQUAL(10000000UL, percentage(50, percentOf));
TEST_ASSERT_EQUAL(15000000UL, percentage(75, percentOf));
TEST_ASSERT_EQUAL(0, percentage(0, percentOf));
TEST_ASSERT_EQUAL(20000000UL, percentage(100, percentOf));
TEST_ASSERT_EQUAL(25000000UL, percentage(125, percentOf));
}
void test_maths_halfpercent_U8(void)
{
uint8_t percentOf = 200;
TEST_ASSERT_EQUAL(50, halfPercentage(50, percentOf));
TEST_ASSERT_EQUAL(75, halfPercentage(75, percentOf));
TEST_ASSERT_EQUAL(0, halfPercentage(0, percentOf));
TEST_ASSERT_EQUAL(100, halfPercentage(100, percentOf));
TEST_ASSERT_EQUAL(125, halfPercentage(125, percentOf));
}
void test_maths_halfpercent_U16(void)
{
uint16_t percentOf = 20000;
TEST_ASSERT_EQUAL(5000, halfPercentage(50, percentOf));
TEST_ASSERT_EQUAL(7500, halfPercentage(75, percentOf));
TEST_ASSERT_EQUAL(0, halfPercentage(0, percentOf));
TEST_ASSERT_EQUAL(10000, halfPercentage(100, percentOf));
TEST_ASSERT_EQUAL(12500, halfPercentage(125, percentOf));
}
void test_maths_halfpercent_U32(void)
{
uint32_t percentOf = 20000000UL;
TEST_ASSERT_EQUAL(5000000UL, halfPercentage(50, percentOf));
TEST_ASSERT_EQUAL(7500000UL, halfPercentage(75, percentOf));
TEST_ASSERT_EQUAL(0, halfPercentage(0, percentOf));
TEST_ASSERT_EQUAL(10000000UL, halfPercentage(100, percentOf));
TEST_ASSERT_EQUAL(12500000UL, halfPercentage(125, percentOf));
}
void test_maths_div100_U8(void)
{
TEST_ASSERT_EQUAL_UINT8(1, div100((uint8_t)100U));
TEST_ASSERT_EQUAL_UINT8(2, div100((uint8_t)200U));
TEST_ASSERT_EQUAL_UINT8(0, div100((uint8_t)0U));
TEST_ASSERT_EQUAL_UINT8(0, div100((uint8_t)50U));
TEST_ASSERT_EQUAL_UINT8(2, div100((uint8_t)250U));
}
void test_maths_div100_U16(void)
{
TEST_ASSERT_EQUAL_UINT16(100, div100((uint16_t)10000U));
TEST_ASSERT_EQUAL_UINT16(400, div100((uint16_t)40000U));
}
void test_maths_div100_U32(void)
{
TEST_ASSERT_EQUAL_UINT32(1000000UL, div100(100000000UL));
TEST_ASSERT_EQUAL_UINT32(2000000UL, div100(200000000UL));
}
void test_maths_div100_S8(void)
{
//Check both the signed and unsigned results
TEST_ASSERT_EQUAL_INT8(1, div100((int8_t)100));
TEST_ASSERT_EQUAL_INT8(0, div100((int8_t)0));
TEST_ASSERT_EQUAL_INT8(0, div100((int8_t)50));
TEST_ASSERT_EQUAL_INT8(-1, div100((int8_t)-100));
TEST_ASSERT_EQUAL_INT8(0, div100((int8_t)-50));
TEST_ASSERT_EQUAL_INT8(-1, div100((int8_t)-120));
}
void test_maths_div100_S16(void)
{
//Check both the signed and unsigned results
TEST_ASSERT_EQUAL_INT16(100, div100((int16_t)10000));
TEST_ASSERT_EQUAL_INT16(0, div100((int16_t)0));
TEST_ASSERT_EQUAL_INT16(0, div100((int16_t)50));
TEST_ASSERT_EQUAL_INT16(-100, div100((int16_t)-10000));
TEST_ASSERT_EQUAL_INT16(0, div100((int16_t)-50));
TEST_ASSERT_EQUAL_INT16(-1, div100((int16_t)-120));
}
void test_maths_div100_S32(void)
{
//Check both the signed and unsigned results
#if defined(__arm__)
TEST_ASSERT_EQUAL_INT32(1000000L, div100((int)100000000L));
TEST_ASSERT_EQUAL_INT32(0, div100((int)0));
TEST_ASSERT_EQUAL_INT32(0, div100((int)50));
TEST_ASSERT_EQUAL_INT32(-1000000L, div100((int)-100000000L));
TEST_ASSERT_EQUAL_INT32(0, div100((int)-50));
TEST_ASSERT_EQUAL_INT32(-1, div100((int)-120));
#else
TEST_ASSERT_EQUAL_INT32(1000000L, div100((int32_t)100000000L));
TEST_ASSERT_EQUAL_INT32(0, div100((int32_t)0));
TEST_ASSERT_EQUAL_INT32(0, div100((int32_t)50));
TEST_ASSERT_EQUAL_INT32(-1000000L, div100((int32_t)-100000000L));
TEST_ASSERT_EQUAL_INT32(0, div100((int32_t)-50));
TEST_ASSERT_EQUAL_INT32(-1, div100((int32_t)-120));
#endif
}

View File

@ -1,18 +0,0 @@
extern void testMaths();
void test_maths_percent_U8(void);
void test_maths_percent_U16(void);
void test_maths_percent_U32(void);
void test_maths_halfpercent_U8(void);
void test_maths_halfpercent_U16(void);
void test_maths_halfpercent_U32(void);
void test_maths_div100_U8(void);
void test_maths_div100_U16(void);
void test_maths_div100_U32(void);
void test_maths_div100_S8(void);
void test_maths_div100_S16(void);
void test_maths_div100_S32(void);
void test_maths_div10_U8(void);
void test_maths_div10_U16(void);
void test_maths_div10_U32(void);

View File

@ -0,0 +1,112 @@
#include <unity.h>
#include "test_fp_support.h"
#include "maths.h"
#include "..\timer.hpp"
static void test_percent(uint8_t percent, uint16_t value) {
assert_rounded_div((uint32_t)percent*value, 100, percentage(percent, value));
}
void test_maths_percent_U8(void)
{
uint8_t percentOf = 77;
test_percent(0, percentOf);
test_percent(33, percentOf);
test_percent(50, percentOf);
test_percent(66, percentOf);
test_percent(75, percentOf);
test_percent(100, percentOf);
test_percent(125, percentOf);
}
void test_maths_percent_U16(void)
{
uint16_t percentOf = 33333;
test_percent(0, percentOf);
test_percent(33, percentOf);
test_percent(50, percentOf);
test_percent(66, percentOf);
test_percent(75, percentOf);
test_percent(100, percentOf);
test_percent(125, percentOf);
}
static void test_halfPercentage(uint8_t percent, uint16_t value) {
assert_rounded_div((int32_t)percent*value, 200, halfPercentage(percent, value));
}
void test_maths_halfpercent_U8(void)
{
uint8_t percentOf = 111;
test_halfPercentage(0, percentOf);
test_halfPercentage(33, percentOf);
test_halfPercentage(50, percentOf);
test_halfPercentage(66, percentOf);
test_halfPercentage(75, percentOf);
test_halfPercentage(100, percentOf);
test_halfPercentage(125, percentOf);
}
void test_maths_halfpercent_U16(void)
{
uint16_t percentOf = 57357;
test_halfPercentage(0, percentOf);
test_halfPercentage(33, percentOf);
test_halfPercentage(50, percentOf);
test_halfPercentage(66, percentOf);
test_halfPercentage(75, percentOf);
test_halfPercentage(100, percentOf);
test_halfPercentage(125, percentOf);
}
void test_maths_halfPercentage_perf(void)
{
constexpr int16_t iters = 4;
constexpr uint8_t start_index = 3;
constexpr uint8_t end_index = 99;
constexpr uint8_t step = 3;
constexpr uint16_t percentOf = 57357;
auto nativeTest = [] (uint8_t index, uint32_t &checkSum) { checkSum += ((uint32_t)percentOf * index) / 200U; };
auto optimizedTest = [] (uint8_t index, uint32_t &checkSum) { checkSum += halfPercentage(index, percentOf); };
TEST_MESSAGE("halfPercentage ");
auto comparison = compare_executiontime<uint8_t, uint32_t>(iters, start_index, end_index, step, nativeTest, optimizedTest);
// The checksums will be different due to rounding. This is only
// here to force the compiler to run the loops above
TEST_ASSERT_INT32_WITHIN(UINT32_MAX/2, comparison.timeA.result, comparison.timeB.result);
TEST_ASSERT_LESS_THAN(comparison.timeA.durationMicros, comparison.timeB.durationMicros);
}
void test_maths_percentage_perf(void)
{
constexpr uint16_t iters = 4;
constexpr uint8_t start_index = 3;
constexpr uint8_t end_index = 99;
constexpr uint8_t step = 3;
constexpr uint16_t percentOf = 57357;
auto nativeTest = [] (uint8_t index, uint32_t &checkSum) { checkSum += ((uint32_t)percentOf * index) / 100U; };
auto optimizedTest = [] (uint8_t index, uint32_t &checkSum) { checkSum += percentage(index, percentOf); };
TEST_MESSAGE("Percentage ");
auto comparison = compare_executiontime<uint8_t, uint32_t>(iters, start_index, end_index, step, nativeTest, optimizedTest);
// The checksums will be different due to rounding. This is only
// here to force the compiler to run the loops above
TEST_ASSERT_INT32_WITHIN(UINT32_MAX/2, comparison.timeA.result, comparison.timeB.result);
TEST_ASSERT_LESS_THAN(comparison.timeA.durationMicros, comparison.timeB.durationMicros);
}
void testPercent()
{
RUN_TEST(test_maths_percent_U8);
RUN_TEST(test_maths_percent_U16);
RUN_TEST(test_maths_halfpercent_U8);
RUN_TEST(test_maths_halfpercent_U16);
RUN_TEST(test_maths_halfPercentage_perf);
RUN_TEST(test_maths_percentage_perf);
}

View File

@ -3,23 +3,21 @@
#include "test_calcs_common.h"
#include "schedule_calcs.h"
#include "crankMaths.h"
#include "Decoders.h"
#define _countof(x) (sizeof(x) / sizeof (x[0]))
extern volatile uint16_t degreesPeruSx2048;
extern void SetRevolutionTime(uint32_t revTime);
constexpr uint16_t DWELL_TIME_MS = 4;
uint16_t dwellAngle;
void setEngineSpeed(uint16_t rpm, int16_t max_crank) {
timePerDegreex16 = ldiv( 2666656L, rpm).quot; //The use of a x16 value gives accuracy down to 0.1 of a degree and can provide noticeably better timing results on low resolution triggers
timePerDegree = timePerDegreex16 / 16;
degreesPeruSx2048 = 2048 / timePerDegree;
degreesPeruSx32768 = 524288L / timePerDegreex16;
revolutionTime = (60L*1000000L) / rpm;
SetRevolutionTime(UDIV_ROUND_CLOSEST(60UL*1000000UL, rpm, uint32_t));
CRANK_ANGLE_MAX_IGN = max_crank;
CRANK_ANGLE_MAX_INJ = max_crank;
dwellAngle = timeToAngle(DWELL_TIME_MS*1000UL, CRANKMATH_METHOD_INTERVAL_REV);
dwellAngle = timeToAngleDegPerMicroSec(DWELL_TIME_MS*1000UL);
}
struct ign_test_parameters
@ -49,11 +47,11 @@ void test_calc_ign_timeout(const ign_test_parameters &test_params)
sprintf_P(msg, PSTR("PENDING advanceAngle: %" PRIi8 ", channelAngle: %" PRIu16 ", crankAngle: %" PRIu16 ", endAngle: %" PRIi16), test_params.advanceAngle, test_params.channelAngle, test_params.crankAngle, endAngle);
schedule.Status = PENDING;
TEST_ASSERT_EQUAL_MESSAGE(test_params.pending, calculateIgnitionTimeout(schedule, startAngle, test_params.channelAngle, test_params.crankAngle), msg);
TEST_ASSERT_INT32_WITHIN_MESSAGE(1, test_params.pending, calculateIgnitionTimeout(schedule, startAngle, test_params.channelAngle, test_params.crankAngle), msg);
sprintf_P(msg, PSTR("RUNNING advanceAngle: %" PRIi8 ", channelAngle: %" PRIu16 ", crankAngle: %" PRIu16 ", endAngle: %" PRIi16), test_params.advanceAngle, test_params.channelAngle, test_params.crankAngle, endAngle);
schedule.Status = RUNNING;
TEST_ASSERT_EQUAL_MESSAGE(test_params.running, calculateIgnitionTimeout(schedule, startAngle, test_params.channelAngle, test_params.crankAngle), msg);
TEST_ASSERT_INT32_WITHIN_MESSAGE(1, test_params.running, calculateIgnitionTimeout(schedule, startAngle, test_params.channelAngle, test_params.crankAngle), msg);
}
void test_calc_ign_timeout(const ign_test_parameters *pStart, const ign_test_parameters *pEnd)
@ -70,24 +68,30 @@ void test_calc_ign_timeout(const ign_test_parameters *pStart, const ign_test_par
void test_calc_ign_timeout_360()
{
setEngineSpeed(4000, 360);
TEST_ASSERT_EQUAL(15000, revolutionTime);
TEST_ASSERT_EQUAL(786, degreesPerMicro);
TEST_ASSERT_EQUAL(10667, microsPerDegree);
TEST_ASSERT_EQUAL(96, dwellAngle);
// Expected test values were generated using floating point calculations (in Excel)
static const ign_test_parameters test_data[] PROGMEM = {
// ChannelAngle (deg), Advance, Crank, Expected Pending, Expected Running
{ 0, -40, 0, 12666, 12666, 304, 40 },
{ 0, -40, 45, 10791, 10791, 304, 40 },
{ 0, -40, 90, 8916, 8916, 304, 40 },
{ 0, -40, 135, 7041, 7041, 304, 40 },
{ 0, -40, 180, 5166, 5166, 304, 40 },
{ 0, -40, 0, 12667, 12667, 304, 40 },
{ 0, -40, 45, 10792, 10792, 304, 40 },
{ 0, -40, 90, 8917, 8917, 304, 40 },
{ 0, -40, 135, 7042, 7042, 304, 40 },
{ 0, -40, 180, 5167, 5167, 304, 40 },
{ 0, -40, 215, 3708, 3708, 304, 40 },
{ 0, -40, 270, 1416, 1416, 304, 40 },
{ 0, -40, 315, 0, 14541, 304, 40 },
{ 0, -40, 360, 0, 12666, 304, 40 },
{ 0, -40, 270, 1417, 1417, 304, 40 },
{ 0, -40, 315, 0, 14542, 304, 40 },
{ 0, -40, 360, 0, 12667, 304, 40 },
{ 0, 0, 0, 11000, 11000, 264, 360 },
{ 0, 0, 45, 9125, 9125, 264, 360 },
{ 0, 0, 90, 7250, 7250, 264, 360 },
{ 0, 0, 135, 5375, 5375, 264, 360 },
{ 0, 0, 180, 3500, 3500, 264, 360 },
{ 0, 0, 215, 2041, 2041, 264, 360 },
{ 0, 0, 215, 2042, 2042, 264, 360 },
{ 0, 0, 270, 0, 14750, 264, 360 },
{ 0, 0, 315, 0, 12875, 264, 360 },
{ 0, 0, 360, 0, 11000, 264, 360 },
@ -100,21 +104,21 @@ void test_calc_ign_timeout_360()
{ 0, 40, 270, 0, 13083, 224, 320 },
{ 0, 40, 315, 0, 11208, 224, 320 },
{ 0, 40, 360, 0, 9333, 224, 320 },
{ 72, -40, 0, 666, 666, 16, 112 },
{ 72, -40, 45, 0, 13791, 16, 112 },
{ 72, -40, 90, 11916, 11916, 16, 112 },
{ 72, -40, 135, 10041, 10041, 16, 112 },
{ 72, -40, 180, 8166, 8166, 16, 112 },
{ 72, -40, 0, 667, 667, 16, 112 },
{ 72, -40, 45, 0, 13792, 16, 112 },
{ 72, -40, 90, 11917, 11917, 16, 112 },
{ 72, -40, 135, 10042, 10042, 16, 112 },
{ 72, -40, 180, 8167, 8167, 16, 112 },
{ 72, -40, 215, 6708, 6708, 16, 112 },
{ 72, -40, 270, 4416, 4416, 16, 112 },
{ 72, -40, 315, 2541, 2541, 16, 112 },
{ 72, -40, 360, 666, 666, 16, 112 },
{ 72, -40, 270, 4417, 4417, 16, 112 },
{ 72, -40, 315, 2542, 2542, 16, 112 },
{ 72, -40, 360, 667, 667, 16, 112 },
{ 72, 0, 0, 0, 14000, 336, 72 },
{ 72, 0, 45, 0, 12125, 336, 72 },
{ 72, 0, 90, 10250, 10250, 336, 72 },
{ 72, 0, 135, 8375, 8375, 336, 72 },
{ 72, 0, 180, 6500, 6500, 336, 72 },
{ 72, 0, 215, 5041, 5041, 336, 72 },
{ 72, 0, 215, 5042, 5042, 336, 72 },
{ 72, 0, 270, 2750, 2750, 336, 72 },
{ 72, 0, 315, 875, 875, 336, 72 },
{ 72, 0, 360, 0, 14000, 336, 72 },
@ -127,21 +131,21 @@ void test_calc_ign_timeout_360()
{ 72, 40, 270, 1083, 1083, 296, 32 },
{ 72, 40, 315, 0, 14208, 296, 32 },
{ 72, 40, 360, 0, 12333, 296, 32 },
{ 90, -40, 0, 1416, 1416, 34, 130 },
{ 90, -40, 45, 0, 14541, 34, 130 },
{ 90, -40, 90, 12666, 12666, 34, 130 },
{ 90, -40, 135, 10791, 10791, 34, 130 },
{ 90, -40, 180, 8916, 8916, 34, 130 },
{ 90, -40, 0, 1417, 1417, 34, 130 },
{ 90, -40, 45, 0, 14542, 34, 130 },
{ 90, -40, 90, 12667, 12667, 34, 130 },
{ 90, -40, 135, 10792, 10792, 34, 130 },
{ 90, -40, 180, 8917, 8917, 34, 130 },
{ 90, -40, 215, 7458, 7458, 34, 130 },
{ 90, -40, 270, 5166, 5166, 34, 130 },
{ 90, -40, 315, 3291, 3291, 34, 130 },
{ 90, -40, 360, 1416, 1416, 34, 130 },
{ 90, -40, 270, 5167, 5167, 34, 130 },
{ 90, -40, 315, 3292, 3292, 34, 130 },
{ 90, -40, 360, 1417, 1417, 34, 130 },
{ 90, 0, 0, 0, 14750, 354, 90 },
{ 90, 0, 45, 0, 12875, 354, 90 },
{ 90, 0, 90, 11000, 11000, 354, 90 },
{ 90, 0, 135, 9125, 9125, 354, 90 },
{ 90, 0, 180, 7250, 7250, 354, 90 },
{ 90, 0, 215, 5791, 5791, 354, 90 },
{ 90, 0, 215, 5792, 5792, 354, 90 },
{ 90, 0, 270, 3500, 3500, 354, 90 },
{ 90, 0, 315, 1625, 1625, 354, 90 },
{ 90, 0, 360, 0, 14750, 354, 90 },
@ -154,21 +158,21 @@ void test_calc_ign_timeout_360()
{ 90, 40, 270, 1833, 1833, 314, 50 },
{ 90, 40, 315, 0, 14958, 314, 50 },
{ 90, 40, 360, 0, 13083, 314, 50 },
{ 144, -40, 0, 3666, 3666, 88, 184 },
{ 144, -40, 45, 1791, 1791, 88, 184 },
{ 144, -40, 90, 0, 14916, 88, 184 },
{ 144, -40, 135, 0, 13041, 88, 184 },
{ 144, -40, 180, 11166, 11166, 88, 184 },
{ 144, -40, 0, 3667, 3667, 88, 184 },
{ 144, -40, 45, 1792, 1792, 88, 184 },
{ 144, -40, 90, 0, 14917, 88, 184 },
{ 144, -40, 135, 0, 13042, 88, 184 },
{ 144, -40, 180, 11167, 11167, 88, 184 },
{ 144, -40, 215, 9708, 9708, 88, 184 },
{ 144, -40, 270, 7416, 7416, 88, 184 },
{ 144, -40, 315, 5541, 5541, 88, 184 },
{ 144, -40, 360, 3666, 3666, 88, 184 },
{ 144, -40, 270, 7417, 7417, 88, 184 },
{ 144, -40, 315, 5542, 5542, 88, 184 },
{ 144, -40, 360, 3667, 3667, 88, 184 },
{ 144, 0, 0, 2000, 2000, 48, 144 },
{ 144, 0, 45, 125, 125, 48, 144 },
{ 144, 0, 90, 0, 13250, 48, 144 },
{ 144, 0, 135, 0, 11375, 48, 144 },
{ 144, 0, 180, 9500, 9500, 48, 144 },
{ 144, 0, 215, 8041, 8041, 48, 144 },
{ 144, 0, 215, 8042, 8042, 48, 144 },
{ 144, 0, 270, 5750, 5750, 48, 144 },
{ 144, 0, 315, 3875, 3875, 48, 144 },
{ 144, 0, 360, 2000, 2000, 48, 144 },
@ -181,21 +185,21 @@ void test_calc_ign_timeout_360()
{ 144, 40, 270, 4083, 4083, 8, 104 },
{ 144, 40, 315, 2208, 2208, 8, 104 },
{ 144, 40, 360, 333, 333, 8, 104 },
{ 180, -40, 0, 5166, 5166, 124, 220 },
{ 180, -40, 45, 3291, 3291, 124, 220 },
{ 180, -40, 90, 1416, 1416, 124, 220 },
{ 180, -40, 135, 0, 14541, 124, 220 },
{ 180, -40, 180, 12666, 12666, 124, 220 },
{ 180, -40, 0, 5167, 5167, 124, 220 },
{ 180, -40, 45, 3292, 3292, 124, 220 },
{ 180, -40, 90, 1417, 1417, 124, 220 },
{ 180, -40, 135, 0, 14542, 124, 220 },
{ 180, -40, 180, 12667, 12667, 124, 220 },
{ 180, -40, 215, 11208, 11208, 124, 220 },
{ 180, -40, 270, 8916, 8916, 124, 220 },
{ 180, -40, 315, 7041, 7041, 124, 220 },
{ 180, -40, 360, 5166, 5166, 124, 220 },
{ 180, -40, 270, 8917, 8917, 124, 220 },
{ 180, -40, 315, 7042, 7042, 124, 220 },
{ 180, -40, 360, 5167, 5167, 124, 220 },
{ 180, 0, 0, 3500, 3500, 84, 180 },
{ 180, 0, 45, 1625, 1625, 84, 180 },
{ 180, 0, 90, 0, 14750, 84, 180 },
{ 180, 0, 135, 0, 12875, 84, 180 },
{ 180, 0, 180, 11000, 11000, 84, 180 },
{ 180, 0, 215, 9541, 9541, 84, 180 },
{ 180, 0, 215, 9542, 9542, 84, 180 },
{ 180, 0, 270, 7250, 7250, 84, 180 },
{ 180, 0, 315, 5375, 5375, 84, 180 },
{ 180, 0, 360, 3500, 3500, 84, 180 },
@ -208,21 +212,21 @@ void test_calc_ign_timeout_360()
{ 180, 40, 270, 5583, 5583, 44, 140 },
{ 180, 40, 315, 3708, 3708, 44, 140 },
{ 180, 40, 360, 1833, 1833, 44, 140 },
{ 270, -40, 0, 8916, 8916, 214, 310 },
{ 270, -40, 45, 7041, 7041, 214, 310 },
{ 270, -40, 90, 5166, 5166, 214, 310 },
{ 270, -40, 135, 3291, 3291, 214, 310 },
{ 270, -40, 180, 1416, 1416, 214, 310 },
{ 270, -40, 0, 8917, 8917, 214, 310 },
{ 270, -40, 45, 7042, 7042, 214, 310 },
{ 270, -40, 90, 5167, 5167, 214, 310 },
{ 270, -40, 135, 3292, 3292, 214, 310 },
{ 270, -40, 180, 1417, 1417, 214, 310 },
{ 270, -40, 215, 0, 14958, 214, 310 },
{ 270, -40, 270, 12666, 12666, 214, 310 },
{ 270, -40, 315, 10791, 10791, 214, 310 },
{ 270, -40, 360, 8916, 8916, 214, 310 },
{ 270, -40, 270, 12667, 12667, 214, 310 },
{ 270, -40, 315, 10792, 10792, 214, 310 },
{ 270, -40, 360, 8917, 8917, 214, 310 },
{ 270, 0, 0, 7250, 7250, 174, 270 },
{ 270, 0, 45, 5375, 5375, 174, 270 },
{ 270, 0, 90, 3500, 3500, 174, 270 },
{ 270, 0, 135, 1625, 1625, 174, 270 },
{ 270, 0, 180, 0, 14750, 174, 270 },
{ 270, 0, 215, 0, 13291, 174, 270 },
{ 270, 0, 215, 0, 13292, 174, 270 },
{ 270, 0, 270, 11000, 11000, 174, 270 },
{ 270, 0, 315, 9125, 9125, 174, 270 },
{ 270, 0, 360, 7250, 7250, 174, 270 },
@ -235,21 +239,21 @@ void test_calc_ign_timeout_360()
{ 270, 40, 270, 9333, 9333, 134, 230 },
{ 270, 40, 315, 7458, 7458, 134, 230 },
{ 270, 40, 360, 5583, 5583, 134, 230 },
{ 360, -40, 0, 12666, 12666, 304, 40 },
{ 360, -40, 45, 10791, 10791, 304, 40 },
{ 360, -40, 90, 8916, 8916, 304, 40 },
{ 360, -40, 135, 7041, 7041, 304, 40 },
{ 360, -40, 180, 5166, 5166, 304, 40 },
{ 360, -40, 0, 12667, 12667, 304, 40 },
{ 360, -40, 45, 10792, 10792, 304, 40 },
{ 360, -40, 90, 8917, 8917, 304, 40 },
{ 360, -40, 135, 7042, 7042, 304, 40 },
{ 360, -40, 180, 5167, 5167, 304, 40 },
{ 360, -40, 215, 3708, 3708, 304, 40 },
{ 360, -40, 270, 1416, 1416, 304, 40 },
{ 360, -40, 315, 0, 14541, 304, 40 },
{ 360, -40, 360, 12666, 12666, 304, 40 },
{ 360, -40, 270, 1417, 1417, 304, 40 },
{ 360, -40, 315, 0, 14542, 304, 40 },
{ 360, -40, 360, 12667, 12667, 304, 40 },
{ 360, 0, 0, 11000, 11000, 264, 360 },
{ 360, 0, 45, 9125, 9125, 264, 360 },
{ 360, 0, 90, 7250, 7250, 264, 360 },
{ 360, 0, 135, 5375, 5375, 264, 360 },
{ 360, 0, 180, 3500, 3500, 264, 360 },
{ 360, 0, 215, 2041, 2041, 264, 360 },
{ 360, 0, 215, 2042, 2042, 264, 360 },
{ 360, 0, 270, 0, 14750, 264, 360 },
{ 360, 0, 315, 0, 12875, 264, 360 },
{ 360, 0, 360, 11000, 11000, 264, 360 },
@ -273,23 +277,24 @@ void test_calc_ign_timeout_720()
{
setEngineSpeed(4000, 720);
// Expected test values were generated using floating point calculations (in Excel)
static const ign_test_parameters test_data[] PROGMEM = {
// ChannelAngle (deg), Advance, Crank, Expected Pending, Expected Running
{ 0, -40, 0, 27666, 27666, 664, 40 },
{ 0, -40, 45, 25791, 25791, 664, 40 },
{ 0, -40, 90, 23916, 23916, 664, 40 },
{ 0, -40, 135, 22041, 22041, 664, 40 },
{ 0, -40, 180, 20166, 20166, 664, 40 },
{ 0, -40, 0, 27667, 27667, 664, 40 },
{ 0, -40, 45, 25792, 25792, 664, 40 },
{ 0, -40, 90, 23917, 23917, 664, 40 },
{ 0, -40, 135, 22042, 22042, 664, 40 },
{ 0, -40, 180, 20167, 20167, 664, 40 },
{ 0, -40, 215, 18708, 18708, 664, 40 },
{ 0, -40, 270, 16416, 16416, 664, 40 },
{ 0, -40, 315, 14541, 14541, 664, 40 },
{ 0, -40, 360, 12666, 12666, 664, 40 },
{ 0, -40, 270, 16417, 16417, 664, 40 },
{ 0, -40, 315, 14542, 14542, 664, 40 },
{ 0, -40, 360, 12667, 12667, 664, 40 },
{ 0, 0, 0, 26000, 26000, 624, 720 },
{ 0, 0, 45, 24125, 24125, 624, 720 },
{ 0, 0, 90, 22250, 22250, 624, 720 },
{ 0, 0, 135, 20375, 20375, 624, 720 },
{ 0, 0, 180, 18500, 18500, 624, 720 },
{ 0, 0, 215, 17041, 17041, 624, 720 },
{ 0, 0, 215, 17042, 17042, 624, 720 },
{ 0, 0, 270, 14750, 14750, 624, 720 },
{ 0, 0, 315, 12875, 12875, 624, 720 },
{ 0, 0, 360, 11000, 11000, 624, 720 },
@ -302,21 +307,21 @@ void test_calc_ign_timeout_720()
{ 0, 40, 270, 13083, 13083, 584, 680 },
{ 0, 40, 315, 11208, 11208, 584, 680 },
{ 0, 40, 360, 9333, 9333, 584, 680 },
{ 72, -40, 0, 666, 666, 16, 112 },
{ 72, -40, 45, 0, 28791, 16, 112 },
{ 72, -40, 90, 26916, 26916, 16, 112 },
{ 72, -40, 135, 25041, 25041, 16, 112 },
{ 72, -40, 180, 23166, 23166, 16, 112 },
{ 72, -40, 0, 667, 667, 16, 112 },
{ 72, -40, 45, 0, 28792, 16, 112 },
{ 72, -40, 90, 26917, 26917, 16, 112 },
{ 72, -40, 135, 25042, 25042, 16, 112 },
{ 72, -40, 180, 23167, 23167, 16, 112 },
{ 72, -40, 215, 21708, 21708, 16, 112 },
{ 72, -40, 270, 19416, 19416, 16, 112 },
{ 72, -40, 315, 17541, 17541, 16, 112 },
{ 72, -40, 360, 15666, 15666, 16, 112 },
{ 72, -40, 270, 19417, 19417, 16, 112 },
{ 72, -40, 315, 17542, 17542, 16, 112 },
{ 72, -40, 360, 15667, 15667, 16, 112 },
{ 72, 0, 0, 0, 29000, 696, 72 },
{ 72, 0, 45, 0, 27125, 696, 72 },
{ 72, 0, 90, 25250, 25250, 696, 72 },
{ 72, 0, 135, 23375, 23375, 696, 72 },
{ 72, 0, 180, 21500, 21500, 696, 72 },
{ 72, 0, 215, 20041, 20041, 696, 72 },
{ 72, 0, 215, 20042, 20042, 696, 72 },
{ 72, 0, 270, 17750, 17750, 696, 72 },
{ 72, 0, 315, 15875, 15875, 696, 72 },
{ 72, 0, 360, 14000, 14000, 696, 72 },
@ -329,21 +334,21 @@ void test_calc_ign_timeout_720()
{ 72, 40, 270, 16083, 16083, 656, 32 },
{ 72, 40, 315, 14208, 14208, 656, 32 },
{ 72, 40, 360, 12333, 12333, 656, 32 },
{ 90, -40, 0, 1416, 1416, 34, 130 },
{ 90, -40, 45, 0, 29541, 34, 130 },
{ 90, -40, 90, 27666, 27666, 34, 130 },
{ 90, -40, 135, 25791, 25791, 34, 130 },
{ 90, -40, 180, 23916, 23916, 34, 130 },
{ 90, -40, 0, 1417, 1417, 34, 130 },
{ 90, -40, 45, 0, 29542, 34, 130 },
{ 90, -40, 90, 27667, 27667, 34, 130 },
{ 90, -40, 135, 25792, 25792, 34, 130 },
{ 90, -40, 180, 23917, 23917, 34, 130 },
{ 90, -40, 215, 22458, 22458, 34, 130 },
{ 90, -40, 270, 20166, 20166, 34, 130 },
{ 90, -40, 315, 18291, 18291, 34, 130 },
{ 90, -40, 360, 16416, 16416, 34, 130 },
{ 90, -40, 270, 20167, 20167, 34, 130 },
{ 90, -40, 315, 18292, 18292, 34, 130 },
{ 90, -40, 360, 16417, 16417, 34, 130 },
{ 90, 0, 0, 0, 29750, 714, 90 },
{ 90, 0, 45, 0, 27875, 714, 90 },
{ 90, 0, 90, 26000, 26000, 714, 90 },
{ 90, 0, 135, 24125, 24125, 714, 90 },
{ 90, 0, 180, 22250, 22250, 714, 90 },
{ 90, 0, 215, 20791, 20791, 714, 90 },
{ 90, 0, 215, 20792, 20792, 714, 90 },
{ 90, 0, 270, 18500, 18500, 714, 90 },
{ 90, 0, 315, 16625, 16625, 714, 90 },
{ 90, 0, 360, 14750, 14750, 714, 90 },
@ -356,21 +361,21 @@ void test_calc_ign_timeout_720()
{ 90, 40, 270, 16833, 16833, 674, 50 },
{ 90, 40, 315, 14958, 14958, 674, 50 },
{ 90, 40, 360, 13083, 13083, 674, 50 },
{ 144, -40, 0, 3666, 3666, 88, 184 },
{ 144, -40, 45, 1791, 1791, 88, 184 },
{ 144, -40, 90, 0, 29916, 88, 184 },
{ 144, -40, 135, 0, 28041, 88, 184 },
{ 144, -40, 180, 26166, 26166, 88, 184 },
{ 144, -40, 0, 3667, 3667, 88, 184 },
{ 144, -40, 45, 1792, 1792, 88, 184 },
{ 144, -40, 90, 0, 29917, 88, 184 },
{ 144, -40, 135, 0, 28042, 88, 184 },
{ 144, -40, 180, 26167, 26167, 88, 184 },
{ 144, -40, 215, 24708, 24708, 88, 184 },
{ 144, -40, 270, 22416, 22416, 88, 184 },
{ 144, -40, 315, 20541, 20541, 88, 184 },
{ 144, -40, 360, 18666, 18666, 88, 184 },
{ 144, -40, 270, 22417, 22417, 88, 184 },
{ 144, -40, 315, 20542, 20542, 88, 184 },
{ 144, -40, 360, 18667, 18667, 88, 184 },
{ 144, 0, 0, 2000, 2000, 48, 144 },
{ 144, 0, 45, 125, 125, 48, 144 },
{ 144, 0, 90, 0, 28250, 48, 144 },
{ 144, 0, 135, 0, 26375, 48, 144 },
{ 144, 0, 180, 24500, 24500, 48, 144 },
{ 144, 0, 215, 23041, 23041, 48, 144 },
{ 144, 0, 215, 23042, 23042, 48, 144 },
{ 144, 0, 270, 20750, 20750, 48, 144 },
{ 144, 0, 315, 18875, 18875, 48, 144 },
{ 144, 0, 360, 17000, 17000, 48, 144 },
@ -383,21 +388,21 @@ void test_calc_ign_timeout_720()
{ 144, 40, 270, 19083, 19083, 8, 104 },
{ 144, 40, 315, 17208, 17208, 8, 104 },
{ 144, 40, 360, 15333, 15333, 8, 104 },
{ 180, -40, 0, 5166, 5166, 124, 220 },
{ 180, -40, 45, 3291, 3291, 124, 220 },
{ 180, -40, 90, 1416, 1416, 124, 220 },
{ 180, -40, 135, 0, 29541, 124, 220 },
{ 180, -40, 180, 27666, 27666, 124, 220 },
{ 180, -40, 0, 5167, 5167, 124, 220 },
{ 180, -40, 45, 3292, 3292, 124, 220 },
{ 180, -40, 90, 1417, 1417, 124, 220 },
{ 180, -40, 135, 0, 29542, 124, 220 },
{ 180, -40, 180, 27667, 27667, 124, 220 },
{ 180, -40, 215, 26208, 26208, 124, 220 },
{ 180, -40, 270, 23916, 23916, 124, 220 },
{ 180, -40, 315, 22041, 22041, 124, 220 },
{ 180, -40, 360, 20166, 20166, 124, 220 },
{ 180, -40, 270, 23917, 23917, 124, 220 },
{ 180, -40, 315, 22042, 22042, 124, 220 },
{ 180, -40, 360, 20167, 20167, 124, 220 },
{ 180, 0, 0, 3500, 3500, 84, 180 },
{ 180, 0, 45, 1625, 1625, 84, 180 },
{ 180, 0, 90, 0, 29750, 84, 180 },
{ 180, 0, 135, 0, 27875, 84, 180 },
{ 180, 0, 180, 26000, 26000, 84, 180 },
{ 180, 0, 215, 24541, 24541, 84, 180 },
{ 180, 0, 215, 24542, 24542, 84, 180 },
{ 180, 0, 270, 22250, 22250, 84, 180 },
{ 180, 0, 315, 20375, 20375, 84, 180 },
{ 180, 0, 360, 18500, 18500, 84, 180 },
@ -410,21 +415,21 @@ void test_calc_ign_timeout_720()
{ 180, 40, 270, 20583, 20583, 44, 140 },
{ 180, 40, 315, 18708, 18708, 44, 140 },
{ 180, 40, 360, 16833, 16833, 44, 140 },
{ 270, -40, 0, 8916, 8916, 214, 310 },
{ 270, -40, 45, 7041, 7041, 214, 310 },
{ 270, -40, 90, 5166, 5166, 214, 310 },
{ 270, -40, 135, 3291, 3291, 214, 310 },
{ 270, -40, 180, 1416, 1416, 214, 310 },
{ 270, -40, 0, 8917, 8917, 214, 310 },
{ 270, -40, 45, 7042, 7042, 214, 310 },
{ 270, -40, 90, 5167, 5167, 214, 310 },
{ 270, -40, 135, 3292, 3292, 214, 310 },
{ 270, -40, 180, 1417, 1417, 214, 310 },
{ 270, -40, 215, 0, 29958, 214, 310 },
{ 270, -40, 270, 27666, 27666, 214, 310 },
{ 270, -40, 315, 25791, 25791, 214, 310 },
{ 270, -40, 360, 23916, 23916, 214, 310 },
{ 270, -40, 270, 27667, 27667, 214, 310 },
{ 270, -40, 315, 25792, 25792, 214, 310 },
{ 270, -40, 360, 23917, 23917, 214, 310 },
{ 270, 0, 0, 7250, 7250, 174, 270 },
{ 270, 0, 45, 5375, 5375, 174, 270 },
{ 270, 0, 90, 3500, 3500, 174, 270 },
{ 270, 0, 135, 1625, 1625, 174, 270 },
{ 270, 0, 180, 0, 29750, 174, 270 },
{ 270, 0, 215, 0, 28291, 174, 270 },
{ 270, 0, 215, 0, 28292, 174, 270 },
{ 270, 0, 270, 26000, 26000, 174, 270 },
{ 270, 0, 315, 24125, 24125, 174, 270 },
{ 270, 0, 360, 22250, 22250, 174, 270 },
@ -437,21 +442,21 @@ void test_calc_ign_timeout_720()
{ 270, 40, 270, 24333, 24333, 134, 230 },
{ 270, 40, 315, 22458, 22458, 134, 230 },
{ 270, 40, 360, 20583, 20583, 134, 230 },
{ 360, -40, 0, 12666, 12666, 304, 400 },
{ 360, -40, 45, 10791, 10791, 304, 400 },
{ 360, -40, 90, 8916, 8916, 304, 400 },
{ 360, -40, 135, 7041, 7041, 304, 400 },
{ 360, -40, 180, 5166, 5166, 304, 400 },
{ 360, -40, 0, 12667, 12667, 304, 400 },
{ 360, -40, 45, 10792, 10792, 304, 400 },
{ 360, -40, 90, 8917, 8917, 304, 400 },
{ 360, -40, 135, 7042, 7042, 304, 400 },
{ 360, -40, 180, 5167, 5167, 304, 400 },
{ 360, -40, 215, 3708, 3708, 304, 400 },
{ 360, -40, 270, 1416, 1416, 304, 400 },
{ 360, -40, 315, 0, 29541, 304, 400 },
{ 360, -40, 360, 27666, 27666, 304, 400 },
{ 360, -40, 270, 1417, 1417, 304, 400 },
{ 360, -40, 315, 0, 29542, 304, 400 },
{ 360, -40, 360, 27667, 27667, 304, 400 },
{ 360, 0, 0, 11000, 11000, 264, 360 },
{ 360, 0, 45, 9125, 9125, 264, 360 },
{ 360, 0, 90, 7250, 7250, 264, 360 },
{ 360, 0, 135, 5375, 5375, 264, 360 },
{ 360, 0, 180, 3500, 3500, 264, 360 },
{ 360, 0, 215, 2041, 2041, 264, 360 },
{ 360, 0, 215, 2042, 2042, 264, 360 },
{ 360, 0, 270, 0, 29750, 264, 360 },
{ 360, 0, 315, 0, 27875, 264, 360 },
{ 360, 0, 360, 26000, 26000, 264, 360 },
@ -464,21 +469,21 @@ void test_calc_ign_timeout_720()
{ 360, 40, 270, 0, 28083, 224, 320 },
{ 360, 40, 315, 0, 26208, 224, 320 },
{ 360, 40, 360, 24333, 24333, 224, 320 },
{ 432, -40, 0, 15666, 15666, 376, 472 },
{ 432, -40, 45, 13791, 13791, 376, 472 },
{ 432, -40, 90, 11916, 11916, 376, 472 },
{ 432, -40, 135, 10041, 10041, 376, 472 },
{ 432, -40, 180, 8166, 8166, 376, 472 },
{ 432, -40, 0, 15667, 15667, 376, 472 },
{ 432, -40, 45, 13792, 13792, 376, 472 },
{ 432, -40, 90, 11917, 11917, 376, 472 },
{ 432, -40, 135, 10042, 10042, 376, 472 },
{ 432, -40, 180, 8167, 8167, 376, 472 },
{ 432, -40, 215, 6708, 6708, 376, 472 },
{ 432, -40, 270, 4416, 4416, 376, 472 },
{ 432, -40, 315, 2541, 2541, 376, 472 },
{ 432, -40, 360, 666, 666, 376, 472 },
{ 432, -40, 270, 4417, 4417, 376, 472 },
{ 432, -40, 315, 2542, 2542, 376, 472 },
{ 432, -40, 360, 667, 667, 376, 472 },
{ 432, 0, 0, 14000, 14000, 336, 432 },
{ 432, 0, 45, 12125, 12125, 336, 432 },
{ 432, 0, 90, 10250, 10250, 336, 432 },
{ 432, 0, 135, 8375, 8375, 336, 432 },
{ 432, 0, 180, 6500, 6500, 336, 432 },
{ 432, 0, 215, 5041, 5041, 336, 432 },
{ 432, 0, 215, 5042, 5042, 336, 432 },
{ 432, 0, 270, 2750, 2750, 336, 432 },
{ 432, 0, 315, 875, 875, 336, 432 },
{ 432, 0, 360, 0, 29000, 336, 432 },
@ -491,21 +496,21 @@ void test_calc_ign_timeout_720()
{ 432, 40, 270, 1083, 1083, 296, 392 },
{ 432, 40, 315, 0, 29208, 296, 392 },
{ 432, 40, 360, 0, 27333, 296, 392 },
{ 576, -40, 0, 21666, 21666, 520, 616 },
{ 576, -40, 45, 19791, 19791, 520, 616 },
{ 576, -40, 90, 17916, 17916, 520, 616 },
{ 576, -40, 135, 16041, 16041, 520, 616 },
{ 576, -40, 180, 14166, 14166, 520, 616 },
{ 576, -40, 0, 21667, 21667, 520, 616 },
{ 576, -40, 45, 19792, 19792, 520, 616 },
{ 576, -40, 90, 17917, 17917, 520, 616 },
{ 576, -40, 135, 16042, 16042, 520, 616 },
{ 576, -40, 180, 14167, 14167, 520, 616 },
{ 576, -40, 215, 12708, 12708, 520, 616 },
{ 576, -40, 270, 10416, 10416, 520, 616 },
{ 576, -40, 315, 8541, 8541, 520, 616 },
{ 576, -40, 360, 6666, 6666, 520, 616 },
{ 576, -40, 270, 10417, 10417, 520, 616 },
{ 576, -40, 315, 8542, 8542, 520, 616 },
{ 576, -40, 360, 6667, 6667, 520, 616 },
{ 576, 0, 0, 20000, 20000, 480, 576 },
{ 576, 0, 45, 18125, 18125, 480, 576 },
{ 576, 0, 90, 16250, 16250, 480, 576 },
{ 576, 0, 135, 14375, 14375, 480, 576 },
{ 576, 0, 180, 12500, 12500, 480, 576 },
{ 576, 0, 215, 11041, 11041, 480, 576 },
{ 576, 0, 215, 11042, 11042, 480, 576 },
{ 576, 0, 270, 8750, 8750, 480, 576 },
{ 576, 0, 315, 6875, 6875, 480, 576 },
{ 576, 0, 360, 5000, 5000, 480, 576 },
@ -518,21 +523,21 @@ void test_calc_ign_timeout_720()
{ 576, 40, 270, 7083, 7083, 440, 536 },
{ 576, 40, 315, 5208, 5208, 440, 536 },
{ 576, 40, 360, 3333, 3333, 440, 536 },
{ 600, -40, 0, 22666, 22666, 544, 640 },
{ 600, -40, 45, 20791, 20791, 544, 640 },
{ 600, -40, 90, 18916, 18916, 544, 640 },
{ 600, -40, 135, 17041, 17041, 544, 640 },
{ 600, -40, 180, 15166, 15166, 544, 640 },
{ 600, -40, 0, 22667, 22667, 544, 640 },
{ 600, -40, 45, 20792, 20792, 544, 640 },
{ 600, -40, 90, 18917, 18917, 544, 640 },
{ 600, -40, 135, 17042, 17042, 544, 640 },
{ 600, -40, 180, 15167, 15167, 544, 640 },
{ 600, -40, 215, 13708, 13708, 544, 640 },
{ 600, -40, 270, 11416, 11416, 544, 640 },
{ 600, -40, 315, 9541, 9541, 544, 640 },
{ 600, -40, 360, 7666, 7666, 544, 640 },
{ 600, -40, 270, 11417, 11417, 544, 640 },
{ 600, -40, 315, 9542, 9542, 544, 640 },
{ 600, -40, 360, 7667, 7667, 544, 640 },
{ 600, 0, 0, 21000, 21000, 504, 600 },
{ 600, 0, 45, 19125, 19125, 504, 600 },
{ 600, 0, 90, 17250, 17250, 504, 600 },
{ 600, 0, 135, 15375, 15375, 504, 600 },
{ 600, 0, 180, 13500, 13500, 504, 600 },
{ 600, 0, 215, 12041, 12041, 504, 600 },
{ 600, 0, 215, 12042, 12042, 504, 600 },
{ 600, 0, 270, 9750, 9750, 504, 600 },
{ 600, 0, 315, 7875, 7875, 504, 600 },
{ 600, 0, 360, 6000, 6000, 504, 600 },

View File

@ -28,19 +28,19 @@ static void test_calc_inj_timeout(const inj_test_parameters &parameters)
{
static constexpr uint16_t injAngle = 355;
char msg[150];
uint16_t PWdivTimerPerDegree = div(parameters.pw, timePerDegree).quot;
uint16_t PWdivTimerPerDegree = timeToAngleDegPerMicroSec(parameters.pw);
FuelSchedule schedule(FUEL2_COUNTER, FUEL2_COMPARE, nullInjCallback, nullInjCallback);
schedule.Status = PENDING;
uint16_t startAngle = calculateInjectorStartAngle(PWdivTimerPerDegree, parameters.channelAngle, injAngle);
sprintf_P(msg, PSTR("PENDING channelAngle: %" PRIu16 ", pw: %" PRIu16 ", crankAngle: %" PRIu16 ", startAngle: %" PRIu16), parameters.channelAngle, parameters.pw, parameters.crankAngle, startAngle);
TEST_ASSERT_EQUAL_MESSAGE(parameters.pending, calculateInjectorTimeout(schedule, parameters.channelAngle, startAngle, parameters.crankAngle), msg);
TEST_ASSERT_INT32_WITHIN_MESSAGE(1, parameters.pending, calculateInjectorTimeout(schedule, parameters.channelAngle, startAngle, parameters.crankAngle), msg);
schedule.Status = RUNNING;
startAngle = calculateInjectorStartAngle( PWdivTimerPerDegree, parameters.channelAngle, injAngle);
sprintf_P(msg, PSTR("RUNNING channelAngle: %" PRIu16 ", pw: %" PRIu16 ", crankAngle: %" PRIu16 ", startAngle: %" PRIu16), parameters.channelAngle, parameters.pw, parameters.crankAngle, startAngle);
TEST_ASSERT_EQUAL_MESSAGE(parameters.running, calculateInjectorTimeout(schedule, parameters.channelAngle, startAngle, parameters.crankAngle), msg);
TEST_ASSERT_INT32_WITHIN_MESSAGE(1, parameters.running, calculateInjectorTimeout(schedule, parameters.channelAngle, startAngle, parameters.crankAngle), msg);
}
@ -59,89 +59,89 @@ static void test_calc_inj_timeout_360()
{
setEngineSpeed(4000, 360);
// Expected test values were generated using floating point calculations (in Excel)
static const inj_test_parameters test_data[] PROGMEM = {
// ChannelAngle (deg), PW (uS), Crank (deg), Expected Pending (uS), Expected Running (uS)
{ 0, 3000, 0, 11562, 11562 },
{ 0, 3000, 45, 9717, 9717 },
{ 0, 3000, 90, 7872, 7872 },
{ 0, 3000, 135, 6027, 6027 },
{ 0, 3000, 180, 4182, 4182 },
{ 0, 3000, 215, 2747, 2747 },
{ 0, 3000, 270, 492, 492 },
{ 0, 3000, 315, 0, 13407 },
{ 0, 3000, 360, 0, 11562 },
{ 72, 3000, 0, 0, 14514 },
{ 72, 3000, 45, 0, 12669 },
{ 72, 3000, 90, 10824, 10824 },
{ 72, 3000, 135, 8979, 8979 },
{ 72, 3000, 180, 7134, 7134 },
{ 72, 3000, 215, 5699, 5699 },
{ 72, 3000, 270, 3444, 3444 },
{ 72, 3000, 315, 1599, 1599 },
{ 72, 3000, 360, 0, 14514 },
{ 80, 3000, 0, 82, 82 },
{ 80, 3000, 45, 0, 12997 },
{ 80, 3000, 90, 11152, 11152 },
{ 80, 3000, 135, 9307, 9307 },
{ 80, 3000, 180, 7462, 7462 },
{ 80, 3000, 215, 6027, 6027 },
{ 80, 3000, 270, 3772, 3772 },
{ 80, 3000, 315, 1927, 1927 },
{ 80, 3000, 360, 82, 82 },
{ 90, 3000, 0, 492, 492 },
{ 90, 3000, 45, 0, 13407 },
{ 90, 3000, 90, 11562, 11562 },
{ 90, 3000, 135, 9717, 9717 },
{ 90, 3000, 180, 7872, 7872 },
{ 90, 3000, 215, 6437, 6437 },
{ 90, 3000, 270, 4182, 4182 },
{ 90, 3000, 315, 2337, 2337 },
{ 90, 3000, 360, 492, 492 },
{ 144, 3000, 0, 2706, 2706 },
{ 144, 3000, 45, 861, 861 },
{ 144, 3000, 90, 0, 13776 },
{ 144, 3000, 135, 0, 11931 },
{ 144, 3000, 180, 10086, 10086 },
{ 144, 3000, 215, 8651, 8651 },
{ 144, 3000, 270, 6396, 6396 },
{ 144, 3000, 315, 4551, 4551 },
{ 144, 3000, 360, 2706, 2706 },
{ 180, 3000, 0, 4182, 4182 },
{ 180, 3000, 45, 2337, 2337 },
{ 180, 3000, 90, 492, 492 },
{ 180, 3000, 135, 0, 13407 },
{ 180, 3000, 180, 11562, 11562 },
{ 180, 3000, 215, 10127, 10127 },
{ 180, 3000, 270, 7872, 7872 },
{ 180, 3000, 315, 6027, 6027 },
{ 180, 3000, 360, 4182, 4182 },
{ 240, 3000, 0, 6642, 6642 },
{ 240, 3000, 45, 4797, 4797 },
{ 240, 3000, 90, 2952, 2952 },
{ 240, 3000, 135, 1107, 1107 },
{ 240, 3000, 180, 0, 14022 },
{ 240, 3000, 215, 0, 12587 },
{ 240, 3000, 270, 10332, 10332 },
{ 240, 3000, 315, 8487, 8487 },
{ 240, 3000, 360, 6642, 6642 },
{ 270, 3000, 0, 7872, 7872 },
{ 270, 3000, 45, 6027, 6027 },
{ 270, 3000, 90, 4182, 4182 },
{ 270, 3000, 135, 2337, 2337 },
{ 270, 3000, 180, 492, 492 },
{ 270, 3000, 215, 0, 13817 },
{ 270, 3000, 270, 11562, 11562 },
{ 270, 3000, 315, 9717, 9717 },
{ 270, 3000, 360, 7872, 7872 },
{ 360, 3000, 0, 11562, 11562 },
{ 360, 3000, 45, 9717, 9717 },
{ 360, 3000, 90, 7872, 7872 },
{ 360, 3000, 135, 6027, 6027 },
{ 360, 3000, 180, 4182, 4182 },
{ 360, 3000, 215, 2747, 2747 },
{ 360, 3000, 270, 492, 492 },
{ 360, 3000, 315, 0, 13407 },
{ 360, 3000, 360, 11562, 11562 },
{ 0, 3000, 0, 11792, 11792 },
{ 0, 3000, 45, 9917, 9917 },
{ 0, 3000, 90, 8042, 8042 },
{ 0, 3000, 135, 6167, 6167 },
{ 0, 3000, 180, 4292, 4292 },
{ 0, 3000, 215, 2833, 2833 },
{ 0, 3000, 270, 542, 542 },
{ 0, 3000, 315, 0, 13667 },
{ 0, 3000, 360, 0, 11792 },
{ 72, 3000, 0, 0, 14792 },
{ 72, 3000, 45, 0, 12917 },
{ 72, 3000, 90, 11042, 11042 },
{ 72, 3000, 135, 9167, 9167 },
{ 72, 3000, 180, 7292, 7292 },
{ 72, 3000, 215, 5833, 5833 },
{ 72, 3000, 270, 3542, 3542 },
{ 72, 3000, 315, 1667, 1667 },
{ 72, 3000, 360, 0, 14792 },
{ 80, 3000, 0, 125, 125 },
{ 80, 3000, 45, 0, 13250 },
{ 80, 3000, 90, 11375, 11375 },
{ 80, 3000, 135, 9500, 9500 },
{ 80, 3000, 180, 7625, 7625 },
{ 80, 3000, 215, 6167, 6167 },
{ 80, 3000, 270, 3875, 3875 },
{ 80, 3000, 315, 2000, 2000 },
{ 80, 3000, 360, 125, 125 },
{ 90, 3000, 0, 542, 542 },
{ 90, 3000, 45, 0, 13667 },
{ 90, 3000, 90, 11792, 11792 },
{ 90, 3000, 135, 9917, 9917 },
{ 90, 3000, 180, 8042, 8042 },
{ 90, 3000, 215, 6583, 6583 },
{ 90, 3000, 270, 4292, 4292 },
{ 90, 3000, 315, 2417, 2417 },
{ 90, 3000, 360, 542, 542 },
{ 144, 3000, 0, 2792, 2792 },
{ 144, 3000, 45, 917, 917 },
{ 144, 3000, 90, 0, 14042 },
{ 144, 3000, 135, 0, 12167 },
{ 144, 3000, 180, 10292, 10292 },
{ 144, 3000, 215, 8833, 8833 },
{ 144, 3000, 270, 6542, 6542 },
{ 144, 3000, 315, 4667, 4667 },
{ 144, 3000, 360, 2792, 2792 },
{ 180, 3000, 0, 4292, 4292 },
{ 180, 3000, 45, 2417, 2417 },
{ 180, 3000, 90, 542, 542 },
{ 180, 3000, 135, 0, 13667 },
{ 180, 3000, 180, 11792, 11792 },
{ 180, 3000, 215, 10333, 10333 },
{ 180, 3000, 270, 8042, 8042 },
{ 180, 3000, 315, 6167, 6167 },
{ 180, 3000, 360, 4292, 4292 },
{ 240, 3000, 0, 6792, 6792 },
{ 240, 3000, 45, 4917, 4917 },
{ 240, 3000, 90, 3042, 3042 },
{ 240, 3000, 135, 1167, 1167 },
{ 240, 3000, 180, 0, 14292 },
{ 240, 3000, 215, 0, 12833 },
{ 240, 3000, 270, 10542, 10542 },
{ 240, 3000, 315, 8667, 8667 },
{ 240, 3000, 360, 6792, 6792 },
{ 270, 3000, 0, 8042, 8042 },
{ 270, 3000, 45, 6167, 6167 },
{ 270, 3000, 90, 4292, 4292 },
{ 270, 3000, 135, 2417, 2417 },
{ 270, 3000, 180, 542, 542 },
{ 270, 3000, 215, 0, 14083 },
{ 270, 3000, 270, 11792, 11792 },
{ 270, 3000, 315, 9917, 9917 },
{ 270, 3000, 360, 8042, 8042 },
{ 360, 3000, 0, 11792, 11792 },
{ 360, 3000, 45, 9917, 9917 },
{ 360, 3000, 90, 8042, 8042 },
{ 360, 3000, 135, 6167, 6167 },
{ 360, 3000, 180, 4292, 4292 },
{ 360, 3000, 215, 2833, 2833 },
{ 360, 3000, 270, 542, 542 },
{ 360, 3000, 315, 0, 13667 },
{ 360, 3000, 360, 11792, 11792 },
};
test_calc_inj_timeout(&test_data[0], &test_data[0]+_countof(test_data));
@ -151,124 +151,127 @@ static void test_calc_inj_timeout_720()
{
setEngineSpeed(4000, 720);
// Expected test values were generated using floating point calculations (in Excel)
static const inj_test_parameters test_data[] PROGMEM = {
// ChannelAngle (deg), PW (uS), Crank (deg), Expected Pending (uS), Expected Running (uS)
{ 0, 3000, 90, 7872, 7872 },
{ 0, 3000, 135, 6027, 6027 },
{ 0, 3000, 180, 4182, 4182 },
{ 0, 3000, 215, 2747, 2747 },
{ 0, 3000, 270, 492, 492 },
{ 0, 3000, 315, 0, 28167 },
{ 0, 3000, 360, 0, 26322 },
{ 72, 3000, 0, 0, 14514 },
{ 72, 3000, 45, 0, 12669 },
{ 72, 3000, 90, 10824, 10824 },
{ 72, 3000, 135, 8979, 8979 },
{ 72, 3000, 180, 7134, 7134 },
{ 72, 3000, 215, 5699, 5699 },
{ 72, 3000, 270, 3444, 3444 },
{ 72, 3000, 315, 1599, 1599 },
{ 72, 3000, 360, 0, 29274 },
{ 80, 3000, 0, 0, 14842 },
{ 80, 3000, 45, 0, 12997 },
{ 80, 3000, 90, 11152, 11152 },
{ 80, 3000, 135, 9307, 9307 },
{ 80, 3000, 180, 7462, 7462 },
{ 80, 3000, 215, 6027, 6027 },
{ 80, 3000, 270, 3772, 3772 },
{ 80, 3000, 315, 1927, 1927 },
{ 80, 3000, 360, 82, 82 },
{ 90, 3000, 0, 0, 15252 },
{ 90, 3000, 45, 0, 13407 },
{ 90, 3000, 90, 11562, 11562 },
{ 90, 3000, 135, 9717, 9717 },
{ 90, 3000, 180, 7872, 7872 },
{ 90, 3000, 215, 6437, 6437 },
{ 90, 3000, 270, 4182, 4182 },
{ 90, 3000, 315, 2337, 2337 },
{ 90, 3000, 360, 492, 492 },
{ 144, 3000, 0, 0, 17466 },
{ 144, 3000, 45, 0, 15621 },
{ 144, 3000, 90, 0, 13776 },
{ 144, 3000, 135, 0, 11931 },
{ 144, 3000, 180, 10086, 10086 },
{ 144, 3000, 215, 8651, 8651 },
{ 144, 3000, 270, 6396, 6396 },
{ 144, 3000, 315, 4551, 4551 },
{ 144, 3000, 360, 2706, 2706 },
{ 180, 3000, 0, 0, 18942 },
{ 180, 3000, 45, 0, 17097 },
{ 180, 3000, 90, 0, 15252 },
{ 180, 3000, 135, 0, 13407 },
{ 180, 3000, 180, 11562, 11562 },
{ 180, 3000, 215, 10127, 10127 },
{ 180, 3000, 270, 7872, 7872 },
{ 180, 3000, 315, 6027, 6027 },
{ 180, 3000, 360, 4182, 4182 },
{ 240, 3000, 0, 0, 21402 },
{ 240, 3000, 45, 0, 19557 },
{ 240, 3000, 90, 0, 17712 },
{ 240, 3000, 135, 0, 15867 },
{ 240, 3000, 180, 0, 14022 },
{ 240, 3000, 215, 0, 12587 },
{ 240, 3000, 270, 10332, 10332 },
{ 240, 3000, 315, 8487, 8487 },
{ 240, 3000, 360, 6642, 6642 },
{ 270, 3000, 0, 0, 22632 },
{ 270, 3000, 45, 0, 20787 },
{ 270, 3000, 90, 0, 18942 },
{ 270, 3000, 135, 0, 17097 },
{ 270, 3000, 180, 0, 15252 },
{ 270, 3000, 215, 0, 13817 },
{ 270, 3000, 270, 11562, 11562 },
{ 270, 3000, 315, 9717, 9717 },
{ 270, 3000, 360, 7872, 7872 },
{ 360, 3000, 0, 0, 26322 },
{ 360, 3000, 45, 0, 24477 },
{ 360, 3000, 90, 0, 22632 },
{ 360, 3000, 135, 0, 20787 },
{ 360, 3000, 180, 0, 18942 },
{ 360, 3000, 215, 0, 17507 },
{ 360, 3000, 270, 0, 15252 },
{ 360, 3000, 315, 0, 13407 },
{ 360, 3000, 360, 11562, 11562 },
{ 480, 3000, 0, 1722, 1722 },
{ 480, 3000, 45, 0, 29397 },
{ 480, 3000, 90, 0, 27552 },
{ 480, 3000, 135, 0, 25707 },
{ 480, 3000, 180, 0, 23862 },
{ 480, 3000, 215, 0, 22427 },
{ 480, 3000, 270, 0, 20172 },
{ 480, 3000, 315, 0, 18327 },
{ 480, 3000, 360, 0, 16482 },
{ 540, 3000, 0, 4182, 4182 },
{ 540, 3000, 45, 2337, 2337 },
{ 540, 3000, 90, 492, 492 },
{ 540, 3000, 135, 0, 28167 },
{ 540, 3000, 180, 0, 26322 },
{ 540, 3000, 215, 0, 24887 },
{ 540, 3000, 270, 0, 22632 },
{ 540, 3000, 315, 0, 20787 },
{ 540, 3000, 360, 0, 18942 },
{ 600, 3000, 0, 6642, 6642 },
{ 600, 3000, 45, 4797, 4797 },
{ 600, 3000, 90, 2952, 2952 },
{ 600, 3000, 135, 1107, 1107 },
{ 600, 3000, 180, 0, 28782 },
{ 600, 3000, 215, 0, 27347 },
{ 600, 3000, 270, 0, 25092 },
{ 600, 3000, 315, 0, 23247 },
{ 600, 3000, 360, 0, 21402 },
{ 630, 3000, 0, 7872, 7872 },
{ 630, 3000, 45, 6027, 6027 },
{ 630, 3000, 90, 4182, 4182 },
{ 630, 3000, 135, 2337, 2337 },
{ 630, 3000, 180, 492, 492 },
{ 630, 3000, 215, 0, 28577 },
{ 630, 3000, 270, 0, 26322 },
{ 630, 3000, 315, 0, 24477 },
{ 630, 3000, 360, 0, 22632 },
};
{ 0, 3000, 0, 11792, 11792 },
{ 0, 3000, 45, 9917, 9917 },
{ 0, 3000, 90, 8042, 8042 },
{ 0, 3000, 135, 6167, 6167 },
{ 0, 3000, 180, 4292, 4292 },
{ 0, 3000, 215, 2833, 2833 },
{ 0, 3000, 270, 542, 542 },
{ 0, 3000, 315, 0, 28667 },
{ 0, 3000, 360, 0, 26792 },
{ 72, 3000, 0, 0, 14792 },
{ 72, 3000, 45, 0, 12917 },
{ 72, 3000, 90, 11042, 11042 },
{ 72, 3000, 135, 9167, 9167 },
{ 72, 3000, 180, 7292, 7292 },
{ 72, 3000, 215, 5833, 5833 },
{ 72, 3000, 270, 3542, 3542 },
{ 72, 3000, 315, 1667, 1667 },
{ 72, 3000, 360, 0, 29792 },
{ 80, 3000, 0, 0, 15125 },
{ 80, 3000, 45, 0, 13250 },
{ 80, 3000, 90, 11375, 11375 },
{ 80, 3000, 135, 9500, 9500 },
{ 80, 3000, 180, 7625, 7625 },
{ 80, 3000, 215, 6167, 6167 },
{ 80, 3000, 270, 3875, 3875 },
{ 80, 3000, 315, 2000, 2000 },
{ 80, 3000, 360, 125, 125 },
{ 90, 3000, 0, 0, 15542 },
{ 90, 3000, 45, 0, 13667 },
{ 90, 3000, 90, 11792, 11792 },
{ 90, 3000, 135, 9917, 9917 },
{ 90, 3000, 180, 8042, 8042 },
{ 90, 3000, 215, 6583, 6583 },
{ 90, 3000, 270, 4292, 4292 },
{ 90, 3000, 315, 2417, 2417 },
{ 90, 3000, 360, 542, 542 },
{ 144, 3000, 0, 0, 17792 },
{ 144, 3000, 45, 0, 15917 },
{ 144, 3000, 90, 0, 14042 },
{ 144, 3000, 135, 0, 12167 },
{ 144, 3000, 180, 10292, 10292 },
{ 144, 3000, 215, 8833, 8833 },
{ 144, 3000, 270, 6542, 6542 },
{ 144, 3000, 315, 4667, 4667 },
{ 144, 3000, 360, 2792, 2792 },
{ 180, 3000, 0, 0, 19292 },
{ 180, 3000, 45, 0, 17417 },
{ 180, 3000, 90, 0, 15542 },
{ 180, 3000, 135, 0, 13667 },
{ 180, 3000, 180, 11792, 11792 },
{ 180, 3000, 215, 10333, 10333 },
{ 180, 3000, 270, 8042, 8042 },
{ 180, 3000, 315, 6167, 6167 },
{ 180, 3000, 360, 4292, 4292 },
{ 240, 3000, 0, 0, 21792 },
{ 240, 3000, 45, 0, 19917 },
{ 240, 3000, 90, 0, 18042 },
{ 240, 3000, 135, 0, 16167 },
{ 240, 3000, 180, 0, 14292 },
{ 240, 3000, 215, 0, 12833 },
{ 240, 3000, 270, 10542, 10542 },
{ 240, 3000, 315, 8667, 8667 },
{ 240, 3000, 360, 6792, 6792 },
{ 270, 3000, 0, 0, 23042 },
{ 270, 3000, 45, 0, 21167 },
{ 270, 3000, 90, 0, 19292 },
{ 270, 3000, 135, 0, 17417 },
{ 270, 3000, 180, 0, 15542 },
{ 270, 3000, 215, 0, 14083 },
{ 270, 3000, 270, 11792, 11792 },
{ 270, 3000, 315, 9917, 9917 },
{ 270, 3000, 360, 8042, 8042 },
{ 360, 3000, 0, 0, 26792 },
{ 360, 3000, 45, 0, 24917 },
{ 360, 3000, 90, 0, 23042 },
{ 360, 3000, 135, 0, 21167 },
{ 360, 3000, 180, 0, 19292 },
{ 360, 3000, 215, 0, 17833 },
{ 360, 3000, 270, 0, 15542 },
{ 360, 3000, 315, 0, 13667 },
{ 360, 3000, 360, 11792, 11792 },
{ 480, 3000, 0, 1792, 1792 },
{ 480, 3000, 45, 0, 29917 },
{ 480, 3000, 90, 0, 28042 },
{ 480, 3000, 135, 0, 26167 },
{ 480, 3000, 180, 0, 24292 },
{ 480, 3000, 215, 0, 22833 },
{ 480, 3000, 270, 0, 20542 },
{ 480, 3000, 315, 0, 18667 },
{ 480, 3000, 360, 0, 16792 },
{ 540, 3000, 0, 4292, 4292 },
{ 540, 3000, 45, 2417, 2417 },
{ 540, 3000, 90, 542, 542 },
{ 540, 3000, 135, 0, 28667 },
{ 540, 3000, 180, 0, 26792 },
{ 540, 3000, 215, 0, 25333 },
{ 540, 3000, 270, 0, 23042 },
{ 540, 3000, 315, 0, 21167 },
{ 540, 3000, 360, 0, 19292 },
{ 600, 3000, 0, 6792, 6792 },
{ 600, 3000, 45, 4917, 4917 },
{ 600, 3000, 90, 3042, 3042 },
{ 600, 3000, 135, 1167, 1167 },
{ 600, 3000, 180, 0, 29292 },
{ 600, 3000, 215, 0, 27833 },
{ 600, 3000, 270, 0, 25542 },
{ 600, 3000, 315, 0, 23667 },
{ 600, 3000, 360, 0, 21792 },
{ 630, 3000, 0, 8042, 8042 },
{ 630, 3000, 45, 6167, 6167 },
{ 630, 3000, 90, 4292, 4292 },
{ 630, 3000, 135, 2417, 2417 },
{ 630, 3000, 180, 542, 542 },
{ 630, 3000, 215, 0, 29083 },
{ 630, 3000, 270, 0, 26792 },
{ 630, 3000, 315, 0, 24917 },
{ 630, 3000, 360, 0, 23042 },
};
test_calc_inj_timeout(&test_data[0], &test_data[0]+_countof(test_data));
}

103
test/timer.hpp Normal file
View File

@ -0,0 +1,103 @@
#pragma once
#include <inttypes.h>
#if defined(__AVR__)
#include <Arduino.h>
#else
#include <sys/time.h>
#endif
#if !defined(MILLIS_PER_SEC)
#define MILLIS_PER_SEC 1000ULL
#endif
#if !defined(MICROS_PER_SEC)
#define MICROS_PER_SEC (MILLIS_PER_SEC*1000)
#endif
#if !defined(NANOS_PER_SEC)
#define NANOS_PER_SEC (MICROS_PER_SEC*1000)
#endif
class timer {
private:
#if defined(__AVR__)
uint32_t start_time;
uint32_t end_time;
#else
struct timeval start_time;
struct timeval end_time;
#endif
public:
timer() {
}
void start() {
#if defined(__AVR__)
start_time = micros();
#else
gettimeofday(&start_time, NULL);
#endif
}
void stop() {
#if defined(__AVR__)
end_time = micros();
#else
gettimeofday(&end_time, NULL);
#endif
}
uint32_t duration_micros() {
#if defined(__AVR__)
return end_time-start_time;
#else
return (uint32_t)(((end_time.tv_sec - start_time.tv_sec) * MICROS_PER_SEC) + (end_time.tv_usec - start_time.tv_usec));
#endif
}
};
template <typename TLoop, typename TParam>
void measure_executiontime(uint16_t iterations, TLoop from, TLoop to, TLoop step, timer &measure, TParam param, void (*pTestFun)(TLoop, TParam)) {
measure.start();
for (uint16_t loop=0; loop<iterations; ++loop)
{
for (TLoop a = from; a < to; a+=step)
{
pTestFun(a, param);
}
}
measure.stop();
}
template <typename TParam>
struct execution_time {
TParam result;
uint32_t durationMicros;
};
template <typename TParam>
struct comparative_execution_times {
execution_time<TParam> timeA;
execution_time<TParam> timeB;
};
template <typename TLoop, typename TParam>
comparative_execution_times<TParam> compare_executiontime(uint16_t iterations, TLoop from, TLoop to, TLoop step, void (*pTestFunA)(TLoop, TParam&), void (*pTestFunB)(TLoop, TParam&)) {
timer timerA;
TParam paramA = 0;
measure_executiontime<TLoop, TParam&>(iterations, from, to, step, timerA, paramA, pTestFunA);
timer timerB;
TParam paramB = 0;
measure_executiontime<TLoop, TParam&>(iterations, from, to, step, timerB, paramB, pTestFunB);
char buffer[128];
sprintf(buffer, "Timing: %" PRIu32 ", %" PRIu32, timerA.duration_micros(), timerB.duration_micros());
TEST_MESSAGE(buffer);
return comparative_execution_times<TParam> {
.timeA = execution_time<TParam> { .result = paramA, .durationMicros = timerA.duration_micros()},
.timeB = execution_time<TParam> { .result = paramB, .durationMicros = timerB.duration_micros()}
};
}