Skip to content

Commit

Permalink
Performance: optimized 32-bit shifts (speeduino#1187)
Browse files Browse the repository at this point in the history
* Add optimized 32-bit shifting

* Tooth based time to angle coversion is only used by a few decoders.
So move the functions into decoders.cpp

* Better separation of deocders and crank maths.

* Apply optimised shifts

* Doxygen
  • Loading branch information
adbancroft authored May 30, 2024
1 parent 478e16c commit 0f13753
Show file tree
Hide file tree
Showing 13 changed files with 1,257 additions and 308 deletions.
380 changes: 267 additions & 113 deletions Doxyfile

Large diffs are not rendered by default.

687 changes: 687 additions & 0 deletions speeduino/bit_shifts.h

Large diffs are not rendered by default.

67 changes: 26 additions & 41 deletions speeduino/crankMaths.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "globals.h"
#include "crankMaths.h"
#include "decoders.h"
#include "bit_shifts.h"

#define SECOND_DERIV_ENABLED 0

Expand All @@ -10,51 +10,36 @@ byte deltaToothCount = 0; //The last tooth that was used with the deltaV calc
int rpmDelta;
#endif

uint32_t angleToTimeMicroSecPerDegree(uint16_t angle) {
UQ24X8_t micros = (uint32_t)angle * (uint32_t)microsPerDegree;
return RSHIFT_ROUND(micros, microsPerDegree_Shift);
typedef uint32_t UQ24X8_t;
static constexpr uint8_t UQ24X8_Shift = 8U;

/** @brief uS per degree at current RPM in UQ24.8 fixed point */
static UQ24X8_t microsPerDegree;
static constexpr uint8_t microsPerDegree_Shift = UQ24X8_Shift;

typedef uint16_t UQ1X15_t;
static 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
*/
static UQ1X15_t degreesPerMicro;
static constexpr uint8_t degreesPerMicro_Shift = UQ1X15_Shift;

void setAngleConverterRevolutionTime(uint32_t revolutionTime) {
microsPerDegree = div360(lshift<microsPerDegree_Shift>(revolutionTime));
degreesPerMicro = (uint16_t)UDIV_ROUND_CLOSEST(lshift<degreesPerMicro_Shift>(UINT32_C(360)), revolutionTime, uint32_t);
}

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);
}
uint32_t angleToTimeMicroSecPerDegree(uint16_t angle) {
UQ24X8_t micros = (uint32_t)angle * (uint32_t)microsPerDegree;
return rshift_round<microsPerDegree_Shift>(micros);
}

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);
}
return rshift_round<degreesPerMicro_Shift>(degFixed);
}

#if SECOND_DERIV_ENABLED!=0
Expand Down Expand Up @@ -92,7 +77,7 @@ void doCrankSpeedCalcs(void)
uint32_t toothDeltaT = toothHistory[toothHistoryIndex];
//long timeToLastTooth = micros() - toothLastToothTime;

rpmDelta = (toothDeltaV << 10) / (6 * toothDeltaT);
rpmDelta = lshift<10>(toothDeltaV) / (6 * toothDeltaT);
}

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
Expand Down
33 changes: 9 additions & 24 deletions speeduino/crankMaths.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ static inline int16_t injectorLimits(int16_t angle)
*/
#define MIN_RPM ((MICROS_PER_DEG_1_RPM/(UINT16_MAX/16UL))+1UL)

/**
* @brief Set the revolution time, from which some of the degree<-->angle conversions are derived
*
* @param revolutionTime The crank revolution time.
*/
void setAngleConverterRevolutionTime(uint32_t revolutionTime);

/**
* @name Converts angular degrees to the time interval that amount of rotation
* will take at current RPM.
Expand All @@ -56,39 +63,17 @@ static inline int16_t injectorLimits(int16_t angle)
* @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.
*
* Inverse of angleToTimeMicroSecPerDegree
*
* @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
85 changes: 61 additions & 24 deletions speeduino/decoders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,6 @@ volatile unsigned long triggerThirdFilterTime; // The shortest time (in uS) that

volatile uint8_t decoderState = 0;

UQ24X8_t microsPerDegree;
UQ1X15_t degreesPerMicro;

unsigned int triggerSecFilterTime_duration; // The shortest valid time (in uS) pulse DURATION
volatile uint16_t triggerToothAngle; //The number of crank degrees that elapse per tooth
byte checkSyncToothCount; //How many teeth must've been seen on this revolution before we try to confirm sync (Useful for missing tooth type decoders)
Expand Down Expand Up @@ -293,6 +290,47 @@ void loggerTertiaryISR(void)
}
}

#if false
#if !defined(UNIT_TEST)
static
#endif
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);
}
}
#endif

static 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);
}
}

static inline bool IsCranking(const statuses &status) {
return (status.RPM < status.crankRPM) && (status.startRevolutions == 0U);
}
Expand All @@ -305,8 +343,7 @@ static __attribute__((noinline)) bool SetRevolutionTime(uint32_t revTime)
{
if (revTime!=revolutionTime) {
revolutionTime = revTime;
microsPerDegree = div360(revolutionTime << microsPerDegree_Shift);
degreesPerMicro = (uint16_t)UDIV_ROUND_CLOSEST((UINT32_C(360) << degreesPerMicro_Shift), revolutionTime, uint32_t);
setAngleConverterRevolutionTime(revolutionTime);
return true;
}
return false;
Expand Down Expand Up @@ -1470,7 +1507,7 @@ void triggerPri_4G63(void)
if(configPage2.nCylinders == 4)
{
triggerToothAngle = 110;
triggerFilterTime = (curGap * 3) >> 3; //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
triggerFilterTime = rshift<3>(curGap * 3UL); //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
}
else if(configPage2.nCylinders == 6)
{
Expand Down Expand Up @@ -1516,7 +1553,7 @@ void triggerPri_4G63(void)
triggerToothAngle = 70;
if(configPage2.nCylinders == 4)
{
triggerFilterTime = (curGap * 11) >> 3;//96.26 degrees with a target of 110
triggerFilterTime = rshift<3>(curGap * 11UL);//96.26 degrees with a target of 110
}
else
{
Expand All @@ -1528,7 +1565,7 @@ void triggerPri_4G63(void)
if(configPage2.nCylinders == 4)
{
triggerToothAngle = 110;
triggerFilterTime = (curGap * 9) >> 5; //61.87 degrees with a target of 70
triggerFilterTime = rshift<5>(curGap * 9UL); //61.87 degrees with a target of 70
}
else
{
Expand Down Expand Up @@ -2451,7 +2488,7 @@ void triggerPri_Miata9905(void)
{
//Lite filter
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) || (toothCurrentCount == 5) || (toothCurrentCount == 7) ) { triggerToothAngle = 70; triggerFilterTime = curGap; } //Trigger filter is set to whatever time it took to do 70 degrees (Next trigger is 110 degrees away)
else { triggerToothAngle = 110; triggerFilterTime = (curGap * 3) >> 3; } //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
else { triggerToothAngle = 110; triggerFilterTime = rshift<3>(curGap * 3UL); } //Trigger filter is set to (110*3)/8=41.25=41 degrees (Next trigger is 70 degrees away).
}
else if(configPage4.triggerFilter == 2)
{
Expand All @@ -2462,8 +2499,8 @@ void triggerPri_Miata9905(void)
else if (configPage4.triggerFilter == 3)
{
//Aggressive filter level
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) || (toothCurrentCount == 5) || (toothCurrentCount == 7) ) { triggerToothAngle = 70; triggerFilterTime = (curGap * 11) >> 3 ; } //96.26 degrees with a target of 110
else { triggerToothAngle = 110; triggerFilterTime = (curGap * 9) >> 5; } //61.87 degrees with a target of 70
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) || (toothCurrentCount == 5) || (toothCurrentCount == 7) ) { triggerToothAngle = 70; triggerFilterTime = rshift<3>(curGap * 11UL) ; } //96.26 degrees with a target of 110
else { triggerToothAngle = 110; triggerFilterTime = rshift<5>(curGap * 9UL); } //61.87 degrees with a target of 70
}
else if (configPage4.triggerFilter == 0)
{
Expand Down Expand Up @@ -2695,7 +2732,7 @@ void triggerPri_MazdaAU(void)

//Whilst this is an uneven tooth pattern, if the specific angle between the last 2 teeth is specified, 1st deriv prediction can be used
if( (toothCurrentCount == 1) || (toothCurrentCount == 3) ) { triggerToothAngle = 72; triggerFilterTime = curGap; } //Trigger filter is set to whatever time it took to do 72 degrees (Next trigger is 108 degrees away)
else { triggerToothAngle = 108; triggerFilterTime = (curGap * 3) >> 3; } //Trigger filter is set to (108*3)/8=40 degrees (Next trigger is 70 degrees away).
else { triggerToothAngle = 108; triggerFilterTime = rshift<3>(curGap * 3UL); } //Trigger filter is set to (108*3)/8=40 degrees (Next trigger is 70 degrees away).

toothLastMinusOneToothTime = toothLastToothTime;
toothLastToothTime = curTime;
Expand Down Expand Up @@ -5709,13 +5746,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 17 degrees
triggerFilterTime = curGap>>3;
triggerFilterTime = rshift<3>(curGap);
break;
case 2: // 50 % 35 degrees
triggerFilterTime = (curGap>>3) + (curGap>>4);
triggerFilterTime = rshift<3>(curGap) + rshift<4>(curGap);
break;
case 3: // 75 % 52 degrees
triggerFilterTime = (curGap>>2) + (curGap>>4);
triggerFilterTime = rshift<2>(curGap) + rshift<4>(curGap);
break;
default:
triggerFilterTime = 0;
Expand All @@ -5728,13 +5765,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 8 degrees
triggerFilterTime = curGap>>3;
triggerFilterTime = rshift<3>(curGap);
break;
case 2: // 50 % 17 degrees
triggerFilterTime = curGap>>2;
triggerFilterTime = rshift<2>(curGap);
break;
case 3: // 75 % 25 degrees
triggerFilterTime = (curGap>>2) + (curGap>>3);
triggerFilterTime = rshift<2>(curGap) + rshift<3>(curGap);
break;
default:
triggerFilterTime = 0;
Expand Down Expand Up @@ -5766,13 +5803,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 17 degrees
triggerFilterTime = curGap>>3;
triggerFilterTime = rshift<3>(curGap);
break;
case 2: // 50 % 35 degrees
triggerFilterTime = curGap>>2;
triggerFilterTime = rshift<2>(curGap);
break;
case 3: // 75 % 52 degrees
triggerFilterTime = (curGap>>2) + (curGap>>3);
triggerFilterTime = rshift<2>(curGap) + rshift<3>(curGap);
break;
default:
triggerFilterTime = 0;
Expand All @@ -5786,13 +5823,13 @@ void triggerPri_SuzukiK6A(void)
switch (configPage4.triggerFilter)
{
case 1: // 25 % 42 degrees
triggerFilterTime = (curGap>>1) + (curGap>>3);
triggerFilterTime = rshift<1>(curGap) + rshift<3>(curGap);
break;
case 2: // 50 % 85 degrees
triggerFilterTime = curGap + (curGap>>2);
triggerFilterTime = curGap + rshift<2>(curGap);
break;
case 3: // 75 % 127 degrees
triggerFilterTime = curGap + (curGap>>1) + (curGap>>2);
triggerFilterTime = curGap + rshift<1>(curGap) + rshift<2>(curGap);
break;
default:
triggerFilterTime = 0;
Expand Down
17 changes: 0 additions & 17 deletions speeduino/decoders.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,23 +303,6 @@ extern unsigned long elapsedTime;
extern unsigned long lastCrankAngleCalc;
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;
Expand Down
Loading

0 comments on commit 0f13753

Please sign in to comment.