FreeEMS  0.2.0-SNAPSHOT-285-g028e24c
outputScheduler.c
Go to the documentation of this file.
1 /* FreeEMS - the open source engine management system
2  *
3  * Copyright 2011-2012 Fred Cooke
4  *
5  * This file is part of the FreeEMS project.
6  *
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.
11  *
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.
16  *
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/
19  *
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!
22  *
23  * Thank you for choosing FreeEMS to run your engine!
24  */
25 
26 
27 /** @file
28  *
29  * @ingroup measurementsAndCalculations
30  *
31  * @brief Precision timed output scheduling.
32  *
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.
35  */
36 
37 
38 #define OUTPUTSCHEDULER_C
39 #include "inc/freeEMS.h"
40 #include "inc/interrupts.h"
41 #include "inc/decoderInterface.h"
42 #include "inc/outputScheduler.h"
43 
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.
47  */
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
51 
52  // TODO Add ability to schedule start and centre of fuel pulse using RPM, injector firing angle and IDT to schedule the events correctly
53 
54  // Sanity checks: TODO migrate these to init time and do something meaninful with the failure
56  return; /// @todo don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
57  }
59  return; /// @todo don't bother doing anything, settings don't make sense... TODO move this to init time to prevent bad config
60  }
61  if(CoreVars->RPM == 0){
62  return; // Don't bother doing anything, can't schedule infinitely in the future ;-)
63  }
64 /// @todo TODO create this check:
65 // if(event angles not valid order/numbers/etc){
66 // return;
67 // }
68 
69 
70  /// @todo TODO Schedule injection with real timing, requires some tweaks to work right.
71 
72 
73  /** @todo TODO move this loop variable to fixedConfig and make a subset of
74  * the remainder of channels configured for fuel with a start time/tooth
75  * directly set for now, ie, make the 6 channels usable as fuel or ignition
76  * from reasonable configuration and write a guide on how to set it up for
77  * any engine.
78  */
79  unsigned char outputEvent;
80  for(outputEvent = 0;outputEvent < fixedConfigs1.schedulingSettings.numberOfConfiguredOutputEvents;outputEvent++){
81 
82  /* pseudo code
83  *
84  * we have:
85  *
86  * - offset between engine and code
87  * - offset for each output event TDC
88  * - desired timing value in degrees BTDC
89  * - a minimum post tooth delay
90  * - angle to ticks conversion number
91  *
92  * we want:
93  *
94  * - which event to fire from
95  * - how much to wait after that event before firing
96  *
97  * we need to:
98  *
99  * - to find the code angle that the spark must jump at
100  * - find nearest event
101  * - find time after nearest event to spark needing to jump
102  * - check that dwell + min delay < time after nearest
103  * - if so, set event number in output as nearest
104  * - and, set after delay to (distance between - dwell)
105  * - if not, set event number in output to one before nearest
106  * - and, set after delay to same + expected delay between nearest and next
107  *
108  * repeat per pin (this is in a loop)
109  *
110  * NOTE this is sub-optimal, the spark firing should be scheduled close to the closest tooth
111  * and dwell start should be = or greater than requested dwell and equal or less than max dwell
112  * ie, dwell can be MUCH more than requested in order to get the closest to event spark possible
113  * the output code was designed for fuel use, hence this current behaviour. It will be adjusted
114  * once xgate bit banging works sweetly.
115  */
116 
117 
118  unsigned char appropriateCuts = KeyUserDebugs.ignitionCuts;
120  appropriateCuts = KeyUserDebugs.injectionCuts;
121  }
122 
123  // 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
124  if(appropriateCuts){
125  // If this becomes more than one line, it should be made explicitly atomic. Duplicate code, see below
126  outputEventInputEventNumbers[outputEvent] = 0xFF;
127  }else{
128  // Default to ignition
129  unsigned short pulsewidthToUseForThisChannel = DerivedVars->Dwell;
130  unsigned short endOfPulseTimingToUseForThisChannel = DerivedVars->Advance;
132  pulsewidthToUseForThisChannel = masterPulseWidth;
133  endOfPulseTimingToUseForThisChannel = 0; // Fixed flat timing for fueling for the time being
134  } // Else we're doing ignition! Leave the defaults in place.
135 
136  // This value is quite large, and used with a latency added, however PWs under about 0.5ms are not useful for dwell or fueling
137  if(pulsewidthToUseForThisChannel < ectSwitchOnCodeTime){
138  // If this becomes more than one line, it should be made explicitly atomic. Duplicate code, see above
139  outputEventInputEventNumbers[outputEvent] = 0xFF;
140  }else{ // Otherwise act normally!
141 
142  /** @todo TODO move sched code to a function or functions (inline?)
143  * that can be unit tested such that we KNOW it performs as anticipated
144  * rather than just trying it out on a 400hp turbo truck engine.
145  */
146 
147  /// @todo TODO refactor this partly into init.c as per more detailed TD above
148  unsigned short codeAngleOfIgnition = 0;
149  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
150  codeAngleOfIgnition = fixedConfigs1.schedulingSettings.anglesOfTDC[outputEvent] - (fixedConfigs1.schedulingSettings.decoderEngineOffset + endOfPulseTimingToUseForThisChannel);
151  }else{
152  codeAngleOfIgnition = (unsigned short)(((unsigned long)totalEventAngleRange + fixedConfigs1.schedulingSettings.anglesOfTDC[outputEvent]) - ((unsigned long)fixedConfigs1.schedulingSettings.decoderEngineOffset + endOfPulseTimingToUseForThisChannel));
153  }
154  /** @todo TODO, do this ^ at init time from fixed config as an array of
155  * angles and a single engine offset combined into this runtime array.
156  */
157 
158  /// @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...
159 
160 
161  // Find the closest event to our desired angle of ignition by working through from what is, by definition, the farthest
162  unsigned char lastGoodEvent = ONES;
163  if(codeAngleOfIgnition == 0){ // Special case, if equal to zero, the last good event will not be found
164  // And the last good event is the last event!
165  lastGoodEvent = numberOfVirtualEvents - 1;
166  }else{
167  // Otherwise iterate through and find the closest one.
168  unsigned char possibleEvent;
169  for(possibleEvent = 0;possibleEvent < numberOfVirtualEvents;possibleEvent++){
170  if(eventAngles[possibleEvent] < codeAngleOfIgnition){
171  lastGoodEvent = possibleEvent;
172  }
173  }
174  }
175 
176  // Don't actually use this var, just need that many iterations to work back from the closest tooth that we found above
177  unsigned char possibleEvent;
178  for(possibleEvent = 0;possibleEvent < numberOfVirtualEvents;possibleEvent++){
179  unsigned long ticksBetweenEventAndSpark = LONGMAX;
180  if(codeAngleOfIgnition > eventAngles[lastGoodEvent]){
181  ticksBetweenEventAndSpark = ((unsigned long)*ticksPerDegree * (codeAngleOfIgnition - eventAngles[lastGoodEvent])) / ticks_per_degree_multiplier;
182  }else{
183  ticksBetweenEventAndSpark = ((unsigned long)*ticksPerDegree * ((unsigned long)codeAngleOfIgnition + (totalEventAngleRange - eventAngles[lastGoodEvent]))) / ticks_per_degree_multiplier;
184  }
185 
186  if(ticksBetweenEventAndSpark > ((unsigned long)pulsewidthToUseForThisChannel + decoderMaxCodeTime)){
187  // generate event mapping from real vs virtual counts, how? better with a cylinder ratio?
188  unsigned char mappedEvent = 0xFF;
189  if(numberOfRealEvents == numberOfVirtualEvents){
190  mappedEvent = lastGoodEvent;
191  }else{
192  mappedEvent = lastGoodEvent % numberOfRealEvents;
193  }
194 
195  // Determine the eventBeforeCurrent outside the atomic block
196  unsigned char eventBeforeCurrent = 0;
197  if(outputEventInputEventNumbers[outputEvent] == 0){
198  eventBeforeCurrent = numberOfRealEvents - 1;
199  }else{
200  eventBeforeCurrent = outputEventInputEventNumbers[outputEvent] - 1;
201  }
202 
203  unsigned long potentialDelay = ticksBetweenEventAndSpark - pulsewidthToUseForThisChannel;
204  if(potentialDelay <= SHORTMAX){ // We can use dwell as is
205  ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
206 
207  /* For this block we need to provide a flag AFTER disabling the interrupts
208  * such that the next input isr can figure out if it should run from the
209  * previous data for a single cycle in the case when moving forward a tooth
210  * between the tooth you are moving forward from and the one you are moving
211  * forward to. In this case a scheduled event will be lost, because the
212  * one its intended for has past, and the one after that is yet to arrive is
213  * not going to fire it.
214  *
215  * Some trickery around the post input min delay could benefit timing or be
216  * required as you will be operating under dynamic conditions and trying to
217  * use a tooth you're not supposed to be, not doing fancy delay semantics will
218  * just mean a single cycle of scheduling is slightly too retarded for a single
219  * event around change of tooth time which could easily be acceptable.
220  */
221  if((mappedEvent == eventBeforeCurrent) && (potentialDelay > outputEventDelayTotalPeriod[outputEvent])){
222  skipEventFlags |= (1UL << outputEvent);
223  }
224 
225  outputEventInputEventNumbers[outputEvent] = mappedEvent;
226  outputEventDelayFinalPeriod[outputEvent] = (unsigned short)potentialDelay;
227  outputEventPulseWidthsMath[outputEvent] = pulsewidthToUseForThisChannel;
228  outputEventExtendNumberOfRepeats[outputEvent] = 0;
229  ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
230  outputEventDelayTotalPeriod[outputEvent] = potentialDelay; // No async accesses occur
231  }else{
232  ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
233 
234  // See comment in above block
235  if((mappedEvent == eventBeforeCurrent) && (potentialDelay > outputEventDelayTotalPeriod[outputEvent])){
236  skipEventFlags |= (1UL << outputEvent);
237  }
238 
239  outputEventInputEventNumbers[outputEvent] = mappedEvent;
240  unsigned char numberOfRepeats = potentialDelay / SHORTMAX;
241  unsigned short finalPeriod = potentialDelay % SHORTMAX;
242  if(finalPeriod > decoderMaxCodeTime){
243  outputEventDelayFinalPeriod[outputEvent] = finalPeriod;
245  outputEventExtendNumberOfRepeats[outputEvent] = numberOfRepeats;
246  }else{
247  unsigned short shortagePerRepeat = (decoderMaxCodeTime - finalPeriod) / numberOfRepeats;
248  unsigned short repeatPeriod = (SHORTMAX - 1) - shortagePerRepeat;
249  finalPeriod += (shortagePerRepeat + 1) * numberOfRepeats;
250  outputEventDelayFinalPeriod[outputEvent] = finalPeriod;
251  outputEventExtendRepeatPeriod[outputEvent] = repeatPeriod;
252  outputEventExtendNumberOfRepeats[outputEvent] = numberOfRepeats;
253  }
254  // find number of max sized chunks and remainder
255  // check remainder for being big enough compared to code runtime
256  // if so, set repeat to max and final to remainder and number of iterations to divs
257  // if not, decrease repeat size in some optimal way and provide new left over to work with that, and same number of divs/its
258  // Always use dwell as requested
259  outputEventPulseWidthsMath[outputEvent] = pulsewidthToUseForThisChannel;
260  ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
261  outputEventDelayTotalPeriod[outputEvent] = potentialDelay; // No async accesses occur
263  }
264  break;
265  }else{
266  if(lastGoodEvent > 0){
267  lastGoodEvent--;
268  }else{
269  lastGoodEvent = numberOfVirtualEvents - 1;
270  }
271  }
272  }
273  }
274  }
275  }
276  // nothing much, L&P:
277 }