Remove fried-CPU hack for the HOTEL from public code base. This hack will now live...
[freeems-vanilla.git] / src / main / outputScheduler.c
blobcdfbddd4208030eb523ee9de81242a535da51d09
1 /* FreeEMS - the open source engine management system
3 * Copyright 2011-2012 Fred Cooke
5 * This file is part of the FreeEMS project.
7 * FreeEMS software is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * FreeEMS software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with any FreeEMS software. If not, see http://www.gnu.org/licenses/
20 * We ask that if you make any changes to this file you email them upstream to
21 * us at admin(at)diyefi(dot)org or, even better, fork the code on github.com!
23 * Thank you for choosing FreeEMS to run your engine!
27 /** @file
29 * @ingroup measurementsAndCalculations
31 * @brief Precision timed output scheduling.
33 * This file contains all of the mathematics and logic that lead to precise
34 * ignition and injection events being output based on configuration parameters.
38 #define OUTPUTSCHEDULER_C
39 #include "inc/freeEMS.h"
40 #include "inc/interrupts.h"
41 #include "inc/decoderInterface.h"
42 #include "inc/outputScheduler.h"
44 /**
45 * Precision timed output scheduling. Calculates which input tooth and post
46 * tooth delay any given event should used based on the configuration provided.
48 void scheduleOutputs(){
49 /// TODO @todo FIXME part of to schedule or not to schedule should be : (masterPulseWidth > injectorMinimumPulseWidth)
50 // IE, NOT in the decoders... KISS in the decoders. This is a hangover from (very) early decoder dev
52 // TODO Add ability to schedule start and centre of fuel pulse using RPM, injector firing angle and IDT to schedule the events correctly
54 // Sanity checks: TODO migrate these to init time and do something meaninful with the failure
55 if(fixedConfigs1.schedulingSettings.decoderEngineOffset >= totalEventAngleRange){
56 return; /// @todo don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
58 if(fixedConfigs1.schedulingSettings.numberOfConfiguredOutputEvents > MAX_NUMBER_OF_OUTPUT_EVENTS){
59 return; /// @todo don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
61 /// @todo TODO create this check:
62 // if(event angles not valid order/numbers/etc){
63 // return;
64 // }
67 /// @todo TODO Schedule injection with real timing, requires some tweaks to work right.
70 /** @todo TODO move this loop variable to fixedConfig and make a subset of
71 * the remainder of channels configured for fuel with a start time/tooth
72 * directly set for now, ie, make the 6 channels usable as fuel or ignition
73 * from reasonable configuration and write a guide on how to set it up for
74 * any engine.
76 unsigned char outputEvent;
77 for(outputEvent = 0;outputEvent < fixedConfigs1.schedulingSettings.numberOfConfiguredOutputEvents;outputEvent++){
79 /* pseudo code
81 * we have:
83 * - offset between engine and code
84 * - offset for each output event TDC
85 * - desired timing value in degrees BTDC
86 * - a minimum post tooth delay
87 * - angle to ticks conversion number
89 * we want:
91 * - which event to fire from
92 * - how much to wait after that event before firing
94 * we need to:
96 * - to find the code angle that the spark must jump at
97 * - find nearest event
98 * - find time after nearest event to spark needing to jump
99 * - check that dwell + min delay < time after nearest
100 * - if so, set event number in output as nearest
101 * - and, set after delay to (distance between - dwell)
102 * - if not, set event number in output to one before nearest
103 * - and, set after delay to same + expected delay between nearest and next
105 * repeat per pin (this is in a loop)
107 * NOTE this is sub-optimal, the spark firing should be scheduled close to the closest tooth
108 * and dwell start should be = or greater than requested dwell and equal or less than max dwell
109 * ie, dwell can be MUCH more than requested in order to get the closest to event spark possible
110 * the output code was designed for fuel use, hence this current behaviour. It will be adjusted
111 * once xgate bit banging works sweetly.
115 unsigned char appropriateCuts = KeyUserDebugs.ignitionCuts;
116 if(fixedConfigs1.schedulingSettings.schedulingConfigurationBits[outputEvent]){
117 appropriateCuts = KeyUserDebugs.injectionCuts;
120 // 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
121 if(appropriateCuts){
122 // If this becomes more than one line, it should be made explicitly atomic. Duplicate code, see below
123 outputEventInputEventNumbers[outputEvent] = 0xFF;
124 }else{
125 // Default to ignition
126 unsigned short pulsewidthToUseForThisChannel = DerivedVars->Dwell;
127 unsigned short endOfPulseTimingToUseForThisChannel = DerivedVars->Advance;
128 if(fixedConfigs1.schedulingSettings.schedulingConfigurationBits[outputEvent]){ //
129 pulsewidthToUseForThisChannel = masterPulseWidth;
130 endOfPulseTimingToUseForThisChannel = 0; // Fixed flat timing for fueling for the time being
131 } // Else we're doing ignition! Leave the defaults in place.
133 // This value is quite large, and used with a latency added, however PWs under about 0.5ms are not useful for dwell or fueling
134 if(pulsewidthToUseForThisChannel < ectSwitchOnCodeTime){
135 // If this becomes more than one line, it should be made explicitly atomic. Duplicate code, see above
136 outputEventInputEventNumbers[outputEvent] = 0xFF;
137 }else{ // Otherwise act normally!
139 /** @todo TODO move sched code to a function or functions (inline?)
140 * that can be unit tested such that we KNOW it performs as anticipated
141 * rather than just trying it out on a 400hp turbo truck engine.
144 /// @todo TODO refactor this partly into init.c as per more detailed TD above
145 unsigned short codeAngleOfIgnition = 0;
146 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
147 codeAngleOfIgnition = fixedConfigs1.schedulingSettings.anglesOfTDC[outputEvent] - (fixedConfigs1.schedulingSettings.decoderEngineOffset + endOfPulseTimingToUseForThisChannel);
148 }else{
149 codeAngleOfIgnition = (unsigned short)(((unsigned long)totalEventAngleRange + fixedConfigs1.schedulingSettings.anglesOfTDC[outputEvent]) - ((unsigned long)fixedConfigs1.schedulingSettings.decoderEngineOffset + endOfPulseTimingToUseForThisChannel));
151 /** @todo TODO, do this ^ at init time from fixed config as an array of
152 * angles and a single engine offset combined into this runtime array.
155 /// @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...
158 // Find the closest event to our desired angle of ignition by working through from what is, by definition, the farthest
159 unsigned char lastGoodEvent = ONES;
160 if(codeAngleOfIgnition == 0){ // Special case, if equal to zero, the last good event will not be found
161 // And the last good event is the last event!
162 lastGoodEvent = numberOfVirtualEvents - 1;
163 }else{
164 // Otherwise iterate through and find the closest one.
165 unsigned char possibleEvent;
166 for(possibleEvent = 0;possibleEvent < numberOfVirtualEvents;possibleEvent++){
167 if(eventAngles[possibleEvent] < codeAngleOfIgnition){
168 lastGoodEvent = possibleEvent;
173 // Don't actually use this var, just need that many iterations to work back from the closest tooth that we found above
174 unsigned char possibleEvent;
175 for(possibleEvent = 0;possibleEvent < numberOfVirtualEvents;possibleEvent++){
176 unsigned long ticksBetweenEventAndSpark = LONGMAX;
177 if(codeAngleOfIgnition > eventAngles[lastGoodEvent]){
178 ticksBetweenEventAndSpark = ((unsigned long)*ticksPerDegree * (codeAngleOfIgnition - eventAngles[lastGoodEvent])) / ticks_per_degree_multiplier;
179 }else{
180 ticksBetweenEventAndSpark = ((unsigned long)*ticksPerDegree * ((unsigned long)codeAngleOfIgnition + (totalEventAngleRange - eventAngles[lastGoodEvent]))) / ticks_per_degree_multiplier;
183 if(ticksBetweenEventAndSpark > ((unsigned long)pulsewidthToUseForThisChannel + decoderMaxCodeTime)){
184 // generate event mapping from real vs virtual counts, how? better with a cylinder ratio?
185 unsigned char mappedEvent = 0xFF;
186 if(numberOfRealEvents == numberOfVirtualEvents){
187 mappedEvent = lastGoodEvent;
188 }else{
189 mappedEvent = lastGoodEvent % numberOfRealEvents;
192 // Determine the eventBeforeCurrent outside the atomic block
193 unsigned char eventBeforeCurrent = 0;
194 if(outputEventInputEventNumbers[outputEvent] == 0){
195 eventBeforeCurrent = numberOfRealEvents - 1;
196 }else{
197 eventBeforeCurrent = outputEventInputEventNumbers[outputEvent] - 1;
200 unsigned long potentialDelay = ticksBetweenEventAndSpark - pulsewidthToUseForThisChannel;
201 if(potentialDelay <= SHORTMAX){ // We can use dwell as is
202 ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
204 /* For this block we need to provide a flag AFTER disabling the interrupts
205 * such that the next input isr can figure out if it should run from the
206 * previous data for a single cycle in the case when moving forward a tooth
207 * between the tooth you are moving forward from and the one you are moving
208 * forward to. In this case a scheduled event will be lost, because the
209 * one its intended for has past, and the one after that is yet to arrive is
210 * not going to fire it.
212 * Some trickery around the post input min delay could benefit timing or be
213 * required as you will be operating under dynamic conditions and trying to
214 * use a tooth you're not supposed to be, not doing fancy delay semantics will
215 * just mean a single cycle of scheduling is slightly too retarded for a single
216 * event around change of tooth time which could easily be acceptable.
218 if((mappedEvent == eventBeforeCurrent) && (potentialDelay > outputEventDelayTotalPeriod[outputEvent])){
219 skipEventFlags |= (1UL << outputEvent);
222 outputEventInputEventNumbers[outputEvent] = mappedEvent;
223 outputEventDelayFinalPeriod[outputEvent] = (unsigned short)potentialDelay;
224 outputEventPulseWidthsMath[outputEvent] = pulsewidthToUseForThisChannel;
225 outputEventExtendNumberOfRepeats[outputEvent] = 0;
226 ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
227 outputEventDelayTotalPeriod[outputEvent] = potentialDelay; // No async accesses occur
228 }else{
229 ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
231 // See comment in above block
232 if((mappedEvent == eventBeforeCurrent) && (potentialDelay > outputEventDelayTotalPeriod[outputEvent])){
233 skipEventFlags |= (1UL << outputEvent);
236 outputEventInputEventNumbers[outputEvent] = mappedEvent;
237 unsigned char numberOfRepeats = potentialDelay / SHORTMAX;
238 unsigned short finalPeriod = potentialDelay % SHORTMAX;
239 if(finalPeriod > decoderMaxCodeTime){
240 outputEventDelayFinalPeriod[outputEvent] = finalPeriod;
241 outputEventExtendRepeatPeriod[outputEvent] = SHORTMAX;
242 outputEventExtendNumberOfRepeats[outputEvent] = numberOfRepeats;
243 }else{
244 unsigned short shortagePerRepeat = (decoderMaxCodeTime - finalPeriod) / numberOfRepeats;
245 unsigned short repeatPeriod = (SHORTMAX - 1) - shortagePerRepeat;
246 finalPeriod += (shortagePerRepeat + 1) * numberOfRepeats;
247 outputEventDelayFinalPeriod[outputEvent] = finalPeriod;
248 outputEventExtendRepeatPeriod[outputEvent] = repeatPeriod;
249 outputEventExtendNumberOfRepeats[outputEvent] = numberOfRepeats;
251 // find number of max sized chunks and remainder
252 // check remainder for being big enough compared to code runtime
253 // if so, set repeat to max and final to remainder and number of iterations to divs
254 // if not, decrease repeat size in some optimal way and provide new left over to work with that, and same number of divs/its
255 // Always use dwell as requested
256 outputEventPulseWidthsMath[outputEvent] = pulsewidthToUseForThisChannel;
257 ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
258 outputEventDelayTotalPeriod[outputEvent] = potentialDelay; // No async accesses occur
259 Counters.timerStretchedToSchedule++;
261 break;
262 }else{
263 if(lastGoodEvent > 0){
264 lastGoodEvent--;
265 }else{
266 lastGoodEvent = numberOfVirtualEvents - 1;
273 // nothing much, L&P: