Skip to content

Commit

Permalink
Performance: inline many functions
Browse files Browse the repository at this point in the history
  • Loading branch information
adbancroft committed Dec 9, 2024
1 parent 9c14170 commit 605e184
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 85 deletions.
26 changes: 4 additions & 22 deletions speeduino/crankMaths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,21 @@ byte deltaToothCount = 0; //The last tooth that was used with the deltaV calc
int rpmDelta;
#endif

typedef uint32_t UQ24X8_t;
static constexpr uint8_t UQ24X8_Shift = 8U;

/** @brief µS per degree at current RPM in UQ24.8 fixed point
*
* Ranges between
* * 1040649 (4065.039 µS/°) at MIN_RPM/MIN_REVOLUTION_TIME
* * 2370 (9.258333 µS/°) at MAX_RPM/MAX_REVOLUTION_TIME
*/
static UQ24X8_t microsPerDegree;
static constexpr uint8_t microsPerDegree_Shift = UQ24X8_Shift;

typedef uint16_t UQ1X15_t;
static constexpr uint8_t UQ1X15_Shift = 15U;
crank_math_detail::UQ24X8_t microsPerDegree; // cppcheck-suppress misra-c2012-8.4 ; To allow inlining in header

/** @brief Degrees per µS in UQ1.15 fixed point.
*
* Ranges between
* * 8 (0.000246 °/µS) at MIN_RPM/MIN_REVOLUTION_TIME
* * 3539 (0.108011 °/µS) at MAX_RPM/MAX_REVOLUTION_TIME
*/
static UQ1X15_t degreesPerMicro;
static constexpr uint8_t degreesPerMicro_Shift = UQ1X15_Shift;
crank_math_detail::UQ1X15_t degreesPerMicro; // cppcheck-suppress misra-c2012-8.4 ; To allow inlining in header

/** @brief The time in µS that one revolution would take at current speed */
uint32_t revolutionTime = MIN_REVOLUTION_TIME; // cppcheck-suppress misra-c2012-8.4 ; To allow inlining in header
Expand All @@ -41,23 +33,13 @@ bool setRevolutionTime(uint32_t revTime) {

if (hasChanged) {
revolutionTime = revTime;
microsPerDegree = div360(lshift<microsPerDegree_Shift>(revTime));
degreesPerMicro = (uint16_t)UDIV_ROUND_CLOSEST(lshift<degreesPerMicro_Shift>(UINT32_C(360)), revTime, uint32_t);
microsPerDegree = div360(lshift<crank_math_detail::microsPerDegree_Shift>(revTime));
degreesPerMicro = (uint16_t)UDIV_ROUND_CLOSEST(lshift<crank_math_detail::degreesPerMicro_Shift>(UINT32_C(360)), revTime, uint32_t);
}

return hasChanged;
}

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

#if SECOND_DERIV_ENABLED!=0
void doCrankSpeedCalcs(void)
{
Expand Down
35 changes: 32 additions & 3 deletions speeduino/crankMaths.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static constexpr uint32_t MAX_REVOLUTION_TIME = MICROS_PER_MIN/MIN_RPM;
* @param angle A crank angle in degrees
* @return int16_t
*/
static inline int16_t ignitionLimits(int16_t angle) {
static CRITICAL_INLINE int16_t ignitionLimits(int16_t angle) {
return nudge(0, CRANK_ANGLE_MAX_IGN-1, angle, CRANK_ANGLE_MAX_IGN);
}

Expand Down Expand Up @@ -87,6 +87,16 @@ static inline uint32_t getRevolutionTime(void) {
return revolutionTime;
}

/// @cond
namespace crank_math_detail {
// Private to crankMath
typedef uint32_t UQ24X8_t;
static constexpr uint8_t UQ24X8_Shift = 8U;
static constexpr uint8_t microsPerDegree_Shift = UQ24X8_Shift;
}
/// @endcond


/**
* @brief Converts angular degrees to the time interval that amount of rotation
* will take at the current crank revolution time (@ref setRevolutionTime).
Expand All @@ -97,7 +107,21 @@ static inline uint32_t getRevolutionTime(void) {
* @param angle Angle in degrees
* @return Time interval in µS
*/
uint32_t angleToTimeMicroSecPerDegree(uint16_t angle);
// uint32_t angleToTimeMicroSecPerDegree(uint16_t angle);
static CRITICAL_INLINE uint32_t angleToTimeMicroSecPerDegree(uint16_t angle) {
extern crank_math_detail::UQ24X8_t microsPerDegree;
crank_math_detail::UQ24X8_t micros = (uint32_t)angle * (uint32_t)microsPerDegree;
return rshift_round<crank_math_detail::microsPerDegree_Shift>(micros);
}

/// @cond
namespace crank_math_detail {
// Private to crankMath
typedef uint16_t UQ1X15_t;
static constexpr uint8_t UQ1X15_Shift = 15U;
static constexpr uint8_t degreesPerMicro_Shift = UQ1X15_Shift;
}
/// @endcond

/**
* @brief Converts a time interval in µS to the equivalent degrees of angular (crank)
Expand All @@ -108,7 +132,12 @@ uint32_t angleToTimeMicroSecPerDegree(uint16_t angle);
* @param time Time interval in µS
* @return Angle in degrees
*/
uint16_t timeToAngleDegPerMicroSec(uint32_t time);
static CRITICAL_INLINE uint16_t timeToAngleDegPerMicroSec(uint32_t time) {
extern crank_math_detail::UQ1X15_t degreesPerMicro;
uint32_t degFixed = time * (uint32_t)degreesPerMicro;
return rshift_round<crank_math_detail::degreesPerMicro_Shift>(degFixed);
}


/** @brief Calculate RPM based on the current crank revolution time (@ref setRevolutionTime). */
static inline uint16_t rpmFromRevolutionTime(void) {
Expand Down
10 changes: 9 additions & 1 deletion speeduino/maths.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@
#include "src/libdivide/constant_fast_div.h"
#endif

uint8_t random1to100(void);
/**
* @brief When a function really must be inline.
*
* Usually best to let the compiler optimize, so use sparingly & only after
* comparing performance with & without applying it.
*/
#define CRITICAL_INLINE inline __attribute__((always_inline))

extern uint8_t random1to100(void);

/**
* @defgroup group-rounded-div Rounding integer division
Expand Down
2 changes: 1 addition & 1 deletion speeduino/pages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ inline const page_iterator_t create_raw_iterator(void *pBuffer, uint8_t pageNum,
//
// Alternative implementation would be to encode the mapping into data structures
// That uses flash memory, which is scarce. And it was too slow.
static inline __attribute__((always_inline)) // <-- this is critical for performance
static CRITICAL_INLINE // <-- this is critical for performance
page_iterator_t map_page_offset_to_entity(uint8_t pageNumber, uint16_t offset)
{
// The start address of the 1st entity in any page.
Expand Down
22 changes: 11 additions & 11 deletions speeduino/schedule_calcs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "maths.h"
#include "timers.h"

static SCHEDULE_INLINE void setOpenAngle(FuelSchedule &schedule, uint16_t pwDegrees, uint16_t injAngle)
static CRITICAL_INLINE void setOpenAngle(FuelSchedule &schedule, uint16_t pwDegrees, uint16_t injAngle)
{
// 0<=injAngle<=720°
// 0<=injChannelDegrees<=720°
Expand All @@ -23,21 +23,21 @@ static SCHEDULE_INLINE void setOpenAngle(FuelSchedule &schedule, uint16_t pwDegr
while (schedule.openAngle>(uint16_t)CRANK_ANGLE_MAX_INJ) { schedule.openAngle = schedule.openAngle - (uint16_t)CRANK_ANGLE_MAX_INJ; }
}

static SCHEDULE_INLINE uint32_t _calculateAngularTime(const Schedule &schedule, uint16_t eventAngle, uint16_t crankAngle, uint16_t maxAngle) {
static CRITICAL_INLINE uint32_t _calculateAngularTime(const Schedule &schedule, uint16_t eventAngle, uint16_t crankAngle, uint16_t maxAngle) {
int16_t delta = eventAngle - crankAngle;
if (delta<0 && isRunning(schedule)) {
delta = delta + (int16_t)maxAngle;
}
return delta<0 ? 0U : angleToTimeMicroSecPerDegree((uint16_t)delta);
}

static SCHEDULE_INLINE uint16_t _adjustToTDC(int16_t angle, uint16_t angleOffset, uint16_t maxAngle) {
static CRITICAL_INLINE uint16_t _adjustToTDC(int16_t angle, uint16_t angleOffset, uint16_t maxAngle) {
angle = angle - (int)angleOffset;
if( angle < 0) { return angle + (int)maxAngle; }
return angle;
}

static SCHEDULE_INLINE uint32_t _calculateAngularTime(const Schedule &schedule, uint16_t angleOffset, uint16_t eventAngle, uint16_t crankAngle, uint16_t maxAngle) {
static CRITICAL_INLINE uint32_t _calculateAngularTime(const Schedule &schedule, uint16_t angleOffset, uint16_t eventAngle, uint16_t crankAngle, uint16_t maxAngle) {
if (angleOffset==0U) { // Optimize for zero channel angle - no need to adjust start & crank angles
return _calculateAngularTime(schedule, eventAngle, crankAngle, maxAngle);
}
Expand All @@ -53,38 +53,38 @@ static SCHEDULE_INLINE uint32_t _calculateAngularTime(const Schedule &schedule,
maxAngle);
}

static SCHEDULE_INLINE uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, int16_t crankAngle)
static CRITICAL_INLINE uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, int16_t crankAngle)
{
return _calculateAngularTime(schedule, schedule.channelDegrees, schedule.openAngle, crankAngle, CRANK_ANGLE_MAX_INJ);
}

static SCHEDULE_INLINE int16_t _calculateSparkAngle(const IgnitionSchedule &schedule, int8_t advance) {
static CRITICAL_INLINE int16_t _calculateSparkAngle(const IgnitionSchedule &schedule, int8_t advance) {
int16_t angle = (schedule.channelDegrees==0U ? CRANK_ANGLE_MAX_IGN : (int16_t)schedule.channelDegrees) - advance;
if(angle > CRANK_ANGLE_MAX_IGN) {angle -= CRANK_ANGLE_MAX_IGN;}
return angle;
}

static SCHEDULE_INLINE int16_t _calculateCoilChargeAngle(uint16_t dwellAngle, int16_t dischargeAngle) {
static CRITICAL_INLINE int16_t _calculateCoilChargeAngle(uint16_t dwellAngle, int16_t dischargeAngle) {
if (dischargeAngle>(int16_t)dwellAngle) {
return dischargeAngle - (int16_t)dwellAngle;
}
return dischargeAngle + CRANK_ANGLE_MAX_IGN - (int16_t)dwellAngle;
}

static SCHEDULE_INLINE void calculateIgnitionAngles(IgnitionSchedule &schedule, uint16_t dwellAngle, int8_t advance)
static CRITICAL_INLINE void calculateIgnitionAngles(IgnitionSchedule &schedule, uint16_t dwellAngle, int8_t advance)
{
schedule.dischargeAngle = _calculateSparkAngle(schedule, advance);
schedule.chargeAngle = _calculateCoilChargeAngle(dwellAngle, schedule.dischargeAngle);
}


static SCHEDULE_INLINE void calculateIgnitionTrailingRotary(IgnitionSchedule &leading, uint16_t dwellAngle, int16_t rotarySplitDegrees, IgnitionSchedule &trailing)
static CRITICAL_INLINE void calculateIgnitionTrailingRotary(IgnitionSchedule &leading, uint16_t dwellAngle, int16_t rotarySplitDegrees, IgnitionSchedule &trailing)
{
trailing.dischargeAngle = (int16_t)ignitionLimits(leading.dischargeAngle + rotarySplitDegrees);
trailing.chargeAngle = (int16_t)ignitionLimits(trailing.dischargeAngle - dwellAngle);
}

static SCHEDULE_INLINE uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedule, int16_t crankAngle)
static CRITICAL_INLINE uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedule, int16_t crankAngle)
{
return _calculateAngularTime(schedule, schedule.channelDegrees, schedule.chargeAngle, crankAngle, CRANK_ANGLE_MAX_IGN);
}
Expand All @@ -94,7 +94,7 @@ static SCHEDULE_INLINE uint32_t _calculateIgnitionTimeout(const IgnitionSchedule
// So the timing to begin & end charging the coil is based on crank angle.
// With a more accurate crank angle, we can increase the precision of the
// spark timing.
static SCHEDULE_INLINE void adjustCrankAngle(IgnitionSchedule &schedule, int16_t crankAngle) {
static CRITICAL_INLINE void adjustCrankAngle(IgnitionSchedule &schedule, int16_t crankAngle) {
constexpr uint8_t MIN_CYCLES_FOR_CORRECTION = 6U;

ATOMIC() {
Expand Down
2 changes: 1 addition & 1 deletion speeduino/schedule_state_machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void defaultRunningToPending(Schedule *schedule) {
schedule->_status = PENDING;
}

static inline bool hasNextSchedule(const Schedule &schedule) {
static CRITICAL_INLINE bool hasNextSchedule(const Schedule &schedule) {
return schedule._status==RUNNING_WITHNEXT;
}

Expand Down
18 changes: 0 additions & 18 deletions speeduino/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1033,24 +1033,6 @@ void setCallbacks(Schedule &schedule, voidVoidCallback pStartCallback, voidVoidC
schedule._pEndCallback = pEndCallback;
}

void _setSchedulePending(Schedule &schedule, uint32_t timeout, uint32_t duration)
{
//The following must be enclosed in the noInterupts block to avoid contention caused if the relevant interrupt fires before the state is fully set
schedule._duration = uS_TO_TIMER_COMPARE(duration);
schedule._compare = schedule._counter + uS_TO_TIMER_COMPARE(timeout);
schedule._status = PENDING; //Turn this schedule on
}

void _setScheduleNext(Schedule &schedule, uint32_t timeout, uint32_t duration)
{
//If the schedule is already running, we can set the next schedule so it is ready to go
//This is required in cases of high rpm and high DC where there otherwise would not be enough time to set the schedule
schedule._nextStartCompare = schedule._counter + uS_TO_TIMER_COMPARE(timeout);
// Schedule must already be running, so safe to reuse this.
schedule._duration = uS_TO_TIMER_COMPARE(duration);
schedule._status = RUNNING_WITHNEXT;
}

static inline void applyChannelOverDwellProtection(IgnitionSchedule &schedule, uint32_t targetOverdwellTime) {
ATOMIC() {
if (isRunning(schedule)) {
Expand Down
46 changes: 23 additions & 23 deletions speeduino/scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,8 @@ See page 136 of the processors datasheet: http://www.atmel.com/Images/doc2549.pd
#include "board_definition.h"
#include "scheduledIO.h"
#include "table3d.h"

// Inlining seems to be very important for AVR performance
#if defined(CORE_AVR)
#define SCHEDULE_INLINE inline __attribute__((always_inline))
#else
#define SCHEDULE_INLINE inline
#endif
#include "timers.h"
#include "maths.h"

#define USE_IGN_REFRESH
#define IGNITION_REFRESH_THRESHOLD 30 //Time in uS that the refresh functions will check to ensure there is enough time before changing the end compare
Expand Down Expand Up @@ -164,7 +159,7 @@ struct Schedule {
compare_t &_compare; ///< **Reference**to the compare register. E.g. OCR3A
};

static SCHEDULE_INLINE bool isRunning(const Schedule &schedule) {
static CRITICAL_INLINE bool isRunning(const Schedule &schedule) {
// Using flags and bitwise AND (&) to check multiple states is much quicker
// than a logical or (||) (one less branch & 30% less instructions)
static constexpr uint8_t flags = RUNNING | RUNNING_WITHNEXT;
Expand All @@ -173,16 +168,21 @@ static SCHEDULE_INLINE bool isRunning(const Schedule &schedule) {

/// @cond
// Private function - not for use external to the scheduler code
void _setScheduleNext(Schedule &schedule, uint32_t timeout, uint32_t duration);
void _setSchedulePending(Schedule &schedule, uint32_t timeout, uint32_t duration);

static SCHEDULE_INLINE void _setSchedule(Schedule &schedule, uint32_t timeout, uint32_t duration) {
static CRITICAL_INLINE void _setSchedule(Schedule &schedule, uint32_t timeout, uint32_t duration) {
if(timeout<MAX_TIMER_PERIOD && duration<MAX_TIMER_PERIOD) {
if(!isRunning(schedule)) { //Check that we're not already part way through a schedule
_setSchedulePending(schedule, timeout, duration);
}
else {
_setScheduleNext(schedule, timeout, duration);
if(!isRunning(schedule)) { //Check that we're not already part way through a schedule
//The following must be enclosed in the noInterupts block to avoid contention caused if the relevant interrupt fires before the state is fully set
schedule._duration = uS_TO_TIMER_COMPARE(duration);
SET_COMPARE(schedule._compare, schedule._counter + (COMPARE_TYPE)uS_TO_TIMER_COMPARE(timeout));
schedule._status = PENDING; //Turn this schedule on
}
else {
//If the schedule is already running, we can set the next schedule so it is ready to go
//This is required in cases of high rpm and high DC where there otherwise would not be enough time to set the schedule
schedule._nextStartCompare = schedule._counter + uS_TO_TIMER_COMPARE(timeout);
// Schedule must already be running, so safe to reuse this.
schedule._duration = uS_TO_TIMER_COMPARE(duration);
schedule._status = RUNNING_WITHNEXT;
}
}
}
Expand All @@ -202,7 +202,7 @@ void setCallbacks(Schedule &schedule, voidVoidCallback pStartCallback, voidVoidC
* @brief Is the schedule in pending state?
* I.e. waiting for a timer interrupt to start the scheduled action. E.g. open an injector
*/
static SCHEDULE_INLINE bool isPending(const Schedule &schedule) {
static CRITICAL_INLINE bool isPending(const Schedule &schedule) {
static constexpr uint8_t flags = PENDING | PENDING_WITH_OVERRIDE;
return (bool)(schedule._status & flags);
}
Expand Down Expand Up @@ -243,7 +243,7 @@ struct IgnitionSchedule : public Schedule {
* @param crankAngle The current crank angle
* @return uint32_t
*/
static SCHEDULE_INLINE uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedule, int16_t crankAngle);
static CRITICAL_INLINE uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedule, int16_t crankAngle);
/// @endcond

/** @brief Set the next schedule for the ignition channel.
Expand All @@ -252,7 +252,7 @@ static SCHEDULE_INLINE uint32_t _calculateIgnitionTimeout(const IgnitionSchedule
* @param crankAngle The current crank angle
* @param dwellDuration The coil dwell time in µS
*/
static SCHEDULE_INLINE void setIgnitionSchedule(IgnitionSchedule &schedule, int16_t crankAngle, uint32_t dwellDuration) {
static CRITICAL_INLINE void setIgnitionSchedule(IgnitionSchedule &schedule, int16_t crankAngle, uint32_t dwellDuration) {
// Do not override the per-tooth timing - quick & dirty check
if (schedule._status!=PENDING_WITH_OVERRIDE) {
uint32_t delay = _calculateIgnitionTimeout(schedule, crankAngle);
Expand Down Expand Up @@ -335,7 +335,7 @@ struct FuelSchedule : public Schedule {

/// @cond
// Private function - not for use external to the scheduler code
static SCHEDULE_INLINE uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, int16_t crankAngle);
static CRITICAL_INLINE uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, int16_t crankAngle);
/// @endcond

/** @brief Set the next schedule for the fuel channel.
Expand All @@ -344,7 +344,7 @@ static SCHEDULE_INLINE uint32_t _calculateInjectorTimeout(const FuelSchedule &sc
* @param schedule The fuel channel
* @param crankAngle The current crank angle
*/
static SCHEDULE_INLINE void setFuelSchedule(FuelSchedule &schedule, int16_t crankAngle) {
static CRITICAL_INLINE void setFuelSchedule(FuelSchedule &schedule, int16_t crankAngle) {
uint32_t delay = _calculateInjectorTimeout(schedule, crankAngle);

ATOMIC() {
Expand All @@ -369,7 +369,7 @@ void moveToNextState(FuelSchedule &schedule);
* @param pwDegrees How many crank degrees the calculated PW will take at the current speed
* @param injAngle The requested injection angle
*/
static SCHEDULE_INLINE void setOpenAngle(FuelSchedule &schedule, uint16_t pwDegrees, uint16_t injAngle);
static CRITICAL_INLINE void setOpenAngle(FuelSchedule &schedule, uint16_t pwDegrees, uint16_t injAngle);

extern FuelSchedule fuelSchedule1;
extern FuelSchedule fuelSchedule2;
Expand Down
Loading

0 comments on commit 605e184

Please sign in to comment.