FreeEMS  0.2.0-SNAPSHOT-285-g028e24c
Macros | Functions
outputScheduler.h File Reference
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Macros

#define EXTERN   extern

Functions

void scheduleOutputs (void) FPAGE_FE
 Precision timed output scheduling.

Detailed Description

Definition in file outputScheduler.h.

Macro Definition Documentation

#define EXTERN   extern

Definition at line 48 of file outputScheduler.h.

Function Documentation

void scheduleOutputs ( void  )

Precision timed output scheduling.

Calculates which input tooth and post tooth delay any given event should used based on the configuration provided.

TODO

Todo:
FIXME part of to schedule or not to schedule should be : (masterPulseWidth > injectorMinimumPulseWidth)
Todo:
don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
Todo:
don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
Todo:
TODO create this check:
Todo:
TODO Schedule injection with real timing, requires some tweaks to work right.
Todo:
TODO move this loop variable to fixedConfig and make a subset of the remainder of channels configured for fuel with a start time/tooth directly set for now, ie, make the 6 channels usable as fuel or ignition from reasonable configuration and write a guide on how to set it up for any engine.
Todo:
TODO move sched code to a function or functions (inline?) that can be unit tested such that we KNOW it performs as anticipated rather than just trying it out on a 400hp turbo truck engine.
Todo:
TODO refactor this partly into init.c as per more detailed TD above
Todo:
TODO keep an eye on overflow here when increasing resolution by scaling angles
Todo:
TODO, do this ^ at init time from fixed config as an array of angles and a single engine offset combined into this runtime array.
Todo:
TODO rather than look for the nearest tooth and then step through till you find the right one that can work, instead figure out the dwell in angle and subtract that too, and find the correct tooth first time, will save cpu cycles, and get same answer and be less complex...

Definition at line 48 of file outputScheduler.c.

References DerivedVar::Advance, schedulingSetting::anglesOfTDC, ATOMIC_END, ATOMIC_START, CoreVars, Counters, schedulingSetting::decoderEngineOffset, decoderMaxCodeTime, DerivedVars, DerivedVar::Dwell, ectSwitchOnCodeTime, eventAngles, fixedConfigs1, KeyUserDebug::ignitionCuts, KeyUserDebug::injectionCuts, KeyUserDebugs, LONGMAX, masterPulseWidth, MAX_NUMBER_OF_OUTPUT_EVENTS, schedulingSetting::numberOfConfiguredOutputEvents, numberOfRealEvents, numberOfVirtualEvents, ONES, outputEventDelayFinalPeriod, outputEventDelayTotalPeriod, outputEventExtendNumberOfRepeats, outputEventExtendRepeatPeriod, outputEventInputEventNumbers, outputEventPulseWidthsMath, CoreVar::RPM, schedulingSetting::schedulingConfigurationBits, fixedConfig1::schedulingSettings, SHORTMAX, skipEventFlags, ticks_per_degree_multiplier, ticksPerDegree, Counter::timerStretchedToSchedule, and totalEventAngleRange.

Referenced by main().

{
/// TODO @todo FIXME part of to schedule or not to schedule should be : (masterPulseWidth > injectorMinimumPulseWidth)
// IE, NOT in the decoders... KISS in the decoders. This is a hangover from (very) early decoder dev
// TODO Add ability to schedule start and centre of fuel pulse using RPM, injector firing angle and IDT to schedule the events correctly
// Sanity checks: TODO migrate these to init time and do something meaninful with the failure
return; /// @todo don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
}
return; /// @todo don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
}
if(CoreVars->RPM == 0){
return; // Don't bother doing anything, can't schedule infinitely in the future ;-)
}
/// @todo TODO create this check:
// if(event angles not valid order/numbers/etc){
// return;
// }
/// @todo TODO Schedule injection with real timing, requires some tweaks to work right.
/** @todo TODO move this loop variable to fixedConfig and make a subset of
* the remainder of channels configured for fuel with a start time/tooth
* directly set for now, ie, make the 6 channels usable as fuel or ignition
* from reasonable configuration and write a guide on how to set it up for
* any engine.
*/
unsigned char outputEvent;
for(outputEvent = 0;outputEvent < fixedConfigs1.schedulingSettings.numberOfConfiguredOutputEvents;outputEvent++){
/* pseudo code
*
* we have:
*
* - offset between engine and code
* - offset for each output event TDC
* - desired timing value in degrees BTDC
* - a minimum post tooth delay
* - angle to ticks conversion number
*
* we want:
*
* - which event to fire from
* - how much to wait after that event before firing
*
* we need to:
*
* - to find the code angle that the spark must jump at
* - find nearest event
* - find time after nearest event to spark needing to jump
* - check that dwell + min delay < time after nearest
* - if so, set event number in output as nearest
* - and, set after delay to (distance between - dwell)
* - if not, set event number in output to one before nearest
* - and, set after delay to same + expected delay between nearest and next
*
* repeat per pin (this is in a loop)
*
* NOTE this is sub-optimal, the spark firing should be scheduled close to the closest tooth
* and dwell start should be = or greater than requested dwell and equal or less than max dwell
* ie, dwell can be MUCH more than requested in order to get the closest to event spark possible
* the output code was designed for fuel use, hence this current behaviour. It will be adjusted
* once xgate bit banging works sweetly.
*/
unsigned char appropriateCuts = KeyUserDebugs.ignitionCuts;
appropriateCuts = KeyUserDebugs.injectionCuts;
}
// needs another || or block here for other reasons to not schedule, in a union of two 8 bit values such that it can be checked here in a single 16 bit operation
if(appropriateCuts){
// If this becomes more than one line, it should be made explicitly atomic. Duplicate code, see below
outputEventInputEventNumbers[outputEvent] = 0xFF;
}else{
// Default to ignition
unsigned short pulsewidthToUseForThisChannel = DerivedVars->Dwell;
unsigned short endOfPulseTimingToUseForThisChannel = DerivedVars->Advance;
pulsewidthToUseForThisChannel = masterPulseWidth;
endOfPulseTimingToUseForThisChannel = 0; // Fixed flat timing for fueling for the time being
} // Else we're doing ignition! Leave the defaults in place.
// This value is quite large, and used with a latency added, however PWs under about 0.5ms are not useful for dwell or fueling
if(pulsewidthToUseForThisChannel < ectSwitchOnCodeTime){
// If this becomes more than one line, it should be made explicitly atomic. Duplicate code, see above
outputEventInputEventNumbers[outputEvent] = 0xFF;
}else{ // Otherwise act normally!
/** @todo TODO move sched code to a function or functions (inline?)
* that can be unit tested such that we KNOW it performs as anticipated
* rather than just trying it out on a 400hp turbo truck engine.
*/
/// @todo TODO refactor this partly into init.c as per more detailed TD above
unsigned short codeAngleOfIgnition = 0;
if(fixedConfigs1.schedulingSettings.anglesOfTDC[outputEvent] > ((unsigned long)fixedConfigs1.schedulingSettings.decoderEngineOffset + endOfPulseTimingToUseForThisChannel)){ /// @todo TODO keep an eye on overflow here when increasing resolution by scaling angles
codeAngleOfIgnition = fixedConfigs1.schedulingSettings.anglesOfTDC[outputEvent] - (fixedConfigs1.schedulingSettings.decoderEngineOffset + endOfPulseTimingToUseForThisChannel);
}else{
codeAngleOfIgnition = (unsigned short)(((unsigned long)totalEventAngleRange + fixedConfigs1.schedulingSettings.anglesOfTDC[outputEvent]) - ((unsigned long)fixedConfigs1.schedulingSettings.decoderEngineOffset + endOfPulseTimingToUseForThisChannel));
}
/** @todo TODO, do this ^ at init time from fixed config as an array of
* angles and a single engine offset combined into this runtime array.
*/
/// @todo TODO rather than look for the nearest tooth and then step through till you find the right one that can work, instead figure out the dwell in angle and subtract that too, and find the correct tooth first time, will save cpu cycles, and get same answer and be less complex...
// Find the closest event to our desired angle of ignition by working through from what is, by definition, the farthest
unsigned char lastGoodEvent = ONES;
if(codeAngleOfIgnition == 0){ // Special case, if equal to zero, the last good event will not be found
// And the last good event is the last event!
lastGoodEvent = numberOfVirtualEvents - 1;
}else{
// Otherwise iterate through and find the closest one.
unsigned char possibleEvent;
for(possibleEvent = 0;possibleEvent < numberOfVirtualEvents;possibleEvent++){
if(eventAngles[possibleEvent] < codeAngleOfIgnition){
lastGoodEvent = possibleEvent;
}
}
}
// Don't actually use this var, just need that many iterations to work back from the closest tooth that we found above
unsigned char possibleEvent;
for(possibleEvent = 0;possibleEvent < numberOfVirtualEvents;possibleEvent++){
unsigned long ticksBetweenEventAndSpark = LONGMAX;
if(codeAngleOfIgnition > eventAngles[lastGoodEvent]){
ticksBetweenEventAndSpark = ((unsigned long)*ticksPerDegree * (codeAngleOfIgnition - eventAngles[lastGoodEvent])) / ticks_per_degree_multiplier;
}else{
ticksBetweenEventAndSpark = ((unsigned long)*ticksPerDegree * ((unsigned long)codeAngleOfIgnition + (totalEventAngleRange - eventAngles[lastGoodEvent]))) / ticks_per_degree_multiplier;
}
if(ticksBetweenEventAndSpark > ((unsigned long)pulsewidthToUseForThisChannel + decoderMaxCodeTime)){
// generate event mapping from real vs virtual counts, how? better with a cylinder ratio?
unsigned char mappedEvent = 0xFF;
if(numberOfRealEvents == numberOfVirtualEvents){
mappedEvent = lastGoodEvent;
}else{
mappedEvent = lastGoodEvent % numberOfRealEvents;
}
// Determine the eventBeforeCurrent outside the atomic block
unsigned char eventBeforeCurrent = 0;
if(outputEventInputEventNumbers[outputEvent] == 0){
eventBeforeCurrent = numberOfRealEvents - 1;
}else{
eventBeforeCurrent = outputEventInputEventNumbers[outputEvent] - 1;
}
unsigned long potentialDelay = ticksBetweenEventAndSpark - pulsewidthToUseForThisChannel;
if(potentialDelay <= SHORTMAX){ // We can use dwell as is
ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
/* For this block we need to provide a flag AFTER disabling the interrupts
* such that the next input isr can figure out if it should run from the
* previous data for a single cycle in the case when moving forward a tooth
* between the tooth you are moving forward from and the one you are moving
* forward to. In this case a scheduled event will be lost, because the
* one its intended for has past, and the one after that is yet to arrive is
* not going to fire it.
*
* Some trickery around the post input min delay could benefit timing or be
* required as you will be operating under dynamic conditions and trying to
* use a tooth you're not supposed to be, not doing fancy delay semantics will
* just mean a single cycle of scheduling is slightly too retarded for a single
* event around change of tooth time which could easily be acceptable.
*/
if((mappedEvent == eventBeforeCurrent) && (potentialDelay > outputEventDelayTotalPeriod[outputEvent])){
skipEventFlags |= (1UL << outputEvent);
}
outputEventInputEventNumbers[outputEvent] = mappedEvent;
outputEventDelayFinalPeriod[outputEvent] = (unsigned short)potentialDelay;
outputEventPulseWidthsMath[outputEvent] = pulsewidthToUseForThisChannel;
ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
outputEventDelayTotalPeriod[outputEvent] = potentialDelay; // No async accesses occur
}else{
ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
// See comment in above block
if((mappedEvent == eventBeforeCurrent) && (potentialDelay > outputEventDelayTotalPeriod[outputEvent])){
skipEventFlags |= (1UL << outputEvent);
}
outputEventInputEventNumbers[outputEvent] = mappedEvent;
unsigned char numberOfRepeats = potentialDelay / SHORTMAX;
unsigned short finalPeriod = potentialDelay % SHORTMAX;
if(finalPeriod > decoderMaxCodeTime){
outputEventDelayFinalPeriod[outputEvent] = finalPeriod;
outputEventExtendNumberOfRepeats[outputEvent] = numberOfRepeats;
}else{
unsigned short shortagePerRepeat = (decoderMaxCodeTime - finalPeriod) / numberOfRepeats;
unsigned short repeatPeriod = (SHORTMAX - 1) - shortagePerRepeat;
finalPeriod += (shortagePerRepeat + 1) * numberOfRepeats;
outputEventDelayFinalPeriod[outputEvent] = finalPeriod;
outputEventExtendRepeatPeriod[outputEvent] = repeatPeriod;
outputEventExtendNumberOfRepeats[outputEvent] = numberOfRepeats;
}
// find number of max sized chunks and remainder
// check remainder for being big enough compared to code runtime
// if so, set repeat to max and final to remainder and number of iterations to divs
// if not, decrease repeat size in some optimal way and provide new left over to work with that, and same number of divs/its
// Always use dwell as requested
outputEventPulseWidthsMath[outputEvent] = pulsewidthToUseForThisChannel;
ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
outputEventDelayTotalPeriod[outputEvent] = potentialDelay; // No async accesses occur
}
break;
}else{
if(lastGoodEvent > 0){
lastGoodEvent--;
}else{
lastGoodEvent = numberOfVirtualEvents - 1;
}
}
}
}
}
}
// nothing much, L&P:
}

Here is the caller graph for this function: