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! */
26 /** @file NipponDensoISR.c
27 * @ingroup interruptHandlers
28 * @ingroup enginePositionRPMDecoders
30 * @brief Reads Nippon Denso 24/2 sensors
32 * This file contains the two interrupt service routines for handling engine
33 * position and RPM signals from mainly Toyota engines using this sensor style.
35 * One ISR handles the 24 evenly spaced teeth and the other handles the two
36 * adjacent teeth. This signal style provides enough information for wasted
37 * spark ignition and semi sequential fuel injection.
39 * Supported engines include:
48 * @todo TODO make this generic for evenly spaced teeth with a pulse per revolution from the second input.
52 #include "inc/freeEMS.h"
53 #include "inc/interrupts.h"
54 #include "inc/utils.h"
59 * Summary of intended engine position capture scheme (out of date as at 3/1/09)
61 * Position/RPM signal interpretation :
62 * Discard edges that have arrived too soon (lose sync here?)
63 * Check to ensure we haven't lost sync (pulse arrives too late)
64 * Compare time stamps of successive edges and calculate RPM
65 * Store RPM and position in globals
68 * loop through all events (spark and fuel), schedule those that fall sufficiently after this tooth and before the next one we expect.
71 * Grab a unified set of ADC readings at one time in a consistent crank location to eliminate engine cycle dependent noise.
72 * Set flag stating that New pulse, advance, etc should be calculated.
76 * @warning These are for testing and demonstration only, not suitable for driving with just yet.
78 * @todo TODO bring the above docs up to date with reality
79 * @todo TODO finish this off to a usable standard
81 void PrimaryRPMISR(void)
83 /* Clear the interrupt flag for this input compare channel */
86 /* Save all relevant available data here */
87 unsigned short codeStartTimeStamp
= TCNT
; /* Save the current timer count */
88 unsigned short edgeTimeStamp
= TC0
; /* Save the edge time stamp */
89 unsigned char PTITCurrentState
= PTIT
; /* Save the values on port T regardless of the state of DDRT */
90 // unsigned short PORTS_BACurrentState = PORTS_BA; /* Save ignition output state */
92 /* Calculate the latency in ticks */
93 ISRLatencyVars
.primaryInputLatency
= codeStartTimeStamp
- edgeTimeStamp
;
95 // TODO discard narrow ones! test for tooth width and tooth period
97 /* Set up edges as per config */
98 unsigned char risingEdge
;
99 if(fixedConfigs1
.coreSettingsA
& PRIMARY_POLARITY
){
100 risingEdge
= PTITCurrentState
& 0x01;
102 risingEdge
= !(PTITCurrentState
& 0x01);
106 /* Echo input condition on J7 */
109 // increment crank pulses TODO this needs to be wrapped in tooth period and width checking
110 primaryPulsesPerSecondaryPulse
++;
112 // calculate rough rpm (this will be wrong when the var is used correctly)
113 *RPMRecord
= ticksPerCycleAtOneRPMx2
/ engineCyclePeriod
; /* 0.8us ticks, 150mil = 2 x 60 seconds, times rpm scale factor of 2 */
115 // don't run until the second trigger has come in and the period is correct (VERY temporary)
116 if(!(coreStatusA
& PRIMARY_SYNC
)){
117 primaryTeethDroppedFromLackOfSync
++;
123 /* Install the low word */
124 timeStamp
.timeShorts
[1] = edgeTimeStamp
;
125 /* Find out what our timer value means and put it in the high word */
126 if(TFLGOF
&& !(edgeTimeStamp
& 0x8000)){ /* see 10.3.5 paragraph 4 of 68hc11 ref manual for details */
127 timeStamp
.timeShorts
[0] = timerExtensionClock
+ 1;
129 timeStamp
.timeShorts
[0] = timerExtensionClock
;
132 // temporary data from inputs
133 primaryLeadingEdgeTimeStamp
= edgeTimeStamp
;
134 timeBetweenSuccessivePrimaryPulses
= lastPrimaryPulseTimeStamp
- primaryLeadingEdgeTimeStamp
;
135 lastPrimaryPulseTimeStamp
= primaryLeadingEdgeTimeStamp
;
136 timeBetweenSuccessivePrimaryPulsesBuffer
= (timeBetweenSuccessivePrimaryPulses
>> 1) + (timeBetweenSuccessivePrimaryPulsesBuffer
>> 1);
138 // 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.
140 /* Check for loss of sync by too high a count */
141 if(primaryPulsesPerSecondaryPulse
> 12){
142 /* Increment the lost sync count */
143 Counters
.crankSyncLosses
++;
145 /* Clear synced status */
146 coreStatusA
&= CLEAR_PRIMARY_SYNC
;
148 /* Reset the count of teeth */
149 primaryPulsesPerSecondaryPulse
= 0;
151 /* Get the hell out of here before we do something bad */
155 // CAUTION came to me lying in bed half asleep idea :
157 // 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.
160 // iterate through ignition first, schedule all of those
161 // iterate through dwell next, schedule all of those
163 // iterate through main fuel next, schedule all of those
164 // if staging enabled and required
165 // iterate through staged fuel last,
167 // 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.
169 // this will be done with an array and per tooth check in future
170 if((primaryPulsesPerSecondaryPulse
% 2) == 0){
172 // TODO sample ADCs on teeth other than that used by the scheduler in order to minimise peak run time and get clean signals
173 sampleBlockADC(ADCArrays
);
174 Counters
.syncedADCreadings
++;
175 *mathSampleTimeStampRecord
= TCNT
;
177 /* Set flag to say calc required */
178 coreStatusA
|= CALC_FUEL_IGN
;
180 /* Reset the clock for reading timeout */
181 Clocks
.timeoutADCreadingClock
= 0;
183 if(masterPulseWidth
> injectorMinimumPulseWidth
){ // use reference PW to decide. spark needs moving outside this area though TODO
184 /* Determine if half the cycle is bigger than short-max */
185 unsigned short maxAngleAfter
;
186 if((engineCyclePeriod
>> 1) > 0xFFFF){
187 maxAngleAfter
= 0xFFFF;
189 maxAngleAfter
= (unsigned short)(engineCyclePeriod
>> 1);
192 /* Check advance to ensure it is less than 1/2 of the previous engine cycle and more than codetime away */
193 unsigned short advance
;
194 if(totalAngleAfterReferenceInjection
> maxAngleAfter
){ // if too big, make it max
195 advance
= maxAngleAfter
;
196 }else if(totalAngleAfterReferenceInjection
< trailingEdgeSecondaryRPMInputCodeTime
){ // if too small, make it min
197 advance
= trailingEdgeSecondaryRPMInputCodeTime
;
198 }else{ // else use it as is
199 advance
= totalAngleAfterReferenceInjection
;
202 // determine the long and short start times
203 unsigned short startTime
= primaryLeadingEdgeTimeStamp
+ advance
;
204 unsigned long startTimeLong
= timeStamp
.timeLong
+ advance
;
206 /* Determine the channels to schedule */
207 unsigned char fuelChannel
= (primaryPulsesPerSecondaryPulse
/ 2) - 1;
208 unsigned char ignitionChannel
= (primaryPulsesPerSecondaryPulse
/ 2) - 1;
210 if(fuelChannel
> 5 || ignitionChannel
> 5){
211 // send("bad fuel : ");
212 // sendUC(fuelChannel);
213 // send("bad ign : ");
214 // sendUC(ignitionChannel);
218 // determine whether or not to reschedule
219 unsigned char reschedule
= 0;
220 unsigned long diff
= startTimeLong
- (injectorMainEndTimes
[fuelChannel
] + injectorSwitchOffCodeTime
);
222 reschedule
= 1; // http://www.diyefi.org/forum/viewtopic.php?f=8&t=57&p=861#p861
225 // schedule the appropriate channel
226 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 */
227 *injectorMainControlRegisters
[fuelChannel
] |= injectorMainEnableMasks
[fuelChannel
];
228 *injectorMainTimeRegisters
[fuelChannel
] = startTime
;
229 TIE
|= injectorMainOnMasks
[fuelChannel
];
230 TFLG
= injectorMainOnMasks
[fuelChannel
];
232 injectorMainStartTimesHolding
[fuelChannel
] = startTime
;
233 selfSetTimer
|= injectorMainOnMasks
[fuelChannel
]; // setup a bit to let the timer interrupt know to set its own new start from a var
236 // 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.
237 // as do array indexs here and in the ISRs...
240 // TODO implement mechanism for dropping a cylinder in event of over queueing or spark cut/round robin
241 // important as ignition sequence disrupted when this occurs as it stands.
243 // 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
247 // If dwell is not currently enabled, set it all up
248 if(!(PITCE
& DWELL_ENABLE
)){
249 /* Schedule Dwell event (do this first because it comes earliest. */
250 // set the channel to fire
251 nextDwellChannel
= ignitionChannel
;
255 // PITLD0 = ignitionAdvances[ignitionChannel] - *currentDwellRealtime; BAD for various reasons!
257 // clear the flags first as they apparently become set any old time whether enabled or not.
258 PITTF
|= DWELL_ENABLE
;
261 PITINTE
|= DWELL_ENABLE
;
263 // clear the flags first as they apparently become set any old time whether enabled or not.
264 PITTF
|= DWELL_ENABLE
;
267 PITCE
|= DWELL_ENABLE
;
268 }else if(dwellQueueLength
== 0){
269 // load time offset such that next period is correct
270 PITLD0
= (advance
- PITCNT0
);
272 // increment queue length
274 }else if(dwellQueueLength
> fixedConfigs2
.combustionEventsPerEngineCycle
){ //TODO sensible figures here for array index OOBE
275 // do nothing, or increment a counter or something similar.
277 unsigned short sumOfDwells
= PITLD0
;
278 // add up the prequeued time periods
280 // queue = 1 pitld is all
281 // queue = 2 one from 0 index of array AND pitld
283 unsigned char index
= 0;
284 while(index
< (dwellQueueLength
-1)){
285 sumOfDwells
+= queuedDwellOffsets
[index
];
288 // for(index = 0;index < (dwellQueueLength -1);index++){ // is this right?
289 // sumOfDwells += queuedDwellOffsets[index];
292 // store time offset in appropriate array location
293 queuedDwellOffsets
[dwellQueueLength
- 1] = advance
- (PITCNT0
+ sumOfDwells
);
295 // increment queue length from one or more
299 // IGNITION experimental stuff
301 // If ignition is not currently enabled, set it all up
302 if(!(PITCE
& IGNITION_ENABLE
)){
303 /* Schedule Ignition event (do this first because it comes earliest. */
304 // set the channel to fire
305 nextIgnitionChannel
= ignitionChannel
;
307 // figure out the time to set the delay reg to
308 PITLD1
= advance
+ injectorMainPulseWidthsRealtime
[fuelChannel
];
309 // PITLD1 = ignitionAdvances[ignitionChannel + outputBankIgnitionOffset];
311 // clear the flags first as they apparently become set any old time whether enabled or not.
312 PITTF
|= IGNITION_ENABLE
;
315 PITINTE
|= IGNITION_ENABLE
;
317 // clear the flags first as they apparently become set any old time whether enabled or not.
318 PITTF
|= IGNITION_ENABLE
;
321 PITCE
|= IGNITION_ENABLE
;
322 }else if(ignitionQueueLength
== 0){
323 // load timer register
324 PITLD1
= ((advance
+ injectorMainPulseWidthsRealtime
[fuelChannel
]) - PITCNT1
);
327 ignitionQueueLength
++;
328 }else if(ignitionQueueLength
> fixedConfigs2
.combustionEventsPerEngineCycle
){ //TODO sensible figures here for array index OOBE
329 // do nothing, or increment a counter or something similar.
331 unsigned short sumOfIgnitions
= PITLD1
;
332 // add up the prequeued time periods
334 // queue = 1 pitld is all
335 // queue = 2 one from 0 index of array AND pitld
338 unsigned char index
= 0;
339 while(index
< (ignitionQueueLength
- 1)){
340 sumOfIgnitions
+= queuedIgnitionOffsets
[index
];
343 // for(index = 0;index < (ignitionQueueLength -1);index++){ // is this right?
344 // sumOfIgnitions += queuedIgnitionOffsets[index];
347 // store time offset in appropriate array location
348 queuedIgnitionOffsets
[ignitionQueueLength
- 1] = advance
- (PITCNT1
+ sumOfIgnitions
);
350 // increment from 1 or more
351 ignitionQueueLength
++;
355 RuntimeVars
.primaryInputLeadingRuntime
= TCNT
- codeStartTimeStamp
;
358 RuntimeVars
.primaryInputTrailingRuntime
= TCNT
- codeStartTimeStamp
;
361 Counters
.primaryTeethSeen
++;
362 // suss out rpm and accurate TDC reference
364 // if you say it quick, it doesn't sound like much :
365 // schedule fuel and ign based on spark cut and fuel cut and timing vars and status vars config vars
369 /** Secondary RPM ISR
371 * Similar to the primary one.
373 * @todo TODO bring this documentation up to date.
374 * @todo TODO finish this off to a usable standard.
376 void SecondaryRPMISR(void)
378 /* Clear the interrupt flag for this input compare channel */
381 /* Save all relevant available data here */
382 unsigned short codeStartTimeStamp
= TCNT
; /* Save the current timer count */
383 unsigned short edgeTimeStamp
= TC1
; /* Save the timestamp */
384 unsigned char PTITCurrentState
= PTIT
; /* Save the values on port T regardless of the state of DDRT */
385 // unsigned short PORTS_BACurrentState = PORTS_BA; /* Save ignition output state */
387 /* Calculate the latency in ticks */
388 ISRLatencyVars
.secondaryInputLatency
= codeStartTimeStamp
- edgeTimeStamp
;
390 // TODO discard narrow ones! test for tooth width and tooth period
392 /* Set up edges as per config */
393 unsigned char risingEdge
;
394 if(fixedConfigs1
.coreSettingsA
& SECONDARY_POLARITY
){
395 risingEdge
= PTITCurrentState
& 0x02;
397 risingEdge
= !(PTITCurrentState
& 0x02);
401 // echo input condition
404 // display the crank pulses
405 PORTM
= (char)primaryPulsesPerSecondaryPulseBuffer
;
407 primaryPulsesPerSecondaryPulseBuffer
= primaryPulsesPerSecondaryPulse
;
408 primaryPulsesPerSecondaryPulse
= 0;
410 // if we didn't get the right number of pulses drop sync and start over
411 if((primaryPulsesPerSecondaryPulseBuffer
!= 12) && (coreStatusA
& PRIMARY_SYNC
)){
412 coreStatusA
&= CLEAR_PRIMARY_SYNC
;
413 Counters
.crankSyncLosses
++;
418 /* Install the low word */
419 timeStamp
.timeShorts
[1] = edgeTimeStamp
;
420 /* Find out what our timer value means and put it in the high word */
421 if(TFLGOF
&& !(edgeTimeStamp
& 0x8000)){ /* see 10.3.5 paragraph 4 of 68hc11 ref manual for details */
422 timeStamp
.timeShorts
[0] = timerExtensionClock
+ 1;
424 timeStamp
.timeShorts
[0] = timerExtensionClock
;
427 // get the data we actually want
428 engineCyclePeriod
= 2 * (timeStamp
.timeLong
- lastSecondaryOddTimeStamp
); // save the engine cycle period
429 lastSecondaryOddTimeStamp
= timeStamp
.timeLong
; // save this stamp for next time round
431 // Because this is our only reference, each time we get this pulse, we know where we are at (simple mode so far)
432 coreStatusA
|= PRIMARY_SYNC
;
433 RuntimeVars
.secondaryInputLeadingRuntime
= TCNT
- codeStartTimeStamp
;
436 RuntimeVars
.secondaryInputTrailingRuntime
= TCNT
- codeStartTimeStamp
;
439 Counters
.secondaryTeethSeen
++;
440 // suss out phase/engine cycle reference showing which bank we are on
442 /* 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 */