Skip to content

Commit

Permalink
Fuel schedule improvements (speeduino#1272)
Browse files Browse the repository at this point in the history
* Avoid overflow of timer period between the current and next schedules

Potential fix for speeduino#1181

* Remove end compare on fuel schedules. Significant RAM improvement

* Cleanup of new code. Align ignition schedules with new fuel schedules

* Slight tweak to fuel scheduling conditions

* Prevent missed injection pulse under specific timing conditions

* Further work handling when injection start angle passes back and forth between 0 and CRANK_ANGLE_MAX_INJ

* Do not queue next fuel schedule if cycle time exceeds max timer duration

* Fix unit tests based on code path changes
  • Loading branch information
noisymime authored Dec 19, 2024
1 parent 94b6a0a commit 41e54d4
Show file tree
Hide file tree
Showing 7 changed files with 824 additions and 822 deletions.
2 changes: 1 addition & 1 deletion speeduino/schedule_calcs.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ extern int channel8InjDegrees; /**< The number of crank degrees until cylinder 8

static inline uint16_t __attribute__((always_inline)) calculateInjectorStartAngle(uint16_t PWdivTimerPerDegree, int16_t injChannelDegrees, uint16_t injAngle);

static 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 injectorStartAngle, int crankAngle);

static inline void __attribute__((always_inline)) calculateIgnitionAngle(const uint16_t dwellAngle, const uint16_t channelIgnDegrees, int8_t advance, int *pEndAngle, int *pStartAngle);

Expand Down
63 changes: 24 additions & 39 deletions speeduino/schedule_calcs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,26 @@ static inline uint16_t calculateInjectorStartAngle(uint16_t pwDegrees, int16_t i
// (CRANK_ANGLE_MAX_INJ can be as small as 360/nCylinders. E.g. 45° for 8 cylinder)

uint16_t startAngle = (uint16_t)injAngle + (uint16_t)injChannelDegrees;
// Avoid underflow
while (startAngle<pwDegrees) { startAngle = startAngle + (uint16_t)CRANK_ANGLE_MAX_INJ; }
// Guaranteed to be >=0.
startAngle = startAngle - pwDegrees;
// Clamp to 0<=startAngle<=CRANK_ANGLE_MAX_INJ
while (startAngle>(uint16_t)CRANK_ANGLE_MAX_INJ) { startAngle = startAngle - (uint16_t)CRANK_ANGLE_MAX_INJ; }

while (startAngle<pwDegrees) { startAngle = startAngle + (uint16_t)CRANK_ANGLE_MAX_INJ; } // Avoid underflow
startAngle = startAngle - pwDegrees; // startAngle guaranteed to be >=0.
while (startAngle>(uint16_t)CRANK_ANGLE_MAX_INJ) { startAngle = startAngle - (uint16_t)CRANK_ANGLE_MAX_INJ; } // Clamp to 0<=startAngle<=CRANK_ANGLE_MAX_INJ

return startAngle;
}

static inline uint32_t _calculateInjectorTimeout(const FuelSchedule &schedule, uint16_t openAngle, uint16_t crankAngle) {
static inline uint32_t calculateInjectorTimeout(const FuelSchedule &schedule, int openAngle, int crankAngle)
{
uint32_t tempTimeout = 0;
int16_t delta = openAngle - crankAngle;
if (delta<0)
{
if ((schedule.Status == RUNNING) && (delta>-CRANK_ANGLE_MAX_INJ))
{
// Guaranteed to be >0
delta = delta + CRANK_ANGLE_MAX_INJ;
}
else
{
return 0;
}
}

return angleToTimeMicroSecPerDegree((uint16_t)delta);
}

static inline int _adjustToInjChannel(int angle, int channelInjDegrees) {
angle = angle - channelInjDegrees;
if( angle < 0) { return angle + CRANK_ANGLE_MAX_INJ; }
return angle;
}

static inline uint32_t calculateInjectorTimeout(const FuelSchedule &schedule, int channelInjDegrees, int openAngle, int crankAngle)
{
if (channelInjDegrees==0) {
return _calculateInjectorTimeout(schedule, openAngle, crankAngle);
if ( (schedule.Status == RUNNING) || (schedule.Status == OFF))
{
while(delta < 0) { delta += CRANK_ANGLE_MAX_INJ; }
tempTimeout = angleToTimeMicroSecPerDegree((uint16_t)delta);
}
return _calculateInjectorTimeout(schedule, _adjustToInjChannel(openAngle, channelInjDegrees), _adjustToInjChannel(crankAngle, channelInjDegrees));

return tempTimeout;
}

static inline void calculateIgnitionAngle(const uint16_t dwellAngle, const uint16_t channelIgnDegrees, int8_t advance, int *pEndAngle, int *pStartAngle)
Expand All @@ -73,9 +53,10 @@ static inline void calculateIgnitionTrailingRotary(uint16_t dwellAngle, int rota
if(*pStartAngle < 0) {*pStartAngle += CRANK_ANGLE_MAX_IGN;}
}

static 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)
if (delta < 0)
{
if ((schedule.Status == RUNNING) && (delta>-CRANK_ANGLE_MAX_IGN))
{
Expand All @@ -84,29 +65,33 @@ static inline uint32_t _calculateIgnitionTimeout(const IgnitionSchedule &schedul
}
else
{
return 0;
return 0U;
}
}

return angleToTimeMicroSecPerDegree(delta);
}

static inline uint16_t _adjustToIgnChannel(int angle, int channelInjDegrees) {
static inline uint16_t _adjustToIgnChannel(int angle, int channelInjDegrees)
{
angle = angle - channelInjDegrees;
if( angle < 0) { return angle + CRANK_ANGLE_MAX_IGN; }
return angle;
}

static inline uint32_t calculateIgnitionTimeout(const IgnitionSchedule &schedule, int startAngle, int channelIgnDegrees, int crankAngle)
{
if (channelIgnDegrees==0) {
if (channelIgnDegrees == 0)
{
return _calculateIgnitionTimeout(schedule, startAngle, crankAngle);
}
return _calculateIgnitionTimeout(schedule, _adjustToIgnChannel(startAngle, channelIgnDegrees), _adjustToIgnChannel(crankAngle, channelIgnDegrees));
}

#define MIN_CYCLES_FOR_ENDCOMPARE 6

inline void adjustCrankAngle(IgnitionSchedule &schedule, int endAngle, int crankAngle) {
inline void adjustCrankAngle(IgnitionSchedule &schedule, int endAngle, int crankAngle)
{
if( (schedule.Status == RUNNING) ) {
SET_COMPARE(schedule.compare, schedule.counter + uS_TO_TIMER_COMPARE( angleToTimeMicroSecPerDegree( ignitionLimits( (endAngle - crankAngle) ) ) ) );
}
Expand Down
72 changes: 37 additions & 35 deletions speeduino/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,44 +212,44 @@ void initialiseSchedulers()

void _setFuelScheduleRunning(FuelSchedule &schedule, unsigned long timeout, unsigned long duration)
{
schedule.duration = duration;

//Need to check that the timeout doesn't exceed the overflow
COMPARE_TYPE timeout_timer_compare;
if (timeout > MAX_TIMER_PERIOD) { timeout_timer_compare = uS_TO_TIMER_COMPARE( (MAX_TIMER_PERIOD - 1) ); } // If the timeout is >4x (Each tick represents 4uS on a mega2560, other boards will be different) the maximum allowed value of unsigned int (65535), the timer compare value will overflow when applied causing erratic behaviour such as erroneous squirts
else { timeout_timer_compare = uS_TO_TIMER_COMPARE(timeout); } //Normal case

//The following must be enclosed in the noInterupts block to avoid contention caused if the relevant interrupt fires before the state is fully set
noInterrupts();
schedule.startCompare = schedule.counter + timeout_timer_compare;
schedule.endCompare = schedule.startCompare + uS_TO_TIMER_COMPARE(duration);
SET_COMPARE(schedule.compare, schedule.startCompare); //Use the B compare unit of timer 3

//The duration of the pulsewidth cannot be longer than the maximum timer period. This is unlikely as pulse widths should never get that long, but it's here for safety
if(duration >= MAX_TIMER_PERIOD) { schedule.duration = MAX_TIMER_PERIOD - 1; }
else { schedule.duration = duration; }

schedule.startCompare = schedule.counter + uS_TO_TIMER_COMPARE(timeout);
SET_COMPARE(schedule.compare, schedule.startCompare);
schedule.Status = PENDING; //Turn this schedule on
interrupts();
schedule.pTimerEnable();
}

void _setFuelScheduleNext(FuelSchedule &schedule, unsigned long timeout, unsigned long 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
noInterrupts();
//The duration of the pulsewidth cannot be longer than the maximum timer period. This is unlikely as pulse widths should never get that long, but it's here for safety
//Duration can safely be set here as the schedule is already running at the previous duration value already used
if(duration >= MAX_TIMER_PERIOD) { schedule.duration = MAX_TIMER_PERIOD - 1; }
else { schedule.duration = duration; }

schedule.nextStartCompare = schedule.counter + uS_TO_TIMER_COMPARE(timeout);
schedule.nextEndCompare = schedule.nextStartCompare + uS_TO_TIMER_COMPARE(duration);
schedule.hasNextSchedule = true;
interrupts();
}

void _setIgnitionScheduleRunning(IgnitionSchedule &schedule, unsigned long timeout, unsigned long duration)
{
schedule.duration = duration;
//The duration of the dwell cannot be longer than the maximum timer period. This is unlikely as dwell timess should never get that long, but it's here for safety
if(duration >= MAX_TIMER_PERIOD) { schedule.duration = MAX_TIMER_PERIOD - 1; }
else { schedule.duration = duration; }

//Need to check that the timeout doesn't exceed the overflow
COMPARE_TYPE timeout_timer_compare;
if (timeout > MAX_TIMER_PERIOD) { timeout_timer_compare = uS_TO_TIMER_COMPARE( (MAX_TIMER_PERIOD - 1) ); } // If the timeout is >4x (Each tick represents 4uS) the maximum allowed value of unsigned int (65535), the timer compare value will overflow when applied causing erratic behaviour such as erroneous sparking.
else { timeout_timer_compare = uS_TO_TIMER_COMPARE(timeout); } //Normal case
COMPARE_TYPE timeout_timer_compare = uS_TO_TIMER_COMPARE(timeout);

noInterrupts();
schedule.startCompare = schedule.counter + timeout_timer_compare; //As there is a tick every 4uS, there are timeout/4 ticks until the interrupt should be triggered ( >>2 divides by 4)
if(schedule.endScheduleSetByDecoder == false) { schedule.endCompare = schedule.startCompare + uS_TO_TIMER_COMPARE(duration); } //The .endCompare value is also set by the per tooth timing in decoders.ino. The check here is so that it's not getting overridden.
//if(schedule.endScheduleSetByDecoder == false) { schedule.endCompare = schedule.startCompare + uS_TO_TIMER_COMPARE(schedule.duration); } //The .endCompare value is also set by the per tooth timing in decoders.ino. The check here is so that it's not getting overridden.
SET_COMPARE(schedule.compare, schedule.startCompare);
schedule.Status = PENDING; //Turn this schedule on
interrupts();
Expand All @@ -260,9 +260,12 @@ void _setIgnitionScheduleNext(IgnitionSchedule &schedule, unsigned long timeout,
{
//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
noInterrupts();
schedule.nextStartCompare = schedule.counter + uS_TO_TIMER_COMPARE(timeout);
schedule.nextEndCompare = schedule.nextStartCompare + uS_TO_TIMER_COMPARE(duration);
if(duration >= MAX_TIMER_PERIOD) { schedule.duration = MAX_TIMER_PERIOD - 1; }
else { schedule.duration = duration; }
schedule.hasNextSchedule = true;
interrupts();
}


Expand Down Expand Up @@ -327,21 +330,20 @@ static inline __attribute__((always_inline)) void fuelScheduleISR(FuelSchedule &
}
else if (schedule.Status == RUNNING)
{
schedule.pEndFunction();
schedule.Status = OFF; //Turn off the schedule

//If there is a next schedule queued up, activate it
if(schedule.hasNextSchedule == true)
{
SET_COMPARE(schedule.compare, schedule.nextStartCompare);
SET_COMPARE(schedule.endCompare, schedule.nextEndCompare);
schedule.Status = PENDING;
schedule.hasNextSchedule = false;
}
else
{
schedule.pTimerDisable();
}
schedule.pEndFunction();
schedule.Status = OFF; //Turn off the schedule

//If there is a next schedule queued up, activate it
if(schedule.hasNextSchedule == true)
{
SET_COMPARE(schedule.compare, schedule.nextStartCompare); //Flip the next start compare time to be the current one. The duration of this next pulse will already have been set in _setFuelScheduleNext()
schedule.Status = PENDING;
schedule.hasNextSchedule = false;
}
else
{
schedule.pTimerDisable();
}
}
else if (schedule.Status == OFF)
{
Expand Down
41 changes: 25 additions & 16 deletions speeduino/scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ See page 136 of the processors datasheet: http://www.atmel.com/Images/doc2549.pd
#define SCHEDULER_H

#include "globals.h"
#include "crankMaths.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 @@ -150,13 +151,19 @@ struct IgnitionSchedule {
void _setIgnitionScheduleRunning(IgnitionSchedule &schedule, unsigned long timeout, unsigned long duration);
void _setIgnitionScheduleNext(IgnitionSchedule &schedule, unsigned long timeout, unsigned long duration);

inline __attribute__((always_inline)) void setIgnitionSchedule(IgnitionSchedule &schedule, unsigned long timeout, unsigned long duration) {
if(schedule.Status != RUNNING) { //Check that we're not already part way through a schedule
_setIgnitionScheduleRunning(schedule, timeout, duration);
}
// Check whether timeout exceeds the maximum future time. This can potentially occur on sequential setups when below ~115rpm
else if(timeout < MAX_TIMER_PERIOD){
_setIgnitionScheduleNext(schedule, timeout, duration);
inline __attribute__((always_inline)) void setIgnitionSchedule(IgnitionSchedule &schedule, unsigned long timeout, unsigned long duration)
{
if((timeout) < MAX_TIMER_PERIOD)
{
if(schedule.Status != RUNNING)
{ //Check that we're not already part way through a schedule
_setIgnitionScheduleRunning(schedule, timeout, duration);
}
// Check whether timeout exceeds the maximum future time. This can potentially occur on sequential setups when below ~115rpm
else if(angleToTimeMicroSecPerDegree(CRANK_ANGLE_MAX_IGN) < MAX_TIMER_PERIOD)
{
_setIgnitionScheduleNext(schedule, timeout, duration);
}
}
}

Expand All @@ -181,14 +188,12 @@ struct FuelSchedule {
{
}

volatile unsigned long duration;///< Scheduled duration (uS ?)
volatile unsigned long duration;///< Scheduled duration (uS)
volatile ScheduleStatus Status; ///< Schedule status: OFF, PENDING, STAGED, RUNNING
volatile COMPARE_TYPE startCompare; ///< The counter value of the timer when this will start
volatile COMPARE_TYPE endCompare; ///< The counter value of the timer when this will end
void (*pStartFunction)(void);
void (*pEndFunction)(void);
COMPARE_TYPE nextStartCompare;
COMPARE_TYPE nextEndCompare;
volatile bool hasNextSchedule = false;

counter_t &counter; // Reference to the counter register. E.g. TCNT3
Expand All @@ -202,13 +207,17 @@ void _setFuelScheduleNext(FuelSchedule &schedule, unsigned long timeout, unsigne

inline __attribute__((always_inline)) void setFuelSchedule(FuelSchedule &schedule, unsigned long timeout, unsigned long duration)
{
if(schedule.Status != RUNNING)
{ //Check that we're not already part way through a schedule
_setFuelScheduleRunning(schedule, timeout, duration);
}
else if(timeout < MAX_TIMER_PERIOD)
if((timeout) < MAX_TIMER_PERIOD)
{
_setFuelScheduleNext(schedule, timeout, duration);
if(schedule.Status != RUNNING)
{ //Check that we're not already part way through a schedule
_setFuelScheduleRunning(schedule, timeout, duration);
}
//If the schedule is already running, we can queue up the next pulse. Only do this however if the maximum time between pulses (Based on CRANK_ANGLE_MAX_INJ) is less than the max timer period
else if(angleToTimeMicroSecPerDegree(CRANK_ANGLE_MAX_INJ) < MAX_TIMER_PERIOD)
{
_setFuelScheduleNext(schedule, timeout, duration);
}
}
}

Expand Down
16 changes: 8 additions & 8 deletions speeduino/speeduino.ino
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 1
if( (maxInjOutputs >= 1) && (currentStatus.PW1 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ1_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule1, channel1InjDegrees, injector1StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule1, injector1StartAngle, crankAngle);
if (timeOut>0U)
{
setFuelSchedule(fuelSchedule1,
Expand All @@ -934,7 +934,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 2
if( (maxInjOutputs >= 2) && (currentStatus.PW2 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ2_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule2, channel2InjDegrees, injector2StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule2, injector2StartAngle, crankAngle);
if ( timeOut>0U )
{
setFuelSchedule(fuelSchedule2,
Expand All @@ -948,7 +948,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 3
if( (maxInjOutputs >= 3) && (currentStatus.PW3 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ3_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule3, channel3InjDegrees, injector3StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule3, injector3StartAngle, crankAngle);
if ( timeOut>0U )
{
setFuelSchedule(fuelSchedule3,
Expand All @@ -962,7 +962,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 4
if( (maxInjOutputs >= 4) && (currentStatus.PW4 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ4_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule4, channel4InjDegrees, injector4StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule4, injector4StartAngle, crankAngle);
if ( timeOut>0U )
{
setFuelSchedule(fuelSchedule4,
Expand All @@ -976,7 +976,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 5
if( (maxInjOutputs >= 5) && (currentStatus.PW5 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ5_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule5, channel5InjDegrees, injector5StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule5, injector5StartAngle, crankAngle);
if ( timeOut>0U )
{
setFuelSchedule(fuelSchedule5,
Expand All @@ -990,7 +990,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 6
if( (maxInjOutputs >= 6) && (currentStatus.PW6 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ6_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule6, channel6InjDegrees, injector6StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule6, injector6StartAngle, crankAngle);
if ( timeOut>0U )
{
setFuelSchedule(fuelSchedule6,
Expand All @@ -1004,7 +1004,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 7
if( (maxInjOutputs >= 7) && (currentStatus.PW7 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ7_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule7, channel7InjDegrees, injector7StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule7, injector7StartAngle, crankAngle);
if ( timeOut>0U )
{
setFuelSchedule(fuelSchedule7,
Expand All @@ -1018,7 +1018,7 @@ void __attribute__((always_inline)) loop(void)
#if INJ_CHANNELS >= 8
if( (maxInjOutputs >= 8) && (currentStatus.PW8 >= inj_opentime_uS) && (BIT_CHECK(fuelChannelsOn, INJ8_CMD_BIT)) )
{
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule8, channel8InjDegrees, injector8StartAngle, crankAngle);
uint32_t timeOut = calculateInjectorTimeout(fuelSchedule8, injector8StartAngle, crankAngle);
if ( timeOut>0U )
{
setFuelSchedule(fuelSchedule8,
Expand Down
Loading

0 comments on commit 41e54d4

Please sign in to comment.