1 /* FreeEMS - the open source engine management system
3 * Copyright 2008 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 NipponDenso.c
28 * @ingroup interruptHandlers
29 * @ingroup enginePositionRPMDecoders
31 * @brief Reads Nippon Denso 24/2 sensors
33 * This file contains the two interrupt service routines for handling engine
34 * position and RPM signals from mainly Toyota engines using this sensor style.
36 * One ISR handles the 24 evenly spaced teeth and the other handles the two
37 * adjacent teeth. This signal style provides enough information for wasted
38 * spark ignition and semi sequential fuel injection.
40 * Supported engines include:
49 * @note Pseudo code that does not compile with zero warnings and errors MUST be commented out.
51 * @todo TODO make this generic for evenly spaced teeth with a pulse per revolution from the second input.
55 #include "inc/freeEMS.h"
56 #include "inc/interrupts.h"
57 #include "inc/utils.h"
62 * Summary of intended engine position capture scheme (out of date as at 3/1/09)
64 * Position/RPM signal interpretation :
65 * Discard edges that have arrived too soon (lose sync here?)
66 * Check to ensure we haven't lost sync (pulse arrives too late)
67 * Compare time stamps of successive edges and calculate RPM
68 * Store RPM and position in globals
71 * loop through all events (spark and fuel), schedule those that fall sufficiently after this tooth and before the next one we expect.
74 * Grab a unified set of ADC readings at one time in a consistent crank location to eliminate engine cycle dependent noise.
75 * Set flag stating that New pulse, advance, etc should be calculated.
79 * @warning These are for testing and demonstration only, not suitable for driving with just yet.
81 * @todo TODO bring the above docs up to date with reality
82 * @todo TODO finish this off to a usable standard
85 /* Clear the interrupt flag for this input compare channel */
88 /* Save all relevant available data here */
89 unsigned short codeStartTimeStamp
= TCNT
; /* Save the current timer count */
90 unsigned short edgeTimeStamp
= TC0
; /* Save the edge time stamp */
91 unsigned char PTITCurrentState
= PTIT
; /* Save the values on port T regardless of the state of DDRT */
92 // unsigned short PORTS_BACurrentState = PORTS_BA; /* Save ignition output state */
94 /* Calculate the latency in ticks */
95 ISRLatencyVars
.primaryInputLatency
= codeStartTimeStamp
- edgeTimeStamp
;
97 // TODO discard narrow ones! test for tooth width and tooth period
99 /* Set up edges as per config */
100 unsigned char risingEdge
;
101 if(fixedConfigs1
.coreSettingsA
& PRIMARY_POLARITY
){
102 risingEdge
= PTITCurrentState
& 0x01;
104 risingEdge
= !(PTITCurrentState
& 0x01);
108 /* Echo input condition on J7 */
111 // increment crank pulses TODO this needs to be wrapped in tooth period and width checking
112 primaryPulsesPerSecondaryPulse
++;
114 // calculate rough rpm (this will be wrong when the var is used correctly)
115 *RPMRecord
= ticksPerCycleAtOneRPMx2
/ engineCyclePeriod
; /* 0.8us ticks, 150mil = 2 x 60 seconds, times rpm scale factor of 2 */
117 // don't run until the second trigger has come in and the period is correct (VERY temporary)
118 if(!(coreStatusA
& PRIMARY_SYNC
)){
119 primaryTeethDroppedFromLackOfSync
++;
125 /* Install the low word */
126 timeStamp
.timeShorts
[1] = edgeTimeStamp
;
127 /* Find out what our timer value means and put it in the high word */
128 if(TFLGOF
&& !(edgeTimeStamp
& 0x8000)){ /* see 10.3.5 paragraph 4 of 68hc11 ref manual for details */
129 timeStamp
.timeShorts
[0] = timerExtensionClock
+ 1;
131 timeStamp
.timeShorts
[0] = timerExtensionClock
;
134 // temporary data from inputs
135 primaryLeadingEdgeTimeStamp
= edgeTimeStamp
;
136 timeBetweenSuccessivePrimaryPulses
= lastPrimaryPulseTimeStamp
- primaryLeadingEdgeTimeStamp
;
137 lastPrimaryPulseTimeStamp
= primaryLeadingEdgeTimeStamp
;
138 timeBetweenSuccessivePrimaryPulsesBuffer
= (timeBetweenSuccessivePrimaryPulses
>> 1) + (timeBetweenSuccessivePrimaryPulsesBuffer
>> 1);
140 // TODO make scheduling either fixed from boot with a limited range, OR preferrably if its practical scheduled on the fly to allow arbitrary advance and retard of both fuel and ignition.
142 /* Check for loss of sync by too high a count */
143 if(primaryPulsesPerSecondaryPulse
> 12){
144 /* Increment the lost sync count */
145 Counters
.crankSyncLosses
++;
147 /* Clear synced status */
148 coreStatusA
&= CLEAR_PRIMARY_SYNC
;
150 /* Reset the count of teeth */
151 primaryPulsesPerSecondaryPulse
= 0;
153 /* Get the hell out of here before we do something bad */
157 // CAUTION came to me lying in bed half asleep idea :
159 // TODO move tooth selection to the calc loop in main such that this routine just iterates through an array of events and schedules those that are destined for this tooth.
162 // iterate through ignition first, schedule all of those
163 // iterate through dwell next, schedule all of those
165 // iterate through main fuel next, schedule all of those
166 // if staging enabled and required
167 // iterate through staged fuel last,
169 // TODO should make for a clean compact scheduling implementation. the fuel code doesn't care when/how it has started in the past, and hopefully ign will be the same.
171 // this will be done with an array and per tooth check in future
172 if((primaryPulsesPerSecondaryPulse
% 2) == 0){
174 // TODO sample ADCs on teeth other than that used by the scheduler in order to minimise peak run time and get clean signals
175 sampleBlockADC(ADCArrays
);
176 Counters
.syncedADCreadings
++;
177 *mathSampleTimeStampRecord
= TCNT
;
179 /* Set flag to say calc required */
180 coreStatusA
|= CALC_FUEL_IGN
;
182 /* Reset the clock for reading timeout */
183 Clocks
.timeoutADCreadingClock
= 0;
185 if(masterPulseWidth
> injectorMinimumPulseWidth
){ // use reference PW to decide. spark needs moving outside this area though TODO
186 /* Determine if half the cycle is bigger than short-max */
187 unsigned short maxAngleAfter
;
188 if((engineCyclePeriod
>> 1) > 0xFFFF){
189 maxAngleAfter
= 0xFFFF;
191 maxAngleAfter
= (unsigned short)(engineCyclePeriod
>> 1);
194 /* Check advance to ensure it is less than 1/2 of the previous engine cycle and more than codetime away */
195 unsigned short advance
;
196 if(totalAngleAfterReferenceInjection
> maxAngleAfter
){ // if too big, make it max
197 advance
= maxAngleAfter
;
198 }else if(totalAngleAfterReferenceInjection
< trailingEdgeSecondaryRPMInputCodeTime
){ // if too small, make it min
199 advance
= trailingEdgeSecondaryRPMInputCodeTime
;
200 }else{ // else use it as is
201 advance
= totalAngleAfterReferenceInjection
;
204 // determine the long and short start times
205 unsigned short startTime
= primaryLeadingEdgeTimeStamp
+ advance
;
206 unsigned long startTimeLong
= timeStamp
.timeLong
+ advance
;
208 /* Determine the channels to schedule */
209 unsigned char fuelChannel
= (primaryPulsesPerSecondaryPulse
/ 2) - 1;
210 unsigned char ignitionChannel
= (primaryPulsesPerSecondaryPulse
/ 2) - 1;
212 if(fuelChannel
> 5 || ignitionChannel
> 5){
213 // send("bad fuel : ");
214 // sendUC(fuelChannel);
215 // send("bad ign : ");
216 // sendUC(ignitionChannel);
220 // determine whether or not to reschedule
221 unsigned char reschedule
= 0;
222 unsigned long diff
= startTimeLong
- (injectorMainEndTimes
[fuelChannel
] + injectorSwitchOffCodeTime
);
224 reschedule
= 1; // http://www.diyefi.org/forum/viewtopic.php?f=8&t=57&p=861#p861
227 // schedule the appropriate channel
228 if(!(*injectorMainControlRegisters
[fuelChannel
] & injectorMainEnableMasks
[fuelChannel
]) || reschedule
){ /* If the timer isn't still running, or if its set too long, set it to start again at the right time soon */
229 *injectorMainControlRegisters
[fuelChannel
] |= injectorMainEnableMasks
[fuelChannel
];
230 *injectorMainTimeRegisters
[fuelChannel
] = startTime
;
231 TIE
|= injectorMainOnMasks
[fuelChannel
];
232 TFLG
= injectorMainOnMasks
[fuelChannel
];
234 injectorMainStartTimesHolding
[fuelChannel
] = startTime
;
235 selfSetTimer
|= injectorMainOnMasks
[fuelChannel
]; // setup a bit to let the timer interrupt know to set its own new start from a var
238 // TODO advance/retard/dwell numbers all need range checking etc done. some of this should be done in the calculator section, and some here. currently none is done at all and for that reason, this will not work in a real system yet, if it works at all.
239 // as do array indexs here and in the ISRs...
242 // TODO implement mechanism for dropping a cylinder in event of over queueing or spark cut/round robin
243 // important as ignition sequence disrupted when this occurs as it stands.
245 // TODO check queue length checks to ensure we dont count up to somewhere we can never count down from. This could be causing the hanging long phenomina
249 // If dwell is not currently enabled, set it all up
250 if(!(PITCE
& DWELL_ENABLE
)){
251 /* Schedule Dwell event (do this first because it comes earliest. */
252 // set the channel to fire
253 nextDwellChannel
= ignitionChannel
;
257 // PITLD0 = ignitionAdvances[ignitionChannel] - *currentDwellRealtime; BAD for various reasons!
259 // clear the flags first as they apparently become set any old time whether enabled or not.
260 PITTF
|= DWELL_ENABLE
;
263 PITINTE
|= DWELL_ENABLE
;
265 // clear the flags first as they apparently become set any old time whether enabled or not.
266 PITTF
|= DWELL_ENABLE
;
269 PITCE
|= DWELL_ENABLE
;
270 }else if(dwellQueueLength
== 0){
271 // load time offset such that next period is correct
272 PITLD0
= (advance
- PITCNT0
);
274 // increment queue length
276 }else if(dwellQueueLength
> fixedConfigs1
.engineSettings
.combustionEventsPerEngineCycle
){ //TODO sensible figures here for array index OOBE
277 // do nothing, or increment a counter or something similar.
279 unsigned short sumOfDwells
= PITLD0
;
280 // add up the prequeued time periods
282 // queue = 1 pitld is all
283 // queue = 2 one from 0 index of array AND pitld
285 unsigned char index
= 0;
286 while(index
< (dwellQueueLength
-1)){
287 sumOfDwells
+= queuedDwellOffsets
[index
];
290 // for(index = 0;index < (dwellQueueLength -1);index++){ // is this right?
291 // sumOfDwells += queuedDwellOffsets[index];
294 // store time offset in appropriate array location
295 queuedDwellOffsets
[dwellQueueLength
- 1] = advance
- (PITCNT0
+ sumOfDwells
);
297 // increment queue length from one or more
301 // IGNITION experimental stuff
303 // If ignition is not currently enabled, set it all up
304 if(!(PITCE
& IGNITION_ENABLE
)){
305 /* Schedule Ignition event (do this first because it comes earliest. */
306 // set the channel to fire
307 nextIgnitionChannel
= ignitionChannel
;
309 // figure out the time to set the delay reg to
310 PITLD1
= advance
+ injectorMainPulseWidthsRealtime
[fuelChannel
];
311 // PITLD1 = ignitionAdvances[ignitionChannel + outputBankIgnitionOffset];
313 // clear the flags first as they apparently become set any old time whether enabled or not.
314 PITTF
|= IGNITION_ENABLE
;
317 PITINTE
|= IGNITION_ENABLE
;
319 // clear the flags first as they apparently become set any old time whether enabled or not.
320 PITTF
|= IGNITION_ENABLE
;
323 PITCE
|= IGNITION_ENABLE
;
324 }else if(ignitionQueueLength
== 0){
325 // load timer register
326 PITLD1
= ((advance
+ injectorMainPulseWidthsRealtime
[fuelChannel
]) - PITCNT1
);
329 ignitionQueueLength
++;
330 }else if(ignitionQueueLength
> fixedConfigs1
.engineSettings
.combustionEventsPerEngineCycle
){ //TODO sensible figures here for array index OOBE
331 // do nothing, or increment a counter or something similar.
333 unsigned short sumOfIgnitions
= PITLD1
;
334 // add up the prequeued time periods
336 // queue = 1 pitld is all
337 // queue = 2 one from 0 index of array AND pitld
340 unsigned char index
= 0;
341 while(index
< (ignitionQueueLength
- 1)){
342 sumOfIgnitions
+= queuedIgnitionOffsets
[index
];
345 // for(index = 0;index < (ignitionQueueLength -1);index++){ // is this right?
346 // sumOfIgnitions += queuedIgnitionOffsets[index];
349 // store time offset in appropriate array location
350 queuedIgnitionOffsets
[ignitionQueueLength
- 1] = advance
- (PITCNT1
+ sumOfIgnitions
);
352 // increment from 1 or more
353 ignitionQueueLength
++;
357 RuntimeVars
.primaryInputLeadingRuntime
= TCNT
- codeStartTimeStamp
;
360 RuntimeVars
.primaryInputTrailingRuntime
= TCNT
- codeStartTimeStamp
;
363 Counters
.primaryTeethSeen
++;
364 // suss out rpm and accurate TDC reference
366 // if you say it quick, it doesn't sound like much :
367 // schedule fuel and ign based on spark cut and fuel cut and timing vars and status vars config vars
371 /** Secondary RPM ISR
373 * Similar to the primary one.
375 * @todo TODO bring this documentation up to date.
376 * @todo TODO finish this off to a usable standard.
378 void SecondaryRPMISR(){
379 /* Clear the interrupt flag for this input compare channel */
382 /* Save all relevant available data here */
383 unsigned short codeStartTimeStamp
= TCNT
; /* Save the current timer count */
384 unsigned short edgeTimeStamp
= TC1
; /* Save the timestamp */
385 unsigned char PTITCurrentState
= PTIT
; /* Save the values on port T regardless of the state of DDRT */
386 // unsigned short PORTS_BACurrentState = PORTS_BA; /* Save ignition output state */
388 /* Calculate the latency in ticks */
389 ISRLatencyVars
.secondaryInputLatency
= codeStartTimeStamp
- edgeTimeStamp
;
391 // TODO discard narrow ones! test for tooth width and tooth period
393 /* Set up edges as per config */
394 unsigned char risingEdge
;
395 if(fixedConfigs1
.coreSettingsA
& SECONDARY_POLARITY
){
396 risingEdge
= PTITCurrentState
& 0x02;
398 risingEdge
= !(PTITCurrentState
& 0x02);
402 // echo input condition
405 // display the crank pulses
406 PORTM
= (char)primaryPulsesPerSecondaryPulseBuffer
;
408 primaryPulsesPerSecondaryPulseBuffer
= primaryPulsesPerSecondaryPulse
;
409 primaryPulsesPerSecondaryPulse
= 0;
411 // if we didn't get the right number of pulses drop sync and start over
412 if((primaryPulsesPerSecondaryPulseBuffer
!= 12) && (coreStatusA
& PRIMARY_SYNC
)){
413 coreStatusA
&= CLEAR_PRIMARY_SYNC
;
414 Counters
.crankSyncLosses
++;
419 /* Install the low word */
420 timeStamp
.timeShorts
[1] = edgeTimeStamp
;
421 /* Find out what our timer value means and put it in the high word */
422 if(TFLGOF
&& !(edgeTimeStamp
& 0x8000)){ /* see 10.3.5 paragraph 4 of 68hc11 ref manual for details */
423 timeStamp
.timeShorts
[0] = timerExtensionClock
+ 1;
425 timeStamp
.timeShorts
[0] = timerExtensionClock
;
428 // get the data we actually want
429 engineCyclePeriod
= 2 * (timeStamp
.timeLong
- lastSecondaryOddTimeStamp
); // save the engine cycle period
430 lastSecondaryOddTimeStamp
= timeStamp
.timeLong
; // save this stamp for next time round
432 // Because this is our only reference, each time we get this pulse, we know where we are at (simple mode so far)
433 coreStatusA
|= PRIMARY_SYNC
;
434 RuntimeVars
.secondaryInputLeadingRuntime
= TCNT
- codeStartTimeStamp
;
437 RuntimeVars
.secondaryInputTrailingRuntime
= TCNT
- codeStartTimeStamp
;
440 Counters
.secondaryTeethSeen
++;
441 // suss out phase/engine cycle reference showing which bank we are on
443 /* If the flag is not cleared at the beginning then the interrupt gets rescheduled while it is running, hence it can't be done at the end of the ISR */